Plan the public facade refactor
This commit is contained in:
@@ -1,379 +1,388 @@
|
||||
# Source origins during resolution: implementation plan
|
||||
# Public facade: implementation plan
|
||||
|
||||
Status: implementation complete (2026-09-12). All stages were completed in order.
|
||||
The [feature roadmap](source-origins-during-resolution.md) owns the target contract
|
||||
and consumer intent. This document owns implementation steps, validation, and
|
||||
handoffs. It is written for one gpt-5.6-terra coding agent per stage; no stage
|
||||
requires parallel agents or changes to a downstream repository.
|
||||
Status: not started. Baseline: `28e7fc5f690b36c60e4d03586afcff00fb1dee90`.
|
||||
The [feature roadmap](public.md) owns the target architecture and user intent.
|
||||
This plan owns implementation decisions, ordered work, validation, and handoffs.
|
||||
Each numbered stage is sized for one gpt-5.6-terra coding-agent assignment;
|
||||
no stage requires subagents or changes to downstream repositories.
|
||||
|
||||
## Required reading and working rules
|
||||
Historical navigation: this filename previously held the completed source-origins
|
||||
plan, including its Stage 5 completion record. That record is preserved at the
|
||||
baseline above: `git show 28e7fc5:docs/roadmap/implementation.md`. Existing incoming
|
||||
links from the source-origins roadmap, remediation record, and development guide
|
||||
refer to that historical plan, not the stages below. Stage 1 repairs those links.
|
||||
|
||||
Before each stage, inspect the worktree and read the preceding stage's completion
|
||||
record. Follow [development](../development.md) and all policies:
|
||||
## Working rules
|
||||
|
||||
Before each stage read [development](../development.md), the feature roadmap,
|
||||
the preceding completion records, and all applicable policies:
|
||||
[architecture](../policy/architecture.md), [documentation](../policy/documentation.md),
|
||||
[testing](../policy/testing.md), and [release](../policy/release.md).
|
||||
Read the roadmap, the configuration contract's resolution and metadata sections,
|
||||
and the relevant code before editing. Use the code graph for discovery and verify
|
||||
current definitions rather than assuming line numbers or private signatures.
|
||||
Read the owning configuration/API contracts and implementation before editing.
|
||||
Prefer the code graph for discovery, verifying its results against current code.
|
||||
Preserve unrelated work and the baseline's already-fixed resolution-history copy.
|
||||
|
||||
Preserve unrelated work. Keep each stage independently buildable with passing
|
||||
tests; do not introduce stub public methods or defer known correctness failures.
|
||||
Run the focused commands listed for the stage and `./scripts/check.sh` from the
|
||||
repository root. Acquire required toolchains/dependencies explicitly before offline
|
||||
checks. Document unsupported filesystem fixtures rather than silently omitting them.
|
||||
Complete stages in order. Every handoff must build and pass its relevant tests;
|
||||
do not leave placeholder public methods, skipped regressions, or known failures
|
||||
for a later stage. Run `./scripts/check.sh` at every code-stage handoff, in addition
|
||||
to the focused gates below. Keep default tests deterministic and offline. Follow
|
||||
the development guide for dependency acquisition and supported toolchains.
|
||||
|
||||
Use real collaborators and temporary files for provenance. Keep default tests
|
||||
offline, deterministic, and credential-free. Prefer codes, paths, layers, locations,
|
||||
and observable snapshots over private call counts or incidental error messages.
|
||||
Only use a narrow I/O or credential seam where it proves the no-extra-access rule.
|
||||
Record completion beneath each stage: changed owners, tests and actual toolchain,
|
||||
results, deviations, remaining risks, and the next handoff. Update current
|
||||
documentation with actual changes, not promised completion. Commit stage work
|
||||
when authorized; this plan does not authorize pushes, tags, release selection,
|
||||
or publication. Release notes and native-platform release evidence remain part
|
||||
of the separate release procedure.
|
||||
|
||||
At each handoff, update that stage's status here with changed files, tests/results,
|
||||
actual toolchains, deviations, and remaining risks. Keep public reference and current
|
||||
contract documentation synchronized with behavior as it lands. Commit coherent
|
||||
stage work when authorized, but do not push, tag, select a version, or prepare a
|
||||
speculative release note as part of this implementation sprint.
|
||||
## Fixed implementation decisions
|
||||
|
||||
## Implementation decisions
|
||||
### Package and migration strategy
|
||||
|
||||
The roadmap's two method signatures are fixed for this sprint:
|
||||
Final production packages are the public root, `internal/engine`, and the existing
|
||||
`internal/document` and `internal/yaml`. Keep the engine cohesive; do not create
|
||||
separate packages for every phase. Root imports engine; engine imports its
|
||||
supporting packages; no internal production package imports root. Only the YAML
|
||||
adapter imports the upstream YAML dependency.
|
||||
|
||||
```go
|
||||
func (r *Resolution) InputInfo(path Path) (FieldInfo, bool)
|
||||
func (r *Resolution) Source(id string) (SourceInfo, bool)
|
||||
```
|
||||
Public named types remain defined in root. Use named private fields for delegated
|
||||
state, explicit forwarding methods, and ordinary public record declarations.
|
||||
Do not alias public types to engine types, anonymously embed engine types, expose
|
||||
engine types in public signatures/fields, or add public bridge methods.
|
||||
Internal type aliases used only inside implementation are not public aliases.
|
||||
|
||||
The required behavior is a stable entry-state view and detached public return
|
||||
records, not a particular internal representation. The recommended implementation
|
||||
is a private `Metadata` value containing effective fields and path shapes, together
|
||||
with a source-ID index. Reuse `Metadata.Lookup` semantics. A historical view is not
|
||||
needed for this feature, and exposing one is outside this sprint's scope.
|
||||
Because private-state references span most root implementation files, use a
|
||||
bounded relocation candidate rather than attempting a live package split in
|
||||
every stage. Stage 2 copies the existing implementation into `internal/engine`;
|
||||
Stages 4–7 build the eventual root facade in temporary `internal/facade` (package
|
||||
name `configkit`). Root remains the sole consumer entry point until Stage 8.
|
||||
The temporary facade is staging space, not a second supported API/package.
|
||||
|
||||
Build the view after `metadataFromDecoded` and `applyOverlayMetadata` in
|
||||
`resolveLoadInput`, immediately before `runResolution`, only when a resolver exists.
|
||||
Its path shapes come from `decoded.final`, not the raw document or mutable Go
|
||||
configuration. Separate copies of field/source records are one straightforward way
|
||||
to isolate the view; immutable sharing or ownership transfer is equally acceptable
|
||||
when it preserves query stability and detached returns. Source records come from
|
||||
`assembledInput.sources`. Avoid aliases that observable consumer operations or
|
||||
subsequent metadata updates could mutate while the view remains usable.
|
||||
Missing or duplicate internally generated IDs are implementation-invariant failures
|
||||
to fix, not new consumer-facing diagnostic categories.
|
||||
This temporary duplication is a migration mechanism only: no feature development,
|
||||
runtime selector, fallback engine, or new consumer import is introduced. Baseline
|
||||
semantics remain fixed; necessary corrections must be reconciled explicitly.
|
||||
Stage 8 moves the facade to root and removes the old implementation and temporary
|
||||
directory in the same handoff. There is one production implementation at completion.
|
||||
This makes the large textual move mechanical while separating it from the more
|
||||
demanding ownership and callback work. Do not keep two engines as a final design.
|
||||
|
||||
Prefer passing the private view explicitly to `runResolution` and retaining it in
|
||||
`resolutionState`. Update all private callers/tests deliberately: construct their
|
||||
entry view from their decoded fixtures and supplied sources where they test these
|
||||
queries. Every production resolution callback must receive complete provenance.
|
||||
Private signatures and representation may change when an idiomatic alternative
|
||||
better serves that requirement; record the reasoning in the stage handoff.
|
||||
### Type and state ownership
|
||||
|
||||
Queries first check callback activity through the existing context guard. Only
|
||||
then may invalid paths or unknown IDs return misses. `InputInfo` delegates to the
|
||||
entry metadata lookup; `Source` clones the matched source on return. Preserve
|
||||
existing `IsExplicit`, diagnostics, resolution recording, and secret timing.
|
||||
| Public family | Selected representation and adaptation |
|
||||
| --- | --- |
|
||||
| `Path` | Root struct with named private `engine.Path` value; constructors and methods delegate. Preserve semantic segment kinds and invalid state by direct private wrapping, never stringify/reparse. |
|
||||
| `Layer`, `ChangeKind`, `Severity`, `Code`, `SecretFileStatus` | Root-defined string types and explicitly typed constants with unchanged values. Convert to engine equivalents at the boundary; engine constants may supply values without making public aliases. |
|
||||
| `Location`, `SourceInfo`, `Diagnostic`, `Origin`, `FieldInfo`, `Change` | Root-defined records with unchanged exported fields. Explicit private conversions cover nested pointers, slices, and paths. |
|
||||
| `ValueSnapshot`, `ValueChange`, `SecretReferenceInfo`, `Explanation`, `SecretFileResult` | Root-defined output records. Convert detached engine records; preserve nil/empty distinctions and field semantics. |
|
||||
| `Source` | Root struct with private `engine.Source` value. Engine owns bytes, constructor validity, and the shared reader claim; wrapper copies must not create new claims or re-read/copy source input. |
|
||||
| `Metadata` | Root struct with private engine metadata value; explicit query methods convert detached results. Engine retains effective and historical indexes. |
|
||||
| `Resolution`, `Validation` | Root structs with private pointers to their engine contexts. Engine alone owns activity and audit state; wrappers never recreate that state. |
|
||||
| `Definition[T]`, `Options[T]`, `FilesystemContext`, `Override`, `VersionRule`, `Version` | Root records retaining all current fields and signatures. Private option/callback conversion into engine records; no extra configuration clone or reflection-based generic record mapper. |
|
||||
| `Result[T]` | Root struct retaining `Config T`, plus private pointer to a non-generic engine inspection result. Engine load returns configuration and inspection separately; facade stores configuration exactly once. |
|
||||
| `Secret` | Root-owned opaque scalar retaining value-copy semantics and existing public methods. Private exact-type adapter lets engine read/build it; no public accessor for plumbing. |
|
||||
| `LoadError`, `SecretsError`, `SecretsReport` | Root structs with private engine backing state and explicit methods. Translate engine typed failures at entry points; never return or unwrap an engine error as the public structured error. |
|
||||
|
||||
Queries must not reload input, read files, resolve symlinks, access credentials,
|
||||
or re-project mutable configuration instead of reading entry-state information.
|
||||
Prefer reusing existing indexes and avoiding unnecessary whole-tree copies.
|
||||
Per-load source indexing is a recommendation, not a complexity guarantee: a simple
|
||||
scan may be appropriate for the existing bounded source collection. Assess costs
|
||||
in context, including path lengths and nested record copies; this feature adds no
|
||||
performance guarantees, resource caps, or benchmark gates.
|
||||
The inventory in Stage 1 verifies every baseline exported declaration, not just
|
||||
these grouped examples. Public type identity stays rooted in `configkit`.
|
||||
Preserve receivers, generics, supported zero/nil behavior, interface satisfaction,
|
||||
error assertions, and supported construction/copy behavior. Private field layout
|
||||
and incidental formatting are not new compatibility promises.
|
||||
|
||||
## Stage 1 — Factor reusable effective-path indexing
|
||||
Use handwritten, local conversion helpers. For detached outputs, transfer already
|
||||
detached data where safe instead of deep-copying everything again. Never expose
|
||||
engine-owned mutable slices/maps through public records. Preserve order and
|
||||
missing/null distinctions. Do not round-trip records through JSON/YAML or use
|
||||
`unsafe`, registration globals, or reflection to bypass private fields.
|
||||
|
||||
Status: complete (2026-09-12). Dependencies: none.
|
||||
### Exact-type secret bridge
|
||||
|
||||
Scope: `metadata.go`, `load_metadata.go`, and their focused test owners.
|
||||
Introduce an engine-internal `SecretBinding` containing `Type reflect.Type`,
|
||||
`Read func(reflect.Value) SecretState`, and
|
||||
`Make func(SecretState) reflect.Value`. `SecretState` carries the existing name,
|
||||
value, specified, and resolved fields. It is operation data, never inspection
|
||||
data. The root constructs the binding privately for exactly `reflect.TypeFor[Secret]()`;
|
||||
Read and Make use ordinary type assertions/construction in the owning package.
|
||||
|
||||
1. Prepare reusable effective-path lookup for the entry view. The recommended
|
||||
approach extracts indexing from `finalizeMetadataPaths`; an equivalent design
|
||||
may reuse existing traversal directly. Keep historical indexing and result
|
||||
publication behavior unchanged, and preserve one set of semantic-path rules.
|
||||
2. Add the private entry-view construction described above using pre-resolution
|
||||
metadata, the decoded final tree, and acquired source records. Share extracted
|
||||
machinery with result finalization where useful. Do not expose public methods yet.
|
||||
3. Establish and document internal ownership sufficient for stable queries and
|
||||
detached returns. Reuse `cloneFieldInfo` and `cloneSourceInfo` where copying is
|
||||
needed; do not require eager copies or duplicate history solely to match this
|
||||
plan's recommended representation.
|
||||
4. Add focused coverage proving the private view recognizes defaulted fields,
|
||||
missing descendants, root, escaped keys, explicit key/index mismatches, and
|
||||
contextual canonical sequence indices. Test stability across actual lifecycle
|
||||
operations and mutation of returned records, not arbitrary mutation of backing
|
||||
data that the implementation owns as immutable. Reuse existing shape tests for
|
||||
exhaustive syntax cases rather than duplicating them.
|
||||
Supply the binding explicitly to the engine load and schema construction. Store
|
||||
it in the per-operation schema/value-operation context, available to recursive
|
||||
decoding, projection, cloning, dynamic-value checks, resolution, and validation.
|
||||
Keep the application configuration type `T` unchanged: no mirror configuration
|
||||
structs and no conversion of arbitrary application objects.
|
||||
|
||||
Acceptance: all existing final metadata/history behavior is unchanged; a complete
|
||||
entry view can be constructed before final publication without a historical index.
|
||||
No public API or configuration contract changes are claimed by this stage.
|
||||
Replace all direct engine assertions/constructions of the old `Secret`, not just
|
||||
schema recognition. Include decoding, unresolved versus validated clone modes,
|
||||
projection, required/optional references, lookup caching, and validator mutation
|
||||
audit. Recognition remains exact-type recognition, not acceptance of any type
|
||||
implementing an interface. Bindings are fixed by the facade, not configurable by
|
||||
consumers, and perform no environment access themselves. Environment lookup
|
||||
remains in its existing lifecycle phase and its existing private test seam.
|
||||
|
||||
Validation: `go test . -run 'Metadata|Path|Provenance|Historical'`, then the everyday
|
||||
checker. Handoff should identify the helper and constructor signatures for Stage 2.
|
||||
Engine unit tests use a test-local secret type and binding. Root integration
|
||||
tests exercise the actual public `Secret`. There is no production engine-owned
|
||||
replacement secret type that consumers must put in their configuration.
|
||||
|
||||
Completion record (2026-09-12): extracted `effectivePathIndex(final)` from final
|
||||
metadata publication and introduced `newResolutionInput(metadata, final, sources)`.
|
||||
The private view owns cloned metadata/source records, indexes source IDs, and exposes
|
||||
private detached lookup helpers for the next step; it intentionally has no public API
|
||||
or load-lifecycle wiring yet. `TestResolutionInputKeepsDetachedEntryMetadata` covers
|
||||
root, defaults, explicit null, missing descendants, escaped keys, contextual parsed
|
||||
sequence indices, key/index mismatches, detached records, and stability after real
|
||||
recorded resolution. `go test . -run 'Metadata|Path|Provenance|Historical'` and
|
||||
`./scripts/check.sh` passed with Go 1.26.7 on Linux. No deviations or remaining
|
||||
Stage 1 risks were identified.
|
||||
### Callback, option, result, and error bridges
|
||||
|
||||
## Stage 2 — Wire the callback API and enforce lifetime
|
||||
Keep the engine lifecycle generic in the real `T`. Convert each supplied options
|
||||
value without selecting/validating options in the facade: engine retains original
|
||||
validation order and diagnostics, including multiple-options rejection. Preserve
|
||||
nil callbacks, version pointers, defaults behavior, and override values without
|
||||
eagerly invoking callbacks or evaluating `any` values.
|
||||
|
||||
Status: complete (2026-09-12). Dependencies: Stage 1.
|
||||
Adapt Resolve/Validate/Version.Check by closures wrapping the exact engine context
|
||||
pointer and passing the same `*T` received from engine. Convert version records and
|
||||
semantic paths at those boundaries. The secret-classification predicate similarly
|
||||
wraps the engine path. Do not recover, retry, close, or extend callbacks in the
|
||||
facade; engine retains the existing lifetime and failure behavior.
|
||||
|
||||
Scope: `load_result.go`, `resolution.go`, necessary private callers, reference
|
||||
comments, and resolution/callback ownership tests.
|
||||
Generic public Default/Normalize forward to generic engine helper functions with
|
||||
the original target pointer and value. Do not implement them as just assignment
|
||||
plus Record: existing target validation and projected expected-value audit matter.
|
||||
Nil wrapper handling must preserve current inactive-context behavior.
|
||||
|
||||
1. Build and pass the entry view at the existing pre-resolution boundary. Use
|
||||
`input.sources`, which includes sources acquired by import, profile, and CLI
|
||||
processing; do not reconstruct source identities from labels or field locations.
|
||||
2. Implement both public methods with the existing activity guard and detached
|
||||
returns. Keep lookup state frozen when recording changes or mutating configuration.
|
||||
3. Add public-load tests for a file-authored scalar, a defaulted field, explicit
|
||||
null, a missing path, empty/unknown source IDs, and `NoSource`. Assert exact
|
||||
`InputInfo.Explicit` agreement with `IsExplicit` for found paths.
|
||||
4. Exercise nil/zero contexts and retained contexts after successful and failed
|
||||
callbacks. Both methods must use the existing fixed closed-context panic behavior.
|
||||
Saved return records remain usable after callback exit. Do not broaden this work
|
||||
into a new concurrency contract or a general callback framework refactor.
|
||||
5. Add Go method documentation and update the configuration contract's resolution
|
||||
section to describe the implemented entry-state semantics, misses, lifetime,
|
||||
and source distinction. Fix the development guide's stale statement that this
|
||||
implementation plan has not yet been written.
|
||||
Refactor engine publication to return a short-lived generic loaded value containing
|
||||
`Config T` and an inspection pointer. Inspection owns frozen trees, metadata,
|
||||
history, sources, diagnostics, and sensitivity; it does not retain `T` or callback
|
||||
bindings. Public `Result[T]` holds `Config` and that pointer, not another complete
|
||||
generic engine result. Result inspection must not reread caller-mutated Config.
|
||||
Existing caller-constructed zero results and nil result receivers retain behavior.
|
||||
|
||||
Acceptance: both methods work during an ordinary public `Load`; calls cannot access
|
||||
live mutable metadata or bypass callback scope. No-rule/no-resolver behavior and
|
||||
existing mutation auditing remain unchanged.
|
||||
Translate engine load/bootstrap failures into root `*LoadError`/`*SecretsError`,
|
||||
respectively. Their backing state can be delegated privately, but no public
|
||||
Unwrap exposes internal failures. Retain bootstrap partial reports and ordinary
|
||||
writer errors/short-write behavior. Do not wrap arbitrary render/write errors into
|
||||
new structured load errors or add error-disclosure guarantees.
|
||||
|
||||
Validation: `go test . -run 'Resolution|Callback|Metadata|Load'`, then the everyday
|
||||
checker. Do not mark complete with public methods that work only in private tests.
|
||||
## Stage 1 — Record the boundary and establish compatibility evidence
|
||||
|
||||
Completion record (2026-09-12): `resolveLoadInput` now creates a
|
||||
`resolutionInput` after decoded/overlay metadata and passes it explicitly into
|
||||
`runResolution`; the private state retains it for the callback. `Resolution.InputInfo`
|
||||
and `Resolution.Source` use the existing activity guard before exact entry lookup or
|
||||
source lookup, and return detached records. Public load tests cover a canonical
|
||||
file-authored value, a retained default, explicit null, misses, empty/unknown IDs,
|
||||
and defaults-only input; they also cover nil, zero, successful-retained, and
|
||||
failed-retained contexts plus saved record independence. Go reference comments,
|
||||
the configuration contract, and the development guide now describe the active work
|
||||
accurately. `go test . -run 'Resolution|Callback|Metadata|Load'` and
|
||||
`./scripts/check.sh` passed with Go 1.26.7 on Linux. No deviations or remaining
|
||||
Stage 2 risks were identified.
|
||||
Status: not started.
|
||||
|
||||
## Stage 3 — Verify layered provenance and snapshot independence
|
||||
1. Run baseline everyday checks. Inventory the root's exported types, constants,
|
||||
fields, functions, receiver method sets, generics, and interface implementations
|
||||
with Go tooling. Record the baseline revision and concise ownership mapping in
|
||||
the stage handoff; do not create a second normative API specification.
|
||||
2. Add the next available ADR (currently `0006-public-facade.md`) recording wrappers,
|
||||
private delegation, the dependency direction, and the alias-first alternative.
|
||||
Record the temporary migration mechanism here, not as durable architecture policy.
|
||||
3. Update architecture policy's public API/dependency section for the agreed
|
||||
boundary, preserving proportionality and existing behavior contracts. Explain
|
||||
that this is accepted design, not completed implementation. Add ADR navigation.
|
||||
4. Repair the three historical incoming references described at the top of this
|
||||
plan to identify the old plan by Git revision. Preserve the completed provenance
|
||||
evidence; do not label this facade plan as its completion record.
|
||||
5. Retain/add a compact external-package facade compatibility test covering the
|
||||
realistic boundary risks: root named-type identity, exported construction,
|
||||
generic helper use, structured error assertions, and relevant interfaces.
|
||||
Use existing tests for behavior already protected. Inspect the complete exported
|
||||
surface separately; do not add golden dumps of private struct layouts.
|
||||
|
||||
Status: complete (2026-09-12). Dependencies: Stage 2.
|
||||
Acceptance: baseline passes, public surface is understood, ADR/policy accurately
|
||||
distinguish decision from implementation, and documentation links pass.
|
||||
Run `go test . -run 'LocalDocumentationLinks|DependencyImportBoundary|Example|Facade'`
|
||||
and the everyday check. No engine migration in this stage.
|
||||
|
||||
Scope: public lifecycle/provenance, import/profile/override, and ownership tests;
|
||||
production fixes only for gaps exposed at those boundaries.
|
||||
## Stage 2 — Establish the internal relocation candidate
|
||||
|
||||
1. Build a real temporary import graph with path-bearing scalars and a sequence
|
||||
element. Include a composite mapping with contributions from distinct files.
|
||||
During resolution, assert scalar ownership, all container contributors, and
|
||||
successful source lookup for every origin location returned by the view.
|
||||
2. Apply a profile replacing one scalar and an equal-text value; verify replacing
|
||||
origins and untouched siblings. Exercise equal-text CLI replacement separately.
|
||||
Keep source IDs derived from returned records, not inferred from value equality.
|
||||
3. Exercise byte and reader roots with filesystem context and file imports: the
|
||||
memory root has no file path while imported files do. Also cover non-file input
|
||||
without imports, default-only fields, and unknown source IDs. Retain default
|
||||
descendants reintroduced by normal decoding instead of treating them as raw
|
||||
input declarations.
|
||||
4. Extend shape coverage through inlined structs/maps, typed arrays, and dynamic
|
||||
structures. Assert misses for invalid indices, signed index tokens in sequences,
|
||||
and explicit key/index mismatches; signed mapping keys remain literal keys.
|
||||
5. Normalize, delete, add, and move fields within a callback using valid records.
|
||||
Queries for original paths must stay fixed; absent destinations stay absent,
|
||||
and pre-existing destinations retain their entry information. Final metadata
|
||||
must still reflect resolution rather than that fixed entry view.
|
||||
6. Mutate returned `Origin.Locations` and `SourceInfo.ImportedFrom`; verify repeated
|
||||
queries, saved records, final results, and another load are independent. Exercise
|
||||
independent loads under the race detector without sharing an active context.
|
||||
Status: not started.
|
||||
|
||||
Acceptance: roadmap criteria 2–8 and 11 are covered at their owning boundaries.
|
||||
No duplicate provenance model, nearest-ancestor fallback, or source-order heuristic
|
||||
is introduced. Any discovered defect is fixed before the stage is marked complete.
|
||||
1. Copy root production implementation except public package orientation into
|
||||
`internal/engine`, changing package declarations/import references mechanically.
|
||||
Preserve file divisions and existing document/YAML packages. Keep internal
|
||||
functions private unless a later facade connection actually needs an export.
|
||||
2. Copy behavior/mechanism tests and needed fixtures for engine validation, excluding
|
||||
repository documentation, release-script, import-boundary, public examples,
|
||||
and consumer integration tests. Keep root unchanged and passing. Repair fixture
|
||||
paths explicitly; do not make tests depend on arbitrary working directories.
|
||||
3. Avoid blind replacements inside user-facing strings, diagnostic codes, fixture
|
||||
YAML, or documentation comments. The package rename is not permission to rename
|
||||
messages or behavior. Record the candidate's source baseline.
|
||||
4. Update the component overview with an explicitly temporary candidate entry.
|
||||
Do not claim root loads delegate yet.
|
||||
|
||||
Validation: `go test . -run 'Resolution|Provenance|Import|Profile|Override|Metadata|Path'`,
|
||||
`go test -race ./...`, then the everyday checker.
|
||||
Acceptance: `go test ./internal/engine` and the everyday check pass. Engine imports
|
||||
no root code, existing internal/parser boundaries remain intact, and root consumers
|
||||
still execute the unchanged original implementation. This stage is deliberately
|
||||
mechanical; do not mix ownership redesign into the relocation diff.
|
||||
|
||||
Completion record (2026-09-12): public lifecycle tests now build real temporary
|
||||
main/import/profile graphs and prove scalar, sequence, composite-container,
|
||||
profile, and equal-text CLI provenance through `InputInfo`/`Source`, deriving source
|
||||
IDs from returned locations. Coverage also exercises byte and reader roots with
|
||||
filesystem imports, a standalone non-file root, retained defaults, unknown IDs,
|
||||
typed/inlined/map/array/dynamic shape matching, signed mapping keys, and invalid
|
||||
sequence shapes. Recorded normalization, deletion, addition, and move operations
|
||||
leave entry queries fixed while final metadata reflects resolution. Returned records,
|
||||
completed results, another load, and concurrent independent loads remain detached.
|
||||
No production gap was exposed. `go test . -run
|
||||
'Resolution|Provenance|Import|Profile|Override|Metadata|Path'`, `go test -race
|
||||
./...`, and `./scripts/check.sh` passed with Go 1.26.7 on Linux. No deviations or
|
||||
remaining Stage 3 risks were identified.
|
||||
## Stage 3 — Decouple reflection from the public secret type
|
||||
|
||||
## Stage 4 — Demonstrate source-aware normalization and safe inspection
|
||||
Status: not started.
|
||||
|
||||
Status: complete (2026-09-12). Dependencies: Stage 3.
|
||||
1. Implement the exact-type SecretBinding/SecretState design above in engine.
|
||||
Thread it through schema and recursive value operations explicitly; do not use
|
||||
a global binding or make it an application option.
|
||||
2. Replace concrete-secret assumptions in `schema.go`, `decode.go`, `clone.go`,
|
||||
`projection.go`, `value_operation.go`, `secret_reference.go`, and their callers.
|
||||
Find all additional references using the graph/text search; the list is not a
|
||||
reason to leave a direct assertion behind.
|
||||
3. Move engine tests to their local secret type/binding and remove the candidate's
|
||||
production concrete Secret and public-style constructor. Preserve named-type
|
||||
rejection/acceptance semantics and dynamic-value checks from the baseline.
|
||||
4. Exercise unresolved/default secrets, pointer and container shapes, borrowed
|
||||
callback values, lookup-once behavior, and validator replacements without
|
||||
disclosing canary credentials in diagnostic expectations.
|
||||
|
||||
Scope: maintained Notarius-shaped example code/fixtures, executable examples,
|
||||
explanation/secret integration tests, and consumer guidance.
|
||||
Acceptance: engine secret/schema/decode/clone/projection/validation tests and race
|
||||
tests pass; binding instances cannot affect each other. Run
|
||||
`go test ./internal/engine -run 'Secret|Schema|Decode|Clone|Projection|Validation|Callback'`,
|
||||
`go test -race ./internal/engine`, and the everyday check.
|
||||
|
||||
1. Add a small application-owned path-bearing configuration example in
|
||||
`examples/consumers`. Keep existing catalog/version examples working. Supply the
|
||||
invocation directory explicitly to the example loader rather than calling
|
||||
`os.Chdir` or adding process-global state to tests.
|
||||
2. Implement the example's policy using the two public queries: absolute paths
|
||||
unchanged; CLI-relative paths based on the supplied invocation directory;
|
||||
default-relative paths based on the canonical main source; file-authored paths
|
||||
based on the scalar's declaring source. Use the existing primary-source identity
|
||||
convention for main-source lookup, not a guess about contributor ordering.
|
||||
3. Demonstrate file-relative normalization with fixtures that have an unambiguous
|
||||
declaring file. If the example handles absent or ambiguous file provenance,
|
||||
make that behavior an explicit example policy, not a correctness requirement
|
||||
or prescribed Notarius policy. Rejection, a caller-supplied fallback directory,
|
||||
retaining a relative value, or warning are application choices. The example's
|
||||
selected behavior must be documented and tested; ConfigKit must continue to
|
||||
expose the distinction rather than fabricate a file from a diagnostic label.
|
||||
4. Record every changed path through `Normalize` or `Record`, including valid
|
||||
dependencies where derived values depend on other configuration paths. Prove
|
||||
runtime values, effective YAML, and explanation agree; retain the original
|
||||
relative declaration and normalization reason in history. No existence or
|
||||
confinement checks are part of the example.
|
||||
5. Add a symlink fixture proving canonical target-directory semantics and an
|
||||
invocation-directory mismatch proving resolution does not depend on process CWD.
|
||||
Make unsupported symlink capability explicit under the testing policy.
|
||||
6. Use synthetic sensitive fields to verify original/effective redaction remains
|
||||
intact. With existing reader/secret lookup seams, prove queries do not reread
|
||||
source data or resolve credentials during the resolver; normal later lookup
|
||||
remains unchanged. Do not create credentials or environment-dependent tests.
|
||||
7. Document the public workflow and canonical symlink choice in the consumer guide;
|
||||
point to executable artifacts instead of duplicating large code examples.
|
||||
## Stage 4 — Build candidate value, source, and diagnostic wrappers
|
||||
|
||||
Acceptance: roadmap criteria 1, 9, 10, and 12 are demonstrated in one-load public
|
||||
workflows. Filesystem policy stays in the example application, not the root library.
|
||||
Status: not started.
|
||||
|
||||
Validation: `go test ./examples/consumers`,
|
||||
`go test . -run 'Resolution|Explain|Secret|Example'`, then the everyday checker.
|
||||
1. Create temporary `internal/facade`, package `configkit`, importing engine.
|
||||
Implement root-owned Path, Source, Secret, scalar enums/constants, and provenance
|
||||
records according to the ownership table. Preserve declaration comments for
|
||||
their eventual root placement. There must be no aliases or embedded engine types.
|
||||
2. Implement private path/record conversions and the real facade secret binding.
|
||||
Keep public Secret's small access/format/JSON methods local; its private scalar
|
||||
state remains value-based, not a shared mutable reference.
|
||||
3. Add LoadError and WriteDiagnostics delegation/conversion. Candidate engine may
|
||||
export narrow constructors/accessors as needed internally, not public bridge
|
||||
functions on the candidate facade. Preserve nil handling and typed errors.
|
||||
4. Test candidate public operations from its external test package. Verify that
|
||||
numeric-key versus index versus parsed paths are not collapsed; source copies
|
||||
share the same reader claim; bytes are snapshotted at construction; formatting
|
||||
and JSON do not expose resolved secret values.
|
||||
|
||||
Completion record (2026-09-12): added the maintained `NotariusPathConfig` example
|
||||
and fixtures. Its application-owned resolver uses `InputInfo`/`Source` only: file
|
||||
values use their exact declaring file, defaults use `source-1`, CLI values use the
|
||||
explicit invocation directory, and absolute values remain unchanged. Missing CLI
|
||||
bases or missing/composite file provenance retain the relative value and report an
|
||||
application warning. Tests prove imported-file, default, CLI, absolute, and canonical
|
||||
symlink-target bases without changing process CWD; the symlink test explicitly skips
|
||||
when the platform cannot create it. They also prove effective/explanation agreement,
|
||||
retained original declaration and reason, sensitive redaction, and that provenance
|
||||
queries do not re-read a reader source or resolve credentials before normal lookup.
|
||||
The consumer guide links to the executable artifact. `go test ./examples/consumers`,
|
||||
`go test . -run 'Resolution|Explain|Secret|Example'`, and `./scripts/check.sh` passed
|
||||
with Go 1.26.7 on Linux. No deviations or remaining Stage 4 risks were identified.
|
||||
Acceptance: candidate wrapper tests and engine tests pass. Run
|
||||
`go test ./internal/facade ./internal/engine` and the everyday check. Lifecycle
|
||||
entry points are added in Stage 6; do not add nonfunctional stubs here.
|
||||
|
||||
## Stage 5 — Audit the complete contract and hand off release readiness
|
||||
## Stage 5 — Separate engine publication and inspection
|
||||
|
||||
Status: complete (2026-09-12). Dependencies: Stage 4.
|
||||
Status: not started.
|
||||
|
||||
Scope: integration review, canonical documentation, roadmap completion records,
|
||||
and proportionate fixes for remaining acceptance failures. No feature expansion.
|
||||
1. Split generic engine Result into the short-lived loaded configuration carrier
|
||||
and non-generic inspection state specified above. Move inspection methods to
|
||||
the inspection receiver without changing their snapshots or algorithms.
|
||||
2. Adapt `load_result.go`, `load_metadata.go`, `metadata.go`, `explain.go`,
|
||||
`explain_render.go`, `render.go`, `history.go`, and `snapshot.go` as necessary.
|
||||
Keep the fixed entry-state view independent of final history, including the
|
||||
baseline fix that avoids copying unused resolution history.
|
||||
3. Update engine tests for the internal receiver/return changes while preserving
|
||||
observable expectations. Do not broaden nil/result-construction contracts.
|
||||
|
||||
1. Check all twelve roadmap acceptance criteria against named tests and current
|
||||
implementation. Record the mapping in this stage's completion entry. Passing
|
||||
broad suites alone is not evidence that a missing scenario was exercised.
|
||||
2. Inspect every new query path for environment/filesystem access, raw values,
|
||||
caller-mutable aliases, and accidental callback-lifetime extension. Review
|
||||
allocation and traversal costs for proportionality without turning the chosen
|
||||
mechanism into a public guarantee or adding unsupported performance gates.
|
||||
Avoid unnecessary history duplication and unused work in no-resolver loads.
|
||||
3. Review public signatures and current configuration behavior against the previous
|
||||
release. Confirm the capability is additive and no parser, path, composition,
|
||||
credential, or existing callback semantics changed unintentionally.
|
||||
4. Update the internal overview and finish canonical Go/configuration/consumer
|
||||
documentation. Transfer implemented contract prose from the feature roadmap
|
||||
to its proper owners and retain a brief completion record with links. Preserve
|
||||
historical ADR rationale and published notes. No new ADR is required for this
|
||||
information-access extension unless implementation actually changes a governing
|
||||
decision; stop and obtain review rather than silently widening scope.
|
||||
5. Run the full source checker with qualifying acquired toolchains, recording the
|
||||
actual versions and host. The following are the repository's recorded versions;
|
||||
follow the release runbook if selecting updated toolchains:
|
||||
Acceptance: engine loading, historical provenance, detached metadata, explanation,
|
||||
and rendering behavior pass. Verify publication still performs its existing
|
||||
configuration detachment but does not store Config in inspection. Run
|
||||
`go test ./internal/engine -run 'Load|Metadata|Provenance|Historical|Explain|Effective|Render|Independence'`,
|
||||
`go test -race ./internal/engine`, and the everyday check.
|
||||
|
||||
```sh
|
||||
CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.5 \
|
||||
CONFIGKIT_NEWER_TOOLCHAIN=go1.26.7 \
|
||||
./scripts/release-check.sh
|
||||
```
|
||||
## Stage 6 — Wire candidate lifecycle and callback wrappers
|
||||
|
||||
6. Hand off remaining publication work separately: maintainer version choice,
|
||||
dependency reassessment, version-matched release notes, completed-roadmap
|
||||
retirement and link repair, exact-commit Linux/macOS validation, publication
|
||||
guards, and clean consumer retrieval. Follow [the runbook](../release.md).
|
||||
Historical one-release exceptions do not carry forward to this feature.
|
||||
Status: not started.
|
||||
|
||||
Acceptance: all target requirements have concrete evidence; available automated
|
||||
checks pass; documentation distinguishes implemented behavior from release status.
|
||||
Implementation completion is not publication approval. Do not create a release
|
||||
tag or mark unavailable native macOS evidence as passed.
|
||||
1. Add candidate Definition, Options, VersionRule, Version, FilesystemContext,
|
||||
Override, Resolution, and Validation with unchanged public declarations.
|
||||
Implement the private option and callback bridges specified above.
|
||||
2. Add candidate Load using the engine and facade secret binding. Add Result[T]
|
||||
with Config and private inspection, initially with Metadata, Sources, and
|
||||
Diagnostics. Implement Metadata's complete public query methods.
|
||||
3. Forward all resolution methods, including InputInfo and Source, and the generic
|
||||
Default/Normalize helpers. Engine owns context activity, helper verification,
|
||||
resolution snapshots, and validation mutation detection.
|
||||
4. Add focused external candidate tests using actual public wrapper types inside
|
||||
application configuration. Exercise version callbacks, defaulting/normalization,
|
||||
retained/copy contexts, root typed error assertions, and public Secret recognition.
|
||||
Private engine secret tests alone are insufficient evidence for this bridge.
|
||||
|
||||
Validation: `./scripts/check.sh`, `go test -race ./...`, and the full checker above.
|
||||
Acceptance: real candidate loads with callbacks succeed/fail identically to the
|
||||
baseline, no callback is called by option conversion, no extra lookup occurs,
|
||||
and detached entry provenance stays fixed. Run
|
||||
`go test ./internal/facade ./internal/engine -run 'Load|Resolution|Validation|Version|Metadata|Secret|Callback|Facade'`,
|
||||
`go test -race ./internal/facade ./internal/engine`, and the everyday check.
|
||||
|
||||
Completion record (2026-09-12): all acceptance criteria have named evidence:
|
||||
## Stage 7 — Complete candidate inspection and bootstrap surface
|
||||
|
||||
1. `TestNotariusPathExampleUsesEntryProvenance` covers canonical main-file and
|
||||
symlink target bases without process-CWD dependence.
|
||||
2. `TestResolutionInputKeepsLayeredOriginsAndSources` covers imported scalars and
|
||||
sequence elements.
|
||||
3. The same layered test covers profile replacement and untouched base/import
|
||||
siblings.
|
||||
4. The layered test covers equal-text profile and CLI replacement.
|
||||
5. `TestResolutionEntryQueriesExposeDetachedLoadInput` and
|
||||
`TestResolutionInputDistinguishesNonFileRootsAndImportedFiles` cover defaults,
|
||||
nulls, misses, CLI/file identities, `NoSource`, bytes, and readers.
|
||||
6. The layered test verifies every composite location has a source;
|
||||
`TestResolutionEntryQueriesExposeDetachedLoadInput` verifies unknown IDs miss
|
||||
safely.
|
||||
7. `TestResolutionInputRemainsFixedAcrossRecordedMutations` covers normalization,
|
||||
deletion, addition, and move stability.
|
||||
8. `TestResolutionInputUsesEntryShapesAcrossTypedAndDynamicValues` covers inline,
|
||||
map, array, dynamic, escaped/signed keys, and invalid shape cases.
|
||||
9. `TestNotariusPathExampleUsesEntryProvenance` verifies runtime, effective YAML,
|
||||
and explanation history together.
|
||||
10. `TestResolutionInputQueriesDoNotReadOrResolveSecrets` plus the Notarius test
|
||||
verify no extra reader/credential access and preserved redaction.
|
||||
11. `TestResolutionInputKeepsLayeredOriginsAndSources` and
|
||||
`TestResolutionEntryQueriesExposeDetachedLoadInput` verify detached records.
|
||||
12. `NotariusPathConfig`, its fixtures, executable example, and consumer-guide
|
||||
link provide the maintained public-API/standard-library workflow.
|
||||
Status: not started.
|
||||
|
||||
The final review found that queries use only the callback guard and detached
|
||||
metadata/source indexes; they do not extend callback lifetime, expose raw values, or
|
||||
perform environment/filesystem work. The view is created only for resolver loads and
|
||||
now retains only effective field/path metadata and source records. Post-implementation
|
||||
review found that the original constructor unnecessarily cloned history; remediation
|
||||
replaced that clone with detached field-only construction, leaving final result
|
||||
history unchanged. The comparison with `v0.2.0` found only additive exported
|
||||
methods; loading, parser, path, composition, credential, and existing callback
|
||||
semantics remain unchanged. Canonical Go, configuration, consumer, internal, and
|
||||
roadmap documentation are synchronized; the feature roadmap is retained only until
|
||||
release preparation. `./scripts/check.sh`, `go test -race ./...`, documentation and
|
||||
import-boundary checks, and `CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.5
|
||||
CONFIGKIT_NEWER_TOOLCHAIN=go1.26.7 ./scripts/release-check.sh` passed on Linux with
|
||||
Go 1.25.5 and Go 1.26.7. Publication remains unapproved: a maintainer must choose a
|
||||
pre-v1 minor version, write its note, retire completed roadmaps, obtain exact
|
||||
candidate native Linux/macOS evidence, and follow the release procedure. No version,
|
||||
release note, candidate, tag, or push was created.
|
||||
1. Add the public explanation/value records and Result Explain, WriteEffective,
|
||||
and WriteExplanation delegation. Inspection uses the frozen engine state,
|
||||
not the mutable public Config field. Complete every remaining public method.
|
||||
2. Add SecretsReport, SecretsError, bootstrap records/status constants, and
|
||||
LoadSecretsDir. Preserve opt-in writes, partial reports, detached entries,
|
||||
writer counts/errors, and non-unwrapping typed errors.
|
||||
3. Exercise complete load-to-inspection workflows through the candidate. Prove
|
||||
that caller mutation of Config or returned records does not rewrite inspection
|
||||
history or affect a later query/load. Include malformed paths and nil receivers
|
||||
where the baseline already defines behavior.
|
||||
4. Compare the complete candidate exported surface with the baseline inventory,
|
||||
normalizing only the temporary import path. Resolve all signature/method/field
|
||||
omissions before cutover. No placeholder methods or ignored API mismatches.
|
||||
|
||||
Acceptance: the candidate implements the entire supported API and has real
|
||||
integration evidence for inspection, credentials, bootstrap, and writer failures.
|
||||
Run `go test ./internal/facade ./internal/engine`, race tests for both packages,
|
||||
and the everyday check. Root remains unchanged until Stage 8.
|
||||
|
||||
## Stage 8 — Cut over the root and remove migration duplication
|
||||
|
||||
Status: not started.
|
||||
|
||||
1. Replace root production plumbing with the tested candidate facade, retaining
|
||||
root package documentation and the root named types. Move files; do not leave
|
||||
forwarding aliases to the temporary facade. Remove `internal/facade` entirely.
|
||||
2. Root-owned unit/integration tests remain at root; engine mechanism tests remain
|
||||
with engine. Remove duplicated baseline copies after confirming coverage owners.
|
||||
Split mixed tests by boundary: assertions requiring private engine state move
|
||||
internally, while public lifecycle/consumer assertions still exercise root.
|
||||
Do not move all tests inward simply to avoid adapting public wrappers.
|
||||
3. Run existing executable API examples and maintained consumers unchanged in
|
||||
their imports/configuration types. Fix wrapper defects rather than migrating
|
||||
consumers to a different API. Test the actual root Secret binding after the move.
|
||||
4. Keep repository check tests at root. Repair documentation/fixture references,
|
||||
fuzz target locations, and development task commands as files move. For links
|
||||
formerly pointing at mixed implementation files, point API references at root
|
||||
and implementation references at engine as appropriate.
|
||||
5. Remove old root algorithms, temporary candidate tests superseded by root tests,
|
||||
and staging documentation. Preserve useful narrow private test seams, but no
|
||||
root accessors solely exposing engine internals to tests.
|
||||
|
||||
Acceptance: one engine serves the sole root facade; no candidate directory,
|
||||
parallel implementation, or root import from internal production code remains.
|
||||
Run the everyday check, `go test ./examples/consumers`, root examples, and
|
||||
`go test -race ./...`. All gates must pass in this handoff; do not leave a broken
|
||||
root suite for the final audit.
|
||||
|
||||
## Stage 9 — Audit the public boundary and finalize documentation
|
||||
|
||||
Status: not started.
|
||||
|
||||
1. Compare the final complete exported API against the baseline. Audit public
|
||||
signatures, fields, aliases, embedded types, method sets, and error returns
|
||||
for internal-type exposure. Review root code for remaining substantive
|
||||
algorithms; explain small value operations deliberately retained there.
|
||||
2. Extend the existing import-boundary check for inward production dependencies.
|
||||
Add a narrowly scoped public-boundary check that detects exported aliases to,
|
||||
embedded exposure of, and exported signatures/fields involving internal types.
|
||||
Use standard Go tooling and avoid private-field/layout snapshots. Account for
|
||||
generic declarations and public records referencing other public records.
|
||||
Keep behavior tests as the evidence for adapter correctness.
|
||||
3. Verify source ownership, secret binding isolation, zero/nil handling, helper
|
||||
audit semantics, typed errors, final Config versus frozen inspection, and the
|
||||
baseline resolution-entry optimization. Add tests only for uncovered risks.
|
||||
4. Finalize architecture-policy wording, ADR navigation, component overview,
|
||||
development test/fuzz commands, and public declaration comments. Add a concise
|
||||
internal facade mechanism document if needed for the secret/callback/record
|
||||
connections; do not spread implementation recipes across consumer docs.
|
||||
5. Update consumer orientation only where useful. Keep YAML behavior in its
|
||||
existing contract. Convert the feature roadmap to an implemented summary with
|
||||
canonical links and evidence; record remaining release preparation separately.
|
||||
|
||||
Acceptance: documented target matches code and no unapproved API/behavior change
|
||||
is hidden by the refactor. Run the everyday check, full race suite, consumer
|
||||
examples, documentation/import/public-boundary checks, and tests/builds using the
|
||||
supported baseline and newer Go toolchains recorded in the release procedure.
|
||||
Record actual versions/platforms; do not treat Linux tests or cross-builds as
|
||||
native macOS release evidence. Do not publish or select a version in this stage.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None blocking implementation. The API, snapshot boundary, missing-value semantics,
|
||||
source identity handling, and ownership are specified by the roadmap and decisions
|
||||
above. Example fallback behavior is illustrative application policy, not a missing
|
||||
library contract; record any selected example behavior without prescribing it to
|
||||
consumers. Release version and publication timing are later
|
||||
maintainer decisions, not unresolved implementation choices. If new evidence
|
||||
contradicts a target contract, document the conflict, a recommended correction and
|
||||
its rationale, and at least one viable alternative before proceeding past it.
|
||||
None requiring a user decision were identified. Wrappers, public type ownership,
|
||||
one final engine, per-operation exact-type secret adaptation, and a single cutover
|
||||
are specified above. Internal helper names and file grouping remain ordinary
|
||||
implementation choices. A newly discovered conflict with an existing contract
|
||||
must be surfaced with evidence and alternatives rather than resolved by changing
|
||||
consumer behavior or reverting to public aliases.
|
||||
|
||||
Reference in New Issue
Block a user