40 KiB
ConfigKit Adoption Implementation Plan
Purpose And Execution Contract
Implement the target in ConfigKit Adoption. That document owns the feature scope and operator choices; this document owns the implementation sequence. Each numbered stage is one implementation prompt for a gpt-5.6-terra coding agent. Execute stages in order and finish the named checks before proceeding. Do not combine the configuration cutover with unrelated pipeline or security refactoring.
Read AGENTS.md, docs/development.md, all three documents under
docs/policy/, and the relevant focused internal documentation before acting.
Apply the implementation-discipline policy: use the dependency's actual APIs,
preserve the requested flexibility, and do not add application restrictions
under the guise of this migration. Tests protect behavior, not exact helper
structure, incidental formatting, or a duplicated implementation of ConfigKit.
ConfigKit v0.3.0 supplies the required source-origin APIs. Pin that release from
Stage 1; there is no remaining upstream gate. Stage 7 uses the released
Resolution.InputInfo and Resolution.Source methods. Keep the fourteen
stages ordered and bounded as below.
Temporary coexistence of the old production loader and the new loader during these stages is implementation scaffolding only. There is no supported dual version loader in the final product and no release should be tagged midway through the cutover. Each stage must compile and pass its scoped tests; the final stage establishes repository-wide readiness.
Repository Baseline
Planning examined Notarius at 946fa10 plus the outstanding policy and roadmap
changes. The dependency review now uses the sibling ConfigKit checkout tagged
v0.3.0, including its release guide and source-aware consumer example.
Relevant current owners are:
| Area | Repository evidence |
|---|---|
| Version 4 YAML and conversion | internal/core/config/file_config.go, including FileConfig, custom UnmarshalYAML methods, ParseFileConfigYAML, LoadFileConfig, and ApplyFileConfig |
| Defaults, runtime ownership, domain validation | internal/core/config/config.go, validation.go, effective_config.go, and their contract tests |
| Field-level environment overrides | internal/core/config/env.go and env_contract_test.go |
| Configuration discovery and command overrides | internal/cli/run.go: loadConfig, runConfigValidate, runPipelinesList, and runPipelineCommand |
| Catalog checks and configured/CLI reference behavior | internal/framework/pipeline/profile.go, references.go, and CLI reference contract tests |
| Dynamic integer consumers | internal/modules/generic/chunk/units/chunker.go and internal/modules/generic/output/json/encoder.go currently require native int |
| Debug configuration and resolved-pipeline output | internal/core/config/redaction.go, internal/core/debugbundle/summary.go, and the CLI debug handoff |
| Maintained examples | examples/dnd-minimal.config.yml, examples/dnd-complete.config.yml, examples/profiles/dnd-extraction.yml, and CLI production contract tests |
| Direct YAML test dependency | internal/cli/production_contract_test.go also parses embedded prompt definitions using yaml.v3 |
| Upstream contract | ConfigKit docs/config.md, docs/consumers/README.md, public load.go, lifecycle.go, metadata.go, records.go, path.go, and consumer examples at v0.3.0 |
The sibling checkout is reference material, not a production dependency path.
Do not commit a local replace, modify upstream, or invent a future API as part
of these Notarius stages.
Decisions Applied Throughout
Configuration And Dependency Boundary
- Pin
gitea.maximumdirect.net/eric/ConfigKit v0.3.0. Use the root public package, never its private engine or an unreviewedmainrevision. - Use a string-backed document version and a
/versionrule accepting exactly text5; both numericversion: 5and quotedversion: "5"are accepted. Maintained examples use the numeric form. Reject missing/null/unsupported versions. Numeric v4 fails the application checker with migration guidance. - Use plain YAML-tagged document structs in
internal/core/config, without custom YAML hooks. Keep runtime framework types independent of ConfigKit. - Keep the existing runtime
Config,ResolveInput, andEffectiveConfigboundary where it is useful. A newLoadedConfigowns the private ConfigKit result and exposes runtime configuration plus safe writer methods. No caller may mutate a returnedResult.Configand expect its frozen inspection output to change. - ConfigKit owns composition, strict decoding, metadata, and safe rendering. Notarius owns typed defaults, field semantics, recorded normalization, conversion, catalog resolution, and physical run preparation.
- Application paths use typed ConfigKit key/index constructors; never build JSON Pointers by concatenating unescaped pipeline IDs or reference keys.
Document Shape And Presence
Use a distinct finite ValidatorDocument containing only module,
llm_profile, structured_output_repair_attempts, retries, and options.
The producer BindingDocument contains those fields plus references,
validation policy, and a validator list. This avoids recursive Go schema types
and makes forbidden nested validator fields strict unknown-field errors.
Use pointer scalars only when nil means inheritance or omission, and ordinary structs/maps/slices elsewhere as appropriate to the contract. Use ConfigKit presence before normalization changes paths. Remove custom parser-presence flags. In particular:
- omitted validators use the production chain; explicit
[]selects an empty chain; presentnullis invalid, matching the existing array contract; - omitted repair attempts inherit; explicit zero disables repair; present null is invalid for this integer setting;
- omitted LLM-profile fields inherit; explicitly blank or null selections are invalid;
- omitted optional module bindings permit existing catalog defaults; a present binding must be a valid object with a nonempty module;
- reference objects have exactly one selected source: nonempty
pathor a completeartifactobject with step and lane; explicit invalid branches must not disappear during pointer conversion; - preserve the single-step
artifactsform and the orderedstepsform; reject simultaneous declarations and explicitly empty/nullsteps; - retain optional null behavior where the existing contract permits clearing an optional object or collection; do not impose a universal no-null rule;
- remove the lane-level reference alias. Keep the existing treatment of the deprecated lane-level validator list during migration (empty allowed, nonempty rejected) rather than adding an unrelated contract removal.
Static defaults stay as currently documented. Derive omitted extraction workers from the final configured total LLM limit. ConfigKit profile null replacement is subject to the destination field's existing domain validity; overlay support does not make null valid for every setting.
CLI And Path Ownership
| Input | Owner / treatment |
|---|---|
--config, NOTARIUS_CONFIG, installed fallback |
Existing CLI discovery; ConfigKit receives the selected file |
--config-profile |
ConfigKit profile selection on all five configuration-loading command forms |
Explicit --output-dir |
ConfigKit override at /output/directory |
Explicit --debug-dir |
ConfigKit override at /debug/directory; preserve --debug requirement |
Explicit --chunk_cache |
ConfigKit override at /cache/chunk_plans/mode |
--llm-profile |
Existing runtime selection overriding all selected LLM bindings; not a replacement for a single YAML field |
| Input, lanes, session ID, reasoning effort, references, resume/recompute, debug enablement, JSON output | Existing invocation actions outside ConfigKit |
| Seven retired operational environment variables | No longer read or applied; no deprecation-warning scan of process environment |
| PromptKit credential environment variables | Existing PromptKit responsibility |
Only explicitly supplied flags create override records, with safe flag names
as provenance. Preserve current blank-value checks and invocation error classes.
For --config-profile, pass the supplied name through ConfigKit's selection
rules; empty and exact default select no overlay. Do not invent stricter name
validation or whitespace normalization than the upstream profile contract.
Enable imports without a separate Notarius enablement flag. The canonical selected file's directory is the configuration root. Every nonempty configured relative filesystem path (external references, PromptKit profile sources, and output/cache/debug directories) uses the surviving declaration's canonical directory. Built-in relative defaults use the main configuration directory; explicit CLI values use the invocation directory. Absolute values remain unchanged. Empty optional cache paths retain their existing meaning.
Resolve paths before runtime conversion without reading target contents. CLI references retain their existing runtime handling and generated-reference protection. Configuration-root confinement applies only to ConfigKit's imported configuration and overlay files, not application paths. Do not infer path semantics for arbitrary dynamic module options.
Stage 1 — Add ConfigKit And The Version 5 Document Types
Status: Completed 2026-09-13.
Goal: Establish the new file contract without switching production commands.
Work:
- Pin v0.3.0 in
go.mod/go.sumand inspect its actual exported API. - Add
document.goandload.goininternal/core/config(or equivalent cohesive files) with the complete plain document types, finite validator type, explicit reference union, and YAML-tagged validation-policy fields. - Provide a private reusable definition factory with fresh static defaults and
the exact version rule. Define a small application
LoadOptionsfor profile selection and explicit configuration overrides; avoid a generic loader abstraction or exposing parser types. - Keep the new load entry point unconnected to production until its normalization and validation stages are complete. Mark it internal scaffolding; do not advertise partially implemented v5 support.
- Add the next available ADR (expected
0018-adopt-configkit.md) recording the accepted ownership, v5 break, imports/profiles, and environment policy. Record application path-base policy and the released source-origin APIs; do not claim the Notarius migration is implemented merely because upstream is.
Acceptance: v5 numeric/quoted versions decode; unsupported versions fail before document decoding; representative canonical bindings and references decode while shorthand/unknown fields fail. Separate loads do not alias defaults. The current CLI still works through its existing loader.
Validation: go test ./internal/core/config; go test ./... to confirm the
new dependency and schema types do not break the existing build. Keep tests
small; generic YAML exhaustiveness remains upstream.
Prompt size: Small to medium; types, dependency, and version boundary only.
Stage 2 — Implement Operational Defaults And Normalization
Status: Completed 2026-09-13.
Goal: Make operational configuration behavior correct and inspectable.
Work:
- Express existing concurrency, output, cache, debug, and PromptKit-local-backend defaults and validations through the definition callbacks.
- Use recorded
Default/Normalizeoperations for derived extraction workers, path whitespace, cache mode, and endpoint whitespace. Derived defaults must follow profile/CLI overlays and preserve explicit zero for validation. - Use presence-aware checks for explicit empty/null PromptKit source selectors and required fields in a present local-backend object. Preserve mutually exclusive profile sources and the existing endpoint/range constraints.
- Keep filesystem checks and PromptKit profile catalog lookup out of callbacks. This stage trims filesystem paths but does not yet resolve their bases; Stage 7 owns all source-aware path normalization.
- Extract small reusable value checks where needed so runtime validation and document validation do not separately implement the same rule. Collect independent errors through ConfigKit with safe messages and field paths.
Acceptance: defaults and explicit values retain their meanings; worker derivation follows effective total concurrency; multiple independent field errors identify their source locations; normalization appears in explanation without generating warnings.
Validation: go test ./internal/core/config. Add load-boundary cases for
profiles overriding concurrency, CLI overrides, invalid output versus empty
cache paths, and local backend presence. No live profile resolution.
Prompt size: Medium; operational fields only.
Stage 3 — Implement Pipeline Document Rules
Status: Completed 2026-09-13.
Goal: Validate and normalize the complete pipeline file shape before catalog resolution.
Work:
- Implement one typed traversal of pipelines, steps, lanes, bindings, validators, and reference sources. Prefer shared binding helpers and explicit walks over reflective generic validation.
- Perform all presence-sensitive checks against original input paths before renaming map keys. Detect whitespace collisions deterministically before writing normalized keys. Report both paths where useful.
- Normalize identifiers, selected profiles, and generated selectors with recorded changes. For map-key moves, record the parent operation and relevant source dependencies; preserve a load-local original-path mapping for Stage 7. Do not guess origin from a normalized key after it has moved.
- Enforce the field and shape contracts above, including global lane-ID uniqueness across steps, valid retry values, validation-policy fields, and allowed binding-local reference/validator placements.
- Keep stage/module compatibility and actual option meaning in the existing catalog resolver. Do not reject a module based on an embedded allowlist in the document schema.
- Factor shared semantic value checks with
Config.Validatewhere necessary. Keep file presence/shape checks in the document callback and direct runtime construction checks at the runtime boundary; avoid parallel complete validators with independently copied rules.
Acceptance: single-step and ordered pipelines behave as specified; explicit empty validator chains and zero repair budgets survive; invalid references, profile selectors, normalized collisions, and duplicate lane IDs fail with useful paths. No mutation is hidden from ConfigKit's change recorder.
Validation: go test ./internal/core/config ./internal/framework/pipeline.
Migrate meaningful rule cases from the old parser tests to the new load
boundary as they gain an owner; retain old-loader coverage until cutover.
Prompt size: Medium to large, bounded to pipeline document semantics; do not combine it with source-origin or CLI work.
Stage 4 — Convert Documents Into Runtime Configuration
Status: Completed 2026-09-13.
Goal: Produce the existing typed runtime composition from the new document.
Work:
- Add a narrow explicit conversion after successful ConfigKit loading. Build
independently owned
Configandpipeline.PipelineProfilevalues, preserving map identities, step order, inheritance pointers, empty chains, and reference source forms. Convert the finite validator document to runtime bindings. - Wrap the result in
LoadedConfig, retaining the private ConfigKit snapshot. Provide safe effective/explanation writers and the runtime configuration; avoid a public unredacted serialization method. - Stop re-deriving workers on the already finalized new-load path. Retain
programmatic
Configvalidation and ownership checks. Any legacy recomputation shim needed by the old loader is temporary and removed at Stage 12. Runtime callers needing defaults should use the same default values as the document definition, not a second literal default table. - Keep
Config.Resolve/pipeline.ResolvePipelineresponsible for selection, capabilities, concrete validation-policy inheritance, options, references, and digest generation. Do not add configuration profile name or source history to semantic pipeline identity.
Acceptance: equivalent canonical documents produce equivalent runtime pipelines; changing a semantic setting changes identity, while merely importing an equivalent definition does not. Mutating caller-owned maps/slices cannot alter the snapshot or resolved pipeline. ConfigKit changes do not force its types into module interfaces.
Validation: go test ./internal/core/config ./internal/framework/pipeline.
Use a real test catalog and representative reference/validator chains; resolve
absolute reference fixtures until Stage 7 supplies relative path resolution.
Prompt size: Medium; conversion and runtime ownership only.
Stage 5 — Support ConfigKit Dynamic Integers In Modules
Goal: Ensure the module boundary accepts valid ConfigKit options.
Work:
- Inspect every production numeric
map[string]anyoption consumer. The known sites are generic unit chunking and JSON evidencewindow_units. - Add one narrow shared option conversion helper in the existing pipeline
option-helper area. Accept
int64from ConfigKit and nativeintfrom programmatically constructed module bindings. Range-checkint64before conversion; keep domain positivity/nonnegativity at the module owner. - Preserve rejection of floats and strings. Do not recursively rewrite all options, change their serialized representation, or add general numeric coercion. Keep options in ConfigKit form through config conversion.
- Update the old test that rejects
int64to protect the newly accepted boundary. Add representative real-loader-to-module tests, including nested JSON evidence options.
Acceptance: integral options work from YAML and programmatic bindings; fractional/string inputs and invalid ranges still fail before preparation. No unchecked narrowing or YAML round-trip remains at these consumers.
Validation: go test ./internal/modules/generic/... ./internal/framework/pipeline ./internal/core/config.
Prompt size: Small; two known consumers and a narrow shared helper.
Stage 6 — Prepare Debug Rendering For The ConfigKit Result
Goal: Prepare the publication boundary before changing command loading.
Work:
- Add a small writer interface such as
WriteEffective(io.Writer) errorto the debug summary boundary.LoadedConfigsatisfies it without exposing a concrete ConfigKit result to debug storage. - Add publication of
effective-config.ymlthrough the existing confined, atomic summary file writer. Buffer redacted output before publishing if the current file API takes bytes; propagate rendering and write errors. - Preserve the existing redacted resolved-pipeline writer and all reference
content exclusion. The old
effective-config.jsonwriter remains only until the run command switches in Stage 10. - Reuse
sensitiveConfigKeyfor the definition's dynamic secret predicate at nested options paths, including validator options, containers, and moved map keys. Keep scalar classification policy in one owner. Do not log raw configuration or metadata snapshots that contain values.
Acceptance: redacted effective YAML publishes atomically; failure leaves no partial artifact; nested sensitive values never appear in effective output or explanation. Resolved runtime summaries still omit materialized references.
Validation: go test ./internal/core/config ./internal/core/debugbundle.
Test rendering failure and caller mutation at these real local boundaries.
Prompt size: Small to medium; rendering interface and publication only.
Stage 7 — Resolve Declaration-Relative Paths
Goal: Normalize file paths using their effective source origins and retain correct ConfigKit inspection history.
Work:
- Query
resolution.InputInfo(originalPath)for each path-bearing scalar. It returns(FieldInfo, bool)from fixed entry-state metadata, unaffected by earlier normalization, additions, removals, or moves. Use Stage 3's original path mapping for renamed keys. ReadOrigin.Layerand the exact scalar'sOrigin.Locations; useresolution.Source(location.SourceID)to obtain(SourceInfo, bool)and its canonicalPath. Do not select an arbitrary origin of a composite parent. - Implement one narrow application path-base helper shared by typed traversals. Base/profile file values use the declaring file directory; default relative values use the canonical main directory; CLI relative values use the invocation directory supplied by the CLI load boundary. Capture that directory once for the load. Obtain canonical main-file identity consistently with ConfigKit's acquired source records; no second parse or load.
- Normalize nonempty relative external references, PromptKit profile sources, and output/cache/debug directories to absolute paths. Preserve already absolute values after existing whitespace handling. Empty optional cache paths retain their automatic/disabled semantics. Do not inspect target existence or read referenced content.
- Record normalized paths and map moves so frozen inspection matches runtime and traces the original declaration. Keep collision checks ahead of mutation and process stable sorted keys. Use retained input metadata only as provenance, not as a substitute for reading the current working value.
- Fail with a safe field-level resolution diagnostic if a relative value
unexpectedly lacks an unambiguous required source/base. Defaults are a known
case, not missing provenance. Do not copy the upstream example's optional
warning-and-leave-relative fallback or impose confinement on application
paths. Queries are callback-scoped; detached records may be retained, but
never retain the
Resolutioncontext for post-load queries. - Enable imports and selected profiles on the public new loader, using
Fileand the selected file's canonical directory as the root. Keep CLI references runtime-owned and use ConfigKit's composition semantics directly.
Acceptance: root, fragment, and overlay values resolve from their own files; inherited values retain their base origin; equal-valued replacements identify their new source; profile-replaced sequences and normalized enclosing IDs retain attribution. Destinations follow the same rule as references. Defaults use the main directory; CLI overrides use the invocation directory. Effective and explanation output agree with runtime. No second load or new file-access policy.
Validation: go test ./internal/core/config ./internal/framework/pipeline.
Use temporary trees with same-named references in different directories,
canonical symlink targets accepted by ConfigKit, profile replacements,
normalized keys, default and CLI destinations, empty cache paths, and absolute
paths outside the config root. Change the working directory in a controlled
test to distinguish the bases. Materialize a real reference in one case to
prove the intended bytes are chosen. Protect Notarius policy at the load
boundary; generic fixed-view/record-detachment tests remain upstream.
Prompt size: Medium; one shared path-base helper and its typed consumers, with no CLI activation or example migration.
Stage 8 — Prepare Shared CLI Loading And Diagnostics
Goal: Make one command-facing configuration entry point ready for cutover.
Work:
- Add a CLI helper that discovers the file, passes profile selection and
explicit override records to the new loader, and returns
LoadedConfigplus the selected config path. Supply the invocation directory needed for relative CLI overrides. Keep discovery order and injected test lookup. - Add a shared load-error reporter using
errors.Asfor ConfigKit load errors andWriteDiagnostics; supply safe source context using the dependency's source records. Do not flatten multi-error output into repeated wrappers or print raw parser/source contents. Preserve status 1 for valid invocations whose configuration cannot load. - Add shared flag registration/parsing only where it reduces actual repeated code. Use existing CLI parsing conventions, including flags surrounding positional arguments. Preserve missing-value and unexpected-argument checks.
- Route successful ConfigKit process diagnostics through one reporting owner. Define no new application warnings for successful imports, defaulting, normalization, or ignored retired environment names. Current application rules should ordinarily return no nonfatal configuration diagnostics.
Acceptance: the helper returns a fully validated loaded value with safe diagnostics and uses the requested source/profile. No production command has switched accidentally; existing command tests remain green.
Validation: go test ./internal/cli ./internal/core/config. Add direct
helper tests for explicit flags versus parser defaults, discovery, safe
multi-error reporting, and profile selection. Use the real ConfigKit boundary.
Prompt size: Medium; helper and diagnostic wiring only.
Stage 9 — Prepare Validation And Pipeline Listing Commands
Goal: Finish the read-only command implementations before the shared production cutover.
Work:
- Implement v5 handlers for
config validateandpipelines listusing the new helper and--config-profile. Preserve--pipeline,--only, JSON listing, sorting, and exit/stream behavior. Keep production dispatch on the old handlers until Stage 10 so all configuration commands switch together. Use temporary explicit handler names, not a runtime version switch. - Plain validation is configuration-only. Selected-pipeline validation still performs catalog and explicit PromptKit profile checks at their existing owners, without provider calls or credential requirements beyond existing offline profile validation.
- Test these handlers with dedicated v5 fixtures and production registries. Keep existing dispatch and maintained-example tests intact until Stage 10. Do not weaken tests to accept both versions or advertise the unconnected handlers as implemented CLI features.
Acceptance: the new handlers see imported pipelines and overlay changes, reject v4, and retain useful module/profile failure context. Existing public command behavior stays coherent until all three handlers switch in Stage 10.
Validation: go test ./internal/cli ./internal/core/config; run the focused
production validation cases using v5 fixtures, including unknown modules and
incompatible generated references.
Prompt size: Medium; two closely related read-only commands.
Stage 10 — Switch Run And The Maintained V5 Configurations
Goal: Complete production cutover and make all runs use one ConfigKit load.
Work:
- Switch
runPipelineCommandto the new loader. Translate only the three config-valued flags in the decision table to ConfigKit overrides; remove their direct post-load mutations. Add--config-profileconsistently to argument reordering and missing-value checks. Activate Stage 9's validation and listing handlers in the same change and remove superseded handlers. - Preserve runtime LLM override, references, input, session identity, resume/recompute, scheduler setup, and output receipt semantics. Config failures must still occur before source parsing and physical run allocation.
- Pass the retained loaded result to the effective YAML debug writer. Continue
to write the separately redacted resolved pipeline after reference
materialization. Add effective configuration profile selection to debug
invocation metadata (
config_profile), with no overlay represented as empty. Do not add profile-selection text to checkpoint or pipeline identity. - Convert both maintained D&D example configurations to v5 object syntax in place, still as self-contained files in this stage. Change the complete example's PromptKit source path for declaration-relative interpretation. Review destination paths in both examples: retain their declared relative strings unless an example's intended location requires a deliberate change; explain their new configuration-file base.
- Migrate run/reference/state tests and the maintained-example loader to v5. Split mechanical fixture updates from behavioral assertions within the diff so regressions remain reviewable. Retired env-based tests now supply YAML or explicit flags; keep one test proving those variables have no effect.
- Update current CLI, configuration, operations, and consumer references for
v5, profile selection, environment retirement, path bases (including defaults
and destinations), and
effective-config.yml.
Acceptance: all production commands use the new helper; imported/profile configuration and explicit overrides agree with execution and debug output; the maintained minimal and full fake-provider workflows still complete with the same artifact families and valid reference handoffs.
Validation: go test ./internal/cli ./internal/core/config ./internal/core/debugbundle ./internal/framework/pipeline.
Use the existing complete D&D fake LLM test instead of a live backend. Verify
JSON run receipt and stderr warning behavior remain unchanged.
Prompt size: Largest cutover prompt, limited to wiring, fixture migration, and its current documentation. Schema, paths, numeric adapters, diagnostic helpers, and debug writer must already be complete in earlier stages.
Stage 11 — Add Effective And Explanation Commands
Goal: Expose the accepted operator inspection workflow.
Work:
- Add
config effectiveandconfig explainwith the syntax in the feature roadmap, including--configand--config-profile. - Effective writes only
LoadedConfig.WriteEffectiveoutput. Explain accepts exactly one JSON Pointer positional argument, parses it with ConfigKit, and calls the safe explanation writer. Accept the empty pointer for the root when explicitly supplied; reject malformed escapes/argument shape with status 2. A well-formed unknown path returns status 1 and no success output. - Use ConfigKit's contextual sequence-index rules. Signed numeric mapping
keys remain valid keys;
/steps/+0cannot identify a sequence element. Do not add a blanket numeric-token restriction that rejects legal map keys. - Preserve writer failure handling. Explanations are human-oriented text; effective configuration is YAML. Neither output is promised to be a reloadable config or a new durable JSON API. Do not print success banners.
- Avoid LLM client construction, module preparation, reference materialization, state allocation, or pipeline selection. Existing catalog registration at CLI composition is acceptable; it must not trigger provider work.
- Update
docs/cli.md, the configuration inspection guidance, and help.
Acceptance: both commands work against imports/overlays, redaction applies to history and effective values, removed keys remain explainable, malformed and unknown paths fail appropriately, and stdout contains only requested data.
Validation: go test ./internal/cli ./internal/core/config. Use real
ConfigKit with temporary files and a provider factory that fails if called.
Prompt size: Medium; two commands sharing the completed load boundary.
Stage 12 — Remove The Legacy Configuration Machinery
Goal: Finish the simplification promised by the adoption.
Work:
- Remove the unused v4 parser, YAML hooks,
FileConfigapplication APIs, environment-overlay code, parser-only presence flags, and old loading helper. - Remove
extractWorkersConfigured/defaultedExtractWorkersinference once file defaults are finalized by ConfigKit. Programmatically constructed configs treat a supplied worker entry as explicit and derive only an absent entry; update internal construction tests accordingly. Keep runtime checks throughConfig.Resolvefor direct callers, without mutating shared maps. - Delete the superseded
effective-config.jsonpublication method and raw configuration summary/redaction functions. Retain runtime ownership cloning and resolved-pipeline redaction that still serve real callers. - Remove obsolete parser/env tests; retain behavioral coverage at the new
owner. Remove
yaml.v3from the embedded-prompt test inproduction_contract_test.go: exercise the production PromptKit loader or existing asset-loading seam, rather than adopting ConfigKit to parse PromptKit assets or adding another YAML dependency for the test. - Run
go mod tidy; remove the directgopkg.in/yaml.v3requirement and verify Notarius Go files no longer import a YAML parser. Dependencies may retain their private YAML implementation.
Acceptance: no alternate loader, environment application layer, redundant configuration redactor, or parser-only clone remains. Legitimate pipeline and artifact serialization code is unaffected. Programmatic config behavior has an explicit tested ownership/default contract.
Validation: go test ./...; go vet ./...; search for UnmarshalYAML,
ApplyFileConfig, ApplyEnvOverrides, gopkg.in/yaml, and old effective-config
artifact references. Inspect matches rather than treating historical docs or
negative migration tests as failures.
Prompt size: Medium; deletion and ownership consolidation after all callers have moved.
Stage 13 — Demonstrate Imports And Development Profiles
Goal: Supply two maintained operator examples covering simple and composed configuration, with a clear distinction between the two profile systems.
Work:
- Keep
examples/dnd-minimal.config.ymlself-contained. - Keep
examples/dnd-complete.config.ymlas the complete root and move its wholednd-sessionpipeline intoexamples/pipelines/dnd-session.yml, imported throughconfigkit.imports. Only the root declares version. Fix fragment-relative campaign/catalog references to../dnd-party.txt,../dnd-glossary.txt, and../dnd-spell-catalog.jsonwhere applicable. - Move the existing PromptKit example to
examples/llm-profiles/dnd-extraction.ymland point the root'spromptkit.profile_fileto./llm-profiles/dnd-extraction.yml. - Add
examples/profiles/development.ymlas a ConfigKit overlay. Setconcurrency.total_llm: 1andstage_workers.extract: 1, plus a development output directory. Include a short comment explaining how an operator can change the pipeline'sllm_profileindependently; do not invent an unavailable model profile just to demonstrate the field. The overlay has no version or import directive. - Extend the existing maintained-example test to load base and development configurations through production code, validate catalogs/profiles offline, and exercise complete fake-provider reference materialization from a working directory other than the example directory.
- Update
docs/config.md,docs/operations.md,docs/consumers/subprocess.md, anddocs/consumers/dnd-pipeline.mdat their canonical scopes. Link the examples; explain disjoint composition and whole-sequence replacement instead of duplicating complete configs in prose.
Acceptance: all example paths resolve, the base and development selections
validate, the full pipeline still contains every implemented D&D lane, and
operators can distinguish --config-profile from --llm-profile.
Validation: go test ./internal/cli; production CLI validation for both
roots and the development overlay. No API key or live generation is needed;
use only the documented placeholder if offline PromptKit validation requires
one. Verify all moved example links.
Prompt size: Medium; examples and their operator-facing documentation.
Stage 14 — Final Architecture And Contract Verification
Goal: Verify the completed architecture and reconcile canonical docs with the implemented boundaries.
Work:
- Review the complete diff against
configkit.mdand this plan. Check that no stage was bypassed, no source-origin workaround weakened the selected contract, and no unsupported step-composition semantics slipped in. - Finalize
docs/internal/configuration.md,docs/internal/cli.md,docs/internal/state.md, anddocs/internal/overview.md. Adddocs/integrations/pkg-configkit.mdfor the pinned API boundary and relevant upstream semantics; link upstream for general library behavior. - Update architecture's configuration ownership/precedence text and finalize the adoption ADR. Document public behavior in its canonical owner; do not retroactively edit historical release notes or prior accepted ADR decisions.
- Ensure ConfigKit warning/diagnostic handling, safe rendering, source-relative paths, runtime option validation, direct construction, and digest semantics all have sufficient focused coverage. Do not duplicate upstream's test suite or create exact-output change detectors.
- Verify current docs no longer recommend v4 syntax, retired environment
overlays, the former PromptKit example path, or
effective-config.json. Migration explanations and historical records may legitimately name them. - Complete the checks below and report any remaining issue explicitly. Do not tag a release, push, or retire the active roadmap during implementation without a separate request.
Acceptance: every feature acceptance criterion holds, current docs and examples agree with runtime behavior, all required checks pass, and the remaining configuration code expresses Notarius policy rather than rebuilding ConfigKit machinery.
Validation:
go test ./...
go vet ./...
go build ./cmd/notarius
go run ./cmd/notarius config validate --config examples/dnd-minimal.config.yml --pipeline dnd-session
go run ./cmd/notarius config validate --config examples/dnd-complete.config.yml --pipeline dnd-session
go run ./cmd/notarius config validate --config examples/dnd-complete.config.yml --config-profile development --pipeline dnd-session
go run ./cmd/notarius pipelines list --config examples/dnd-complete.config.yml --config-profile development --json
go run ./cmd/notarius config effective --config examples/dnd-complete.config.yml --config-profile development
go run ./cmd/notarius config explain --config examples/dnd-complete.config.yml --config-profile development /concurrency/total_llm
git diff --check
Use the project's normal link-check mechanism if present; otherwise check changed relative links and heading anchors manually. Live model evaluation is unnecessary for a configuration migration.
Prompt size: Medium; verification and focused documentation reconciliation, with no planned production redesign.
Released API And Readiness
ConfigKit v0.3.0 provides both required query methods in its public
lifecycle.go:
(*Resolution).InputInfo(Path) (FieldInfo, bool): fixed decoded entry metadata, including retained defaults and explicit null; missing or shape-mismatched paths return false.(*Resolution).Source(string) (SourceInfo, bool): detached acquired-source records; empty/unknown IDs return false; non-file sources have no invented filesystem path.
Both methods are callback-scoped and perform no new filesystem or credential
lookup. Their entry-state contract permits querying original paths even after
map-key normalization. Existing Normalize/Record and result renderers
complete the one-load workflow; no upstream extension remains outstanding.
Review docs/releases/v0.3.0.md, the recorded-resolution contract in
docs/config.md, and examples/consumers/consumers.go /
consumers_test.go in the pinned dependency. The example demonstrates
LoadNotariusPaths; adopt its public-API usage, not its optional fallback
policy. Its warning-and-leave-relative behavior is not the Notarius contract.
The public-facade refactor needs no consumer migration; do not reach into the private engine. Go 1.25 compatibility and the private YAML v4 prerelease pin are unchanged. Upstream macOS support is best-effort, matching Notarius policy; do not add platform gates as part of this adoption.
No open product decisions or upstream prerequisites remain.