Retire completed roadmaps before the next release

This commit is contained in:
2026-10-27 19:15:40 +00:00
parent 68400ffdfb
commit 21c9bc0ad0
11 changed files with 39 additions and 3320 deletions

View File

@@ -43,7 +43,7 @@ public behavior.
| Shared source-version checks | [Configuration contract](config.md#shared-source-version-checks), [ADR 0013](adr/0013-shared-source-version-checks.md), and the version/source-version tests below |
| Documentation changes | The owning document |
| Licensing, dependencies, attribution, or release-document changes | [Project grant](../COPYRIGHT) and [attribution](oss/attribution.md); release changes also follow the release procedure |
| Architecture decisions or future work | [Future features](roadmap/future.md), [remediation roadmap](roadmap/roadmap.md), and relevant ADRs listed below |
| Architecture decisions or future work | [Future features](roadmap/future.md) and relevant ADRs listed below |
| Code and test changes | The owning contract and tests from the map below |
| Examples or copyable assets | The owning implemented contract and [consumer adoption guide](consumers/README.md) |
| Release preparation | [Release procedure](release.md) |
@@ -199,8 +199,25 @@ git show 34cbc0d:docs/roadmap/remediation.md
```
Potential enhancements are tracked in [future features](roadmap/future.md).
The completed audit remediation's scope is recorded in the
[remediation roadmap](roadmap/roadmap.md). The consolidated audit, including
The completed audit-remediation, internal-boundary simplification, shared-history,
and public API cleanup roadmaps, reviews, and resolved findings are preserved at
`68400ff` under `docs/roadmap/`. For example:
```sh
git show 68400ff:docs/roadmap/api.md
git show 68400ff:docs/roadmap/remediation.md
```
Their earlier completed implementation plans are preserved at
`bb5540b:docs/roadmap/implementation.md` (audit remediation),
`f6d54a0:docs/roadmap/implementation.md` (internal-boundary simplification), and
`07e4ae5:docs/roadmap/implementation.md` (shared history). The plan at
`68400ff:docs/roadmap/implementation.md` records the public API cleanup.
Current behavior and migration information belong to the
[configuration contract](config.md), public declaration comments, and
[consumer guide](consumers/README.md#pre-v1-public-api-cleanup).
The consolidated audit, including
findings, evidence, and settled scope decisions, is preserved at
`453da68:docs/roadmap/audit.md`; the original nine reports are preserved at
`ecd5d5d` under `docs/roadmap/`. Retrieve the consolidated record with:

View File

@@ -1,282 +0,0 @@
# Pre-v1 Public API Cleanup Roadmap
Status: implemented. The accepted API changes and integrated validation are
recorded in the [implementation plan](implementation.md).
Planning baseline: `7548fa1` (accepted decisions, with the v0.10.2 implementation
and subsequent interface/history remediation already present).
This roadmap owns the targeted end state, required changes, and completion
criteria. The [implementation plan](implementation.md) owns concrete edits,
sequential stages, validation commands, and progress tracking. The
[preliminary review](review.md) retains the API catalog and original proposals;
the [additional review](audit.md) retains
its evidence and alternatives. Proposal IDs below provide traceability.
## Purpose And Scope
Make the public API clearer before v1.0.0 through the accepted renames, narrower
recording methods, redundant-state removal, and direct source lookups. Preserve
existing configuration behavior except for the selected public replacements,
removals, and observable string changes. The root package remains the supported
facade; private engine structure can evolve within that boundary.
This is a breaking pre-v1 cleanup, not a compatibility-shim rollout. The renamed
or replaced public declarations give way to their selected forms. Existing Go
declaration comments and the [configuration contract](../config.md) continue to
own behavior that this roadmap does not change. No redesign of loading,
composition, history ownership, or YAML syntax is selected.
## Current Baseline
- [lifecycle.go](../../lifecycle.go), [records.go](../../records.go),
[discovery.go](../../discovery.go), and [secrets.go](../../secrets.go) expose
the old declarations listed below. There are corresponding engine declarations
and facade conversions, including [conversion.go](../../conversion.go).
- `Resolution.Record` accepts the inspection `ChangeKind` type, then rejects
kinds other than derived-default and normalization during loading. The generic
assignment helpers already select their own supported intent.
- `ImportSelection` represents only empty selections, but still carries a
single-valued outcome. Source records expose version and destination evidence
through names that do not fully describe that evidence.
- `Resolution.Source(id)` already performs passive, detached lookup during a
callback. `Result` and `LoadError` expose ordered `Sources()` lists but no
direct lookup. Failure records contain only the evidence available at failure.
- The shared history implementation and its admission boundary are already in
place. Provenance still uses `"base"`, and retained-history failures still use
`"explain.limit_exceeded"`.
- Current production-reference checks find only declarations for
`CodeOptionsUnavailable`, and the kind guard in `resolutionState.addRecord`
produces `CodeResolveInvalidKind`. Their removal requires the checks described
below, rather than inventing a new supported use for either code.
## Targeted Public API
The following table is the complete accepted change set. Unless stated otherwise,
renames retain the existing signature/type and behavior. Internal names are not
part of this public replacement list.
| Review item | Decision |
| --- | --- |
| D1 | Rename `Resolution.InputInfo` to `EntryInfo`, describing the fixed resolution-entry metadata view, including retained defaults and CLI assignments. Preserve its return type and lookup behavior. |
| D2 | Rename `Definition.Secret` to `Sensitive`. Preserve classification behavior, the `configkit:"secret"` tag, and typed `Secret` references. |
| D3 | Rename `SourceVersionModeDefault` to `SourceVersionModeMainOnly`, `SourceVersionCheck` to `SourceVersionEvidence`, and `SourceInfo.VersionCheck` to `VersionEvidence`. Retain `SourceVersionModeIfPresent` and `SourceVersionModeRequired`. |
| D4 | Rename `ImportSelection` to `EmptyImportSelection` and `SourceInfo.ImportSelections` to `EmptyImportSelections`. Retain path, location, and selector; remove `Outcome`, `ImportSelectionOutcome`, and `ImportSelectionNone`. |
| D5 | Rename `SourceInfo.DestinationUnresolved` to `DestinationIsFallback`, preserving its existing meaning and destination coordinates. |
| D6 (including N4) | Replace `Resolution.Record` with the two intent-specific methods below and remove `CodeResolveInvalidKind` once its public trigger is eliminated. Retain `ChangeKind` and its inspection constants. |
| D7 | Rename `LoadSecretsDir` to `LoadSecretsIntoEnvironment`, making process-environment mutation explicit. Preserve its signature apart from the name, report-plus-error behavior, and existing bootstrap semantics. |
| D8 | Remove `CodeOptionsUnavailable` after confirming there is no intentional supported role. Do not introduce a failure merely to give the code a producer; a concrete reason to retain it should be brought back for discussion. |
| A1 (including N5) | Add `Result.Source(id)` and `LoadError.Source(id)` for direct lookup of source evidence on successful and failed loads, alongside their existing `Sources()` enumeration. Keep source evidence distinct from passive resource access through `SourceContext(id)`. |
| N1 | Rename `Paths(paths ...string) Search` to `SearchPaths(paths ...string) Search`, replacing the old name outright. Preserve literal file candidates, ordering, deferred acquisition, and selection/failure behavior. Leave semantic `Path` APIs and `Searches` unchanged. |
| N2 | Replace `LayerBase = "base"` with `LayerMain = "main"`, without a compatibility alias. Update Go references and affected string comparisons, serialization, and rendered labels. Preserve the existing main-tier category, imported contributions' owning tiers, precedence, and acquisition order. |
| N3 | Replace `CodeExplainLimitExceeded = "explain.limit_exceeded"` with `CodeHistoryLimitExceeded = "history.limit_exceeded"`, without a compatibility alias. Update Go references and affected diagnostic-code consumers. Preserve the existing quota, retained-node accounting, failure conditions, diagnostic locations, and admission timing before effective secret lookup and application validation. |
| N6 | Rename `ImportDefaults.Follow` to `FollowSymlinks` and `ImportDefaults.Hidden` to `IncludeHidden`, replacing the old fields outright. Preserve `*bool` types, nil defaulting, explicit true/false, declaration overrides, traversal behavior, and search scope. Keep YAML keys `follow` and `hidden`, and Go fields `Required`, `Recursive`, and `Pattern`, unchanged. |
### Recording Methods (D6)
The selected signatures are:
```go
func (r *Resolution) RecordDerivedDefault(path Path, reason string, dependencies ...Path)
func (r *Resolution) RecordNormalization(path Path, reason string, dependencies ...Path)
```
Both record intent for work already performed by application code. They do not
assign values; `RecordDerivedDefault` does not check absence or suppress work on
explicit input. Keep `Default` and `Normalize` as the assignment-and-recording
helpers. Preserve path/reason/dependency validation, container coverage,
coalescing, net-change auditing, and callback lifetime. Replace the old `Record`
method outright rather than retain parallel public recording interfaces.
Include N4's diagnostic cleanup in this change: confirm that no supported public
input can still produce `resolve.invalid_kind`, then remove `CodeResolveInvalidKind`
without a replacement or compatibility alias. Preserve other recording diagnostics.
Internal defensive checks remain an implementation choice. If another legitimate
public trigger is found, bring it back for discussion rather than silently
reclassifying the failure.
### Version Policy And Evidence (D3)
`MainOnly` remains the zero mode and does not enable checking without a configured
version rule. The broader modes retain their requirement for a usable rule.
Document the modes together, preserving these distinctions:
- `MainOnly` retains the existing main-version check at its effective path.
Broader modes inspect each participating document in source-local coordinates
before placement and composition.
- `IfPresent` passes an absent main declaration to the application checker, while
absent non-main declarations skip the checker. `Required` rejects missing
declarations in participating documents. Existing `NoSource` behavior remains.
- Version evidence describes observed declarations or absence, not checker
invocation or acceptance. Preserve nil-versus-observed-absence meaning,
explicit null, locations, consumption, and redaction. No acceptance flag is added.
### Direct Source Lookup (A1)
```go
func (r *Result[T]) Source(id string) (SourceInfo, bool)
func (e *LoadError) Source(id string) (SourceInfo, bool)
```
Look up an acquired source by its load-local ID, returning the same information
represented in `Sources()` as a detached record. Empty or unknown IDs return
false. The query performs no source acquisition, filesystem access, or credential
lookup. Reuse existing source lookup and conversion machinery where practical;
no public registry, indexing strategy, or constant-time guarantee is required.
The failed-load lookup returns records from `LoadError.Sources()`'s partial
inventory and returns false for nil or zero-valued errors as well. Preserve
existing failed-load redaction and the evidence available at failure, including
incomplete placement information. Lookup neither completes acquisition nor
promises a record for every attempted source. This incorporates N5 into A1.
## Work Required From The Baseline
### Public Declarations And Facade Conversions
Update the public declarations, comments, wrappers, and conversions as a coherent
surface. Useful existing owners are:
| Area | Baseline owners and required work |
| --- | --- |
| Entry metadata, sensitivity, version mode, and import defaults | [lifecycle.go](../../lifecycle.go), [layered.go](../../layered.go), and [conversion.go](../../conversion.go): update both ordinary and layered loading paths, callback wrappers, and definition/default conversions. |
| Source evidence | [records.go](../../records.go) and [conversion.go](../../conversion.go): rename records/fields and remove the empty-selection outcome. Cover source records returned through results, callbacks, and load errors. |
| Recording intents | [lifecycle.go](../../lifecycle.go) and [engine resolution](../../internal/engine/resolution.go): provide the two methods, route them to the existing recording/auditing machinery, and remove the public kind-selecting method. |
| Discovery and bootstrap | [discovery.go](../../discovery.go) and [secrets.go](../../secrets.go): replace constructor/function names and migrate their callers without changing acquisition or environment mutation behavior. |
| Provenance and diagnostic values | [records.go](../../records.go), [engine records](../../internal/engine/records.go), producers, conversions, and renderers: apply the selected labels and codes consistently. |
| Direct lookup | [lifecycle.go](../../lifecycle.go), [diagnostic.go](../../diagnostic.go), and their engine collaborators: expose the existing retained source evidence through the new result/error methods. |
Use explicit root/engine conversion as needed; no public aliases or engine types
are needed to implement these replacements. Private naming and helper placement
belong to implementation planning. Avoid unrelated internal renames merely to
make every private identifier match the facade.
Preserve distinctions that the new names clarify:
- `EntryInfo` remains a fixed, detached entry-state lookup with its current
unknown/invalid/shape-mismatch behavior. `IsExplicit` retains its own existing
validation behavior; the rename does not unify those two query contracts.
- `Sensitive` remains the classification predicate, separate from typed secret
references. Existing tag/predicate/dependency classification and redaction
still apply.
- `DestinationIsFallback` still describes the nearest resolved enclosing
container when the full destination is unavailable. It does not promise an
item's exact coordinates or upgrade evidence retained at an earlier stage.
- Empty-selection records keep their existing scope, order, and declaration-local
coordinates. Removing `Outcome` does not create a complete import ledger.
- `FollowSymlinks` and `IncludeHidden` retain pointer defaulting and load-preflight
snapshot behavior. Do not mechanically rename YAML `follow` or `hidden` keys.
### Recording Entry Points And Obsolete Diagnostics
Implement the two recording methods over the existing resolution lifecycle.
Migrate manual-record callers according to the intent they currently supply;
keep assignment helper calls as helpers. Tests of container coverage, overlapping
records, dependencies, equal-valued intent, and net-change auditing continue to
protect the same behavior through the new entry points.
Complete the N4 check after replacing `Record`: trace supported recording routes,
then remove the obsolete exported invalid-kind code if its public trigger is
gone. Update tests of the removed input mechanism without weakening unrelated
record validation. Do not introduce a different public diagnostic solely to
represent an unreachable kind.
For D8, confirm both producer references and any documented intentional role for
`CodeOptionsUnavailable`, then remove the unused declaration and associated
active documentation. A concrete supported role discovered during this check
should be brought back for discussion. These are verification tasks for accepted
removals, not permission to delete other diagnostics that happen to be uncommon.
### Direct Lookup And Source Evidence
Implement the result and error methods against each receiver's retained source
inventory. Reuse source-copying and facade-conversion helpers where practical so
lookup and enumeration agree, including nested locations, version evidence,
empty-selection evidence, and detached paths. Follow existing result-query
nil/zero conventions; failed-load nil/zero behavior is specified above.
Keep `Sources()` enumeration and `SourceContext(id)` responsibilities intact.
Lookup must not repeat acquisition, finalize failure evidence, or change source
IDs. A slice search or a suitable existing index can satisfy the contract; no
new lookup complexity promise or shared lazy cache is selected.
### Observable Strings And Their Producers And Readers
Replace the main-tier category's `"base"` spelling with `"main"` wherever that
category crosses the public boundary or is rendered. Imported contributions keep
their owning tier and ordinary source-location attribution. Other uses of the
word "base", such as filesystem base directories, are unrelated.
Change every retained-history-limit diagnostic producer and relevant reader to
`"history.limit_exceeded"`, including inheritance and final load admission.
Preserve the quota, accounting, and failure timing. This rename does not make
history optional or add a new limit control.
Review Go constant references and literal comparisons/expected output separately:
compilation catches old symbols but not stale diagnostic or provenance strings.
Other codes and layer values retain their existing meaning and spelling.
### Consumer Guidance And Migration Information
Update declaration comments, applicable sections of [config.md](../config.md),
the [consumer guide](../consumers/README.md), README examples, and maintained
consumer code/fixtures under `examples/`. Use the new recording methods and source
lookups in relevant existing examples without expanding unrelated workflows.
Update internal documentation only where implemented ownership or terminology
changes. Accepted ADR decisions do not need revision for this cleanup.
Record the old-to-new Go names, removed outcome fields/codes, recording-call
replacements, and the two observable string changes as migration information.
Downstream applications may need to update literal comparisons and serialized
expectations as well as Go references. Version-specific release notes and
publication remain part of the normal [release process](../release.md); this
roadmap does not select a release version or authorize publication.
Keep revision-pinned review evidence and historical release records accurate for
their original revisions. Old names in those records are not stale active API
usage. Do not rewrite history or require external consumer migrations to claim
the repository implementation is complete.
## Validation And Completion
Follow the [testing policy](../policy/testing.md) and
[development guide](../development.md#baseline-validation). Reuse existing
behavioral tests for mechanical renames; new tests should protect the changed
entry points and added lookups, rather than duplicate implementation details.
- Check the root API exposes every selected replacement/addition and no obsolete
public declarations from the accepted removals. Preserve root-owned type
identity and the existing public/dependency boundary checks. Historical
references and useful private implementation names do not invalidate this check.
- Exercise both recording intents through the new public methods. Preserve
helper assignment behavior, validation, recorded origins, and history under
the existing resolution tests; update the old invalid-kind case when its input
mechanism is removed.
- Cover successful and failed source lookup against each receiver's enumeration,
missing IDs, applicable nil/zero cases, detachment of nested evidence, and
unchanged records after caller mutation. Reuse existing source/version/resource
fixtures for redaction, partial failure evidence, and passive access behavior.
Do not add source-completeness, performance, or concurrency guarantees.
- Retain the existing entry-shape, sensitivity, version-mode, empty-selection,
placement, import-default snapshot, discovery, and secrets-bootstrap coverage.
In particular, Go field renames must leave YAML declarations and tri-state
defaulting behavior intact.
- Update relevant provenance and diagnostic assertions for the selected strings.
Existing history-budget cases should continue to check rejection before secret
lookup and application validation, using the new code and unchanged accounting.
- Run the applicable focused checks during development and the everyday check
for the integrated code changes. Apply existing race-check guidance if the
implementation changes ownership/concurrency behavior. Validate documentation
links and maintained examples. No benchmark campaign or additional release
gate is introduced.
Completion means all accepted proposals are implemented, the diagnostic-removal
checks are resolved, maintained consumers and current documentation use the new
surface, and applicable checks pass with behavior preserved except for the
expressly selected changes. Update roadmap/review status with the actual evidence;
passing the current baseline suite is not evidence of future implementation.
No significant open design decision remains. D8's role check and D6/N4's public
reachability check are implementation verification tasks; unexpected evidence of
a legitimate public use should be discussed before changing that use. Private
helper choices and lookup mechanics are selected in the implementation plan.
Public path comparison (A2) remains deferred and outside this scope.

View File

@@ -1,201 +0,0 @@
# Additional Public API Review
Reviewed revision: `3ebb848eadf6e61275ecec6e35838ad6576cdd3a`.
Status: N1–N6 accepted, with N4 incorporated into D6 and N5 into A1.
Implementation has not begun.
## Scope And Assessment
This review accepts the inventory in [review.md](review.md) and independently
assesses the public interface through consumer workflows, declaration comments,
the [configuration contract](../config.md), and targeted implementation tracing.
It covers loading/discovery, source and resource ownership, paths and collection
registration, callbacks, inspection/diffs, secrets, and diagnostics. It does not
repeat the symbol inventory or conduct a correctness/performance audit.
[api.md](api.md) owns the accepted D1–D8, A1, and N1–N6 changes. Those decisions remain
settled; public path comparison (A2) remains deferred. The findings here are
additional opportunities, including consequences of the accepted cleanup.
The principal API boundaries remain useful. No broader consolidation of loading
options, filesystem roles, callbacks, or the two history projections is warranted
by this review. The remaining opportunities concern vocabulary, removal of an
obsolete input-error category, and a missing convenience on failed loads.
| Finding | Recommendation | Compatibility impact if accepted |
| --- | --- | --- |
| N1: discovery `Paths` (accepted) | Rename to `SearchPaths`; decision recorded in [api.md](api.md). | Go function rename. |
| N2: main-tier provenance (accepted) | Rename `LayerBase` to `LayerMain`, with value `"main"`; decision recorded in [api.md](api.md). | Go constant rename and observable string change. |
| N3: shared-history quota (accepted) | Rename `CodeExplainLimitExceeded` to `CodeHistoryLimitExceeded`, with value `"history.limit_exceeded"`; decision recorded in [api.md](api.md). | Go constant rename and machine-readable code change. |
| N4: recording-kind diagnostic (accepted through D6) | Incorporated into [D6 in api.md](api.md#recording-methods-d6). | Conditional constant removal. |
| N5: failed-load source lookup (accepted through A1) | Incorporated into [A1 in api.md](api.md#direct-source-lookup-a1). | Compatible addition. |
| N6: import-default field names (accepted) | Rename to `FollowSymlinks` and `IncludeHidden`; decision recorded in [api.md](api.md). | Go field renames; YAML keys unchanged. |
These accepted design changes are not defect severities or additional release
gates. The string-value changes in N2 and N3 are explicitly accepted.
## N1. Distinguish Discovery Paths From Semantic Paths
Status: accepted. [api.md](api.md) owns the selected change; the discussion below
preserves its rationale and alternatives.
Evidence: [path.go](../../path.go) defines `Path`, `Keys`, and `ParsePath` for
configuration-value coordinates. [discovery.go](../../discovery.go) defines
`Paths(paths ...string) Search` for literal host-file candidates. In the same
package, the plural of `Path` therefore constructs a different concept rather
than a collection of semantic paths. The preliminary catalog recommends retaining
the name and explaining the distinction.
Recommendation: rename the discovery constructor to
`SearchPaths(paths ...string) Search`. This aligns it with `SearchDirectory` and
makes a call such as `SearchPaths("./app.yml", "/etc/app.yml")` recognizable
without inspecting its return type. Keep literal-path interpretation, candidate
order, deferred acquisition, and all search behavior unchanged. No rename of
`Path`, new path type, or discovery redesign is needed.
Alternative: retain `Paths`. It is concise inside `SearchConfig` and its comment
is accurate, but package completion and standalone calls still mix filesystem
and configuration-path vocabulary. `FileCandidates` is another viable name,
although it is less consistent with `SearchDirectory`.
## N2. Use Main Consistently In Provenance
Status: accepted, including the string-value change. [api.md](api.md) owns the
selected change; the discussion below preserves its rationale and alternatives.
Evidence: `LoadLayered(main, ...)`, `SourceInfo.UnderlayIndex`, the configuration
contract, and the accepted `SourceVersionModeMainOnly` name distinguish main input
from lower-priority underlays. [records.go](../../records.go) instead exposes
`LayerBase = "base"` for the main tier. A consumer presenting provenance must
learn that this base is above underlays and is distinct from application defaults.
The preliminary catalog treats this as a documentation concern.
Recommendation: use `LayerMain = "main"` so API calls, provenance records, and
rendered layer labels use the same term. Preserve the existing layer category and
all precedence rules; imported contributions retain their owning tier. This is
a vocabulary change, not a new layer or a change to source acquisition order.
Changing the literal affects applications that compare, serialize, or display
layer strings as well as those referring to the Go constant. If selected, migrate
those uses and document the change together before v1.
Alternative: rename only the Go constant and keep `"base"`. That reduces string
compatibility impact but preserves two names for the same concept in application
code and rendered evidence. Keeping both the existing symbol and value is also
reasonable if the modest clarity gain does not justify migration; the preferred
pre-v1 outcome is one consistent term.
## N3. Name The Retained-History Limit For Its Actual Scope
Status: accepted, including the diagnostic-code string change. [api.md](api.md)
owns the selected change; the discussion below preserves its rationale and alternatives.
Evidence: [records.go](../../records.go) exposes
`CodeExplainLimitExceeded = "explain.limit_exceeded"`. Its production sites include
`executeLoadRequest` in [load.go](../../internal/engine/load.go) and
`inheritanceHistoryBudgetDiagnostic` in
[inheritance_walk.go](../../internal/engine/inheritance_walk.go). It can fail a load
without an application calling `Explain`. The completed shared model supplies
metadata, explanation, historical membership, and internal attribution readers.
Recommendation: rename the constant to `CodeHistoryLimitExceeded` and its value
to `"history.limit_exceeded"`. This describes the resource being bounded and
avoids suggesting that skipping explanation would avoid the load failure.
Preserve the quota, retained-node accounting, diagnostic locations, and admission
timing. This does not add a limit setting or change which configurations load.
Alternative: rename the Go constant but retain the existing code string, or keep
both names unchanged and clarify the diagnostic documentation. Either preserves
more consumer compatibility, but a retained `"explain.limit_exceeded"` remains
misleading to applications that work directly with diagnostic codes. A coordinated
pre-v1 change is preferable if the new terminology is accepted.
## N4. Review The Diagnostic Made Obsolete By D6
Status: accepted and incorporated into [D6](api.md#recording-methods-d6). The
discussion below preserves the rationale and alternative.
Evidence: the production use of `CodeResolveInvalidKind` is the kind guard in
`resolutionState.addRecord` in
[resolution.go](../../internal/engine/resolution.go). Today, public `Record` can
pass arbitrary `ChangeKind` values into it. Accepted D6 replaces that method with
two methods that select supported intents; `Default` and `Normalize` also supply
fixed supported kinds. This is distinct from D8's already unproduced
`CodeOptionsUnavailable`.
Recommendation: as part of D6 implementation, confirm that no supported public
input can produce `resolve.invalid_kind`, then remove its exported constant.
Retain the other recording diagnostics and the inspection `ChangeKind` values.
Internal defensive checks can remain where useful; their representation does
not require an unreachable public configuration-error category.
Alternative: retain the constant for consistency with previous diagnostics or
a concrete future use. That costs little code but leaves an unexplained public
error category after its input mechanism has disappeared. Do not add a public
trigger simply to justify keeping it. The removal remains conditional on the
final D6 implementation, rather than assuming a guard must be deleted now.
## N5. Offer Direct Source Lookup On Failed Loads
Status: accepted and incorporated into [A1](api.md#direct-source-lookup-a1). The
discussion below preserves the rationale and alternative.
Evidence: [diagnostic.go](../../diagnostic.go) exposes `LoadError.Diagnostics()`
and `LoadError.Sources()`. Diagnostic primary and related locations refer to
source IDs. Accepted A1 adds direct lookup on successful results, while
`Resolution.Source` already supports it during callbacks. A custom error reporter
still has to scan or index the failed load's source slice to follow the same IDs.
Recommendation: add this companion to A1:
```go
func (e *LoadError) Source(id string) (SourceInfo, bool)
```
It should return a detached record from the same partial inventory as
`LoadError.Sources()`, with false for empty/unknown IDs and nil/zero errors.
Preserve the existing failed-load redaction and available evidence; a lookup
does not complete acquisition or upgrade a partial destination/version record.
This is passive lookup, with no filesystem access or new source-completeness
guarantee. Existing source lookup/conversion helpers can be shared privately;
no public registry, interface hierarchy, or constant-time guarantee is needed.
Alternative: keep only `Sources()`. That is sufficient for applications using
the standard diagnostic renderer or building an index once. The addition can
also wait until after v1, since it is compatible. Adding it with A1 is a small,
coherent way to remove the same consumer bookkeeping on both outcomes of a load.
## N6. Make The Go Import Defaults More Self-Describing
Status: accepted. [api.md](api.md) owns the selected change; the discussion below
preserves its rationale and alternative.
Evidence: [lifecycle.go](../../lifecycle.go) exposes `ImportDefaults.Follow` and
`ImportDefaults.Hidden`. Their comments explain symbolic-link traversal and
inclusion of hidden files/directories, but an options literal alone does not
name the object being followed or say whether hidden entries are included.
Recommendation, at lower priority: consider `FollowSymlinks` and `IncludeHidden`
for those Go fields. Preserve `*bool`, nil defaulting, explicit false, the current
search scope, and the YAML keys `follow` and `hidden`. This would make the public
Go configuration more descriptive without changing the import language. Leave
`Required`, `Recursive`, and `Pattern` alone; they do not have the same ambiguity
in context.
Alternative: retain the current Go names to mirror YAML exactly. That is a real
benefit for readers moving between authored imports and application defaults,
and existing comments already resolve the ambiguity. This is an optional naming
refinement, weaker than N1's collision between two different path concepts.
## Discussion And Validation
All six findings have been accepted and incorporated into [api.md](api.md), with
N4 folded into D6 and N5 into A1. This audit retains the review evidence and
rationale; it is not an implementation plan or a permanent second API reference.
The review used the trusted catalog, root declarations and comments, current
contracts and consumer guidance, and graph-assisted production-reference checks.
No production code, tests, accepted decisions, or deferred features were changed.
No independent behavioral audit or performance benchmarks were run. Validation
for this document consists of the local documentation-link check and whitespace
review; it is not evidence that the proposed API changes are implemented.

View File

@@ -6,14 +6,6 @@ plans, or release commitments. Adopting a candidate requires an explicit design
decision and a separate plan. Significant architectural exclusions and their rationale belong
in ADRs; this document does not maintain a list of rejected proposals.
## 1. Separate glob entry preparation from traversal
Implemented in the [internal-boundary roadmap](internal-boundaries.md#glob-entry-preparation) (C03).
## 2. Simplify redundant facade-to-engine transport
Implemented in the [internal-boundary roadmap](internal-boundaries.md#facade-to-engine-transport) (F07).
## 3. Recursive body merging for identified records
Status: deferred. The implemented
@@ -91,3 +83,22 @@ evidence are preserved in Git history at `8e91a2b` under `docs/roadmap/`.
across loads without introducing aliasing?
- What cache lifetime and concurrency design would make schema reuse worthwhile
without excessive retained memory?
## 6. Public path comparison
Status: deferred pending a concrete consumer need. This preserves A2 from the
completed API review at `68400ff:docs/roadmap/review.md`.
`Path.String()` is a JSON Pointer spelling, not a complete semantic identity:
an explicit numeric mapping key and a sequence index can share it. The public
type is opaque and currently non-comparable.
If consumers need comparison, consider a small exact-representation operation
rather than exposing mutable segments or introducing a pattern language. A
proposal must distinguish exact segment representation, including parsed versus
explicit segments and invalid state, from resolution against a document. Pointer
string equality must not be presented as semantic equality.
Keeping `Path` solely as an opaque query/building value remains an alternative.
No current contract changes or implementation commitment follow from this entry;
a comparison operation can be added compatibly later.

View File

@@ -1,498 +0,0 @@
# Pre-v1 Public API Cleanup Implementation Plan
Status: complete; Stages 1–11 are complete.
Baseline: `07e4ae5d9236935389e81b207d4a8f00543fafce`.
Audience: a Luna coding agent executing the stages sequentially.
## Scope And Execution
Implement the accepted changes in the [API roadmap](api.md). That document owns
the target, rationale, and behavioral boundaries. The revision-pinned
[catalog](review.md) and [additional review](audit.md) preserve evidence and
alternatives; they are not additional work lists. The preceding completed
interface-remediation plan is preserved at
`07e4ae5:docs/roadmap/implementation.md`. References in older audit records to
that completed plan refer to its historical version, not this sprint.
This is one end-to-end sprint. Intermediate stages need not compile or leave a
coherent repository. Do not create compatibility aliases, temporary public
adapters, or intermediate releases to make individual stages green. Run focused
checks when buildable and record deferred checks until they can actually run.
Commit focused nonempty units under the [agent policy](../policy/agents.md),
recording actual completion and validation below. A stage is not complete merely
because its commands passed against the unchanged baseline.
Read `AGENTS.md`, the [development guide](../development.md), and applicable
[architecture](../policy/architecture.md), [testing](../policy/testing.md), and
[documentation](../policy/documentation.md) policies. Use graph-first discovery
and check current references before editing. Preserve unrelated work.
The sprint changes Go names, removes selected public state, adds two passive
queries, and changes exactly the two selected public strings. It does not change
loading/composition, YAML syntax, version policy, callback auditing, history
ownership, sensitivity, source acquisition, or secrets-bootstrap semantics.
Do not add performance promises, restrictions, dependencies, release gates, or
tests for incidental mechanics. Public path comparison (A2) remains deferred.
Do not select a release version, write version-specific release notes, publish,
or update external consumers in this sprint.
### Coverage Map
| Accepted item | Implementation owner |
| --- | --- |
| D1: `EntryInfo`; D2: `Sensitive` | Stage 1 |
| D3: version policy/evidence vocabulary | Stage 2 |
| D4: empty-selection records; D5: destination fallback name | Stage 3 |
| N6: descriptive Go import defaults | Stage 4 |
| N1: `SearchPaths`; D7: `LoadSecretsIntoEnvironment` | Stage 5 |
| D6/N4: recording methods and invalid-kind diagnostic removal | Stage 6 |
| N2: main-tier provenance name and string | Stage 7 |
| N3: history-limit name/string; D8: unproduced diagnostic removal | Stage 8 |
| A1/N5: successful and failed source lookup | Stage 9 |
| Current guidance, migration information, integrated completion | Stages 10–11 |
### Private Implementation Choices
- Keep the public facade root-owned, with explicit forwarding and conversion.
For facade-only renames, retain existing engine names unless steps below
require a change. Root `Sensitive` can forward to engine `Secret`, and root
`EntryInfo` can call engine `InputInfo`. Engine source-version records,
import-default fields, discovery/bootstrap functions, and destination-fallback
helpers need no cosmetic renames.
- Remove empty-selection outcome state through its small private transport path.
Keep candidate selection and admission decisions distinct from empty-selection
evidence.
- Replace root and engine kind-selecting `Resolution.Record` entry points with
fixed-intent methods. Keep common `addRecord` and assignment helpers; remove
the unreachable invalid-kind category after tracing remaining routes.
- Use one engine-local slice lookup helper for new result/error queries. Scan
the retained inventory and clone only a matching source with existing
`cloneSourceInfo`; do not clone `Sources()` in full to search it. Keep the
callback's existing indexed entry-state lookup unchanged. This is a private
implementation choice, not a public complexity guarantee.
- Rename main-layer and history-limit constants in both root and engine, so
producers, conversion, and rendering use the selected strings directly.
### Validation Conventions
Commands run from the repository root using an installed compatible Go toolchain
and already acquired dependencies. For Go checks use:
```sh
export GOTOOLCHAIN=local
export GOPROXY=off
export GOTELEMETRY=off
```
Keep disposable inventories outside the repository. Reuse behavioral tests for
mechanical renames. Do not add negative-compilation fixtures or a permanent
exported-symbol inventory test simply to prove old names disappeared; Stage 11
performs a one-time review. Focused commands name existing test families and the
proposed new lookup/recording cases. Include differently named new tests
explicitly. A no-test match is not validation.
Update affected declaration comments and maintained Go callers, including
examples, with each change. Stage 10 owns the integrated prose/migration sweep;
it is not a reason to leave obvious neighboring comments wrong.
## Stage 1 — Clarify Resolution Entry Metadata And Sensitivity
Status: complete. Implements D1 and D2.
1. In [lifecycle.go](../../lifecycle.go), rename root `Resolution.InputInfo` to
`EntryInfo` and `Definition.Secret` to `Sensitive`, without old aliases.
Update `toEngineDefinition` to adapt the new predicate into engine `Secret`.
Both loading entry points already share this conversion; retain that route.
2. Keep `EntryInfo`'s existing `(FieldInfo, bool)` forwarding and silent
unknown/invalid/shape-mismatch behavior. Its fixed entry view includes retained
typed defaults and CLI assignments. Do not substitute final metadata or
change `IsExplicit`'s separate schema-path validation behavior.
3. Migrate root tests and maintained consumer calls. Update inactive-context
cases in `facade_context_test.go`. Leave engine `InputInfo`, private
sensitivity names, the `configkit:"secret"` tag, and typed `Secret` references
unchanged. Do not broadly replace every identifier `Secret`.
4. Reuse `facade_resolution_presence_test.go`, `facade_candidate_test.go`,
`facade_context_test.go`, and existing secret/provenance owners. Preserve
ordinary/container classification and its union with tags/dependencies.
Focused validation:
```sh
go test . -run 'Resolution|InactiveContexts|FacadeLoad|Secret|Inspection|Diff' -count=1
go test ./internal/engine -run 'Resolution|Provenance|Secret|Sensitivity' -count=1
```
Completion: selected root names are in use; entry queries, lifecycle guards, and
classification behavior are unchanged.
## Stage 2 — Rename Version Policy And Evidence
Status: complete. Implements D3.
1. Replace root `SourceVersionModeDefault` with `SourceVersionModeMainOnly`
in the same zero slot in [lifecycle.go](../../lifecycle.go). Preserve numeric
values and behavior of `IfPresent`/`Required` and the numeric engine bridge
used by ordinary/layered loads.
2. In [records.go](../../records.go), rename `SourceVersionCheck` to
`SourceVersionEvidence` and `SourceInfo.VersionCheck` to `VersionEvidence`.
Update [conversion.go](../../conversion.go); name its root-returning helper
`fromEngineSourceVersionEvidence`, still accepting engine
`SourceVersionCheck`. Preserve fields, nil handling, detached paths/locations,
and sanitized text.
3. Migrate Go callers/tests/examples and version comments together. `MainOnly`
checks the effective path only with a configured rule; broader modes use
source-local paths and require a usable rule. Evidence means observation,
not acceptance or checker invocation. Add neither acceptance flags nor rules.
4. Reuse `records_test.go` and `facade_source_version_mode_test.go`: zero-mode
compatibility, missing-main checker invocation, absent non-main skipping,
`NoSource`, explicit null, consumption, mounted/item coordinates, redaction,
failure evidence, and detachment. Rename test names directly naming the
replaced declaration; do not duplicate their case matrix.
Focused validation:
```sh
go test . -run 'SourceVersion|VersionCheckConversion|VersionEvidenceConversion|Facade.*Version' -count=1
go test ./internal/engine -run 'SourceVersion' -count=1
```
Completion: all three replacements cross every source-record exposure correctly,
with existing version behavior intact.
## Stage 3 — Simplify Empty-Selection And Destination Evidence
Status: complete. Implements D4 and D5.
1. Replace root `ImportSelection` with `EmptyImportSelection` and
`SourceInfo.ImportSelections` with `EmptyImportSelections`. Retain `Path`,
`Location`, and `Selector`; remove `Outcome`, `ImportSelectionOutcome`,
and `ImportSelectionNone`. Keep `ImportSelector` and its three constants.
2. Update root conversion/tests. In engine `records.go`, remove `Outcome`
from `ImportSelectionForFacade`, the private evidence `outcome` field,
and `importSelectionOutcomeString`. Remove its initializer in
`import_acquisition.go` and the now-unused selection-outcome enum in
`import_selection.go` after checking references. Its unused
`importSelectionHasCandidates` member does not justify creating evidence.
Preserve actual admission outcomes such as `importAdmissionOptionalMissing`,
candidate handling, and accounting.
Existing private transport/helper names may stay unchanged.
3. Rename root `SourceInfo.DestinationUnresolved` to `DestinationIsFallback`.
Conversion still reads existing private fallback evidence. Keep destination
coordinates and placement modes; do not derive exact item coordinates where
existing evidence does not contain them.
4. Migrate root/internal assertions and maintained Go callers. Preserve empty
selection scope/order, declaration-local paths, and copies on results,
callbacks, and errors. Remove assertions of the retired outcome only, keeping
real selection assertions. Reuse nested-item fallback/partial-failure cases;
do not invent new placement states.
Focused validation:
```sh
go test . -run 'ImportSelection|EmptyImport|Destination|SourceInfo|Fallback|RelativeImport' -count=1
go test ./internal/engine -run 'ImportSelection|ImportEvidence|Import|Destination|Item' -count=1
```
Completion: source records use the reduced record/new fields; selection,
placement, partial evidence, and detachment retain established behavior.
## Stage 4 — Make Go Import Defaults Self-Describing
Status: complete. Implements N6.
1. Replace root `ImportDefaults.Follow` with `FollowSymlinks` and `Hidden`
with `IncludeHidden`, preserving `*bool` and the other three fields.
Update comments and `toEngineImportDefaults` to map to existing engine
`Follow`/`Hidden`.
2. Migrate root option literals, examples, and tests by type/context. Do not
rename YAML `follow`/`hidden`, declaration parser fields, or unrelated
hidden-file controls. Do not snapshot earlier in the facade: existing engine
preflight owns the once-per-load snapshot before acquisition/callbacks.
3. Reuse `facade_import_defaults_test.go` for nil, explicit false/true,
transitive/underlay application, declaration overrides, pattern selection,
and callback-mutation isolation. Preserve traversal/search scope and nil
versus non-nil empty `Pattern`.
Focused validation:
```sh
go test . -run 'ImportDefaults|ImportSelection|EmptyImport' -count=1
go test ./internal/engine -run 'ImportDefaults|Import.*(Hidden|Follow|Symlink|Pattern)' -count=1
```
Completion: descriptive Go fields feed unchanged YAML policy and snapshots in
ordinary/layered loading.
## Stage 5 — Clarify Discovery And Environment Bootstrap Names
Status: complete. Implements N1 and D7.
1. In root [discovery.go](../../discovery.go), rename `Paths` to `SearchPaths`.
Forward the same literal candidates to existing engine machinery. Migrate
callers without touching semantic `Path`, `Keys`, or `Searches`. Retain
order, deferred acquisition, and selected-file terminal failures.
2. In root [secrets.go](../../secrets.go), rename `LoadSecretsDir` to
`LoadSecretsIntoEnvironment`, preserving arguments, return values, error
conversion, and engine bootstrap call. Comments should plainly describe
explicit process-environment mutation, already-set/empty preservation,
reports, and existing partial-application behavior.
3. Migrate maintained calls, affected executable example identifiers, and nearby
documentation. Do not couple bootstrap to `Load`, rename report/error types,
or add filesystem/transactional guarantees. Process-mutating tests remain
serial and restore state. Private engine function names may remain unchanged.
Focused validation:
```sh
go test . -run 'Search|Discovery|SecretsBootstrap|LoadSecrets|Example_weatherStartup' -count=1
go test ./internal/engine -run 'Search|Discovery|LoadSecretsDir' -count=1
go test ./examples/consumers -run 'Discovery|WorkingDirectoryPreset|SequenceCatalog|Weather' -count=1
```
Completion: old root functions are replaced outright; acquisition and safe
bootstrap behavior remain unchanged.
## Stage 6 — Replace Kind-Selecting Recording With Intent Methods
Status: complete. Implements D6 and N4.
1. Replace root and engine `Resolution.Record` with:
```go
RecordDerivedDefault(path Path, reason string, dependencies ...Path)
RecordNormalization(path Path, reason string, dependencies ...Path)
```
Root methods forward through existing live-context guards/path conversion.
Engine methods call `activeState().addRecord` with their fixed
`ChangeDerivedDefault` or `ChangeNormalization` and no helper expected
value. Share existing validation/coalescing/auditing; neither method assigns
values or tests absence.
2. Migrate manual callers according to their existing supported kind in root
tests, engine tests, examples, and current snippets. Leave `Default` and
`Normalize` as assignment-and-recording helpers using existing `addRecord`
routes. Retain `ChangeKind` and all inspection constants.
3. Check every remaining `addRecord` caller supplies a supported fixed kind.
Then remove the invalid-kind guard and `CodeResolveInvalidKind` from both
root/engine. Remove only the obsolete arbitrary-kind case in
`resolution_test.go`; retain invalid path, target, reason, dependency,
projected-value, and unrecorded-change cases. If a legitimate supported
public trigger remains, report it before changing its behavior.
4. Update nil/zero/closed-context coverage for both methods. Add/adapt a small
root case, `TestFacadeResolutionRecordingIntents`, proving each method's
resulting origin/history kind after application assignment. Include explicit
input changed and recorded as a derived default, so this cannot accidentally
become the absence-checking helper. Reuse container coverage, overlapping
records, repeated/equal intent, dependencies, and net-change tests elsewhere.
Focused validation:
```sh
go test . -run 'Resolution|InactiveContexts|FacadeLoad|Historical|History' -count=1
go test ./internal/engine -run 'Resolution|Provenance|Callback|History' -count=1
```
Completion: root exposes only the selected manual recording methods; supported
input cannot produce `resolve.invalid_kind`. Other auditing/lifetime behavior
is unchanged.
## Stage 7 — Use Main Consistently In Provenance
Status: complete. Implements N2.
1. Replace `LayerBase = "base"` with `LayerMain = "main"` in root/engine
records. Migrate producer references across acquisition, composition,
decoding, inheritance origins, overrides, discovery, and history/rendering.
Keep existing tier assignments.
2. Review literal `"base"` provenance comparisons/rendered expectations, not just
Go references. Update only the main-tier category. Filesystem base
directories, domain values, fixture labels, and unrelated words are unchanged.
Retain other layer spellings and source IDs/order.
3. Update existing assertions in root, engine, and examples. Add a focused root
records assertion for `string(LayerMain) == "main"`; reuse a public
provenance/rendering fixture to verify main/imported-main attribution reaches
consumers with that label. Imports retain their owning tier; other categories,
precedence, and acquisition order are unchanged.
Focused validation:
```sh
go test . -run 'Facade|Historical|History|Layer|PublicValues' -count=1
go test ./internal/engine -run 'Compose|Overlay|Decode|Provenance|History|Explain|Grouped|Discovery' -count=1
```
Completion: main-tier evidence/rendering consistently uses the selected constant
and spelling without attribution/precedence changes.
## Stage 8 — Rename The History Diagnostic And Remove The Unused Code
Status: complete. Implements N3 and D8.
1. Replace root/engine `CodeExplainLimitExceeded` with
`CodeHistoryLimitExceeded = "history.limit_exceeded"`. Update every producer,
including `executeLoadRequest` in engine `load.go` and
`inheritanceHistoryBudgetDiagnostic` in `inheritance_walk.go`, plus
readers/literal assertions. Other explanation errors are unchanged.
2. Reuse `history_budget_test.go` and inheritance-budget cases to preserve exact
retained-node accounting and failure before effective secret lookup/application
validation. Change expected code, not quota, fixtures, accounting, or timing.
Add the new literal to focused root constant-value assertions, not a general
API snapshot test.
3. Reconfirm `CodeOptionsUnavailable`/`options.unavailable` has no producer or
documented supported/reserved role. Baseline references are the two
declarations and planning/review evidence. Remove both declarations. If a
concrete supported role is discovered, bring it back for discussion rather
than remove behavior or create a trigger. Other diagnostic codes stay intact.
Focused validation:
```sh
go test ./internal/engine -run 'HistoryBudget|RetainedNode|InheritanceExpansionEnforcesRetainedHistoryBudget' -count=1
go test . -run 'PublicValues|LoadReturnsFacadeLoadError|Diagnostics|Historical' -count=1
```
Completion: history failures carry the new code under unchanged conditions;
D8's role check is resolved and its unproduced declaration is removed.
## Stage 9 — Add Detached Source Lookup On Results And Load Errors
Status: complete. Implements A1 and N5.
1. Add engine-local `lookupSourceInfo(sources []SourceInfo, id string)
(SourceInfo, bool)` beside source cloning helpers. Return zero/false for
empty/unknown IDs; otherwise scan the retained slice and clone the match.
Add nil-safe engine `Inspection.Source` and `LoadError.Source` using it.
Do not change inventories, query-time evidence, or the callback's source map.
2. Add the selected root methods in [lifecycle.go](../../lifecycle.go) and
[diagnostic.go](../../diagnostic.go):
```go
func (r *Result[T]) Source(id string) (SourceInfo, bool)
func (e *LoadError) Source(id string) (SourceInfo, bool)
```
Return zero/false for nil or zero receivers/absent delegated state. Convert a
found engine record through the Stage 2–3 `fromEngineSourceInfo` path.
Keep enumeration, passive `SourceContext`, and callback `Source` intact.
3. Document passive lookup, load-local IDs, detachment, and partial failed-load
evidence. Read `LoadError`'s already-sanitized retained sources, not raw
acquisition/version state. Do not complete placement, reinterpret absence
as acceptance, open resources, or perform credential lookup.
4. Add focused external-package tests `TestResultSourceLookup` and
`TestLoadErrorSourceLookup`, or extend equivalent facade owners. Compare found
records to enumeration; check empty/unknown IDs and nil/zero receivers.
Mutate nested returned evidence and verify repeated lookup/enumeration are
unchanged. Reuse import/version fixtures for `ImportedFrom`,
`VersionEvidence.Location`, and empty-selection slices. Cover representative
layered sources and failure evidence with redaction/partial placement.
Reuse counting-resource fixtures where useful to demonstrate no query I/O;
do not add a full load-mode matrix, timing assertions, or concurrency promises.
Focused validation:
```sh
go test . -run 'SourceLookup|SourceVersionEvidence|ImportSelection|EmptyImport|SourceContext|Fallback' -count=1
go test ./internal/engine -run 'SourceVersion|ImportSelection|Resource|ResolutionInput' -count=1
```
Completion: both lookups agree with enumeration, return detached records, and
preserve success/failure evidence without new load activity.
## Stage 10 — Complete Guidance And Consumer Migration Information
Status: complete. Consumer guidance, current contracts, declaration comments, and
maintained examples use the selected API; historical report references identify
their completed plan revision.
1. Review declaration comments and [config.md](../config.md)'s owning sections:
discovery, import defaults/evidence, source versions, layered metadata,
sensitivity, bootstrap, resolution, and history limits. Update relevant names
and strings while preserving semantics. Keep detailed Go contracts beside
declarations rather than reproducing them throughout prose.
2. Migrate README, [consumer guidance](../consumers/README.md), root executable
examples, and maintained `examples/` code/fixtures. Use new source lookup where
an existing example enumerates solely to find an ID; retain enumeration where
that is the task. Use new intent methods for manual recording while retaining
assignment helpers where they do the work.
3. Add a concise **Pre-v1 public API cleanup** subsection to the consumer guide's
existing migration section. Include every old-to-new name from the roadmap,
removed empty-selection outcome field/type/constant and diagnostic constants,
both `Record` call rewrites, new lookups, and `"base"` → `"main"` /
`"explain.limit_exceeded"` → `"history.limit_exceeded"`. Explain unchanged
YAML `follow`/`hidden` and `configkit:"secret"`; literal comparisons and
serialized expectations may need migration as well as Go code. Do not claim
external migrations or select a version for this breaking pre-v1 work.
4. Update internal docs only for changed descriptions. Do not rewrite accepted
ADR decisions, pinned reviews, or historical release notes. If retained
completed reports link to this overwritten plan, make their old-plan reference
revision-specific rather than implying it concerns this sprint. Preserve
unrelated proposals/status.
Validation:
```sh
go test ./examples/consumers -count=1
go test . -run '^Example|TestLocalDocumentationLinks' -count=1
git diff --check
```
Validation passed with the commands above.
Completion: current guidance/examples use the selected API, migration information
is complete, and historical evidence remains accurate.
## Stage 11 — Verify The Integrated Surface And Finish The Sprint
Status: complete. The root export audit matches the accepted table, D6/N4 and
D8 reference checks are resolved, and the required focused and repository-wide
checks passed.
1. Compare actual root exports/comments against the roadmap's complete accepted
table using `go doc -all .` and, if needed, a disposable AST/type inventory
outside the checkout. Check fields/method sets/constants as well as top-level
names. Confirm both recording and lookup signatures and no removed root
names/compatibility aliases.
2. Search active Go callers/current documentation for old names and the two old
strings. Review matches by context: retained private engine names, domain
text, migration instructions, pinned reviews, and historical records are valid.
Do not globally rewrite `base`, `Secret`, `Paths`, `Follow`, `Hidden`, or
`Record`, or enforce a blanket zero-match rule. Recheck D6/N4 and D8 reference
evidence against the final tree.
3. Run deferred focused tests and existing boundary/link checks:
```sh
go test . -run 'TestPublicFacadeBoundary|TestDependencyImportBoundary|TestLocalDocumentationLinks' -count=1
./scripts/check.sh
```
Validation passed: the focused boundary/link test and `./scripts/check.sh`.
`go doc -all .` confirmed the selected root fields, constants, methods, and
signatures; removed root names and old diagnostic strings were absent.
Graph tracing showed `addRecord` is called only by the two fixed-intent
methods and the assignment helpers. No Go declaration or use of either
removed diagnostic remains.
The everyday check already runs the full default suite, vet, and build,
including examples. No additional full-suite pass or benchmarks are needed
solely for this plan. If implementation changed mutable ownership/concurrency
beyond forwarding/existing clone paths, follow the development guide with
`go test -race ./...`; record the reason/result. This introduces no permanent
release gate and requires no CI waiting.
4. Inspect the final diff for unselected semantics, ownership changes,
dependencies, artifacts, and strengthened guarantees. Record actual stage
completion/validation here. Mark the API roadmap implemented only after its
selected surface and behavior preservation are verified. Keep completed
planning/review records until normal release preparation. Commit completed
nonempty units with plain-English messages; report remaining work accurately.
Completion: all accepted items are implemented, diagnostic role/reachability
checks are resolved, applicable checks pass, and current docs/examples agree.
No unselected public or semantic changes remain. This is implementation
completion, not release publication or external consumer verification.
## Decision Status
No open design question is required to execute this plan. D8's role check and
D6/N4's reachability check are verification tasks with an explicit discussion path
if unexpected supported use is found. Private helper naming may be adjusted for
cohesion without changing the selected surface or behavior.

View File

@@ -1,337 +0,0 @@
# Interface Contract Remediation Roadmap
Status: implemented. The shared history owner and all four audit corrections
are complete; validation is recorded in the completed plan at
`07e4ae5:docs/roadmap/implementation.md`.
Planning baseline: `1f64673` (v0.10.2 implementation plus documentation changes).
This roadmap covers the four findings in the [interface audit](interfaces.md).
The audit owns reproductions, evidence, and finding numbers. This document owns
the combined target, required changes, and design intent. The completed plan at
`07e4ae5:docs/roadmap/implementation.md` records concrete stages, private
implementation choices, validation commands, and progress.
## Purpose And Scope
Restore agreement between ConfigKit's presence, metadata, history, and diagnostic
interfaces under their existing contracts. The first three findings concern
semantic paths that share a JSON Pointer spelling; the fourth concerns a
composition claimant order that is lost during diagnostic canonicalization.
Build one shared internal history model from which metadata history and
explanation are derived. It will own the historical evidence and semantic
attribution used by both views, replacing their independent reconstruction of
that attribution. This is the selected resolution of OD-1.
The [Path declarations](../../path.go), [metadata contract](../config.md#layered-metadata),
[resolution contract](../config.md#recorded-application-resolution), and
[strict-composition contract](../config.md#strict-document-composition) remain
authoritative. This work changes the implementation of those contracts without
selecting a new public API, path syntax, precedence rule, or configuration
restriction. The existing root-owned facade and engine responsibilities remain.
## Current Baseline And Targeted End State
An explicit mapping-key path and an explicit sequence-index path can both render
as `/shape/0`. `ParsePath` deliberately leaves numeric tokens contextual, whereas
`Keys` and `Index` preserve the caller's explicit choice. The engine already has
shape-aware effective/historical path indexes and a frozen resolution-entry view,
but some queries and metadata updates still use pointer spelling alone.
History currently reconstructs definition and previous-origin records, while
explanation replays source contributions, inheritance, decoding checkpoints, CLI
assignments, and grouped resolution snapshots. Neither public representation
contains everything needed by the other. The target is shared underlying evidence
with distinct projections for each interface, preserving their existing behavior.
| Finding | Current discrepancy | Target |
| --- | --- | --- |
| 1: resolution presence | `IsExplicit` and `Default` consult string-keyed presence after schema validation, which cannot determine actual shape beneath dynamic values. | Presence decisions use the matching semantic path in the fixed resolution-entry state. |
| 2: effective explicitness | Resolution retains old field information when pointer spelling survives a container-shape change. | Input explicitness carries forward for the same semantic path; a newly introduced key or index does not inherit another path's explicitness. |
| 3: history attribution | A historical-shape check admits the query, then `History` returns the entire textual-pointer bucket; explanation selects evidence separately. | Both interfaces derive attribution from one shared history model, selecting exact definitions, previous origins, and applicable ancestor operations by semantic path. |
| 4: claimant order | Composition constructs related locations in assembly order, then canonicalization sorts them by location. | Structured and rendered composition conflicts preserve the selected claimant order. |
For example, if input contains `shape: {'0': old}` and recorded application
resolution changes `shape` to a sequence, the old key remains an input path in
the callback's entry view. The new index is effective in final metadata but was
not explicitly supplied by that input. Its history must not present the old key's
definition as its own. This distinction concerns container shape, not identity
tracking for records that move within a sequence.
## Required Changes
### 1. Use Entry-State Shape For Resolution Presence
In [resolution.go](../../internal/engine/resolution.go), align `IsExplicit` and
the explicitness decision in `Default` with the frozen, shape-aware entry metadata
already used by `InputInfo` in
[load_metadata.go](../../internal/engine/load_metadata.go).
Reuse the existing semantic-path matching machinery. A shared private lookup can
avoid divergent presence decisions without requiring every public query to have
identical error behavior. In particular:
- Preserve `IsExplicit`'s schema-path validation and its existing diagnostics.
`InputInfo` remains a query that returns false for missing or mismatched paths
without those diagnostics.
- Preserve `Default`'s helper-target validation, assignment, recording, and final
helper-value checks. Correct its presence gate; do not bypass the other checks.
- Use entry-state shape even after application code changes the working value.
Explicit false, zero, empty, and null retain their existing presence meaning.
- Keep callback lifetime and detached metadata behavior intact.
A shape mismatch beneath a schema-valid dynamic path must not become explicit
merely because its pointer spelling matches an input path. Contextually parsed
numeric tokens continue to resolve against the entry state's actual containers.
### 2. Preserve Explicitness By Semantic Path During Resolution
Update resolution metadata application and field refresh in
[load_metadata.go](../../internal/engine/load_metadata.go) to compare the relevant
entry and final shapes before reusing presence information.
The comparison must account for container kinds along the full path. A mapping
key becoming a sequence index at an ancestor can change a descendant's semantic
path even when its last segment and full pointer spelling stay unchanged.
Keep explicitness for matching surviving paths, including descendants covered by
an ancestor normalization record. New semantic paths introduced by resolution
remain non-explicit, and retain the applicable recorded resolution origin.
Removed paths leave effective metadata while remaining available to historical
queries under the existing contract. Do not clear every descendant's presence
just because its parent was normalized.
Use the existing before/after projection and entry metadata where possible.
This work does not require tracking every transient mutation inside a callback;
the existing net-change and coalesced-record model continues to apply.
### 3. Build A Shared Internal History Model
#### Evidence And Ownership
Give historical evidence one private owner in the engine. Composition,
inheritance, decoding, CLI assignment, and resolution continue to perform their
existing work and supply evidence to that owner. The model does not drive a
second configuration load or repeat application callbacks.
Organize the evidence into ordered groups with the phase boundaries required by
the existing inspection contracts:
| Evidence group | Information to preserve |
| --- | --- |
| Default baseline | Canonical typed application defaults or Go-zero values and their default attribution. |
| Source contributions | Underlay, main-tier, and selected-overlay contributions in their existing order; exact definitions, composite origins, displaced definitions, raw policy-validated values, and sequence-position projections. |
| Inheritance | Parent transfers, child contributions including equal-valued replacements, controlling directives and their consumption, identity-field preservation, and contribution origins/dependencies. |
| Typed checkpoints | Pre-CLI and post-CLI typed projections and attribution needed to reconcile retained defaults and raw contributions with decoded values. |
| CLI contributions | Explicit assignments, including equal-valued assignments, raw supplied representation, source attribution, and affected paths. |
| Resolution | Original recorded operations, reasons, dependencies, and before/after evidence, with the grouping needed to explain overlapping records as net subtree changes. |
The logical order remains baseline, source contributions, inheritance, pre-CLI
decoding reconciliation, CLI contributions, post-CLI typed reconciliation, then
recorded resolution. The two typed checkpoints belong on their respective sides
of the CLI contribution phase.
The groups describe logical inspection chronology, not a transcript of every
physical mutation. Retain the existing net-change and coalescing rules for
resolution. Preserve superseded raw input that need not decode into the final
application type; typed checkpoints do not replace that evidence.
Use a small set of ordinary private structures and existing document/path
mechanisms. Exact types, indexes, and storage layout belong in the implementation
plan. Complete evidence means enough for the existing history, explanation, and
internal attribution consumers; it does not add persistence, a public event API,
or recording of transient application effects.
#### Shared Selection And Attribution
Retain concrete semantic-path evidence when it is available. A query over the
model must distinguish exact definitions from operations affecting an ancestor
or descendant, determine the relevant previous origins, and apply sequence
contribution coordinates at their assigned positions.
The affected semantic path and the operation's reported path are distinct facts.
For example, a replacement at `/shape` can remove a mapping key and introduce a
sequence index with the same pointer spelling. Preserve enough before/after shape
evidence to answer each explicit query correctly. Merely filtering `Change.Path`
after concrete segment kinds have been discarded is insufficient.
Make this selection and attribution common to both projections, including
inheritance integration and `Previous` reconstruction. A false definition must
not survive as a previous origin after it has been excluded from the query's
history. Preserve genuine ancestor replacement/removal effects and chronology.
A path that never existed does not acquire history solely because an ancestor
changed.
Keep the contextual meaning of parsed numeric tokens across historical states
and the one-entry-per-canonical-pointer enumeration of `HistoricalPaths`.
Historical-path membership and enumeration must also derive from the shared
evidence. Preserve the existing inventory of default-only paths, displaced
descendants, consumed inheritance controls, and historical mapping-key and
sequence-index shapes, including evidence that a public projection coalesces or
omits. Indexes and compact derived records may remain, but their membership and
attribution must come from that evidence. Metadata queries should not require
full configuration replay or YAML rendering to obtain definition and origin
records.
#### Distinct Public Projections
`Metadata.History` produces detached definition, inheritance, and resolution
records from the model. `Explain` reconstructs and safely renders values from
the same evidence, retaining its existing decoding and grouping behavior. Neither
public API should be implemented by translating the other's public result.
Preserve these intentional differences:
| Case | Metadata history | Explanation |
| --- | --- | --- |
| Several strict contributions to a mapping | Composite definition/origin information. | Each contribution's effect on the mapping. |
| Raw input differs from canonical typed values | Definition and attribution records. | The applicable decoding reconciliation transition. |
| Overlapping resolution records | Specific recorded attribution under the existing coverage rules. | One net subtree event with combined reasons and dependencies. |
| Equal-valued input or inheritance operations | Explicit definition and operation attribution remains visible. | Relevant operations remain visible even when before and after values are equal. |
Agreement means consistent semantic identity and attribution, not identical
record lists. Retain original resolution records as evidence from which net
replay groups are derived; a grouped explanation alone loses information needed
by metadata.
#### Replace Existing Attribution Machinery
The common model must replace independent historical attribution, rather than
become another representation alongside it. The completed plan at
`07e4ae5:docs/roadmap/implementation.md` mapped the following owners to their
replacements:
| Current owner | Required change |
| --- | --- |
| History-building portions of `metadataFromDecoded`, `applyOverlayMetadata`, and `appendPostDecodeCLIHistory` in [load_metadata.go](../../internal/engine/load_metadata.go) and [override_context.go](../../internal/engine/override_context.go) | Produce or project shared evidence instead of independently reconstructing pointer-keyed histories. Retain effective-field construction where it is still needed. |
| Resolution-history construction in `applyResolutionMetadata` and `appendResolutionDescendantHistory` in [load_metadata.go](../../internal/engine/load_metadata.go) | Derive resolution records and previous origins through common semantic selection and attribution. Retain the effective-field updates still needed by final metadata. |
| Historical-path inventory in `finalizeMetadataPaths` in [metadata.go](../../internal/engine/metadata.go) | Derive historical membership, shape indexes, and enumeration from the shared evidence while preserving the existing path inventory. Retain effective-path indexing where needed. |
| Previous-origin reconstruction and inheritance insertion in [inheritance_history.go](../../internal/engine/inheritance_history.go) | Use common semantic selection and attribution while preserving the existing inheritance projection rules. |
| Relevance and chronology orchestration in [explain.go](../../internal/engine/explain.go) | Consume the shared model; retain useful value reconstruction and redaction helpers. |
| Separate published inheritance evidence for metadata and explanation in [load_result.go](../../internal/engine/load_result.go) | Consolidate under the frozen history owner where independent copies are unnecessary; keep required output detachment. |
| Original and coalesced resolution evidence in [history.go](../../internal/engine/history.go) | Own original records in the model and derive the replay grouping without losing specific attribution or maintaining a second independently interpreted history. |
Existing source, sequence-combination, and inheritance evidence producers may
remain useful. Their algorithms need not be rewritten merely to contribute to
the model. Likewise, final effective fields, the frozen resolution-entry view,
source records, and final configuration snapshots retain their responsibilities.
Findings 1 and 2 still require explicit presence corrections.
#### Other Consumers, Lifetime, And Resource Accounting
Move historical dependency reads in
[secret classification](../../internal/engine/secret.go) and historical location
fallbacks in [override_context.go](../../internal/engine/override_context.go) to
the shared evidence. Do not retain the old history machinery solely for those
readers. Preserve existing sensitivity propagation and historical classification;
correcting attribution does not authorize narrowing redaction for paths that
share a pointer spelling.
Evidence must be available at the lifecycle stages where these internal readers
need it, including diagnostic attribution before publication. Keep the callback's
entry metadata fixed while later resolution evidence is added. Finalize the
model for published queries without introducing lazy mutation of shared state.
Private frozen evidence may be shared by the two views while their returned
records remain detached under the existing ownership contract.
Reuse retained trees and existing lookup machinery where practical. Review
retained-history accounting against the resulting ownership graph, including
typed checkpoints and inheritance/resolution evidence. The model does not require
a full document copy per event, a new quota, or a new concurrency guarantee.
Reduced duplication is a design benefit to evaluate, not a promised memory or
speed improvement.
Preserve final retained-history budget admission before effective secret lookup
and application validation. Do not defer that check to publication or a later
query. Count the resulting retained ownership graph; tests should exercise its
budget boundary and failure timing without requiring obsolete duplicate copies
or the old representation's exact node totals.
### 4. Preserve Composition Claimant Order Through Diagnostics
Keep the order produced by `compositionConflictDiagnostic` in
[compose.go](../../internal/engine/compose.go) through accumulation, `LoadError`,
facade conversion, and `WriteDiagnostics` in
[diagnostic.go](../../internal/engine/diagnostic.go).
Prefer a narrow internal canonicalization rule based on the diagnostic semantics
already carried across these boundaries. Identify the composition diagnostic
producers governed by the claimant-order contract rather than changing all
related-location lists. Preserve canonical ordering of diagnostic records and
the existing sorting of related locations for other diagnostics.
The root renderer converts public diagnostic records back to engine records and
canonicalizes them again. An engine-only marker discarded by facade conversion
would therefore be insufficient. No public ordering option is needed for this
correction. Keep primary selection, conflict detection, acquisition order, and
dependency-ready scheduling unchanged.
## Validation And Completion
Follow the [testing policy](../policy/testing.md) and use the
[development guide](../development.md#baseline-validation) for checks. Extend
existing test owners rather than recreating full internal matrices at the facade.
The focused cases should cover:
- Both mapping-key/index mismatch directions beneath dynamic values, including
explicit paths and contextual parsed pointers. Check callback presence and
`Default` behavior, including the fixed entry view after working-value changes.
- Both container-shape transitions during recorded resolution, a descendant
affected by an ancestor shape change, and a same-shape normalization control.
Check explicitness and recorded origins through final metadata and explanation.
- Shape-changing CLI and resolution history, checking exact definitions,
`Previous`, and genuine ancestor replacement/removal. Include contextual
enumeration/query coverage, including historical paths whose evidence is
coalesced or omitted by a public projection. Preserve existing inheritance and
overlay cases; add another case only where those integrations expose an
unprotected risk.
- Shared-model projections across composite mapping origins, typed decoding and
default reconciliation, equal-valued writes, inheritance controls/transfers,
mapped sequence replacements and removed descendants, and overlapping
resolution records. Reuse existing cases where they protect those semantics;
check the intentional differences between the public projections as well as
agreement on attribution.
- Historical sensitivity/dependency propagation, diagnostic source-location
fallback, detached returned records, and complete retained-history accounting
under the new ownership graph. Preserve budget rejection before effective
secret lookup and application validation. Use small controlled budget cases
and existing disclosure fixtures rather than memory or timing thresholds.
- A composition conflict whose acquisition and assembly orders differ, using the
audit fixture. Check both published diagnostics and rendered related-location
order, while retaining coverage for ordinary diagnostic canonicalization.
Retain existing checks for invalid schema paths, callback lifetime, detached
records, safe historical rendering, and default/null presence semantics. Test
observable behavior rather than a particular private index or number of copies.
No benchmark campaign, broader audit, or new release gate is part of this roadmap.
Completion requires all four findings to have implementation and regression
evidence, appropriate development checks, and both history interfaces deriving
semantic attribution from the shared model. Review the final producer/consumer
map to confirm superseded independent reconstruction has been removed,
historical-path discovery derives from shared evidence, and internal attribution
readers have migrated. Passing the four defect regressions while retaining the
old parallel attribution machinery is not completion.
The audit's previous passing suite is not validation of these future changes.
Scope here does not promise identical output from different inspection interfaces
or a universal guarantee about unrelated path-processing code.
## Decision Status
OD-1 is settled: build one shared internal history model with distinct metadata
and explanation projections. The design investigation found that both views
require evidence the other's public representation does not preserve. Shared
ownership can replace independent attribution and redundant retained evidence,
while keeping the current lifecycle and public contracts.
The narrower repair of stored history events would leave the separate replay
attribution owner in place. Promoting current explanation output to authoritative
history would lose composite-definition and specific resolution information.
The selected common underlying model addresses both concerns. This accepts a
larger internal refactor than a local shape-filtering fix.
No significant open decision remains at roadmap level. Private representation,
indexing, and helper choices are recorded in the completed plan at
`07e4ae5:docs/roadmap/implementation.md`.
This roadmap does not revise accepted ADRs.

View File

@@ -1,213 +0,0 @@
# API and Interface Contract Audit
Reviewed baseline: `01b08e76a3fecc22c4414437bfeaa07b4b7a85aa`.
Status: all four findings resolved. This remains an audit record, not an
implementation plan; resolution evidence is recorded with each finding.
The [interface remediation roadmap](interface-remediation.md) describes the
settled target and its dependencies, including a shared internal history model
for metadata history and explanation. The completed implementation and
integrated validation are recorded at
`07e4ae5:docs/roadmap/implementation.md`.
This audit retains the findings and original reproduction evidence.
## Scope and evidence
Reviewed the root-owned public facade and its conversions, engine loading and
callback boundaries, source/discovery and filesystem ownership, schema and typed
value handling, metadata/history, safe rendering and comparison, secrets, and
the document/YAML boundary against declaration comments, the
[configuration contract](../config.md), and applicable architectural decisions.
The root delegates to the synchronous engine lifecycle; parser-independent
documents support source assembly and inheritance, and completed results retain
detached configuration and frozen inspection state.
The findings below were reproduced through the public API with a disposable Go
program outside the repository, using labeled byte sources and `fstest.MapFS`.
The full existing suite passed with Go 1.26.10 on Linux:
```sh
GOTOOLCHAIN=local GOTELEMETRY=off GOPROXY=off go test ./... -count=1
```
The existing tests cover many ownership, lifetime, error, and zero-value cases,
but do not detect these cross-interface discrepancies. No benchmarks, external
service calls, or new test files were needed. The recommendations restore
existing contracts rather than introduce new API restrictions or guarantees.
## 1. Resolution presence queries confuse explicit keys with sequence indexes
Priority: P2. Status: resolved. Evidence: `TestFacadeResolutionPresenceUsesDynamicEntryShape`,
the engine resolution/provenance/default regressions, and the integrated checks
recorded at `07e4ae5:docs/roadmap/implementation.md`.
Relevant code:
- [`Resolution.IsExplicit`](../../internal/engine/resolution.go), lines 75–82.
- `Resolution.InputInfo` and `resolutionInput.lookup` in
[resolution.go](../../internal/engine/resolution.go) and
[load_metadata.go](../../internal/engine/load_metadata.go).
- `resolveSchemaPath` in [schema.go](../../internal/engine/schema.go).
The public [path contract](../../path.go) distinguishes explicit mapping keys
from sequence indexes. The [resolution contract](../config.md#recorded-application-resolution)
defines surviving-input presence and a fixed entry-state metadata view;
[metadata queries](../config.md#layered-metadata) preserve container shapes.
`IsExplicit` validates against the schema but then looks up presence using only
`path.String()`. In dynamic `any` state, schema validation cannot establish the
actual container shape. Consequently these distinct queries collide:
```go
key := configkit.Keys("shape", "0")
index := configkit.Keys("shape").Index(0)
```
Load `map[string]any` from `shape: [input]` with a resolver that queries both
paths, without changing the configuration. Observed results:
| Query | `Resolution.IsExplicit` | `Resolution.InputInfo` found | Final `Metadata.IsExplicit` |
| --- | --- | --- | --- |
| Explicit mapping key | true | false | false |
| Explicit sequence index | true | true | true |
The mapping-key path never existed and should not be reported as explicit.
Applications branching on presence receive inconsistent answers depending on
which public query they use, even during an otherwise unchanged load.
Potential remediation: use the retained entry-state semantic shape when checking
resolution presence, consistently with `InputInfo` and `Metadata.IsExplicit`.
Review the same string-only presence lookup in `Default` while preserving its
existing assignment and callback-lifetime rules. Add focused public coverage for
both mapping-to-index and index-to-mapping mismatches under dynamic values,
including contextually parsed pointers. This does not require changing the
documented treatment of invalid schema paths.
## 2. Resolution can transfer explicitness to a different semantic path
Priority: P2. Status: resolved. Evidence: the engine resolution/metadata/provenance
regressions, the full engine suite, and the integrated checks recorded at
`07e4ae5:docs/roadmap/implementation.md`.
Relevant code: `applyResolutionMetadata`, `applyResolutionFieldOrigins`, and
`refreshResolutionFields` in
[load_metadata.go](../../internal/engine/load_metadata.go), especially lines
371–387; final shape checking in
[`Metadata.effectiveField`](../../internal/engine/metadata.go).
The [metadata contract](../config.md#layered-metadata) distinguishes key/index
shapes and keeps derived values for absent input non-explicit. Recorded
resolution changes origins without creating input explicitness.
Reproduction:
1. Load `map[string]any` from `shape: {'0': old}`.
2. In resolution, replace `configuration["shape"]` with `[]any{"new"}` and
record normalization at `Keys("shape")`.
3. Query final metadata using the two paths from finding 1.
The old explicit mapping-key path correctly stops being effective, but the newly
created sequence index reports `Explicit: true`. That sequence index was never
supplied by input. `refreshResolutionFields` reuses the old `FieldInfo` whenever
its textual pointer survives; it updates null state without checking whether
the old and new containers give that pointer different semantic shapes. Final
lookup validates only the new shape, so it cannot recover the lost distinction.
This can mislead consumers inspecting whether a final value was explicitly
configured rather than introduced by application resolution. The converse
sequence-to-numeric-key transition has the same underlying collision.
Potential remediation: retain entry/effective shape evidence across resolution
and carry input explicitness forward only for the matching semantic path.
Preserve existing explicitness for same-shape paths and the recorded resolution
origin; do not clear every descendant's presence merely because an ancestor was
normalized. Cover both shape transitions and agreement among `Lookup`,
`IsExplicit`, and explanation. Fixing finding 1's callback query alone does not
repair this publication path.
## 3. Metadata history mixes numeric-key and sequence-index definitions
Priority: P2. Status: resolved. Evidence: `TestPublicHistoryUsesResolutionShapeAttribution`,
`TestPublicHistoricalPathsEnumerateContextualShapes`, shared history-selection
and explanation regressions, and the integrated checks recorded at
`07e4ae5:docs/roadmap/implementation.md`.
Relevant code:
- [`Metadata.History`](../../internal/engine/metadata.go), lines 56–65.
- `metadataFromDecoded`, `applyOverlayMetadata`, and history event construction
in [load_metadata.go](../../internal/engine/load_metadata.go).
- Shape-aware replay in [`Inspection.Explain`](../../internal/engine/explain.go).
The [metadata contract](../config.md#layered-metadata) promises semantic
key/index distinctions for historical as well as effective queries. The
historical-path index retains both shapes, but `History` uses that index only to
check that the query was ever known. It then retrieves the entire history bucket
by textual pointer, without selecting the query's shape.
Reproduction: load `shape: {'0': old}` into `map[string]any`, then apply a CLI
override at `Keys("shape")` with `[]any{"new"}`. Both explicit shapes were
observed and share `/shape/0`.
`History(Keys("shape").Index(0))` incorrectly includes the original base
definition of the mapping key, and identifies that base origin as the previous
origin of the CLI operation. The old input defined a key, not an index. In the
same successful result, `Explain` for the explicit index correctly shows only
its CLI introduction. `History` for the key and index returns identical records
despite their different lifetimes.
Consequences include false source attribution and disagreement between the two
public history interfaces when consumers inspect removed or introduced paths.
Potential remediation: retain enough container-shape evidence with stored
history events, or select relevant events from existing shape-aware replay
evidence. Filter exact definitions and previous origins by the queried semantic
shape while retaining genuine ancestor replacement/removal events. Filtering
only on the current `Change.Path` is insufficient where construction already
replaced concrete segment kinds with parsed tokens. Preserve the intentional
one-pointer-per-entry `HistoricalPaths` enumeration and contextual behavior of
`ParsePath`; neither public contract needs redesign. Cover both direction changes
and compare metadata history with explanation.
## 4. Diagnostic canonicalization breaks composition claimant ordering
Priority: P3. Status: resolved. Evidence: `TestFacadeCompositionConflictKeepsClaimantOrder`,
the engine diagnostic-order regression, and the integrated checks recorded at
`07e4ae5:docs/roadmap/implementation.md`.
Relevant code: `compositionConflictDiagnostic` in
[compose.go](../../internal/engine/compose.go) and `canonicalDiagnostics` in
[diagnostic.go](../../internal/engine/diagnostic.go), particularly lines 88–92.
The latter runs both while constructing `LoadError` and in `WriteDiagnostics`.
The [strict-composition contract](../config.md#strict-document-composition)
specifies the first claim in scoped assembly order as primary and every remaining
claim in the same order as related locations.
A native-filesystem fixture demonstrates the discrepancy:
```text
main.yml imports a.yml, b.yml, c.yml
a.yml imports z.yml
b.yml, c.yml, and z.yml each define x
```
Acquisition assigns main=source-1, a=source-2, z=source-3, b=source-4,
c=source-5. Dependency-ready assembly considers the three conflicting claims
in b, c, z order. The composer correctly chooses b as primary and constructs
related locations c, z. Generic canonicalization sorts those related locations
by acquisition ID, publishing z, c instead. The reproduced public diagnostic
has primary source-4 and related source-3, source-5.
All claimants remain present and the conflict still fails loading, so this is
lower impact than the presence/history defects. It nevertheless violates the
documented reporting order and gives consumers two different ordering rules
within one conflict report.
Potential remediation: preserve the selected semantic ordering for composition
claimant lists through accumulation and rendering, while retaining canonical
ordering of diagnostic records and the established sorting of related locations
for diagnostics without a semantic-order contract. Add one public failure-path
case whose acquisition and claimant orders differ and verify both structured
diagnostics and rendered output. No scheduler or precedence change is needed.

View File

@@ -1,186 +0,0 @@
# Import Discovery And Facade Boundary Simplification
Status: target defined; execution is tracked in the [implementation plan](implementation.md).
Baseline: `80cf23e7102fca2c5d7c7621e75f930a1ef1ecad`.
This roadmap brings together the two refactors deferred until after the audit
remediation sprint: glob entry preparation (**C03**) and facade-to-engine
transport simplification (**F07**). Their original motivation is retained in the
[glob](future.md#1-separate-glob-entry-preparation-from-traversal) and
[transport](future.md#2-simplify-redundant-facade-to-engine-transport) future-work
entries and the [consolidated audit](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md).
The goal is clearer responsibility and ownership with fewer redundant internal
representations. Both changes preserve the public API and configuration model.
They are independent work areas within one maintenance effort, not a new loading
workflow. The [implementation plan](implementation.md) owns concrete edits,
stages, validation commands, and execution progress.
## Baseline And Targeted End State
The preceding sprint repaired directory-failure classification and shared link
dependency evaluation, clarified acquisition handoffs, and removed unused
placement bridges. Those changes provide the baseline; this work does not repeat
or redesign them.
| Area | Current baseline | Target |
| --- | --- | --- |
| Glob discovery | `selectGlobImportCandidates` interleaves traversal and set operations with substantial per-entry preparation. | The selector coordinates discovery; a cohesive private operation prepares a wildcard-discovered entry. |
| Facade transport | Import defaults pass through root conversion, an exported engine transport record, an equivalent private engine record, and repeated snapshots. Placement construction also uses setter-style bridges. | Root-owned public records convert explicitly to one engine defaults representation, with straightforward engine option/input construction and identifiable ownership copies. |
The [architecture policy](../policy/architecture.md), [configuration
contract](../config.md), and public declarations remain authoritative. [ADR
0006](../adr/0006-public-facade.md) continues to govern the facade boundary. These
refactors do not require a new architectural decision or changes to its accepted
decision. The [component overview](../internal/overview.md) and [import
internals](../internal/imports.md) own implemented structure.
## Glob Entry Preparation
### Target Responsibility Split
In [glob.go](../../internal/engine/glob.go), the selector continues to own pattern
validation, component advancement, literal-segment handling, directory
enumeration and its accounting, candidate union and sorting, final target
validation, exclusions, and requiredness.
A private glob-specific preparation operation owns the patterned branch's work
for one directory entry: matching and hidden-entry filtering, lexical/access path
construction, containment checks, target inspection and discovered-link policy,
intermediate-directory eligibility, host/native access-path selection, labels,
and candidate construction. Its result distinguishes a selected candidate, an
ordinary skip, and a diagnostic. It receives the facts needed for that entry
rather than mutating the selector's candidate set or surrounding traversal state.
Use the existing filesystem access and link-resolution helpers. Directory
selection already has `prepareImportDirectoryEntry`; it remains a separate owner
because its recursion, matching, absence handling, and traversal state differ.
Share a smaller operation only where it actually expresses the same semantics
and reduces duplication. No common visitor or discovery strategy is needed for
this extraction.
### Behavior To Preserve
Preserve the current distinctions and processing order documented by
[explicit imports](../config.md#explicit-imports) and exercised by the selection
tests:
- Literal components remain explicit selections; discovery filters apply at
their existing wildcard boundaries. Moving matching or hidden filtering after
filesystem inspection would change which errors are observable.
- Keep lexical candidate identity separate from resolved access identity.
Native source identity and relative imports retain namespace spelling; host
access retains its existing canonicalization and containment behavior.
- Keep discovered-link follow rules, intermediate-directory eligibility,
missing-target handling, and structured diagnostic sites/classifications.
Do not make glob and directory rules identical as a side effect of extraction.
- Preserve deterministic candidate and error order, union/deduplication,
directory-entry accounting, and the existing point of source admission.
Final selected-file validation remains before exclusions where it is today;
exclusions do not hide discovery or resource errors.
- Retain the preceding sprint's distinction between missing-on-open directories
and failures after opening, along with operation-local completed-link reuse.
The intended benefit is that a maintainer can understand traversal separately
from entry eligibility and identity preparation. This is a readability change;
it does not select a different search algorithm or claim a speedup.
## Facade-To-Engine Transport
### Target Representations
The root keeps its public `ImportDefaults`, `PlacementMode`, `Options`, `Input`,
and `LayeredOptions` types. Explicit conversions remain appropriate at this
boundary; consumers must not acquire engine type identity, fields, or methods.
Inside the engine, consolidate the equivalent `ImportDefaults` transport and
private `importDefaults` records into one presence-aware input-defaults
representation. Carry that representation through the ordinary and layered
options, normalized request, and acquisition policy instead of translating the
same five fields between equivalent engine records.
Keep `resolvedImportPolicy` distinct: it represents concrete per-declaration
choices, including selector-specific rules, rather than another copy of optional
load-wide input. Likewise, registration compilation and the normalized loading
request have real responsibilities and are not replacement targets.
Make placement and defaults straightforward to construct on the existing engine
option/input records. Remove decorator-style bridges made unnecessary by that
construction surface. Exporting fields or types within `internal/engine` where
needed by the root converter does not make them part of ConfigKit's supported
public API. Keep explicit enum conversion and preserve unknown values until the
existing option validation can report them; conversion must not silently choose
a default or move validation into facade setters.
### Ownership And Copying
Make the actual mutable ownership boundaries identifiable. Public load calls
continue to snapshot pointed-to default booleans and the pattern slice before
source acquisition, as specified by the [public
declaration](../../lifecycle.go). Preserve omitted, explicit false, nil pattern,
and non-nil empty pattern as distinct inputs where they are distinct today.
Review the entire handoff, not just the duplicate type definitions: root
conversion in [conversion.go](../../conversion.go), ordinary construction in
[lifecycle.go](../../lifecycle.go), layered construction in
[layered.go](../../layered.go), engine options and normalization, acquisition
policy construction, and declaration resolution in
[import_spec.go](../../internal/engine/import_spec.go).
Remove snapshots that merely recopy already-owned read-only defaults as they
cross those private representations. Keep copies needed when establishing
independent mutable ownership, including any independently owned resolved
policy. The target is explainable ownership and less redundant work, not an exact
copy count or removal of copying throughout the library. Shared defaults within
one load can be read-only by implementation discipline; this creates no new
caller immutability or concurrency requirement.
Preserve ordinary and layered option preflight before source reads and application
hooks, independent declaration overrides, transitive/underlay propagation,
selected-overlay behavior, destination normalization, and invalid-enum
diagnostics. Keep callback, source, path, secret, and returned-record conversions
whose ownership or domain meaning is not redundant transport.
The intended benefit is fewer concepts, field mappings, and construction paths
to maintain. Reduced copying is useful but does not establish an end-to-end
performance guarantee.
## Documentation Ownership
Internal documentation describes the resulting discovery and transport
boundaries once implemented. Public declarations continue to own the existing
snapshot and type contracts; this effort selects no new public behavior.
The corresponding C03/F07 future-work entries serve as navigation to this
roadmap rather than parallel target or progress inventories. Unrelated future
candidates remain separate. The implementation plan owns the documentation
updates and retirement of replaced private representations and helpers.
## Validation And Completion
Follow the [testing policy](../policy/testing.md) and [development
guide](../development.md#baseline-validation). Reuse the existing glob selection,
directory/link failure, import-default, placement, public facade, and dependency
boundary coverage. Add a focused behavioral case only where a concrete
extraction or ownership risk lacks protection.
For discovery, verify literal versus wildcard filters, host/native identity,
contained and escaping links, optional absence versus read/close failures,
ordering, exclusions, regular-file checks, and existing resource accounting. For
transport, verify nil/empty and explicit-false distinctions, mutation of caller
default storage after the load snapshot through a supported callback, invalid
options before input consumption, both loading APIs, and transitive/underlay
defaults. Existing conversion-focused tests may change with private construction;
preserve the useful ownership behavior rather than fossilizing a helper's shape.
Completion requires an easier-to-follow selector, one engine input-defaults
representation, fewer redundant mapping/construction sites, justified remaining
copies, and preserved public behavior under the relevant checks. Review source
and ownership flow as well as passing tests; moving complexity into a helper or
renaming a duplicate record alone is insufficient.
No benchmark campaign, complexity score, allocation threshold, exact filesystem
call trace, new resource limit, or additional release gate is required. This work
adds no dependencies, caches, background activity, or new consumer controls, and
does not reopen other audit findings or deferred performance experiments. Routine
helper signatures, names, and file placement belong to the implementation plan;
no significant product or architectural decision remains open for this scope.

View File

@@ -1,523 +0,0 @@
# Implementation Remediation Review Findings
Current follow-up: the public API cleanup finding (9) is complete.
The earlier interface-remediation review and completed findings 1–8 below are
retained as historical evidence. The current review's scope and validation are
recorded with finding 9.
Reviewed commit: `98c8b7c8423a816acdfceace36165366b13719ee`.
Algorithmic follow-up: `91769e9` (unchanged production code).
Comparison baseline: `0dd16aa8a935645ae2847d32edd86461dc4618f0`
(the planning commit, before the implementation sprint).
Scope: the completed implementation plan at
`07e4ae5:docs/roadmap/implementation.md` and
[interface remediation roadmap](interface-remediation.md). This report records
remaining defects and newly introduced scalability problems; it does not change
the selected architecture or public contracts. All eight findings are complete;
implementation and validation evidence is recorded per finding.
## Architecture And Review Evidence
The public root-owned facade still delegates to the synchronous engine. A
per-load history builder now normalizes baseline, source, inheritance, typed
checkpoint, CLI, and resolution evidence. Metadata and inspection share its
frozen model, with separate public projections and detached query results. The
resolution callback retains a separate fixed entry-state view. Retained-root
admission remains before effective secret lookup and application validation.
The old independent metadata history and published inheritance copies are gone.
The original presence/explicitness corrections and composition claimant-order
correction are present. The shared-history migration is substantial, but the
findings below prevent treating its behavioral preservation and indexed-query
targets as fully satisfied. No additional public restriction, guarantee, or
prohibition requiring reversal was identified in the reviewed changes. In
particular, private builder lifecycle guards are not new consumer obligations.
Review combined code/plan inspection, graph-based producer/consumer tracing,
existing tests, and disposable public-API probes run against both revisions.
The first three findings were reproduced; the remaining five follow from the
new loops and index contents, without performance measurements. Probe programs
and the baseline checkout were kept outside the repository. Production code and
repository tests were not changed during the review.
The following checks passed with Go 1.26.10 on Linux, using the installed toolchain
and cached dependencies:
```sh
GOTOOLCHAIN=local GOTELEMETRY=off GOPROXY=off go test ./... -count=1
GOTOOLCHAIN=local GOTELEMETRY=off GOPROXY=off go test -race ./...
GOTOOLCHAIN=local GOTELEMETRY=off GOPROXY=off ./scripts/check.sh
git diff --check
```
The passing suite does not detect the reproduced regressions. Suggested coverage
below targets those gaps rather than adding a general test matrix or new gates.
The algorithmic follow-up checked normalization, lookup/index construction,
selection, replay dispatch, sensitivity preparation, and retained-root traversal.
It expanded finding 4 to the inheritance/resolution adapters and added findings
7–8. The existing focused engine history, explanation, inheritance, and secret
tests passed with `go test ./internal/engine -run 'History|Explain|Inheritance|Secret' -count=1`.
No benchmark data was collected. The selected shared model, ordered-ID sorting,
net resolution grouping, scratch-state normalization, whole-tree explanation
replay, and fixed-point sensitivity closure are not being reconsidered. The
deferred contributor-trace and branch-local replay experiments remain separate.
## 1. Final Dynamic Field Tags Can Be Lost From Redaction
Priority: P1. Status: complete.
Completion: final inventory/sensitivity is registered unconditionally before
freezing. `TestFinalDynamicTagRedactsEqualValuedResolution` failed before the fix
and passes afterward, protecting effective and historical public rendering.
Validation: the full ordinary suite and `git diff --check` passed.
Relevant code: `resolveLoadInput` in
[load_result.go](../../internal/engine/load_result.go), lines 56–73;
`freezeMetadataHistory` in [metadata.go](../../internal/engine/metadata.go),
lines 146–155; and `sensitivityCandidates` in
[history_select.go](../../internal/engine/history_select.go), lines 201–253.
Resolution evidence is observed only when resolution produces snapshots. The
final projection is assigned to `model.final` but never unconditionally observed
for path/sensitivity facts. Classification now consumes model observations,
instead of also traversing the final projection as the baseline did.
Reproduction through the public API:
```go
type classified struct {
Token string `yaml:"token" configkit:"secret"`
}
type configuration struct {
Value any `yaml:"value"`
}
result, err := configkit.Load[configuration](
configkit.Bytes("input", []byte("value: {token: AUDIT-CANARY}\n")),
configkit.Options[configuration]{
Definition: configkit.Definition[configuration]{
Resolve: func(c *configuration, _ *configkit.Resolution) error {
c.Value = classified{Token: "AUDIT-CANARY"}
return nil
},
},
},
)
```
The map-to-struct assignment leaves canonical values unchanged, so the existing
net-change audit accepts the load without a recorded normalization. The final
projection carries the concrete struct's secret tag, but no snapshot adds that
classification to the model. `WriteEffective` and `Explain(Keys("value",
"token"))` expose the canary, including historical explanation values. At the
baseline they redact it and report `Redacted: true`.
Expected behavior follows the existing
[secret-classification contract](../config.md#secret-classification-and-effective-rendering):
concrete dynamic tags and final/historical classification remain effective.
The plan also expressly requires observing the final projection. This concerns
ordinary tagged configuration, not evidence of resolved credential bytes being
stored in history.
Remediation: register final concrete path and sensitivity facts before freezing
and classification, independently of whether resolution recorded a value
change. Preserve pointer-conservative historical masking and dependency closure.
Do not require extra resolver records or reject equal-valued dynamic type
changes as a workaround. Add a public lifecycle regression for this case that
checks effective and historical redaction.
## 2. Intermediate Source Definitions Are Duplicated In Metadata History
Priority: P2. Status: complete.
Completion: displaced definitions are omitted only when a preceding operation
already introduced that concrete path with the same represented origin.
`TestTierHistoryRecordsEachContributionOnce` failed before the fix and passes
for both differing and equal-valued tiers. The full ordinary suite and
`git diff --check` passed, including the existing shape-change regressions.
Relevant code: `observeSourceEvidence` in
[history_source.go](../../internal/engine/history_source.go), lines 82–117;
and previous-origin construction in `selectHistory` in
[history_select.go](../../internal/engine/history_select.go), lines 108–117.
Each overlay record unconditionally registers its displaced definitions. A
definition already represented by the preceding replacement operation is
therefore emitted again when the next tier displaces it. Selection chains
`Previous` through both records. The baseline's `appendInputOriginEvent`
suppressed an already represented origin; the new normalization does not apply
that rule to displaced definitions.
Reproduction: load `map[string]any` with two ordered underlays containing
`x: low` and `x: mid`, and main input containing `x: high`. Query
`Metadata().History(Keys("x"))`.
- Expected and baseline: three contributions, low → mid → high.
- Actual: four contributions, low → mid → mid → high. The second mid record
names the same source location as its own previous origin.
This is not a genuine additional equal-valued write. It is one source
contribution registered twice, contrary to the plan's explicit preservation of
origin containment and duplicate suppression.
Remediation: normalize displaced definitions against already represented
semantic operations in the shared model. Preserve genuine independent writes,
including equal values, and keep key/index shapes distinct. Add a three-tier
history regression checking source chronology and previous-origin links.
## 3. Container Explanations Take The Last Descendant Replacement's Origin
Priority: P2. Status: complete.
Completion: explanation uses the model's direct query-scoped contribution when
available and reserves operation fallback for paths without that contribution.
`TestOverlayExplanationUsesQueryScopedOrigin` failed before the fix and now
checks root, enclosing mapping, and leaf locations. The full ordinary suite and
`git diff --check` passed, including removed/replaced descendant coverage.
Relevant code: `historySelectionForApplication` in
[explain.go](../../internal/engine/explain.go), lines 183–207; and descendant
selection in [history_select.go](../../internal/engine/history_select.go).
The explanation helper obtains a direct application selection, then overwrites
it with every selected replacement linked to that application. For a container
query, multiple child replacements can be relevant; the last one supplies the
origin for the entire application event, regardless of the query's scope.
Reproduction: main YAML is:
```yaml
a: base
group: {x: X, y: Y}
```
Select an overlay containing:
```yaml
a: overlay
group: {x: O}
```
The overlay event in `Explain(Keys("group"))` is attributed to the `O` scalar
(line 2, column 12), instead of the contributing mapping (baseline: line 2,
column 8). The root explanation also attributes the whole overlay event to
that scalar, despite the independent change to `a` (baseline: document root,
line 1, column 1). Values are correct; the contribution location is not.
The roadmap requires retaining source attribution and using common semantic
selection, while allowing different record grouping between public projections.
It does not authorize replacing enclosing attribution with an arbitrary leaf.
Remediation: resolve application attribution at the requested semantic scope in
the common selection/attribution path. Prefer a valid direct contribution for
an enclosing query; retain replacement fallback where it is needed for removed
or replaced descendants. Do not restore a second independent explanation
attribution system. Cover enclosing root/mapping and precise leaf attribution
in one focused overlay regression.
## 4. History Construction Repeatedly Performs Unindexed Mapping Searches
Priority: P2. Status: complete.
Completion: checkpoints, inheritance sides, and resolution before/final states
use one immutable document lookup per stable state, rather than scanning broad
mappings for every path. Lookups are rebuilt after scratch replay changes.
Removed the unused summary contribution list and its searches; the existing
test still verifies composite metadata and individual explanation attribution.
The full ordinary suite and `git diff --check` passed, including collection,
inheritance, resolution shape, and typed-checkpoint cases.
Relevant code: `observeTypedCheckpoint` in
[history_cli.go](../../internal/engine/history_cli.go), lines 38–49;
`sourceContributions` in [history_source.go](../../internal/engine/history_source.go),
lines 185–218; `observeInheritanceTransfer` and `documentAtInheritancePath` in
[history_inheritance.go](../../internal/engine/history_inheritance.go),
lines 87–141 and 211–217; `historyConcretePathAt` and its callers in
[history_resolution.go](../../internal/engine/history_resolution.go); and the lookup implementation in
[document_walk.go](../../internal/engine/document_walk.go).
For every checkpoint path, construction calls `documentAtPath` on both trees.
That helper supplies no lookup index, so each mapping step linearly searches
its entries. For a flat mapping with n fields, locating all n children requires
Theta(n²) entry comparisons. Both checkpoints run even on ordinary loads with
no CLI overrides. Source-summary construction introduces the same pattern:
it calls `sourceContributions` for surviving paths, and that helper searches
each raw application again without an index.
The new inheritance adapter similarly enumerates every before/parent/effective
path and then calls `documentAtInheritancePath` for each one. That helper falls
back to the same unindexed search: one inheritance transfer of a broad n-field
mapping incurs Theta(n²) sibling comparisons. Resolution normalization calls
`historyConcretePathAt` before and after for each record; its hand-written
mapping scan costs O(rn) for r records under an n-field mapping, or Theta(n²)
when all n fields are independently recorded. These are the same lookup problem,
not separate findings about the selected inheritance or resolution algorithms.
These are new repeated sibling scans, not the deliberately retained whole-tree
explanation replay. Existing document lookup machinery or paired traversal can
avoid them. Existing sorting and genuinely repeated producer evidence still
have their own costs; no universal linear-time guarantee is implied.
Remediation: build/reuse phase-local lookups or collect corresponding nodes
during shared traversal instead of independently searching a broad mapping
for every child or record. Respect scratch-tree mutation boundaries: do not
reuse a stale lookup after replacing or editing its indexed state. Concrete
resolution paths must still reflect the corresponding before/after containers.
Review the summary membership work as well: the new
`historySourcePayload.contributions` list is populated and tested but currently
has no production reader. Preserve any membership the selected design needs,
but avoid computing unused data through repeated searches. Retain behavior
coverage; do not add timing thresholds or implementation-specific scan counts.
## 5. Prefix Indexing Multiplies One Operation By All Its Descendants
Priority: P2. Status: complete.
Completion: indexes locate concrete affected paths rather than placing every
descendant at the reported ancestor. Exact path buckets drive source-origin
containment (with early success); enclosing queries visit only their affected
subtree. Logical operations remain payload references, deduplicated once by the
common selector. Thus a leaf no longer scans sibling facts of an ancestor
replacement. The full ordinary suite and `git diff --check` passed, including
parsed key/index sides, removed descendants, inheritance, and enclosing queries.
Relevant code: `addObservation` in
[history_model.go](../../internal/engine/history_model.go), lines 116–139;
replacement observation construction in
[history_source.go](../../internal/engine/history_source.go), lines 101–116;
`sourceOriginRepresentedByOperation` in that file, lines 245–263; and candidate
collection in [history_select.go](../../internal/engine/history_select.go),
lines 35–53. CLI and resolution effects use the same operation index.
An ancestor replacement registers before/after facts for every affected
descendant, indexing every distinct observation ID at the same reported
operation pointer. The prefix index consequently holds O(n) entries for one
logical replacement of an n-node subtree, not one indexed operation plus
shape-specific affected-path lookup.
During source normalization, each surviving descendant calls
`sourceOriginRepresentedByOperation`. It visits all those ancestor entries and
re-resolves the same operation attribution, even after finding a match. One
large subtree replacement therefore creates Theta(n²) visits. Later, a single
leaf `History` query gathers and sorts all n entries at its enclosing operation
before rejecting unrelated affected paths. Querying n such leaves performs
O(n² log n) candidate work despite each leaf's short returned history.
Remediation: distinguish logical operation indexing from exact affected-path
and before/after-shape indexing. Visit operation attribution once, then locate
the query's relevant shape facts rather than expanding every sibling fact.
Preserve parsed-query deduplication, removed descendants, ancestor replacement
effects, and enclosing queries. This should implement the selected localized
candidate-query design, not replace the existing prefix mechanism wholesale or
coarsen semantic identity.
## 6. Sensitivity Candidate Deduplication Cannot Match Parsed Paths
Priority: P3. Status: complete.
Completion: candidate and dependency deduplication uses structural segment
equality, including parsed tokens, without conflating explicit keys and indexes.
`TestSensitivityCandidatesMergeRepeatedParsedPaths` failed before the fix and
now verifies unique semantic/dependency candidates and merged sensitivity facts.
The full ordinary suite and `git diff --check` passed.
Relevant code: `sensitivityCandidates` and `appendDistinctHistoryPath` in
[history_select.go](../../internal/engine/history_select.go), especially
lines 207–234; and `sameConcreteHistoryPath` in
[history_model.go](../../internal/engine/history_model.go), lines 271–294.
The candidate collector adds a parsed version of every observation's pointer,
then uses a concrete-only comparator to deduplicate candidates. That comparator
returns false for a parsed segment, including comparison of a non-root parsed
path with itself. Repeated observations of one path therefore append repeated
parsed candidates instead of merging them. Parsed resolution dependencies have
the same problem in `appendDistinctHistoryPath`.
For k observations of one non-root pointer, the per-pointer candidate list grows
with k and insertion repeatedly scans it: Theta(k²) comparisons and unnecessary
retained temporary paths/dependency entries. The outer classifier later uses
structural equality to suppress duplicate predicate candidates; this finding
does not establish repeated public predicate calls or another redaction leak.
Remediation: use structural path equality that handles parsed segments for
candidate/dependency deduplication, while retaining concrete-only comparison
where concrete inventory requires it. Preserve distinct parsed, explicit-key,
and explicit-index candidates until classification is unioned by pointer.
Extend focused candidate/dependency coverage for repeated parsed paths without
asserting timing or promising a new public call-count contract.
## 7. Explanation Dispatch Repeatedly Searches All Selections And Payloads
Priority: P2. Status: complete.
Completion: a query-local payload-reference index and application-fallback map
are built in one pass over ordered selections. Source/CLI, inheritance, and
resolution replay use direct lookups while retaining model chronology and the
query-scoped attribution correction. The tier-history regression now also
checks explanation contribution order/origins. The full ordinary suite and
`git diff --check` passed; no replay algorithm or shared-state lifetime changed.
Relevant code: `Inspection.Explain`, `historySelectionForPayload`,
`historySelectionForApplication`, and `historySelectionForResolutionGroup` in
[explain.go](../../internal/engine/explain.go), especially lines 64–71,
106–113, and 174–232; and `appendInheritanceExplanation` in
[inheritance_explain.go](../../internal/engine/inheritance_explain.go).
After the model has selected relevant evidence, each source/CLI application
linearly searches the selection list, scans the entire phase payload list to
find linked replacement records, and searches selections again for each linked
record. Inheritance steps and resolution group members also repeatedly search
the same selection slice. Payload references already provide stable,
comparable keys; these are lookup operations, not semantic comparisons.
For A applications, P phase payloads, S selections, and R linked replacements,
source/CLI dispatch alone costs O(A(P+S) + RS). With m successive underlay tiers
each setting the same scalar in a constant-size configuration, A, P, and S all
grow with m: this introduces Theta(m²) dispatch work even though the existing
value replay and rendered history for that query have O(m) size/work. It is
independent of finding 5's candidate expansion and remains after that index is
repaired. The inheritance/resolution lookup loops have the corresponding
operation-count times selection-count cost.
Remediation: index the ordered selections by `historyPayloadRef` once per query
and index or group replacement-to-application links once, rather than rediscover
them for every application. Reuse that lookup for inheritance substeps and
original resolution record IDs. Dispatch can then be linear in the selected
records and traversed relationships, excluding the deliberately selected sorting,
replay, cloning, and rendering costs. Keep replay chronology unchanged and
resolve the attribution precedence correctly with finding 3 rather than
preserving its last-descendant bug. Use query-local or eagerly frozen indexes;
no lazy mutation of published history is needed. Existing explanation owners
should protect output equivalence without a timing assertion.
## 8. Sensitivity Preparation Expands One Dependency List Per Affected Path
Priority: P2. Status: complete.
Completion: ordinary dependency lists are expanded once per recorded target/
attribution, and dependency candidates once per attribution, without descendant
dependency-list copies. Classification supplies already computed inheritance
links to candidate preparation, avoiding a second ordinary-edge construction.
`TestResolutionSensitivityKeepsLogicalEdgesWithoutSubtreeDuplication` failed
before the fix and now verifies the original edges, including a coalesced child
record, and sensitivity isolation from unrelated paths. The full ordinary
suite, full race suite, everyday check, and `git diff --check` passed. The
original public redaction/history probes also now produce the expected results.
Relevant code: `sensitivityCandidates`, lines 238–247, and
`sensitivityDependencies`, lines 272–286, in
[history_select.go](../../internal/engine/history_select.go);
`observeResolutionEvidence` and `observeResolutionSubtree` in
[history_resolution.go](../../internal/engine/history_resolution.go); and
`classifySecretPathsWithEvidence` in [secret.go](../../internal/engine/secret.go).
One original resolution record owns one dependency list, and all its before/
after subtree observations share its attribution reference and reported target.
Nevertheless, `sensitivityDependencies` resolves and clones that attribution
and emits its full dependency list for every observation. For a single recorded
normalization of an n-node subtree with d distinct dependencies, this creates
Theta(nd) ordinary edges although there are only d logical target/dependency
pairs. With n and d growing together, edge materialization alone is quadratic
in the O(n+d) underlying evidence. Those duplicates also become separate
triggers in the unchanged sensitivity-closure implementation.
The candidate collector repeats the attribution dependency loop for every
affected path and retains a dependency list on each concrete candidate, although
its consumer uses those lists to register dependency paths, not distinct
descendant-target edges. It also calls `sensitivityDependencies` a second time
for inheritance links, rebuilding and discarding the ordinary edge list. These
costs remain even if finding 6's parsed-path comparator is corrected.
Remediation: enumerate ordinary dependencies once per logical recorded target/
attribution, or directly from the original resolution payload, and register each
required dependency candidate without broadcasting its list to all descendant
observations. A single record's edge preparation should require O(n+d) traversal
and at most O(d) ordinary edges, apart from path/annotation sizes and unrelated
evidence. Avoid rebuilding ordinary edges merely to obtain inheritance links.
Preserve original records omitted by explanation grouping, concrete/parsed
predicate candidates, dependency order where observable, and existing
pointer-conservative propagation. The fixed-point algorithm and inheritance
link semantics need not change. Reuse transitive/cyclic sensitivity regressions
and add focused coverage for a subtree record with several dependencies; no
performance threshold or new public dependency restriction is warranted.
## 9. Source Lookup Test Compares Canonical Paths With Symlinked Fixture Paths
Priority: P2. Status: complete. This is a test portability defect, not a production
source-lookup defect.
Completion: `TestResultSourceLookup` canonicalizes its fixture directory with
`filepath.EvalSymlinks` before creating files, preserving all source-attribution,
redaction, and detachment assertions. The focused test failed with a symlinked
temporary root before the fix. Both source-lookup tests now pass with `TMPDIR`
pointing to either the real directory or its symlink. The everyday
`./scripts/check.sh` also passed. Production code and behavior are unchanged.
Reviewed revision: `69bd9ead53b0bd1d4b325de2f357b566291a4803`.
Comparison baseline: `556b10b2cd32cbe3240ced79f72bfcc2fe1ebc35`.
Scope: the completed [API cleanup plan](implementation.md) and
[accepted roadmap](api.md), including facade conversions, recording intents,
source lookup, observable string migrations, maintained examples, and current
documentation. Existing findings 1–8 were not reopened.
Relevant code: `TestResultSourceLookup` in
[source_lookup_test.go](../../source_lookup_test.go), lines 23–26 and 62–74.
The test identifies imported and underlay sources by comparing `SourceInfo.Path`
with the original paths returned by its fixture writer. Host source paths are
canonicalized by ConfigKit, but `t.TempDir()` can return a path containing a
symlink when the temporary-directory environment names one. The same files then
have different lexical path spellings. Both records are present with the correct
layers and version evidence, but neither comparison matches, so the test fails
with "imported or underlay source evidence missing".
This is reproducible on Linux with a symlinked `TMPDIR`; it does not depend on
access to macOS or establish a new platform-validation gate. The failure-path
lookup test in the same file already uses `filepath.EvalSymlinks` for its fixture
path and does not have this particular comparison defect.
Reproduction from the repository root, with disposable files outside the checkout:
```sh
probeRoot=$(mktemp -d /tmp/configkit-source-review.XXXXXX)
mkdir "$probeRoot/real"
ln -s "$probeRoot/real" "$probeRoot/link"
TMPDIR="$probeRoot/link" GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off \
go test . -run '^TestResultSourceLookup$' -count=1
```
Expected: the focused test passes for either spelling of a valid temporary
directory, retaining its source-evidence assertions. Actual: it fails at line 74
with canonical records under the real directory and expected paths under its
symlink. The same focused test passes with `TMPDIR` set to the real directory.
This false failure can prevent ordinary development/release checks from completing
in an otherwise valid environment; it does not indicate lost source evidence.
Remediation: canonicalize the test's fixture directory before creating its files,
or resolve the imported/underlay expected paths with `filepath.EvalSymlinks`, as
the failed-load case already does. Preserve record attribution and detachment
assertions. Do not change production canonical-path behavior, forbid symlinked
temporary directories, or add a platform/CI gate. Verify the existing focused
test with both real and symlinked temporary roots after correction.
Other review results: the root inventory contains the expected 370 exported
declarations/members, with exactly the accepted name/signature/value changes
against the prior 372-entry catalog. The two fixed recording methods and the
existing assignment helpers are the only `addRecord` callers. Source lookup
clones retained records using existing ownership/sanitization machinery. No other
required architectural gap, production regression, or inadvertently strengthened
public contract was identified in this sprint.
Validation passed on Linux with Go 1.26.10 and cached dependencies:
```sh
GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off ./scripts/check.sh
GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test -race ./...
GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off \
go test . -run 'SourceLookup|ResolutionRecordingIntents|TestPublicFacadeBoundary|TestDependencyImportBoundary|TestLocalDocumentationLinks' -count=1
```
The additional symlink-root invocation above failed as described. Production
code and repository tests were not changed during this review.

View File

@@ -1,793 +0,0 @@
# Public API Catalog And v1.0 Review
Reviewed revision: `fb71421956ba6b431bdc8745dcf7ab02607dac3a`.
Status: discussion draft; no API changes are selected or implemented by this review.
## Scope And Method
The supported consumer surface is the root package
`gitea.maximumdirect.net/eric/ConfigKit`, package name `configkit`.
[ADR 0006](../adr/0006-public-facade.md) owns root type identity and the private
implementation boundary. Exported engine names are not consumer API.
This catalog includes every exported root type, function, method, struct field,
and constant in the current build: **372 entries** comprising **54 types,
31 functions, 36 methods, 116 fields, and 135 constants**. There are no exported
package variables, named interfaces, type aliases, or anonymously embedded public
fields. Of the constants, 104 are diagnostic codes: the symbol count does not
mean that consumers must learn 372 independent operations.
Inventory was extracted from the root non-test Go declarations, cross-checked
against the package's build-selected files and `go doc -all .`, and reviewed
against declaration comments, the [configuration contract](../config.md),
[consumer examples](../consumers/README.md), the
[architecture policy](../policy/architecture.md), and relevant accepted ADRs.
This is an API-design review, not another correctness or performance audit.
Each catalog row has an assessment addressing clarity, ergonomics, and/or
consolidation. **Retain** means no revision is currently warranted, not a new
invariant or a promise to preserve private mechanics. **Discuss Dn** identifies
a possible pre-v1 naming/removal/signature decision below; **An** is an optional
additive idea. Struct declarations show their kind without private fields; all
exported fields are separate rows. Callable signatures include generic constraints
and receiver types. Constants include their current types and values.
Go declaration comments and the configuration contract remain the canonical
behavior owners. This roadmap is a revision-pinned review record, not a second
permanent API reference. Nothing here creates new release gates or consumer
requirements. Under the [release policy](../policy/release.md#versioning-and-compatibility),
v1 establishes compatibility expectations; it does not prevent later compatible
additions. The highest-value decisions now are potential renames/removals that
would otherwise require a later incompatible release.
## Overall Assessment
The facade has a coherent core: explicit source selection, one complete load,
application-owned rules, separate presence/provenance, and safe frozen inspection.
Its private implementation can evolve without changing public type ownership.
Most of the API should remain. The useful cleanup opportunities are concentrated
in vocabulary and small redundant state, not a wholesale loading framework.
Prioritize D1–D4: these names can encourage an incorrect interpretation of the
state a consumer receives. D5–D7 are useful additional naming/signature discussions;
D8 is a small surface-hygiene question. A1–A2 can wait unless a concrete consumer
need makes them useful now. These are recommendations for discussion, not approved
changes or a declaration that v1 must wait for every suggestion.
## Pre-v1 Discussion Candidates
### D1. Name the fixed resolution view as entry state
Affected: `Resolution.InputInfo`; the documentation of `Resolution` and
`FieldInfo`.
The lookup includes retained typed defaults and remains fixed while the resolver
changes the working configuration. “Input” can therefore suggest either raw
authored values or the mutable current value, neither of which is its complete
meaning.
Recommendation: consider renaming the method to `EntryInfo` and consistently
calling this the resolution-entry view. Keep its return type and silent
unknown/shape-mismatch result. Keep `IsExplicit` as the convenient surviving-input
question with its existing path-validation behavior.
Alternative: retain `InputInfo`, whose comment already explains the contract,
and improve the nearby example. That avoids consumer migration and is defensible
if the terminology is already well understood. Do not expose the growing history
model through the callback or collapse entry and final metadata to solve a naming
problem.
### D2. Distinguish sensitivity classification from secret references
Affected: `Definition.Secret`; related terminology around `Secret` and
`ValueSnapshot.Redacted`.
The function marks arbitrary semantic paths sensitive, including ordinary strings
and containers. It neither creates nor resolves the typed environment references
represented by `Secret`.
Recommendation: consider `Definition.Sensitive func(Path) bool`. This makes a
definition such as `Sensitive: func(path Path) bool { ... }` describe its purpose
without implying credential lookup. Preserve the existing tag/predicate union,
historical masking, and reference semantics.
Alternative: keep `Secret` on the field because it aligns with the existing
`configkit:"secret"` tag and the established documentation. The current name is
not functionally wrong. No tag rename, new classification policy, credential
provider, or declassification option follows from this discussion.
### D3. Make version-policy and version-evidence names literal
Affected: `SourceVersionModeDefault`, `SourceVersionCheck`, and
`SourceInfo.VersionCheck`.
“Default” describes an implementation/history relationship rather than the
selected main-only behavior. A “Check” record can exist for an absent declaration
without proving that its checker ran or accepted a value.
Recommendation: consider `SourceVersionModeMainOnly` for the existing zero mode,
`SourceVersionEvidence` for the record, and `SourceInfo.VersionEvidence` for the
field. Keep all existing mode values, invocation/absence rules, source-local
coordinates, consumption, and redaction behavior. In particular, an if-present
mode still has the documented main-absence checker behavior; naming cleanup
must not silently change it.
Alternative: retain the names and make their precise meanings prominent in
declaration comments and examples. “Default” is familiar for zero enums, and
existing consumers may already understand the evidence qualification. Do not add
an “accepted” boolean unless ConfigKit actually chooses to retain that fact under
a separately agreed contract.
### D4. Describe the empty-import-selection subset and remove redundant state
Affected: `ImportSelection`, `SourceInfo.ImportSelections`,
`ImportSelection.Outcome`, `ImportSelectionOutcome`, and `ImportSelectionNone`.
These records describe optional declarations that selected no source. They are
not a complete selection ledger. Their outcome has exactly one supported value,
`"none"`; successful acquisition is represented elsewhere by source records.
Recommendation: consider `EmptyImportSelection` and
`SourceInfo.EmptyImportSelections`, retaining path, location, and selector.
With that explicit scope, remove the constant-only outcome field/type/constant.
This reduces the public model without losing information that these records
currently communicate.
Alternative: retain the broad names and explicit outcome. It leaves room for
compatible additional outcomes and avoids migration, but consumers must learn
that the collection currently reports empty selections only. Future flexibility
alone should not imply an approved complete event-history feature.
### D5. Describe destination fallback evidence more precisely
Affected: `SourceInfo.DestinationUnresolved`.
The destination can be a useful nearest resolved enclosing container. The flag
does not mean every aspect of placement is unresolved, and a source's own item
container can be an exact destination without publishing its item index here.
Recommendation: consider `DestinationIsFallback` with the existing meaning.
Retain `Destination` and `PlacementMode`; another placement object/state machine
is not needed just to clarify this boolean.
Alternative: retain `DestinationUnresolved` and its detailed comment. It is
already specific enough for informed consumers. Neither option adds exact item
coordinates, identity tracking, or extra source-location guarantees.
### D6. Separate manual recording intents from inspection-only operation kinds
Affected: `Resolution.Record` and its use of `ChangeKind`.
Four named operation values are exported, but only derived-default and
normalization are valid manual recording intents. Decoding and inheritance
describe library processing. A caller can choose an apparently suitable exported
constant only to receive a load-time invalid-kind diagnostic.
Recommendation for discussion: consider replacing manual `Record` with
`RecordDerivedDefault(path Path, reason string, dependencies ...Path)` and
`RecordNormalization(path Path, reason string, dependencies ...Path)` methods.
Keep the generic assignment helpers `Default` and `Normalize`, which do different
work, and keep all inspection `ChangeKind` values. The explicit methods remove a
misleading input choice without changing the two accepted intents.
Alternative: retain the single method and prominently document its accepted
subset. A separate input-only kind type is another viable design, but adds a
second enum and conversion vocabulary; it is less attractive unless consumers
need to select recording intent dynamically. Do not change net-change auditing
or impose new recording obligations through this cleanup.
### D7. Make environment mutation evident in the bootstrap function name
Affected: `LoadSecretsDir`.
Unlike ordinary `Load`, this explicit startup operation writes previously unset
process environment variables and can report partial application failure.
Recommendation: consider `BootstrapSecretsDir`. It follows the repository's
existing bootstrap terminology and makes the distinction from configuration
loading easier to notice. Retain its report-plus-error return and all current
preflight, skip, partial-failure, and ownership behavior.
Alternative: retain `LoadSecretsDir`, whose comment clearly states the side
effects. Avoid a broad new bootstrap options framework or merging this operation
into ordinary loading. The purpose is naming clarity, not stronger filesystem
or process-transaction guarantees.
### D8. Resolve the intent of the unproduced options diagnostic code
Affected: `CodeOptionsUnavailable`.
The scoped implementation search found its root and engine declarations but no
named production producer. That is a reason to ask about its intended role, not
evidence that an exported symbol has no consumers.
Recommendation: establish whether there is an intentional supported/reserved case;
remove the constant before v1 if there is none. Do not invent a new failure merely
to give it a producer.
Alternative: retain it as deliberately reserved classification if there is a
concrete reason. Document that intent rather than automatically deleting it.
The other diagnostic families should not be collapsed just to reduce the count.
## Optional Additive Ideas
### A1. Direct source lookup on successful results
`Resolution.Source(id)` and both `SourceContext(id)` methods accept a source ID,
while a final result offers only `Sources()` for source records. A consumer
following many origin locations must scan or index that slice itself.
Consider `Result.Source(id string) (SourceInfo, bool)` with the same detached,
passive lookup meaning as the resolution query. Prefer it over a general source
registry abstraction if representative consumers repeatedly need this lookup.
Keeping only the ordered slice is a viable smaller API for consumers that
enumerate once. This is an addition that can follow v1; it need not block the
existing API freeze.
### A2. Path comparison without losing segment meaning
`Path.String()` is a JSON Pointer spelling, not a complete semantic identity:
an explicit numeric mapping key and an index can share it. The public type is
opaque and currently non-comparable, so consumers cannot directly compare its
segment choices without relying on reflection or private representation.
If consumers need path comparison, consider an explicitly documented comparison
operation. Decide whether it means exact segment representation (including
parsed-versus-explicit choice and invalid state), or resolution against a
document; those are different operations. Prefer a small exact-representation
operation over exposing mutable segments or adding another pattern language.
Do not label pointer-string equality semantic equality.
Alternatively keep Path solely as an opaque query/building value and clarify
the limitation in examples. No concrete need currently justifies a public
segment-inspection framework. A comparison method can be added compatibly later.
## Consolidation Assessment
| Area | Assessment |
| --- | --- |
| C1: ordinary versus layered loading | Keep two entry points. Ordinary options combine one input's choices with load-wide rules; layered Input owns the former and LayeredOptions the latter. Reusing one options type would introduce ignored/competing fields. Anonymous embedding also makes keyed composite literals less convenient. Existing Definition, ImportDefaults, Input, and Override sharing is useful consolidation already. |
| C2: filesystem vocabulary | Keep Filesystem (borrowed identity), FilesystemContext (input roots), and ResourceContext (passive returned access) distinct. FS means different concrete types because the roles differ. Clarify with paired host/native examples, rather than introducing one universal context or changing ownership. |
| C3: history versus explanation | Keep Change and ValueChange, Metadata.History and Result.Explain distinct. Metadata has composite/previous attribution; explanation has safe values and decoding/net replay. Their shared private evidence is the correct consolidation boundary. |
| C4: semantic path versus sequence selector | Keep Path and SequenceSelector distinct. One describes concrete/contextual queries, the other limited mapping-key wildcard registration. Unifying them would broaden grammar and validation for little current benefit. |
| Errors and renderers | Keep LoadError and SecretsError separate because bootstrap can accompany partial external effects. Keep structured diagnostics plus safe rendering. SecretsReport.WriteTo follows io.WriterTo; other renderers need not return byte counts merely for symmetry. |
| Presence records | Keep FieldInfo, Version and ValueSnapshot's purpose-specific booleans. A generic optional-value/tri-state framework would add vocabulary without simplifying the application configuration structs. |
| Result ownership | Keep caller-owned Config and frozen inspection. A copying accessor would not make arbitrary T language-enforced immutable and would add work; emphasize the established snapshot distinction instead. |
## Related Consumer Integration Contracts
The Go symbols are not the entire compatibility commitment. These established
integration surfaces also matter to v1, but are defined by their canonical
contracts rather than counted as additional exported Go declarations:
| Surface | Owner and assessment |
| --- | --- |
| Configuration structs, YAML field tags, standard text codecs, defaults and typed decoding | [Schema binding](../config.md#application-schema-binding), [defaults](../config.md#defaults-and-typed-decoding), and [projection](../config.md#typed-projection-and-ownership). Retain ordinary application types and supported standard codec integration; do not add parser-specific public hooks. |
| YAML declarations, composition, imports, overlays and sibling inheritance | [Configuration contract](../config.md) and accepted ADRs linked there. Retain the established coherent language; this review does not propose reserved-key or merge-policy changes. |
| Presence, provenance, recorded resolution and validation | [Metadata](../config.md#layered-metadata), [resolution](../config.md#recorded-application-resolution), and [validation](../config.md#semantic-validation). Retain the distinctions that remove consumer bookkeeping; naming discussions do not change these semantics. |
| Secret references, classification and explicit environment bootstrap | [Secret references](../config.md#typed-secret-references), [classification](../config.md#secret-classification-and-effective-rendering), and [bootstrap](../config.md#explicit-secrets-directory-bootstrap). D2/D7 concern API vocabulary, not a new credential or security model. |
| One-shot completion, resource ownership, safe rendering and comparison | [Completion](../config.md#completion-and-resource-lifetime), [source contexts](../config.md#source-resource-contexts), [explanation](../config.md#historical-value-explanation), and [diffs](../config.md#effective-configuration-diffs). Retain established lifetimes and safe snapshot boundaries rather than adding watching, lazy work or a required result Close operation. |
## Complete Catalog
All rows below assess the current declarations, not the proposed replacement
names. No proposed symbol is counted in the 372-entry inventory.
### Loading and application policy
Declarations: [layered.go](../../layered.go), [lifecycle.go](../../lifecycle.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `Input` | `type Input struct` | Retain a source plus placement/import/access settings; zero Source is invalid, unlike explicit NoSource. |
| `Input.Source` | `Source` | Retain the opaque source union; no provider interface or parser node exposure. |
| `Input.Destination` | `Path` | Retain the same root-relative placement meaning as ordinary options. |
| `Input.PlacementMode` | `PlacementMode` | Retain the same explicit placement enum as ordinary options. |
| `Input.Imports` | `bool` | Retain per-input traversal choice; it differs from load-wide declaration defaults. |
| `Input.Filesystem` | `*FilesystemContext` | Retain per-input access context, not a global context for unrelated namespaces. |
| `Underlay` | `type Underlay struct` | Retain a named strict tier; flattening all inputs would lose the peer-versus-precedence boundary. |
| `Underlay.Inputs` | `[]Input` | Retain ordered inputs in one strict group; an empty group remains a no-op. |
| `LayeredOptions` | `type LayeredOptions[T any] struct` | Retain separate layered entry-point options; input-level settings already belong to Input (C1). |
| `LayeredOptions.Definition` | `Definition[T]` | Retain the same Definition type as ordinary loading. |
| `LayeredOptions.SourceVersionMode` | `SourceVersionMode` | Discuss D3's mode name; retain one rule across the load rather than per-source policy switches. |
| `LayeredOptions.ImportDefaults` | `ImportDefaults` | Retain the shared type and one load-wide default policy. |
| `LayeredOptions.Underlays` | `[]Underlay` | Retain ordered low-to-high strict groups; no separate precedence numbers are needed. |
| `LayeredOptions.Overlay` | `string` | Retain the same selected-overlay concept as ordinary loading. |
| `LayeredOptions.OverlayFilesystem` | `*FilesystemContext` | Retain its explicit overlay-only purpose; merging it into main input context would change behavior (C1/C2). |
| `LayeredOptions.Overrides` | `[]Override` | Retain shared Override records and the same CLI semantics. |
| `LoadLayered` | `func LoadLayered[T any](main Input, supplied ...LayeredOptions[T]) (*Result[T], error)` | Retain explicit tiers and independent main input; do not make composition strategy a switch. See consolidation assessment C1. |
| `Definition` | `type Definition[T any] struct` | Retain reusable application rules separate from per-load selections; zero disables hooks and uses Go-zero defaults. |
| `Definition.Defaults` | `func() T` | Retain a fresh-value factory; zero-value configuration and shared mutable defaults are different concerns. |
| `Definition.Resolve` | `func(*T, *Resolution) error` | Retain audited mutation callback and error return; do not move application normalization outside loading. |
| `Definition.Validate` | `func(*T, *Validation)` | Retain read-only audited validation despite pointer parameter; a value parameter would not make maps/pointers immutable. |
| `Definition.Version` | `*VersionRule` | Retain optional application version policy; nil disables the rule. Do not confuse it with the ConfigKit release version. |
| `Definition.Secret` | `func(Path) bool` | Discuss D2: rename the classification predicate to Sensitive, distinguishing it from environment-backed Secret references. |
| `Definition.SequenceIdentities` | `[]SequenceIdentity` | Retain authoritative identity registrations; no new per-consumer merge strategy is needed. |
| `PlacementMode` | `type PlacementMode uint8` | Retain small explicit enum; zero is mapping placement, not automatic inference. |
| `SourceVersionMode` | `type SourceVersionMode uint8` | Discuss D3: the zero behavior deserves a descriptive MainOnly name rather than a historical Default label. |
| `ImportDefaults` | `type ImportDefaults struct` | Retain independent tri-state defaults; zero leaves library defaults in effect. Do not replace pointers with false-as-unspecified. |
| `ImportDefaults.Required` | `*bool` | Retain *bool: nil is unspecified, false is an explicit optional selection. |
| `ImportDefaults.Follow` | `*bool` | Retain *bool: explicit false must override the true library default. |
| `ImportDefaults.Hidden` | `*bool` | Retain *bool: explicit false must override the true library default. |
| `ImportDefaults.Recursive` | `*bool` | Retain *bool: distinguishes declaration defaulting from an explicit choice. |
| `ImportDefaults.Pattern` | `[]string` | Retain nil-versus-empty selection meaning; a new pattern language is unwarranted. |
| `Options` | `type Options[T any] struct` | Retain ordinary-load options with input-specific settings; do not consolidate by anonymously embedding another public options struct (C1). |
| `Options.Definition` | `Definition[T]` | Retain the shared Definition type; avoids duplicating callback configuration. |
| `Options.Destination` | `Path` | Retain literal mapping placement independent of filesystem paths; root is the zero Path. |
| `Options.PlacementMode` | `PlacementMode` | Retain explicit mapping/item choice; avoid inferring it from destination spelling. |
| `Options.Imports` | `bool` | Retain explicit opt-in; import defaults alone must not enable traversal. |
| `Options.ImportDefaults` | `ImportDefaults` | Retain one shared load-wide default type, not duplicate boolean fields here. |
| `Options.Overlay` | `string` | Retain one selected overlay name; empty selects none. |
| `Options.Filesystem` | `*FilesystemContext` | Retain context for ordinary source/import/overlay handling; distinguish from the layered overlay-only context (C1/C2). |
| `Options.Overrides` | `[]Override` | Retain explicit CLI assignments; do not generalize these into another arbitrary source tier. |
| `Options.SourceVersionMode` | `SourceVersionMode` | Discuss D3's zero-mode name; retain load-wide version policy here. |
| `FilesystemContext` | `type FilesystemContext struct` | Retain input access scope, distinct from returned ResourceContext. Consider documentation terminology rather than merging lifetimes (C2). |
| `FilesystemContext.BaseDir` | `string` | Retain host-absolute versus namespace-relative coordinates; clarify with paired examples (C2). |
| `FilesystemContext.Root` | `string` | Retain explicit scope separate from the source-relative base; do not silently strengthen filesystem security policy. |
| `FilesystemContext.FS` | `*Filesystem` | Retain *Filesystem identity, not raw fs.FS: nil selects host context and namespace identity matters (C2). |
| `Override` | `type Override struct` | Retain parser-neutral explicit CLI input, distinct from defaults and ordinary sources. |
| `Override.Path` | `Path` | Retain application-root semantic coordinates. |
| `Override.Value` | `any` | Retain built-in neutral values; accepting arbitrary typed objects would add another conversion contract. |
| `Override.Flag` | `string` | Retain safe display attribution, not the raw CLI argument or value. |
| `Load` | `func Load[T any](source Source, supplied ...Options[T]) (*Result[T], error)` | Retain the source-only call and one typed options record; the optional form avoids a mandatory empty options value or request builder. |
### Sources and filesystem namespaces
Declarations: [resource_context.go](../../resource_context.go), [source.go](../../source.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `ResourceContext` | `type ResourceContext struct` | Retain passive post-acquisition resource access distinct from input scope; zero has no filesystem (C2). |
| `ResourceContext.FS` | `fs.FS` | Retain borrowed fs.FS. Callers close files they open; a successful load itself needs no Close. |
| `ResourceContext.BaseDir` | `string` | Retain slash-relative source base; do not replace it with a host path that excludes embedded filesystems. |
| `Source` | `type Source struct` | Retain opaque acquisition descriptions and deferred validation; zero is invalid and NoSource is deliberate absence. |
| `Filesystem` | `type Filesystem struct` | Retain a reusable borrowed fs.FS namespace with identity; zero is not a valid namespace. |
| `NewFilesystem` | `func NewFilesystem(label string, filesystem fs.FS) Filesystem` | Retain label plus borrowed fs.FS, returning a namespace whose argument failures surface during load. |
| `Filesystem.File` | `func (f Filesystem) File(name string) Source` | Retain native namespace file selection as a method; replacing it with a raw fs.FS Source overload would lose identity/context. |
| `File` | `func File(path string) Source` | Retain the short reusable host-file constructor. |
| `Bytes` | `func Bytes(label string, data []byte) Source` | Retain immediate byte ownership snapshot and a safe label; delayed constructor diagnostics match other sources. |
| `Reader` | `func Reader(label string, input io.Reader) Source` | Retain explicit single-use borrowed-reader semantics; do not add automatic Close or hidden buffering for reuse. |
| `Mapping` | `func Mapping(label string, values map[string]any) Source` | Retain immediate neutral-map snapshot; nil root is invalid and empty mapping is meaningful. |
| `NoSource` | `func NoSource() Source` | Retain explicit absence separate from zero Source and an empty document. |
### Explicit main-file discovery
Declarations: [discovery.go](../../discovery.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `Search` | `type Search struct` | Retain immutable explicit host-search description; zero contributes no candidates. No independent discovery/execution stage is needed. |
| `FilenameGroup` | `type FilenameGroup struct` | Retain a small opaque description; zero contributes no names. It is not a general glob matcher. |
| `SearchOptions` | `type SearchOptions struct` | Retain whole-search absence policy separate from per-directory filename choices. |
| `SearchOptions.Required` | `bool` | Retain optional-by-default search exhaustion; selected-file errors remain terminal. |
| `DirectorySearchOptions` | `type DirectorySearchOptions struct` | Retain the filename-group option record; preserve nil/default versus empty/no-candidates distinction. |
| `DirectorySearchOptions.Filenames` | `[]FilenameGroup` | Retain ordered groups; no candidate list or candidate-attempt history is necessary. |
| `NamedFilenames` | `func NamedFilenames() FilenameGroup` | Retain application-named YAML filename preset; it keeps common consumer setup short. |
| `GenericFilenames` | `func GenericFilenames() FilenameGroup` | Retain generic YAML filename preset; separate selection avoids implicit convention changes. |
| `CustomFilenames` | `func CustomFilenames(names ...string) FilenameGroup` | Retain explicit filename components and order, not a second glob/template mechanism. |
| `SearchDirectory` | `func SearchDirectory(directory string, supplied ...DirectorySearchOptions) Search` | Retain directory search plus optional filenames; argument problems remain load-time diagnostics. |
| `Paths` | `func Paths(paths ...string) Search` | Retain literal host-candidate description. Document that this is discovery, not semantic Path construction. |
| `Searches` | `func Searches(searches ...Search) Search` | Retain ordered concatenation; it expresses one search, not repeated loads or fallback after parse failure. |
| `UnixSystemDirectories` | `func UnixSystemDirectories() Search` | Retain explicit platform preset; do not turn its presence into implicit cross-platform guarantees. |
| `UserConfigDirectory` | `func UserConfigDirectory() Search` | Retain explicit OS user-config location preset. |
| `HomeDirectory` | `func HomeDirectory() Search` | Retain explicit home-root search preset, distinct from user-config location. |
| `WorkingDirectory` | `func WorkingDirectory() Search` | Retain explicit working-directory preset; do not activate it automatically. |
| `SearchConfig` | `func SearchConfig(application string, search Search, supplied ...SearchOptions) Source` | Retain deferred source selection inside loading. Optional exhaustion and selected-file failure are intentionally different. |
### Paths and sequence identity registration
Declarations: [path.go](../../path.go), [sequence_identity.go](../../sequence_identity.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `Path` | `type Path struct` | Discuss A2: preserve opaque semantic paths; consider exact segment comparison only if consumers need it. Zero is root; current type is not comparable. |
| `Keys` | `func Keys(keys ...string) Path` | Retain concise literal-key construction and zero-argument root; do not interpret numeric keys as indexes. |
| `ParsePath` | `func ParsePath(pointer string) (Path, error)` | Retain contextual numeric tokens and an error return; pointer spelling alone is not full semantic identity (A2). |
| `Path.Key` | `func (p Path) Key(key string) Path` | Retain immutable literal-key append, including empty and numeric keys. |
| `Path.Index` | `func (p Path) Index(index int) Path` | Retain explicit sequence indexing; negative indexes produce an invalid path rejected by configuration operations. |
| `Path.String` | `func (p Path) String() string` | Retain canonical JSON Pointer formatting, but explicitly avoid calling it a complete semantic identity (A2). |
| `SequenceSelector` | `type SequenceSelector []SequenceSelectorSegment` | Retain the named slice of literal/wildcard mapping-key segments; zero selects root. Do not merge it with general Path syntax (C4). |
| `SequenceSelectorSegment` | `type SequenceSelectorSegment struct` | Retain the opaque segment; zero is the literal empty key, not a wildcard. |
| `SequenceLiteralKey` | `func SequenceLiteralKey(key string) SequenceSelectorSegment` | Retain explicit literal construction, including '*' as a real key. |
| `SequenceAnyMappingKey` | `func SequenceAnyMappingKey() SequenceSelectorSegment` | Retain an explicit wildcard constructor; textual '*' must not acquire a second meaning. |
| `SequenceIdentity` | `type SequenceIdentity struct` | Retain immutable registration value, not a pluggable merge strategy. Zero lacks required identity fields and is rejected during load. |
| `SequenceIdentityAt` | `func SequenceIdentityAt(path Path, fields ...string) SequenceIdentity` | Retain the exact-path convenience; it avoids building a selector for ordinary static destinations. |
| `SequenceIdentityMatching` | `func SequenceIdentityMatching(selector SequenceSelector, fields ...string) SequenceIdentity` | Retain wildcard mapping destinations for repeated namespaces; constructor copies inputs. |
### Resolution, validation, and application schema versions
Declarations: [lifecycle.go](../../lifecycle.go), [resource_context.go](../../resource_context.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `Resolution` | `type Resolution struct` | Retain a library-issued callback capability; nil/zero/closed contexts are invalid and panic. Do not add a general resolver service interface. |
| `VersionRule` | `type VersionRule struct` | Retain one application-owned version path/checker. Zero Path means root; a non-nil rule needs a checker. |
| `VersionRule.Path` | `Path` | Retain mode-defined effective/source-local coordinates; clarify the distinction in examples, not separate version systems. |
| `VersionRule.Check` | `func(Version, *Validation) error` | Retain Version plus diagnostic context and error return; application acceptance remains application-owned. |
| `Version` | `type Version struct` | Retain the compact absent/null/text record rather than imposing a semver or numeric version type. |
| `Version.Present` | `bool` | Retain: omission cannot be inferred from Text being empty. |
| `Version.Null` | `bool` | Retain: an explicit null is different from absence and an empty string. |
| `Version.Text` | `string` | Retain exact accepted spelling; do not expose YAML scalar kinds when string/integer equivalence is intentional. |
| `Validation` | `type Validation struct` | Retain the callback-only diagnostic capability; no public constructor or reusable context is needed. |
| `Resolution.IsExplicit` | `func (r *Resolution) IsExplicit(path Path) bool` | Retain surviving-input presence semantics, including inherited input; document its schema-path diagnostics versus a lookup miss. |
| `Resolution.InputInfo` | `func (r *Resolution) InputInfo(path Path) (FieldInfo, bool)` | Discuss D1: EntryInfo more accurately names the fixed pre-resolution view, including defaults. |
| `Resolution.Source` | `func (r *Resolution) Source(id string) (SourceInfo, bool)` | Retain direct acquired-source lookup by ID; optional Result parity is A1. |
| `Resolution.Record` | `func (r *Resolution) Record(path Path, kind ChangeKind, reason string, dependencies ...Path)` | Discuss D6: its ChangeKind argument admits inspection-only constants that loading rejects; consider intent-specific record methods. |
| `Resolution.Error` | `func (r *Resolution) Error(path Path, message string, related ...Path)` | Retain safe diagnostic collection; do not expose raw application errors automatically. |
| `Resolution.Warning` | `func (r *Resolution) Warning(path Path, message string, related ...Path)` | Retain warning/error symmetry and related semantic paths. |
| `Default` | `func Default[V any](resolution *Resolution, path Path, target *V, value V, reason string, dependencies ...Path)` | Retain the generic typed assignment helper; explicit zero/false/null must suppress absence-based assignment. |
| `Normalize` | `func Normalize[V any](resolution *Resolution, path Path, target *V, value V, reason string, dependencies ...Path)` | Retain unconditional typed assignment plus audit intent; not redundant with absence-based Default. |
| `Validation.Error` | `func (v *Validation) Error(path Path, message string, related ...Path)` | Retain value-free errors collected at semantic paths, distinct from unwrapping arbitrary callback errors. |
| `Validation.Warning` | `func (v *Validation) Warning(path Path, message string, related ...Path)` | Retain matching warning API; no generic severity switch is necessary. |
| `Resolution.SourceContext` | `func (r *Resolution) SourceContext(id string) (ResourceContext, bool)` | Retain passive source-context lookup during the callback, with no I/O. |
### Results, presence, and source/provenance evidence
Declarations: [inspection.go](../../inspection.go), [lifecycle.go](../../lifecycle.go), [metadata.go](../../metadata.go), [records.go](../../records.go), [resource_context.go](../../resource_context.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `Result.Explain` | `func (r *Result[T]) Explain(path Path) (Explanation, bool)` | Retain structured safe history plus found boolean; query semantics differ from Metadata.History (C3). |
| `Result.WriteEffective` | `func (r *Result[T]) WriteEffective(output io.Writer) error` | Retain frozen redacted YAML output and error-only writer convention; nil/zero snapshot or nil writer is an error. |
| `Result.WriteExplanation` | `func (r *Result[T]) WriteExplanation(output io.Writer, path Path) error` | Retain human-readable output. Clarify that unknown paths return an error without output; this is not a new behavior. |
| `Result` | `type Result[T any] struct` | Retain the successful configuration plus frozen inspection boundary; zero/nil has no usable loaded snapshot. |
| `Result.Config` | `T` | Retain caller-owned mutable T. Clarify that later mutation does not update inspection; replacing it with a cloning getter adds cost without immutability. |
| `Result.Metadata` | `func (r *Result[T]) Metadata() Metadata` | Retain detached effective/history query access; do not translate Explain output into metadata. |
| `Result.Sources` | `func (r *Result[T]) Sources() []SourceInfo` | Retain ordered detached acquisition records; optional direct Source lookup is A1. |
| `Result.Diagnostics` | `func (r *Result[T]) Diagnostics() []Diagnostic` | Retain successful-load nonfatal diagnostics, parallel to structured load failures. |
| `Metadata` | `type Metadata struct` | Retain opaque detached queries; zero contains no known paths. |
| `Metadata.Lookup` | `func (m Metadata) Lookup(path Path) (FieldInfo, bool)` | Retain effective FieldInfo plus found boolean; this handles absence without optional wrappers in application structs. |
| `Metadata.IsExplicit` | `func (m Metadata) IsExplicit(path Path) bool` | Retain ergonomic convenience over Lookup; inherited input is explicit and defaults/resolution do not invent presence. |
| `Metadata.History` | `func (m Metadata) History(path Path) []Change` | Retain attribution-only history, including removed paths; it intentionally differs from value replay (C3). |
| `Metadata.HistoricalPaths` | `func (m Metadata) HistoricalPaths() []Path` | Retain canonical-pointer enumeration, including removed/default/control paths; no extra per-shape enumeration API is justified. |
| `Layer` | `type Layer string` | Retain string-valued provenance categories; base/main terminology merits documentation clarity, not an automatic wire-value rename. |
| `SourceKind` | `type SourceKind string` | Retain acquisition kind independently of precedence layer. |
| `ChangeKind` | `type ChangeKind string` | Discuss D6's recording input; retain all inspection operations and the empty/default definition state. |
| `Location` | `type Location struct` | Retain source ID plus optional line/column coordinates; zero coordinates mean unavailable/synthetic. |
| `Location.SourceID` | `string` | Retain a load-local acquired-source key, not a host filename. |
| `Location.Line` | `int` | Retain line coordinate; zero means unavailable. |
| `Location.Column` | `int` | Retain column coordinate; zero means unavailable. |
| `SourceInfo` | `type SourceInfo struct` | Retain structured acquisition and placement evidence; simplify misleading evidence names in D3–D5, not source identity. |
| `SourceInfo.ID` | `string` | Retain load-local ID independent of physical path or label. |
| `SourceInfo.Label` | `string` | Retain safe display label separate from path and stable identity. |
| `SourceInfo.Path` | `string` | Retain host-canonical/native-relative location where present; do not imply every source has a path. |
| `SourceInfo.Destination` | `Path` | Retain effective mapping/sequence-container placement; it is not always an item's exact value path. |
| `SourceInfo.PlacementMode` | `PlacementMode` | Retain explicit placement evidence, independent of acquisition kind. |
| `SourceInfo.DestinationUnresolved` | `bool` | Discuss D5: DestinationIsFallback would describe the enclosing-container evidence more precisely. |
| `SourceInfo.Layer` | `Layer` | Retain provenance tier independent of acquisition order and source kind. |
| `SourceInfo.Kind` | `SourceKind` | Retain acquisition category rather than inferring it from path syntax. |
| `SourceInfo.UnderlayIndex` | `int` | Retain one-based original tier position, zero otherwise; do not renumber around empty tiers. |
| `SourceInfo.ImportedFrom` | `*Location` | Retain optional declaring location rather than a second parent-source object graph. |
| `SourceInfo.VersionCheck` | `*SourceVersionCheck` | Discuss D3: VersionEvidence avoids implying that the checker ran or accepted anything. |
| `SourceInfo.ImportSelections` | `[]ImportSelection` | Discuss D4: name the empty-selection-only scope explicitly. |
| `ImportSelector` | `type ImportSelector string` | Retain path/directory/glob selector classification; do not combine it with source acquisition kind. |
| `ImportSelectionOutcome` | `type ImportSelectionOutcome string` | Discuss D4: this public enum has only one supported outcome and may be redundant in an explicitly empty-selection record. |
| `ImportSelection` | `type ImportSelection struct` | Discuss D4: EmptyImportSelection would accurately name the currently reported subset. |
| `ImportSelection.Path` | `Path` | Retain declaration-local path if the record is renamed; not the selected source's destination. |
| `ImportSelection.Location` | `Location` | Retain declaration mapping/source coordinates. |
| `ImportSelection.Selector` | `ImportSelector` | Retain the authored selector kind; empty path, directory, and glob choices differ. |
| `ImportSelection.Outcome` | `ImportSelectionOutcome` | Discuss D4: remove the constant-only none field if empty-only scope is made explicit. |
| `SourceVersionCheck` | `type SourceVersionCheck struct` | Discuss D3: SourceVersionEvidence better describes observed declaration state without implying validation success. |
| `SourceVersionCheck.Path` | `Path` | Retain requested source-local version path, including absent evidence. |
| `SourceVersionCheck.Version` | `Version` | Retain absent/null/text observation, not an acceptance result. |
| `SourceVersionCheck.Location` | `*Location` | Retain optional actual/nearest authored location. |
| `SourceVersionCheck.Redacted` | `bool` | Retain explicit withheld-text indication; absence cannot be inferred from empty Text. |
| `InheritanceStep` | `type InheritanceStep struct` | Retain explicit transfer/control provenance; it adds no acquired source or precedence layer. |
| `InheritanceStep.From` | `Path` | Retain contributor semantic path. |
| `InheritanceStep.To` | `Path` | Retain destination semantic path. |
| `InheritanceStep.Reference` | `Path` | Retain controlling extends path, distinct from the inherited value path. |
| `InheritanceStep.Locations` | `[]Location` | Retain directive coordinates rather than mislabeling them as value coordinates. |
| `Origin` | `type Origin struct` | Retain composite locations, precedence, operation and transfer trace; no generic untyped metadata bag. |
| `Origin.Layer` | `Layer` | Retain provenance classification; inheritance does not invent another layer. |
| `Origin.Locations` | `[]Location` | Retain ordered composite origins; one SourceID field would lose strict composition evidence. |
| `Origin.Operation` | `ChangeKind` | Retain inspection ChangeKind, including decoding/inheritance (D6 concerns recording input only). |
| `Origin.Inheritance` | `[]InheritanceStep` | Retain concrete derivation steps separately from acquired source locations. |
| `FieldInfo` | `type FieldInfo struct` | Retain compact effective presence/null/origin record. |
| `FieldInfo.Explicit` | `bool` | Retain input presence independent of the final Go zero value. |
| `FieldInfo.Null` | `bool` | Retain explicit null independently of missing lookup and scalar zero. |
| `FieldInfo.Origin` | `Origin` | Retain final effective attribution. |
| `Change` | `type Change struct` | Retain attribution-only history and Previous origins; not redundant with ValueChange (C3). |
| `Change.Path` | `Path` | Retain reported operation path, which may enclose the queried affected path. |
| `Change.Origin` | `Origin` | Retain this definition/operation's attribution. |
| `Change.Previous` | `[]Origin` | Retain preceding selected origins after shape filtering. |
| `Change.Reasons` | `[]string` | Retain ordered recorded reasons without embedding configuration values. |
| `Change.Dependencies` | `[]Path` | Retain original recorded dependency paths independently of replay grouping. |
| `Result.SourceContext` | `func (r *Result[T]) SourceContext(id string) (ResourceContext, bool)` | Retain passive lookup by origin source ID; do not open or cache resource files implicitly. |
### Safe value inspection and comparison
Declarations: [diff.go](../../diff.go), [inspection.go](../../inspection.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `Difference` | `type Difference struct` | Retain a separate changed-region record; effective comparison is not a provenance/history diff. |
| `Difference.Path` | `Path` | Retain canonical changed-region coordinates. |
| `Difference.Left` | `ValueSnapshot` | Retain the safe left snapshot under combined classification, not independently rendered context. |
| `Difference.Right` | `ValueSnapshot` | Retain left/right symmetry; before/after belongs to chronological history. |
| `Difference.HiddenChanged` | `bool` | Retain actual concealed-change indication distinct from Redacted. |
| `Diff` | `func Diff[T any](left, right *Result[T]) ([]Difference, error)` | Retain same-type frozen-result comparison; nil/zero results are errors and credential contents are not compared. |
| `ValueSnapshot` | `type ValueSnapshot struct` | Retain detached Present/Redacted/YAML state, shared by explanation and diff without exposing parser nodes. |
| `ValueSnapshot.Present` | `bool` | Retain absence/deletion versus explicit null; YAML text alone is insufficient. |
| `ValueSnapshot.Redacted` | `bool` | Retain a structured mask flag; consumers must not interpret marker text. |
| `ValueSnapshot.YAML` | `string` | Retain deterministic safe text, not a reloadable or mutable node promise. |
| `ValueChange` | `type ValueChange struct` | Retain value-bearing replay record distinct from metadata Change; do not force both projections into one record (C3). |
| `ValueChange.Before` | `ValueSnapshot` | Retain the earlier safe value with explicit presence. |
| `ValueChange.After` | `ValueSnapshot` | Retain chronological before/after symmetry. |
| `ValueChange.Origin` | `Origin` | Retain attribution from shared evidence, without requiring identical metadata/explanation event lists. |
| `ValueChange.Reasons` | `[]string` | Retain ordered application intent; consumers remain responsible for safe prose. |
| `ValueChange.Dependencies` | `[]Path` | Retain recorded dependencies and their semantic paths. |
| `SecretReferenceInfo` | `type SecretReferenceInfo struct` | Retain explicit resolved-reference annotation; it is different from redaction classification. |
| `SecretReferenceInfo.Name` | `string` | Retain public environment name only; do not add credential content. |
| `Explanation` | `type Explanation struct` | Retain effective state plus safe chronological values; not a second mutable configuration object. |
| `Explanation.Path` | `Path` | Retain the queried semantic path. |
| `Explanation.Effective` | `ValueSnapshot` | Retain final safe value even for a path now absent. |
| `Explanation.Origin` | `Origin` | Retain effective provenance separate from each historical event's origin. |
| `Explanation.History` | `[]ValueChange` | Retain ValueChange replay, not a converted Metadata.History list (C3). |
| `Explanation.SecretReference` | `*SecretReferenceInfo` | Retain optional resolved-reference name; a classified path can withhold this annotation. |
### Typed credentials and explicit bootstrap
Declarations: [secret.go](../../secret.go), [secrets.go](../../secrets.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `Secret` | `type Secret struct` | Retain opaque environment reference and explicit credential access. Zero is unresolved/unspecified; a surviving non-pointer Secret is required. |
| `SecretEnv` | `func SecretEnv(name string) Secret` | Retain an explicit reference constructor; no plain-string credential constructor. |
| `Secret.EnvName` | `func (s Secret) EnvName() string` | Retain public reference access separate from credential access. |
| `Secret.Value` | `func (s Secret) Value() string` | Retain deliberate credential accessor; empty means unresolved. Do not combine it with formatting. |
| `Secret.IsResolved` | `func (s Secret) IsResolved() bool` | Retain cheap explicit resolution-state query despite nonempty-value validation. |
| `Secret.Format` | `func (s Secret) Format(state fmt.State, _ rune)` | Retain fmt.Formatter safety for all formatting verbs; do not replace it with only String. |
| `Secret.MarshalJSON` | `func (s Secret) MarshalJSON() ([]byte, error)` | Retain name-only json.Marshaler behavior, not a credential serialization API. |
| `SecretFileStatus` | `type SecretFileStatus string` | Retain distinct loaded/skipped/not-applied/failed states; partial bootstrap needs more than success/failure. |
| `SecretFileResult` | `type SecretFileResult struct` | Retain value-free per-entry bootstrap outcome, distinct from load sources. |
| `SecretFileResult.Name` | `string` | Retain entry/environment name, never content. |
| `SecretFileResult.Path` | `string` | Retain safe source-file attribution. |
| `SecretFileResult.Status` | `SecretFileStatus` | Retain structured status, not interpretation of report text. |
| `SecretFileResult.ExistingEmpty` | `bool` | Retain empty-existing winner evidence for skipped entries; zero does not mean that an entry was loaded. |
| `SecretsReport` | `type SecretsReport struct` | Retain detached bootstrap outcomes; zero/nil has no entries. A report may accompany an error. |
| `SecretsReport.Entries` | `func (r *SecretsReport) Entries() []SecretFileResult` | Retain copied filename-ordered outcomes. |
| `SecretsReport.WriteTo` | `func (r *SecretsReport) WriteTo(output io.Writer) (int64, error)` | Retain io.WriterTo-compatible count/error convention; do not force other renderers to return counts just for symmetry. |
| `SecretsError` | `type SecretsError struct` | Retain separate bootstrap failure type: reports partial external effects, unlike a failed configuration load. |
| `SecretsError.Error` | `func (e *SecretsError) Error() string` | Retain safe value-free summary and errors.As support through its type. |
| `SecretsError.Diagnostics` | `func (e *SecretsError) Diagnostics() []Diagnostic` | Retain detached bootstrap diagnostics; no automatic raw filesystem/environment error unwrapping. |
| `LoadSecretsDir` | `func LoadSecretsDir(directory string) (*SecretsReport, error)` | Discuss D7: BootstrapSecretsDir makes process-environment mutation clearer than another Load name. |
### Diagnostics and failed loads
Declarations: [diagnostic.go](../../diagnostic.go), [records.go](../../records.go).
| Entry | Declaration/type | Assessment |
| --- | --- | --- |
| `LoadError` | `type LoadError struct` | Retain a concrete inspectable load error and errors.As workflow; zero/nil renders a safe generic failure. |
| `LoadError.Error` | `func (e *LoadError) Error() string` | Retain value-free summary, not detailed raw parser/application errors. |
| `LoadError.Diagnostics` | `func (e *LoadError) Diagnostics() []Diagnostic` | Retain detached structured diagnostics; do not add Unwrap that defeats safe error boundaries. |
| `LoadError.Sources` | `func (e *LoadError) Sources() []SourceInfo` | Retain acquired-source evidence available before failure. |
| `WriteDiagnostics` | `func WriteDiagnostics(output io.Writer, diagnostics []Diagnostic) error` | Retain writer-first safe deterministic rendering; do not promise stable prose beyond established contracts. |
| `Severity` | `type Severity string` | Retain the two diagnostic severities; no arbitrary numeric priority surface. |
| `Code` | `type Code string` | Retain detailed stable diagnostic codes; collapsing categories would lose useful consumer handling. |
| `Diagnostic` | `type Diagnostic struct` | Retain a common safe problem record across loading, hooks, and bootstrap. |
| `Diagnostic.Code` | `Code` | Retain stable machine classification rather than matching prose. |
| `Diagnostic.Severity` | `Severity` | Retain explicit severity, independent of diagnostic code spelling. |
| `Diagnostic.Path` | `Path` | Retain semantic configuration path, not a filesystem location. |
| `Diagnostic.Message` | `string` | Retain human-readable safe context; no automatic secret inference or blanket prose-stability guarantee. |
| `Diagnostic.Primary` | `*Location` | Retain optional primary source position; synthetic reports may lack one. |
| `Diagnostic.Related` | `[]Location` | Retain ordered related locations; do not homogenize semantically meaningful composition claimant order. |
### Constants and their values
Each constant is separately listed. Retaining a diagnostic code preserves a
machine-readable failure distinction; consumers should not infer new guarantees
from a name or assume that the current set can never grow. D8 notes the one
unproduced named code identified for discussion. This catalog does not prescribe
new diagnostics or merge existing failure categories.
#### PlacementMode constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `PlacementModeMapping` | `PlacementMode = 0` | Retain explicit mapping default. |
| `PlacementModeSequenceItem` | `PlacementMode = 1` | Retain explicit record placement. |
#### SourceVersionMode constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `SourceVersionModeDefault` | `SourceVersionMode = 0` | Discuss D3: MainOnly names the existing zero behavior more precisely. |
| `SourceVersionModeIfPresent` | `SourceVersionMode = 1` | Retain; keep the documented main-absence checker behavior. |
| `SourceVersionModeRequired` | `SourceVersionMode = 2` | Retain participating-document requirement and existing NoSource behavior. |
#### Layer constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `LayerDefault` | `Layer = "default"` | Retain distinct provenance layer. |
| `LayerBase` | `Layer = "base"` | Retain established base/main attribution; no automatic wire-value rename. |
| `LayerUnderlay` | `Layer = "underlay"` | Retain distinct provenance layer. |
| `LayerOverlay` | `Layer = "overlay"` | Retain distinct provenance layer. |
| `LayerCLI` | `Layer = "cli"` | Retain distinct provenance layer. |
| `LayerResolution` | `Layer = "resolution"` | Retain distinct provenance layer. |
#### SourceKind constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `SourceKindFile` | `SourceKind = "file"` | Retain distinct acquisition kind, independent of layer. |
| `SourceKindFilesystem` | `SourceKind = "filesystem"` | Retain distinct acquisition kind, independent of layer. |
| `SourceKindBytes` | `SourceKind = "bytes"` | Retain distinct acquisition kind, independent of layer. |
| `SourceKindReader` | `SourceKind = "reader"` | Retain distinct acquisition kind, independent of layer. |
| `SourceKindMapping` | `SourceKind = "mapping"` | Retain distinct acquisition kind, independent of layer. |
| `SourceKindCLI` | `SourceKind = "cli"` | Retain distinct acquisition kind, independent of layer. |
#### ChangeKind constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `ChangeDecoding` | `ChangeKind = "decoding"` | Retain inspection value; D6 addresses its misleading availability as a Record argument. |
| `ChangeInheritance` | `ChangeKind = "inheritance"` | Retain inspection value; D6 addresses its misleading availability as a Record argument. |
| `ChangeDerivedDefault` | `ChangeKind = "derived_default"` | Retain recorded/inspected intent; discuss the Record input shape in D6. |
| `ChangeNormalization` | `ChangeKind = "normalization"` | Retain recorded/inspected intent; discuss the Record input shape in D6. |
#### Severity constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `SeverityError` | `Severity = "error"` | Retain explicit diagnostic severity. |
| `SeverityWarning` | `Severity = "warning"` | Retain explicit diagnostic severity. |
#### Code constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `CodeSourceInvalid` | `Code = "source.invalid"` | Retain distinct source / invalid classification. |
| `CodeSourceMissing` | `Code = "source.missing"` | Retain distinct source / missing classification. |
| `CodeSourcePermission` | `Code = "source.permission"` | Retain distinct source / permission classification. |
| `CodeSourceRead` | `Code = "source.read"` | Retain distinct source / read classification. |
| `CodeSourceNotRegular` | `Code = "source.not_regular"` | Retain distinct source / not regular classification. |
| `CodeSourceLimitExceeded` | `Code = "source.limit_exceeded"` | Retain distinct source / limit exceeded classification. |
| `CodeSourceReaderUsed` | `Code = "source.reader_used"` | Retain distinct source / reader used classification. |
| `CodeYAMLInvalid` | `Code = "yaml.invalid"` | Retain distinct yaml / invalid classification. |
| `CodeYAMLEmpty` | `Code = "yaml.empty"` | Retain distinct yaml / empty classification. |
| `CodeYAMLMultipleDocuments` | `Code = "yaml.multiple_documents"` | Retain distinct yaml / multiple documents classification. |
| `CodeYAMLRootType` | `Code = "yaml.root_type"` | Retain distinct yaml / root type classification. |
| `CodeYAMLDuplicateKey` | `Code = "yaml.duplicate_key"` | Retain distinct yaml / duplicate key classification. |
| `CodeYAMLTag` | `Code = "yaml.tag"` | Retain distinct yaml / tag classification. |
| `CodeYAMLAnchor` | `Code = "yaml.anchor"` | Retain distinct yaml / anchor classification. |
| `CodeYAMLAlias` | `Code = "yaml.alias"` | Retain distinct yaml / alias classification. |
| `CodeYAMLMergeKey` | `Code = "yaml.merge_key"` | Retain distinct yaml / merge key classification. |
| `CodeYAMLScalar` | `Code = "yaml.scalar"` | Retain distinct yaml / scalar classification. |
| `CodeYAMLDepthLimit` | `Code = "yaml.depth_limit"` | Retain distinct yaml / depth limit classification. |
| `CodeYAMLNodeLimit` | `Code = "yaml.node_limit"` | Retain distinct yaml / node limit classification. |
| `CodeSchemaRoot` | `Code = "schema.root"` | Retain distinct schema / root classification. |
| `CodeSchemaUnsupportedType` | `Code = "schema.unsupported_type"` | Retain distinct schema / unsupported type classification. |
| `CodeSchemaAmbiguousField` | `Code = "schema.ambiguous_field"` | Retain distinct schema / ambiguous field classification. |
| `CodeSchemaTag` | `Code = "schema.tag"` | Retain distinct schema / tag classification. |
| `CodeSchemaTextCodec` | `Code = "schema.text_codec"` | Retain distinct schema / text codec classification. |
| `CodeSchemaInline` | `Code = "schema.inline"` | Retain distinct schema / inline classification. |
| `CodeSchemaCycle` | `Code = "schema.cycle"` | Retain distinct schema / cycle classification. |
| `CodeSchemaProjectionLimit` | `Code = "schema.projection_limit"` | Retain distinct schema / projection limit classification. |
| `CodeSchemaCodec` | `Code = "schema.codec"` | Retain distinct schema / codec classification. |
| `CodeSchemaNonFinite` | `Code = "schema.non_finite"` | Retain distinct schema / non finite classification. |
| `CodeSchemaDynamicType` | `Code = "schema.dynamic_type"` | Retain distinct schema / dynamic type classification. |
| `CodeDecodeType` | `Code = "decode.type"` | Retain distinct decode / type classification. |
| `CodeDecodeNull` | `Code = "decode.null"` | Retain distinct decode / null classification. |
| `CodeDecodeRange` | `Code = "decode.range"` | Retain distinct decode / range classification. |
| `CodeDecodeDuration` | `Code = "decode.duration"` | Retain distinct decode / duration classification. |
| `CodeDecodeText` | `Code = "decode.text"` | Retain distinct decode / text classification. |
| `CodeDecodeUnknownField` | `Code = "decode.unknown_field"` | Retain distinct decode / unknown field classification. |
| `CodeDecodeArrayLength` | `Code = "decode.array_length"` | Retain distinct decode / array length classification. |
| `CodeOptionsInvalid` | `Code = "options.invalid"` | Retain distinct options / invalid classification. |
| `CodeOptionsUnavailable` | `Code = "options.unavailable"` | Discuss D8: confirm intentional reservation or remove the unproduced classification. |
| `CodeImportDisabled` | `Code = "import.disabled"` | Retain distinct import / disabled classification. |
| `CodeImportInvalid` | `Code = "import.invalid"` | Retain distinct import / invalid classification. |
| `CodeImportMissing` | `Code = "import.missing"` | Retain distinct import / missing classification. |
| `CodeImportPermission` | `Code = "import.permission"` | Retain distinct import / permission classification. |
| `CodeImportRead` | `Code = "import.read"` | Retain distinct import / read classification. |
| `CodeImportOutsideRoot` | `Code = "import.outside_root"` | Retain distinct import / outside root classification. |
| `CodeImportCycle` | `Code = "import.cycle"` | Retain distinct import / cycle classification. |
| `CodeImportRepeated` | `Code = "import.repeated"` | Retain distinct import / repeated classification. |
| `CodeImportLimitExceeded` | `Code = "import.limit_exceeded"` | Retain distinct import / limit exceeded classification. |
| `CodeImportVersion` | `Code = "import.version"` | Retain distinct import / version classification. |
| `CodeSequenceInvalid` | `Code = "sequence.invalid"` | Retain distinct sequence / invalid classification. |
| `CodeSequenceConflict` | `Code = "sequence.conflict"` | Retain distinct sequence / conflict classification. |
| `CodeUnderlayVersion` | `Code = "underlay.version"` | Retain distinct underlay / version classification. |
| `CodeOverlayInvalid` | `Code = "overlay.invalid"` | Retain distinct overlay / invalid classification. |
| `CodeOverlayMissing` | `Code = "overlay.missing"` | Retain distinct overlay / missing classification. |
| `CodeOverlayOutsideRoot` | `Code = "overlay.outside_root"` | Retain distinct overlay / outside root classification. |
| `CodeOverlayRepeated` | `Code = "overlay.repeated"` | Retain distinct overlay / repeated classification. |
| `CodeOverlayDirective` | `Code = "overlay.directive"` | Retain distinct overlay / directive classification. |
| `CodeOverlayVersion` | `Code = "overlay.version"` | Retain distinct overlay / version classification. |
| `CodeOverlayLimitExceeded` | `Code = "overlay.limit_exceeded"` | Retain distinct overlay / limit exceeded classification. |
| `CodeComposeInvalid` | `Code = "compose.invalid"` | Retain distinct compose / invalid classification. |
| `CodeComposeConflict` | `Code = "compose.conflict"` | Retain distinct compose / conflict classification. |
| `CodeVersionType` | `Code = "version.type"` | Retain distinct version / type classification. |
| `CodeVersionError` | `Code = "version.error"` | Retain distinct version / error classification. |
| `CodeVersionWarning` | `Code = "version.warning"` | Retain distinct version / warning classification. |
| `CodeVersionHookFailed` | `Code = "version.hook_failed"` | Retain distinct version / hook failed classification. |
| `CodeResolveError` | `Code = "resolve.error"` | Retain distinct resolve / error classification. |
| `CodeResolveWarning` | `Code = "resolve.warning"` | Retain distinct resolve / warning classification. |
| `CodeResolveHookFailed` | `Code = "resolve.hook_failed"` | Retain distinct resolve / hook failed classification. |
| `CodeResolveInvalidPath` | `Code = "resolve.invalid_path"` | Retain distinct resolve / invalid path classification. |
| `CodeResolveInvalidKind` | `Code = "resolve.invalid_kind"` | Retain distinct resolve / invalid kind classification. |
| `CodeResolveInvalidRecord` | `Code = "resolve.invalid_record"` | Retain distinct resolve / invalid record classification. |
| `CodeResolveInvalidTarget` | `Code = "resolve.invalid_target"` | Retain distinct resolve / invalid target classification. |
| `CodeResolveInvalidValue` | `Code = "resolve.invalid_value"` | Retain distinct resolve / invalid value classification. |
| `CodeResolveUnrecordedChange` | `Code = "resolve.unrecorded_change"` | Retain distinct resolve / unrecorded change classification. |
| `CodeValidateError` | `Code = "validate.error"` | Retain distinct validate / error classification. |
| `CodeValidateWarning` | `Code = "validate.warning"` | Retain distinct validate / warning classification. |
| `CodeValidateInvalidPath` | `Code = "validate.invalid_path"` | Retain distinct validate / invalid path classification. |
| `CodeValidateMutation` | `Code = "validate.mutation"` | Retain distinct validate / mutation classification. |
| `CodeSecretsInvalidPath` | `Code = "secrets.invalid_path"` | Retain distinct secrets / invalid path classification. |
| `CodeSecretsMissing` | `Code = "secrets.missing"` | Retain distinct secrets / missing classification. |
| `CodeSecretsPermission` | `Code = "secrets.permission"` | Retain distinct secrets / permission classification. |
| `CodeSecretsRead` | `Code = "secrets.read"` | Retain distinct secrets / read classification. |
| `CodeSecretsNotRegular` | `Code = "secrets.not_regular"` | Retain distinct secrets / not regular classification. |
| `CodeSecretsInvalidName` | `Code = "secrets.invalid_name"` | Retain distinct secrets / invalid name classification. |
| `CodeSecretsLimitExceeded` | `Code = "secrets.limit_exceeded"` | Retain distinct secrets / limit exceeded classification. |
| `CodeSecretsInvalidValue` | `Code = "secrets.invalid_value"` | Retain distinct secrets / invalid value classification. |
| `CodeSecretsApplyFailed` | `Code = "secrets.apply_failed"` | Retain distinct secrets / apply failed classification. |
| `CodeSecretRequired` | `Code = "secret.required"` | Retain distinct secret / required classification. |
| `CodeSecretInvalidName` | `Code = "secret.invalid_name"` | Retain distinct secret / invalid name classification. |
| `CodeSecretUnset` | `Code = "secret.unset"` | Retain distinct secret / unset classification. |
| `CodeSecretEmpty` | `Code = "secret.empty"` | Retain distinct secret / empty classification. |
| `CodeOverrideInvalidPath` | `Code = "override.invalid_path"` | Retain distinct override / invalid path classification. |
| `CodeOverrideInvalidLabel` | `Code = "override.invalid_label"` | Retain distinct override / invalid label classification. |
| `CodeOverrideInvalidValue` | `Code = "override.invalid_value"` | Retain distinct override / invalid value classification. |
| `CodeOverrideDuplicate` | `Code = "override.duplicate"` | Retain distinct override / duplicate classification. |
| `CodeOverrideOverlap` | `Code = "override.overlap"` | Retain distinct override / overlap classification. |
| `CodeOverrideDirective` | `Code = "override.directive"` | Retain distinct override / directive classification. |
| `CodeOverrideVersion` | `Code = "override.version"` | Retain distinct override / version classification. |
| `CodeOverrideLimitExceeded` | `Code = "override.limit_exceeded"` | Retain distinct override / limit exceeded classification. |
| `CodeExplainLimitExceeded` | `Code = "explain.limit_exceeded"` | Retain distinct explain / limit exceeded classification. |
| `CodeInheritanceInvalid` | `Code = "inheritance.invalid"` | Retain distinct inheritance / invalid classification. |
| `CodeInheritanceMissing` | `Code = "inheritance.missing"` | Retain distinct inheritance / missing classification. |
| `CodeInheritanceCycle` | `Code = "inheritance.cycle"` | Retain distinct inheritance / cycle classification. |
| `CodeInheritanceVersion` | `Code = "inheritance.version"` | Retain distinct inheritance / version classification. |
#### ImportSelector constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `ImportSelectorPath` | `ImportSelector = "path"` | Retain distinct authored selector kind. |
| `ImportSelectorDirectory` | `ImportSelector = "dir"` | Retain distinct authored selector kind. |
| `ImportSelectorGlob` | `ImportSelector = "glob"` | Retain distinct authored selector kind. |
#### ImportSelectionOutcome constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `ImportSelectionNone` | `ImportSelectionOutcome = "none"` | Discuss D4: redundant if the record explicitly describes empty selections only. |
#### SecretFileStatus constants
| Entry | Type and value | Assessment |
| --- | --- | --- |
| `SecretFileLoaded` | `SecretFileStatus = "loaded"` | Retain successful application outcome. |
| `SecretFileSkippedExisting` | `SecretFileStatus = "skipped_existing"` | Retain existing-environment winner, including empty winners. |
| `SecretFileNotApplied` | `SecretFileStatus = "not_applied"` | Retain planned but unapplied outcome, distinct from a failed entry. |
| `SecretFileFailed` | `SecretFileStatus = "failed"` | Retain failed-entry outcome, distinct from unapplied siblings. |
## Validation And Next Discussion
The catalog has one row for each of the 372 exported declarations/members
found by the syntax inventory. The callable/type/field signatures were read from
the declarations rather than reconstructed from the graph. The graph supported
navigation and reference checks; its inventory alone was not treated as proof of
completeness. Implicit interfaces include error for `*LoadError` and
`*SecretsError`, fmt.Stringer for Path, fmt.Formatter and json.Marshaler for Secret,
and io.WriterTo for `*SecretsReport`; there are no separate exported interface types.
Useful existing behavioral owners include the public facade boundary and
compatibility tests, callback-context guards, consumer examples, and metadata,
secret, explanation, source-version, discovery, and sequence tests linked by the
[development guide](../development.md#task-to-test-map). This documentation-only
review adds no tests or production changes. Validation completed: the syntax
comparison found exactly one catalog row for all 372 entries, with no missing
entries. The local documentation-link and public-facade boundary tests passed,
as did the whitespace check.
Next, decide which D1–D8 candidates merit changes. Prepare a scoped roadmap/plan
only for accepted decisions and update the declaration comments, contract owners,
examples and consumers together during that implementation. Preserve the current
loading, presence, composition, callback-lifetime, redaction and ownership contracts
unless a separately explicit decision changes one. No release or tagging action
is authorized by this review.

View File

@@ -1,276 +0,0 @@
# Audit Remediation Roadmap
Status: target accepted; completed execution is recorded in the historical plan
at `bb5540b:docs/roadmap/implementation.md`.
Prepared: 2026-10-25. Planning baseline: `e1e94ee`.
This roadmap describes the target state and related work for the 29 entries
selected in the [consolidated audit review](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#settled-scope-decisions).
It is intended for maintainers reviewing the remediation's scope and architecture.
## Document Ownership
- [Archived audit](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md) records finding IDs, original evidence, detailed remedies and
acceptance cases, and the settled scope decisions. Its evidence has not been
independently reproduced by writing this roadmap.
- This document owns the combined target state, work areas, and design intent.
Finding IDs below provide traceability without repeating the full audit inventory.
- The historical implementation plan at
`bb5540b:docs/roadmap/implementation.md` records concrete stages, private
implementation choices, edits, validation commands, and completion evidence.
The [configuration contract](../config.md), public Go declarations, and
[architecture policy](../policy/architecture.md) remain the owners of behavior
and guarantees. Update their applicable implementation documentation when work
lands; this planning document does not redefine their contracts.
## Current Baseline And Targeted End State
The existing loading model remains the foundation. Public loading APIs delegate
to the shared engine lifecycle; acquisition retains source scopes for composition,
then precedence, inheritance, decoding, CLI overrides, application resolution,
secret lookup, and validation produce a detached result with inspection metadata.
The [component overview](../internal/overview.md) describes the current owners.
The audits identify failures at particular traversal, lifetime, error, and
accounting boundaries, alongside redundant representations and repeated work.
The target is to repair those boundaries and simplify their implementation while
preserving ConfigKit's public API and configuration model.
| Current gap | Targeted outcome |
| --- | --- |
| Recursive descriptors can fail to terminate, failed dynamic builds can leave partial state, and slice aliases can be mistaken for cycles. | Traversal terminates for the reported cases, failed construction leaves reusable state valid, and supported acyclic values remain accepted. |
| Callback and owned-root cleanup can be skipped during panic unwinding; directory errors lose their failure phase. | Cleanup follows the lifetime of each owned resource, and selected-directory failures retain the information needed for correct error handling. |
| Finite symlink dependencies can be expanded repeatedly. | Completed dependencies are reused within a resolution while active cycles remain detectable. |
| Projection reservations and published history can escape existing accounting. | Existing budgets govern the relevant allocations and complete retained document graph. |
| Collection-policy work repeatedly scans or copies unrelated state. | Policy preparation, application, and retained evidence have clear ownership, with work localized to affected policy. |
| Acquisition has redundant paths and difficult handoffs; several private representations have no production consumer. | Production paths and their tests share the active protocol, with cohesive handoffs and unused representations removed. |
| Some lookups repeat full scans, and some tests assert incidental representation or miss meaningful mutations. | Local indexing avoids the identified repeated work, and tests protect observable contracts at useful boundaries. |
This is a bounded remediation effort, not a redesign of configuration semantics.
Correcting erroneous acceptance, rejection, cleanup, or diagnostics in the
reported cases restores existing contracts. It does not select new loading APIs,
configuration options, limits, background work, or persistent caches.
## Selected Work
The following areas account for all 29 selected entries. Detailed examples and
acceptance cases remain with their linked audit group.
### 1. Safe Typed-Value Traversal And Descriptor Reuse
Selected entries: **A01, A02, A03, A04, A05** in
[schema and typed values](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#a-schema-and-typed-values).
Make pointer-only descriptor traversal terminate in decoding and path resolution,
while preserving supported null values and ordinary recursive structures. Make
dynamic descriptor construction transactional: a failed build must not leave
partial descriptors available to subsequent operations. Staging successful
additions or rolling back the complete failed addition set are implementation
choices; both must handle recursive references and preserve prior valid entries.
Correct active-path cycle detection to distinguish finite slice views sharing
storage from actual cycles. Keep detachment and nil/empty behavior intact across
the callers that share this traversal machinery.
Enforce existing projection budgets before large container reservations. Include
map-key materialization and overflow-safe lower-bound checks in the review; a
check after the reservation does not address the reported failure. This work
does not introduce a general heap limit or reject large containers independently
of existing budgets.
Dynamic-type eligibility reuse depends on safe descriptor construction and retains
completed successful checks within the value operation. Keep failure diagnostics
specific to their current path and distinguish completed results from traversal visits.
This removes repeated descriptor-graph walks without a cross-load cache.
### 2. Callback, Filesystem, And Dependency Lifetimes
Selected entries: **B01, B02** in [callback lifetime](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#b-callback-and-filesystem-lifetime)
and **C01, C02** in [filesystem traversal](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#c-import-selection-and-link-resolution).
Tie callback-context deactivation to the callback invocation with deferred cleanup
so it runs on normal return and panic unwinding. Independently give the owned
import root a deferred cleanup fallback. Preserve normal close-error handling,
caller ownership, and propagation of the original callback panic. Fixing either
state lifetime or handle lifetime does not fix the other.
Preserve the phase of a directory failure. A genuinely absent directory before
successful opening remains subject to existing permissiveness rules; a later
read or close failure must not be treated as absence. Apply this distinction to
both supported directory-reading paths. Strengthen the shared filesystem tests
to check each handle's closure and structured, non-disclosing diagnostics,
including partial reads and failures masked by another successful directory.
Do not change regular-file close-error policy as a side effect.
Avoid repeatedly expanding completed finite symlink dependencies in both host
and native filesystem resolution. Use operation-local memoization of completed
targets, or an equivalent dependency evaluation, with keys that respect the
resolution namespace. Keep active-path cycle detection separate from completed
work. Preserve containment, supported finite reuse, and existing host-path
semantics. The fix must work for directory and glob selection without a new
arbitrary traversal cap or changed symlink defaults.
### 3. Retained History And Combination Evidence
Selected entries: **D01, D02, D03, D04** in
[history and retained state](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#d-retained-history-and-unused-evidence).
Remove the unconsumed override-result pre-CLI copy and the unqueried published
structural-input copy. Preserve the separate pre-CLI state used by active replay
and the structural input still used before publication for metadata and secret
classification. Remove unused combination-evidence members and the work needed
solely to produce them, retaining projections, mappings, origins, and snapshots
that have active consumers.
With retained roots clarified, enforce the existing history allowance against
the complete graph published through inspection, including replay and inheritance
state and publication-created copies. Count shared nodes once by pointer identity;
distinct retained copies still consume the allowance. Account for required copies
before creating them where practical. Removing an unused tree alone does not
resolve the accounting omissions.
Keep required before/after evidence and existing inspection behavior. Validate
the complete publication path with small controlled allowances, including the
interaction of CLI, resolution replay, and inheritance, rather than relying only
on tests of an isolated counting helper.
### 4. Collection-Policy Preparation And Application
Selected entries: **E01, E02, E03** in
[collection policy and projection](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#e-collection-processing-and-efficiency).
Distinguish the existing policy state used for lookup, changes introduced by an
incoming tier, and evidence retained by the assembly. Prepare incoming claims and
declarations against read-only lower state, then apply the owned changes at the
appropriate combination boundary. Avoid copying every unrelated policy for
small tiers and scanning all policies for each independent replacement.
Use a semantic-path index, localized updates, or equivalent bounded lookup to
find affected policy. Preserve retirement timing, policy established by the
incoming contribution, pending declarations, persistent explicit modes, and
nested record ownership. Copying remains appropriate wherever mutable ownership
requires it. Removing only one duplicate clone or shortening a function does not
address the identified growth patterns.
Clarify preparation, evidence application, and finalization responsibilities.
Share straightforward history-descriptor translation where useful; evaluate
shared record-selection logic while keeping overlay and inheritance origin and
mode lifetimes distinct. Retain the common sequence-combination owner. This work
does not call for a generic merging framework.
### 5. Active Acquisition Handoffs And Private Cleanup
Selected entries: **F01, F02, F03, F04, F05, F06** in
[acquisition, cleanup, and test quality](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#f-remaining-maintenance-and-test-improvements).
Remove the test-only eager acquisition mode so acquisition retains scopes through
the active production protocol. Tests that need composition should invoke the
actual composition boundary explicitly. Retire the parallel test-only import
parser and route relevant fixtures through the active directive parser or the
appropriate declaration boundary, retaining malformed-input coverage.
Clarify active acquisition and coordinator handoffs around cohesive tasks:
parsed-input directive/version handling, standalone placement, and repeated tier
application/projection. Use explicit inputs and results consistent with existing
state owners. Extracting helpers that continue to share surrounding mutable state
does not establish a clearer handoff. Preserve ordinary/layered differences,
overlay and diff behavior, source identity, callback timing, diagnostics, and
accounting scopes.
Remove the disconnected root record-clone family, uncalled engine helpers, and
unread private transport/result members identified in the audit. Reconfirm
references on the implementation baseline, including function values and relevant
build configurations. Preserve live conversion paths and validation side effects;
the audit's absence of field reads is not a general rule for deleting state.
Replace meaningful ownership coverage through the public boundary before removing
tests that only exercise disconnected helpers.
### 6. Local Lookup Improvements
Selected entries: **E04, E05** in
[collection policy and projection](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#e-collection-processing-and-efficiency)
and **F08** in [secret-failure lookup](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#f08-index-secret-failure-location-queries-lazily).
Reuse a local document lookup or equivalent traversal for tier-history projection
instead of rescanning wide mappings for each sequence. Preserve authored
coordinates, sparse projections, and evidence ownership.
Restore compound identity fields using indexed mapping positions and membership
checks rather than rescanning child and merged mappings for every field. Preserve
child-authored values and locations, mapping order, and detached nodes.
Build secret-failure location lookup state lazily when failures need it. Preserve
all diagnostics, nearest-location fallbacks, and confidentiality; successful
loads should not incur an unnecessary index merely to support failure reporting.
These changes address specific repeated work and do not promise a measured
startup improvement.
### 7. Tests That Protect The Intended Behavior
Selected entries: **F09, F10, F11, F12** in
[acquisition, cleanup, and test quality](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/453da684d7c7d3843a5f505f5c468b4c889dc6e2/docs/roadmap/audit.md#f-remaining-maintenance-and-test-improvements),
in addition to the regression coverage attached to each reported defect.
Cover historical-path enumeration through the public API, including removed
paths, queryability, ordering, and detached returned slices. Use those assertions
to replace private representation checks where they address the same risk.
Strengthen fuzz detachment checks by mutating existing descendants or in-length
elements, so spare capacity cannot hide aliases. Replace exact filesystem stat
call sequences with observable candidate ordering and meaningful failure
selection, retaining relevant assertions such as not opening excluded inputs.
Consolidate facade scaffolding around public construction, loading, errors,
ownership, and lifecycle behavior. Preserve distinct integration risks and
reader-byte snapshot coverage. Existing coverage percentages or a desired test
count are not acceptance criteria for this work.
## Implementation Plan
The historical implementation plan at `bb5540b:docs/roadmap/implementation.md`
defines the sequential, Luna-scoped stages and their dependencies, including
coordinated changes to descriptor state, history ownership/accounting, collection
policy, acquisition, and replacement public coverage. It also records validation
commands and the complete finding-to-stage map. The audit remains the owner of
original evidence and detailed acceptance cases.
## Validation And Completion
Validation follows the audit's detailed acceptance cases and the
[testing policy](../policy/testing.md). For fatal or hanging regressions, use bounded
subprocesses; for budget cases, use small controlled limits. For symlink expansion
and repeated lookup work, prefer bounded operation evidence and algorithm review
over timing thresholds. Use real filesystem fixtures where filesystem semantics
matter and narrow fault injection for read/close failures. State platform limits.
Applicable development checks come from the [development guide](../development.md),
including appropriate race coverage for changed ownership and lifetime boundaries.
The historical implementation plan owns their execution. Existing audit results
are baseline evidence, not validation of the eventual changes. No performance
benchmark campaign is required by this roadmap, and no new benchmark results are
claimed.
Completion means all 29 selected findings have implementation and acceptance
evidence, the affected public behavior remains consistent with its contracts,
and canonical internal documentation describes the resulting owners. The
historical implementation plan records execution status without duplicate
progress inventories. Retire completed planning records under the normal
documentation lifecycle.
## Scope Boundaries And Decision Status
All three audit scope questions are settled. No significant architectural or
product decision remains open for this roadmap. The historical implementation
plan records private design choices and sequencing, subject to the acceptance
cases above.
If that work exposes a need to change a public contract, bring the specific
decision back for review rather than treating it as an implicit part of remediation.
The glob preparation refactor (C03) and wider facade transport simplification
(F07) have since been selected in the
[internal-boundary roadmap](internal-boundaries.md). Other deferred features,
clone-traversal redesign, and broader performance experiments remain outside
this effort. Keep
existing ownership boundaries and loading APIs; no ADR or architecture-policy
change is proposed by this roadmap.