Update ConfigKit adoption plan for version 0.3.0

This commit is contained in:
2026-09-13 15:49:26 +00:00
parent 00972e8d48
commit c403a38b50
2 changed files with 168 additions and 139 deletions

View File

@@ -16,12 +16,10 @@ 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.
The selected approach requires an upstream extension: ConfigKit v0.2.0 cannot
expose source origins during its resolution callback. See **Upstream
Prerequisite**. Stages 1–6 are independent of that missing capability. Stage 7
and all later stages have an explicit gate; the sprint is not ready for
unattended execution through that gate until the upstream release is available.
Do not mark a gated stage complete by weakening the feature contract.
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
@@ -32,7 +30,8 @@ final stage establishes repository-wide readiness.
## Repository Baseline
Planning examined Notarius at `946fa10` plus the outstanding policy and roadmap
changes. The sibling ConfigKit checkout is tagged `v0.2.0`.
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:
@@ -47,7 +46,7 @@ Relevant current owners are:
| 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`, `load.go`, `resolution.go`, `metadata.go`, `records.go`, `path.go`, and consumer examples at v0.2.0 |
| 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
@@ -57,9 +56,8 @@ of these Notarius stages.
### Configuration And Dependency Boundary
- Initially pin `gitea.maximumdirect.net/eric/ConfigKit v0.2.0`. Stage 7 may
require a reviewed release providing the agreed upstream extension. A later
pin must be explicit, never an unreviewed `main` revision.
- Pin `gitea.maximumdirect.net/eric/ConfigKit v0.3.0`. Use the root public
package, never its private engine or an unreviewed `main` revision.
- Use a string-backed document version and a `/version` rule accepting exactly
text `5`; both numeric `version: 5` and quoted `version: "5"` are accepted.
Maintained examples use the numeric form. Reject missing/null/unsupported
@@ -135,14 +133,18 @@ 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. Relative file references
and PromptKit profile sources use the surviving declaration's directory;
absolute values remain absolute. Resolve these to absolute paths before
runtime conversion, without reading the referenced contents. Output/cache/debug
relative directories retain working-directory semantics. CLI reference paths
retain the existing working-directory semantics and generated-reference
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.
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
@@ -150,7 +152,7 @@ configuration and overlay files.
**Work:**
1. Pin v0.2.0 in `go.mod`/`go.sum` and inspect its actual exported API.
1. Pin v0.3.0 in `go.mod`/`go.sum` and inspect its actual exported API.
2. Add `document.go` and `load.go` in `internal/core/config` (or equivalent
cohesive files) with the complete plain document types, finite validator
type, explicit reference union, and YAML-tagged validation-policy fields.
@@ -163,7 +165,8 @@ configuration and overlay files.
scaffolding; do not advertise partially implemented v5 support.
5. Add the next available ADR (expected `0018-adopt-configkit.md`) recording
the accepted ownership, v5 break, imports/profiles, and environment policy.
Record the source-origin dependency without claiming it is implemented.
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
@@ -191,7 +194,8 @@ small; generic YAML exhaustiveness remains upstream.
and required fields in a present local-backend object. Preserve mutually
exclusive profile sources and the existing endpoint/range constraints.
4. Keep filesystem checks and PromptKit profile catalog lookup out of callbacks.
This stage trims profile-source paths but does not yet resolve their bases.
This stage trims filesystem paths but does not yet resolve their bases;
Stage 7 owns all source-aware path normalization.
5. 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.
@@ -343,46 +347,58 @@ Test rendering failure and caller mutation at these real local boundaries.
**Goal:** Normalize file paths using their effective source origins and retain
correct ConfigKit inspection history.
**Prerequisite gate:** A reviewed ConfigKit release must expose surviving input origin and canonical
source information during resolution. Confirm the actual API and pin that
release before implementing this stage. If only v0.2.0 is available, stop here
and report the unmet dependency. Do not implement an upstream fork, reload the
configuration twice, parse YAML independently, or silently change the promised
effective output.
**Work:**
**Work after the gate:**
1. Resolve path origins at original input paths before map-key normalization
and before recording changes can replace a source origin. Use the scalar
origin and source ID to get the canonical declaring file; do not pick the
first origin of a composite parent mapping.
2. Join nonempty relative external reference paths and PromptKit profile-source
paths to that file's directory. Preserve absolute values. Do not read or
require existence of referenced files in this callback.
3. Record normalized paths and map moves so the frozen effective view matches
runtime values and explanation can trace back to the original declaration.
Keep map collision checks ahead of mutation; process stable sorted keys.
4. Leave destination directories unchanged except existing whitespace
normalization. CLI reference paths remain runtime-owned. No configuration
root confinement or symlink policy is added to referenced application files.
5. Enable imports and selected profiles on the public new loader, using
`File` and the selected file's canonical directory as the root. Use
ConfigKit's composition and profile behavior directly.
1. 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. Read `Origin.Layer` and the exact scalar's
`Origin.Locations`; use `resolution.Source(location.SourceID)` to obtain
`(SourceInfo, bool)` and its canonical `Path`. Do not select an arbitrary
origin of a composite parent.
2. 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.
3. 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.
4. 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.
5. 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 `Resolution` context for post-load queries.
6. Enable imports and selected profiles on the public new loader, using
`File` and 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; profile-replaced sequences use the
new declarations; normalized enclosing IDs do not lose attribution. Effective
and explanation output agree with the actual paths. Removed/overridden file
values are not opened or resolved. Root containment affects only ConfigKit
configuration sources.
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 configuration trees with same-named reference files in different
directories, aliases/symlinks accepted by ConfigKit, profile replacements,
normalized keys, and absolute external paths outside the config root. Use real
reference materialization for one case to prove the intended bytes are chosen.
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, with a firm upstream gate; no CLI or example migration.
**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
@@ -392,7 +408,8 @@ reference materialization for one case to prove the intended bytes are chosen.
1. Add a CLI helper that discovers the file, passes profile selection and
explicit override records to the new loader, and returns `LoadedConfig`
plus the selected config path. Keep discovery order and injected test lookup.
plus the selected config path. Supply the invocation directory needed for
relative CLI overrides. Keep discovery order and injected test lookup.
2. Add a shared load-error reporter using `errors.As` for ConfigKit load errors
and `WriteDiagnostics`; supply safe source context using the dependency's
source records. Do not flatten multi-error output into repeated wrappers or
@@ -469,12 +486,16 @@ incompatible generated references.
4. 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.
5. 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.
6. Update current CLI, configuration, operations, and consumer references for
v5, profile selection, environment retirement, and `effective-config.yml`.
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;
@@ -658,43 +679,32 @@ unnecessary for a configuration migration.
**Prompt size:** Medium; verification and focused documentation reconciliation,
with no planned production redesign.
## Upstream Prerequisite
## Released API And Readiness
### Source Origins During ConfigKit Resolution
ConfigKit v0.3.0 provides both required query methods in its public
`lifecycle.go`:
**Evidence:** In v0.2.0, `Resolution` exports `IsExplicit`, `Record`, `Error`,
and `Warning`, with generic `Default` and `Normalize` helpers. It does not
expose source metadata. `Result.Metadata()` and `Result.Sources()` become
available only after `Load` returns. Mutating `Result.Config` afterward does
not change `WriteEffective` or `WriteExplanation`, which use frozen snapshots.
Therefore a single v0.2.0 load cannot both use declaration origins to normalize
paths and record that normalization in ConfigKit's inspection history.
- `(*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.
**Selected approach:** Request a small upstream API exposing surviving
input origins and canonical source records during the existing resolution
callback. It should distinguish base/profile/CLI/default origins, identify the
declaring file for a scalar, and permit lookup at original paths before
application map-key normalization. Keep the existing recorded mutation API.
Exact method names and the containing release must be confirmed upstream;
none are assumed to exist by this plan. This preserves one load, ConfigKit
ownership of composition/provenance, and the agreed effective-output contract.
The tradeoff is an upstream release dependency at Stage 7. This plan does not
authorize changes to the sibling repository.
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.
**Alternative considered:** Stay on v0.2.0 and resolve paths in the narrow Notarius
runtime converter using completed-result metadata. Preserve original document
paths for origin lookup. Present ConfigKit's effective configuration as a
logical configuration view, and expose resolved runtime paths separately in
debug/inspection output. This is implementable without an upstream change and
keeps composition centralized, but changes the agreed inspection contract,
requires Notarius to own the extra path report, and cannot make ConfigKit's
explanation record the post-load normalization. It requires explicit approval
and revision of Stage 7 and the feature roadmap before dependent work begins;
the user selected the upstream extension instead.
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.
Do not resolve this by silently dropping imports/profiles, changing all file
paths to working-directory-relative, reparsing YAML, or performing a second
load that might observe different files. All other product decisions in this
plan are settled; the upstream release is the sole remaining gate. No product
questions remain open. Once the API is released, review its exact signatures,
update the pinned version and this stage's API mapping, and then resume Stage 7.
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.