30 KiB
ConfigKit Adoption Implementation Plan
Status: Pending
Implement the ConfigKit adoption roadmap in the order below. The target is Narratio v2.0.0: ConfigKit v0.1.1, the reduced platform matrix, and the artifact-authoring workflow described there. Each stage is a bounded implementation assignment for a gpt-5.6-terra coding agent. Mark a stage complete only after its changes and acceptance checks succeed; record a short completion and verification note beside its status.
Instructions for every stage
- Read
docs/development.mdand all three policies underdocs/policy/. Read the relevant roadmap section and the stage-specific sources listed below. Paths identify starting points, not an exhaustive edit allowlist; inspect callers and tests before changing a package boundary. - Use the tagged ConfigKit public API. The local
../ConfigKitcheckout is useful reference material, but verify its version before relying on it. The module cache for v0.1.1 is an alternative. Do not require a sibling checkout, a localreplace, a Go workspace, an upstream patch, or an unpinned revision to build. - Keep Narratio domain policy in
internal/config, startup and selectors ininternal/app, and credential delivery in the owning adapters. Do not build a general configuration framework around ConfigKit. - Keep each stage buildable and run focused behavioral tests for its changed boundary. Temporary migration helpers may exist between stages only where named here; they must not introduce runtime format auto-detection or fallback.
- Update each canonical current-behavior document when its behavior changes. The roadmap remains the owner of unimplemented work. Preserve historical release notes; their descriptions of old platforms and formats are history.
- Tests use real ConfigKit and temporary files, with fake external adapters or storage where needed. Use synthetic credentials and isolate environment mutation; such tests must not run in parallel. Retain domain and security coverage, remove superseded mechanism tests, and avoid exact diagnostic prose or private callback choreography assertions.
- Do not tag or publish v2.0.0 as part of this plan. Release preparation belongs here; release authorization and tag publication are a later task. The existing release completion boundary remains successful upstream tag push, with no CI polling or waiting.
Decisions that apply across stages
The roadmap is authoritative for product scope. These implementation choices make that scope concrete:
- Enable ConfigKit imports and profile selection for pipeline files only.
Campaign, session, and party are single-source definitions with imports and
profiles disabled. Preserve their domain schemas and source-relative inputs.
Omitted or empty profile and the exact name
defaultselect the base; other names use ConfigKit's fixedprofiles/<name>.ymlconvention. - Defaults apply to absent fields. Explicit empty/zero/false/null values follow ConfigKit decoding and then domain validation; do not refill invalid explicit values with legacy defaults. Optional null stage sections remain absent; required sections fail domain validation rather than being resurrected.
- Keep ConfigKit metadata/result snapshots separate from runtime-only Narratio state. ConfigKit discards ignored and unexported fields when detaching values; attach root paths, retained party bytes, family catalogs, and digest metadata after loading. No validator may mutate bound configuration.
- Use one Narratio-owned safe effective projection for the final expanded pipeline. It contains configuration values and credential reference names, never resolved credential bytes. It feeds show, semantic diff, digest, and catalog data. ConfigKit metadata remains the authority for input provenance; Narratio adds only domain-generated attribution.
- Rename
storage.s3.access_key_id_envtoaccess_key_id,storage.s3.secret_access_key_envtosecret_access_key, andaudita.llm_api_key_envtollm_api_key. Each is an optional*configkit.Secret. An explicit reference always resolves during loading, even if the associated stage will not execute. Require both S3 fields together or neither. With neither, retain the AWS SDK default credential chain; do not synthesize mandatory secret references that would break role-based authentication or local-only inspection. With no Audita reference, preserve its existing downstream credential discovery. Explicit references replace Narratio's old per-adapter environment lookup. - Keep absent S3 credentials out of defaults; migration examples explicitly name the existing AWS variables when static credentials are intended. A non-nil missing/empty reference fails instead of silently falling back. This preserves provider-managed credentials while making explicit configuration trustworthy.
config artifactsuses the existing configuration command selectors and text output convention. No new JSON mode, artifact creation wizard, inferred filename identity, or profile catalog is added.- Preserve the roadmap's unique base artifact definition rule explicitly: ConfigKit rejects conflicting leaves but can compose disjoint children of the same mapping. Use its public source metadata to reject an ordinary artifact key declared by multiple base files, even if their fields are disjoint. Apply the same rule to family keys. A selected profile may override or extend a definition. This is a narrow Narratio declaration rule, not a different merge algorithm.
- Relax only the obsolete configuration/secrets filesystem restrictions identified by the roadmap. Do not remove session restore, cleanup, publication, workspace confinement, or ordinary shared-workspace mode guarantees as collateral work.
Stage 1 — Reduce the platform and release matrix
Status: Pending
Read: roadmap “Platform Contract”; scripts/build-release-assets.sh,
scripts/check-release-candidate.sh, .woodpecker/, internal/releasecheck,
docs/release.md, and platform-specific files in application, file operations,
and subprocess packages.
- Make Linux amd64, Linux arm64, and macOS arm64 the only maintained build and release targets. Remove Windows suffix/packaging branches and macOS amd64 targets from scripts, workflows, tests, and current documentation.
- Delete Windows-only implementations and tests; simplify paired build-tag files where the remaining implementations can be shared. Preserve portable code when retaining it adds no special support machinery. Preserve functioning Unix subprocess cancellation, locking, and file operations on both supported OSes.
- Retain the local Linux verification gates and cross-build gates for all three published assets. Do not introduce a native macOS runner requirement or claim that cross-compilation verifies runtime behavior.
- Update architecture/operations platform statements and contributor/release guidance. Keep existing release protections and the no-CI-wait boundary intact.
Acceptance: focused platform/subprocess/fileops/releasecheck tests pass; build all
three targets with CGO_ENABLED=0; verify the asset builder produces exactly the
three expected versioned binaries and the host binary reports the supplied version.
Existing release tests still prove guarded publication. No current support promise
or asset list retains Windows or macOS amd64.
Stage 2 — Separate configuration normalization from validation
Status: Pending
Read: internal/config/load.go, defaults.go, normalize.go, validate*.go,
artifact_families.go, and ConfigKit's “Recorded application resolution”,
“Semantic validation”, and “Defaults and typed decoding” contracts.
- Separate value-producing normalization from read-only validation, including
Notarius reference/output normalization currently performed by
validateNotarius. Audit all validators for map, slice, pointer, and scalar mutations, including changes that would occur only on error paths. - Keep reusable domain normalization ordinary package code. Validation checks the already normalized candidate and must work for both loaded and programmatically constructed values without silently fixing them.
- Inventory defaults in
load.go: static defaults, per-map-entry/list-element defaults, and inherited or presence-sensitive defaults. Retain named constants indefaults.go. This inventory should guide code and concise comments, not become a permanent parallel defaults document. - Keep the current loader calling normalization explicitly before validation during this preparatory stage. The ConfigKit resolver replaces that loading choreography in Stage 4. Preserve family expansion as an explicit domain step.
Acceptance: focused configuration tests prove normalization is deterministic and validation does not mutate accepted or rejected inputs. Cover representative Notarius normalization/collision handling and existing artifact/publish rules. Do not add a separate mutation test for every trivial validator.
Stage 3 — Replace composition-dependent effective projection
Status: Pending
Read: internal/config/effective_pipeline.go, effective_digest.go,
pipeline_composition.go, internal/app/config_commands.go, the config-diff
implementation, and docs/internal/configuration.md.
- Introduce one package-owned, schema-specific safe projection of the effective pipeline. Use explicit typed projections with stable configuration field names; preserve null versus empty containers and exact integers. Ordinary nested configuration types may be reused where they contain no runtime state. Credential fields must be projected explicitly as reference names when Stage 7 changes their types. Do not introduce a generic reflection schema engine.
- Render this projection as deterministic YAML using the existing YAML encoder;
encoding output is not a replacement YAML loader. Produce semantic records
directly from the safe projection using standard JSON encoding/decoding with
number preservation if a plain value tree is useful. Do not reparse YAML or
reconstruct
compositionNodetrees to compare or hash effective values. - Make show, diff values, and digest use that common projection. Mapping order is immaterial; sequences remain atomic for diff; nil and empty remain distinct. Exclude family declarations once expanded, all runtime metadata, profile names, source locations, and raw credentials. Preserve logical Notarius path handling so moving an equivalent bundle does not change its digest.
- Version the changed digest encoding explicitly using the package's digest version constant. Digest/profile metadata remains provenance, never a blanket resume cache key. Keep stage/artifact semantic evidence behavior intact.
- Decouple enumeration of effective field paths from the legacy composition renderer. Input ownership can still come from the current loader until Stage 4.
Acceptance: show/diff/digest tests cover equivalent monolithic/split values, different map insertion order, null/empty distinction, family-expanded outputs, and bundle relocation. A meaningful artifact change changes the digest; formatting and profile naming alone do not. Existing semantic resume tests pass. No effective projection consumer needs the custom composition parser/renderer.
Stage 4 — Cut pipeline loading over to ConfigKit
Status: Pending
Read: ConfigKit v0.1.1 docs/config.md, docs/consumers/README.md, and its
Narratio-shaped compiled example; Narratio internal/config/load.go,
composition.go, pipeline_composition.go, and profile/composition tests.
- Pin
gitea.maximumdirect.net/eric/ConfigKit v0.1.1ingo.mod/go.sum. Implement a small pipeline definition usingLoad,File, enabled imports,Options.Profile, defaults, recorded resolution, and read-only validation. - Migrate static defaults to fresh default values. Apply map-entry, list-element,
and inherited defaults with
Resolution.Default/IsExplicit; use recorded normalization for transformations from Stage 2. Do not call old post-decode zero-filling defaults. RemoveNormalizeConfig.UnmarshalYAMLand its private presence bit. Include publication list-entry defaults, optional section allocation, and trim's Scriptorium requirement in this conversion. - Perform file-local semantic checks in the definition. Defer checks needing canonical party expansion or campaign/session context to the existing explicit resolution boundary; do not reject references to future concrete family members merely because the campaign has not loaded yet. Run full checks after expansion.
- Replace pipeline composition and profile loaders.
configkit.importsis the only directive; nocompositionor_configkitalias is accepted. Pass profile selection directly under the agreed convention; unselected overlays are not read.LoadPipelineProfilePairmay remain a thin pair of independent loads, with no shared parse cache or paired source-snapshot promise. - Adapt ConfigKit sources and metadata to Narratio's existing reporting records. Translate paths structurally using public path APIs, not error-message parsing. Preserve multiple contributing locations for composite/profile-owned mappings and distinguish defaults/resolution from authored values. Check artifact/family map-entry ownership for duplicate base declarations using those locations; ignore synthetic defaults and allow the selected overlay.
- Attach runtime metadata only after load succeeds. Preserve root-relative runtime path interpretation and capture logical paths before making them absolute. Retain family declarations and original ConfigKit metadata for later expansion.
- Replace generic mechanism tests with a small integration set proving actual
Narratio wiring. Convert fixtures affected by this stage. Delete
composition.go, obsolete pipeline traversal/overlay/presence machinery, and their redundant tests once the callers are migrated.
Acceptance: a real root with imports and a testing overlay loads; duplicate claims
fail with useful source attribution, including two base files contributing disjoint
fields to one artifact or family key; base/default selection works; malformed
unselected profiles do not fail base loading. Omitted defaults and representative
explicit false/zero/empty/null inputs obey the new semantics. Invalid domain values
still fail. Commands use the new pipeline loader with no format fallback. Update
docs/config.md and configuration internals for the implemented boundary.
Stage 5 — Migrate campaign, session, and canonical party loading
Status: Pending
Read: internal/config/load.go, party.go, party_resolution.go,
artifact_families.go, session-template tests, internal/app/config_loader.go,
and ConfigKit source/schema/version contracts.
- Use separate ConfigKit definitions for campaign, session, and party. Replace
decodeStrictYAML*and the custom party YAML-node parser. Model the party input as the existing string schema version and character-keyed typed map, with the existingaliaslist, ordered class list, and optional positive class level. - Keep party domain checks: valid IDs, required display names and classes,
trimmed/control-free names, case-insensitive name/alias ambiguity, and duplicate
class checks. Validate the required version through
VersionRule. Keep an exact independent copy of authored bytes outside ConfigKit's schema object. - Preserve authored character order using successful metadata source coordinates
for the single party input when constructing
CanonicalParty.Characters. Preserve class/alias sequence order naturally. Family expansion and players projection continue using their existing deterministic ID sorting. Do not keep a second YAML parse just to recover mapping order. - Preserve the narrow session placeholder rejection before typed load, expected session/previous-session identity checks, campaign selection, stable-input precedence, and local versus remotely downloaded session provenance. Retain the initially loaded pipeline/campaign during remote session acquisition.
- Where bytes must be retained or inspected before
ConfigKit.Bytes, bound the initial read too, using the existing fileops bounded-read helper and a named package constant of 8 MiB matching the pinned source limit. Otherwise preferConfigKit.File. Do not read an unbounded file and rely on later rejection. - Complete post-party family and publish-rule expansion, then full cross-config validation and digest refresh. Keep generated origins outside the library snapshot. Remove unused decoder plumbing and obsolete party parsing tests.
Acceptance: retained authored party bytes are identical; domain errors, aliases, class order, family origins, and derived players are correct. Local and remote session loads preserve identity, input path bases, and the original pipeline. Unknown fields and disabled directives fail through ConfigKit. Focused config, prepare/extract, and application loading tests pass.
Stage 6 — Move secrets bootstrap before all configuration loads
Status: Pending
Read: roadmap “Credential and Secrets Convention”; ConfigKit's bootstrap
contract; internal/app/secrets_env*.go, config_loader.go, config_commands.go,
object_store.go, command dependencies, and command argument owners.
- Add a shared
--secrets-dirselector to every CLI path that loads a pipeline, including configuration inspection/diff, execution aliases, planning, publish, and session/storage operations. Route all through one command-owned bootstrap context. Help/version and commands that do not load configuration do not bootstrap. - Discover the root pipeline first. An explicit relative directory is based on
caller CWD; otherwise choose
secrets/beside the resolved root pipeline. An explicitly empty flag is invalid. Missing conventional directories are skipped; missing explicit directories and unreadable/invalid existing directories fail. Use ConfigKit's selected-directory symlink behavior without custom rejection. - Call
LoadSecretsDirat most once per command before any ConfigKit load or adapter creation, including both loads of config diff and remote session lookup. Use invocation state, not a package-globalsync.Once. A new command gets its own bootstrap; library config loaders themselves never mutate the environment. - Present safe bootstrap warnings/failures through stderr or the existing safe diagnostic boundary, never into YAML show output. Preserve the partial report on bootstrap failure. Do not add rollback or reinterpret ConfigKit file rules.
- Remove late bootstrap calls in object-store and runner composition, the custom
directory reader and permission platform files,
pipeline.secrets, its defaults and validators, and their obsolete tests. Convert affected fixtures immediately.
Acceptance: test absent conventional directory, explicit override from another CWD, invalid explicit directory, already-set empty/nonempty environment values, and bootstrap failure before adapter construction. A diff invocation and a remote session acquisition each bootstrap once. Config inspection requires no session, workspace writes, manifests, or runtime clients. Update CLI, config, operations, architecture, and troubleshooting to the new bootstrap/permission contract.
Stage 7 — Resolve typed credentials and protect all output boundaries
Status: Pending
Read: ConfigKit “Typed secret references” and rendering contracts;
internal/config/config.go, defaults.go, validate_adapters.go, the safe
projection from Stage 3, internal/adapters/storage/s3_backend.go,
internal/adapters/audita/subprocess.go, and their composition callers.
- Implement the three renamed optional typed fields and S3 pair policy above.
Reject old
_envschema keys; remove their default-name and lookup helpers. Convert maintained fixtures with synthetic environment setup where references are present. Do not add credentials to unrelated local pipeline fixtures. - Pass resolved S3 values to the SDK adapter's explicit static provider only when
the pair is configured. With neither, preserve its default provider chain.
Pass Audita the resolved value and safe reference name; deliver the value only
through the child environment's existing
AUDITA_LLM_API_KEYoverride. Do not re-read the environment for an explicitly resolved reference. - Adapt every serialization, digest, source/diff output, diagnostic, generated
invocation config, manifest, and provenance caller to safe reference names.
Never YAML-marshal ConfigKit's opaque secret directly or call
Value()in an inspection path. Secret changes alone must not alter effective digests or invalidate artifact evidence; reference-name changes may change provenance. - Keep ConfigKit errors structured and safe. Domain validation messages must avoid raw credential values; do not unwrap private parser errors or interpolate whole trusted configuration structs into error messages.
- Preserve downstream tool ownership for Notarius/Scriptorium credentials that Narratio does not configure. Do not add a parallel credential schema for them.
Acceptance: use distinctive synthetic secrets to prove no leakage in show, sources, diff, catalog-ready projections, success/failure diagnostics, manifests, or generated Audita configuration. Verify the child receives the credential and an environment change after loading does not change explicit delivery. Test S3 paired references, partial-pair rejection, missing effective references, and nil SDK fallback offline. Update adapter and credential documentation and remove obsolete lookup tests.
Stage 8 — Infer and normalize artifact dependencies once
Status: Pending
Read: internal/config/validate_scriptorium.go, artifact_families.go,
internal/artifactpolicy, internal/artifacts, analyze planning/execution, and
their artifact dependency and semantic-evidence tests.
- At the final configuration resolution boundary, union explicit
depends_onwith configured-artifact input references. Use the existing source-ID parser; do not infer edges from filenames, arbitrary strings, or non-artifact inputs. - Infer after family member inputs become concrete
narratio.artifact.<key>sources. Normalize, deduplicate, and sort the effective dependency list once. Keep explicit ordering-only dependencies valid. Repeated finalization must not accumulate edges or alter ordering. Store source-edge attribution separately from the effective graph for diagnostics and generated provenance. - Apply existing existence, self-reference, enabled-dependency, output, and cycle
validation to the union. Remove the error requiring an input to be repeated in
depends_on. Retain diagnostics pointing to the explicit list entry or the input that contributed an invalid edge, including family origin where relevant. - Ensure execution, targeted analysis, planning, prerequisite closure, reuse, forced regeneration, and semantic evidence consume this same concrete graph. No adapter or command may infer a second graph. Refresh final projection/digest after inference; equivalent explicit and inferred declarations compare equally.
Acceptance: cover input-only dependency ordering, duplicate explicit/inferred edges, ordering-only edges, missing/self/cyclic references, and rewritten family inputs. Use an assembled fake-artifact workflow to prove upstream output is supplied to a dependent artifact and failure/resume retains successful predecessors. Existing force/selection and invalidation tests remain valid. Update config and analyze internals with the implemented inference rule.
Stage 9 — Add the effective artifact catalog command
Status: Pending
Read: internal/app/config_commands.go, config diff/source rendering, the safe
projection and family-origin catalogs, and docs/cli.md.
- Add
narratio config artifactsto dispatch and help. Reuse ordinary pipeline, profile, campaign, campaign-file, and secrets-directory parsing/resolution. Resolve a campaign when families require it or when explicitly selected; a pipeline with only ordinary artifacts can be inspected without a campaign. - Render one stable record per effective concrete artifact in key order with
key,
narratio.artifact.<key>source ID, defining source location(s), enabled state, output path, and sorted effective dependencies. Family members additionally identify family key, character ID, defining family source, and party source. - Derive identity from the map key. A profile may contribute to an existing artifact, so report contributing definition locations instead of inventing a single filename owner. Use a deterministic escaped tabular text rendering; include a header and no records for an empty catalog. Propagate writer errors.
- Use the same finalized state as show/sources/diff and runtime configuration. No session download, adapter initialization, manifest access, or workspace materialization is allowed. Secret bootstrap remains the agreed startup exception.
Acceptance: CLI tests cover ordinary and generated artifacts, disabled entries, profile-added/overridden artifacts, empty catalog, no-campaign ordinary inspection, required-campaign failure for families, stable ordering, and no runtime side effects. Check identity stays the same after moving an artifact file without changing its key. Document the command in its canonical CLI owner and link to config layout examples.
Stage 10 — Finish the operator layout and v2 migration documentation
Status: Pending
Read: roadmap “Artifact Definition Ergonomics” and “Compatibility and Migration”;
examples/README.md, existing example bundles and their loader test,
docs/config.md, docs/operations.md, docs/release.md, and internal configuration
and artifact documentation.
- Convert maintained pipeline examples to a production base with explicit
configkit.imports,artifacts/<key>.yml,artifact-families/<key>.yml, andprofiles/testing.yml. Each recommended artifact/family file defines exactly one explicit map entry. Fold old production overlays into the base. - Include an input-only dependency example and at least two character-family examples sharing a campaign-owned party roster. Keep supported inline artifacts legal; file layout is guidance, not a filename-based validation requirement.
- Update the example validation harness to load root configurations and selected testing profiles, not attempt to load each imported fragment as a full pipeline. Use synthetic environment setup for explicit references. No example includes real credentials or depends on local tools/cloud services to validate.
- Consolidate canonical documentation updates from earlier stages. Remove current
references to old composition/profile catalogs, default-profile configuration,
secrets.env_dir, credential
_envfields, credential-free inspection guarantees, paired diff snapshots, and removed platform support. Explain explicit-value and null changes without promising reloadable inspection output. - Add
docs/releases/v2.0.0.mdusing the release-note structure, with a concrete migration checklist linking to the canonical config/CLI docs and examples. Include static S3-reference migration versus SDK credential discovery, new inspection prerequisites, artifact input dependency inference, and platform removals. Do not claim publication or edit older immutable release notes.
Acceptance: go test ./internal/doccheck and
go test ./internal/config -run '^TestExamplesLoadAndValidate$' pass. Walk through
the new operator examples with config validate/show/sources/diff/artifacts offline.
Their identities, family members, origins, and testing overrides agree.
Stage 11 — Verify the complete migration and remove remaining scaffolding
Status: Pending
Read: the whole feature roadmap, completed stage notes, and the current test and release procedures. Review the diff from the commit introducing this plan.
-
Audit every roadmap requirement against current code and executable evidence. Remove temporary migration adapters and any residual generic YAML parser, composer, profile traversal, explicit-presence store, old secrets loader, decoder hooks, or unused tests.
gopkg.in/yaml.v3may remain for output encoding and unrelated external formats; it must not supply a second configuration loader. -
Confirm result-owned snapshots versus final expanded configuration are handled consistently; every inspection/runtime path sees normalized inferred dependencies and generated publish rules. Recheck secret safety and semantic resume boundaries.
-
Inspect current platform conditions and asset lists. Retain historical notes but no active Windows/macOS amd64 obligation. Compare owned production/test code before and after; report removals and any remaining complexity with reasons. Do not chase a LOC quota by removing useful domain checks or regressions.
-
Run the repository gates with the maintained Go 1.26 toolchain:
go test ./... go test -race ./... go vet ./... go build ./... go test ./internal/doccheck go test ./internal/config -run '^TestExamplesLoadAndValidate$' -
Run
scripts/check-release-candidate.sh v2.0.0after the release note and all candidate changes exist. This also verifies module hygiene and all three release assets withGOWORK=off. Resolve failures; do not bypass checks or publish a tag. Record native versus cross-build evidence accurately.
Acceptance: all gates pass and every roadmap outcome has direct evidence. No dependency on the sibling ConfigKit checkout remains. Record completion and any best-effort platform limitations, then hand off for a separate release decision.
Open Questions
None required before implementation. The agreed roadmap and the explicit choices above resolve the migration decisions. Upstream credential-free inspection and native macOS arm64 CI remain deferred, not prerequisites for this plan.