Add safe historical configuration explanations

This commit is contained in:
2026-09-06 22:14:54 +00:00
parent d4ec8607d1
commit 6ef240b1a0
17 changed files with 1032 additions and 141 deletions

View File

@@ -1365,7 +1365,43 @@ Acceptance:
## Stage 19 — Expose safe historical-value explanation
Status: pending.
Status: complete.
Completion evidence (2026-09-06):
- Added detached `ValueSnapshot`, `ValueChange`, `SecretReferenceInfo`, and
`Explanation` records plus `Result.Explain` and `Result.WriteExplanation` in
[`explain.go`](../../explain.go). Effective/default/domain values use canonical
typed projection; base/profile/CLI events preserve policy-validated input, with
explicit absent, null, equal-write, replacement, and deletion states.
- Extended composition, profile, and CLI handling to retain immutable applications
in traversal/precedence order. Explanation replays those applications on demand
with mapping recursion and atomic sequence replacement, then applies the existing
audited resolution subtree deltas. This reconstructs container and descendant
histories without a full retained tree per field, including superseded input that
could not decode into the final destination type.
- Applied tag, predicate, and dependency-cycle sensitivity across final and
historical application paths before snapshot projection. Snapshots recursively
redact classified descendants, resolved `Secret` metadata retains only successful
effective environment names, and classified references omit that annotation.
Shared immutable nodes count once toward a fixed 4,000,000-node retained-history
budget; excess fails safely with `explain.limit_exceeded`.
- Added deterministic human rendering with canonical quoted pointer paths, effective
and composite source locations/labels, representation labels, chronological
before/after YAML, reasons, dependencies, and secret lookup attribution. Unknown
paths write nothing; writer errors and short writes propagate. Added executable
`ExampleResult_Explain` and moved normative behavior to the
[current explanation contract](../config.md#historical-value-explanation), keeping
bootstrap reporting explicitly separate.
- Added focused coverage in [`explain_test.go`](../../explain_test.go) for the
imported-default/profile sequence 4 → 8 → 1, CLI and normalization, equal writes,
removed paths, parent replacement, composite mappings, atomic sequences,
superseded invalid input, null/empty values, successful and classified references,
historical/dependency-cycle canaries, returned-record mutation, deterministic and
failing writers, structural-sharing budgets, and an end-to-end secrets-bootstrap/
load/explain flow. Verified `go test ./...`, `go test -race ./...`, `go vet ./...`,
`go build ./...`, `go mod tidy -diff`, and `git diff --check` with Go 1.26.7 on
linux/amd64. No deviations were required; consumer ergonomics remain later work.
Read: [explanation contract](initial.md#historical-value-explanation), redaction
rules, and history bounds above.

View File

@@ -916,7 +916,9 @@ the [current redaction contract](../config.md#secret-classification-and-effectiv
`Result.Config`, `Metadata`, `Sources`, `Diagnostics`, and `WriteEffective`, together
with `LoadError` and `WriteDiagnostics`, are implemented at the current loading boundary.
The [current loading contract](../config.md#single-source-loading) and Go reference
comments own their behavior. Historical-value explanation below remains target work.
comments own their behavior. Historical-value explanation is also implemented and
owned by the
[current explanation contract](../config.md#historical-value-explanation).
```go
type Result[T any] struct {
@@ -976,74 +978,14 @@ func InspectPipeline(path, profile string, output io.Writer) error {
Applications can use `Metadata().Lookup`, `Metadata().History`, and `Sources()`
for structured inspection without implementing value rendering or importing a
parser. The explanation API below supplies safe values and formatting for application-owned
`config explain` commands.
parser. The implemented explanation API supplies safe values and formatting for
application-owned `config explain` commands.
### Historical-value explanation
```go
func (r *Result[T]) Explain(path Path) (Explanation, bool)
func (r *Result[T]) WriteExplanation(output io.Writer, path Path) error
type Explanation struct {
Path Path
Effective ValueSnapshot
Origin Origin
History []ValueChange
SecretReference *SecretReferenceInfo
}
type ValueSnapshot struct {
Present bool
Redacted bool
YAML string
}
type ValueChange struct {
Before, After ValueSnapshot
Origin Origin
Reasons []string
Dependencies []Path
}
type SecretReferenceInfo struct { Name string } // successful resolved lookup
```
Snapshots contain detached deterministic YAML text, never `any`, parser objects,
or mutable internal trees. `Present=false` is absence/deletion and has empty YAML;
null is present with `null` YAML. Redacted snapshots use the quoted marker
`[REDACTED]`; `Redacted` also marks containers containing redacted descendants.
Apply the same historical sensitivity/dependency closure as effective rendering
before exposing any snapshots. A `Secret` projects only its environment name;
its resolved value is never captured, even privately as history. For classified
reference paths omit `SecretReference` as well as masking the projected name.
Retain private persistent node snapshots or operation deltas with structural
sharing; do not copy the entire tree for every changed leaf. Explain a known final
or historical path, including removed paths; unknown paths return false, and the
writer returns a safe unknown-path error without output. Malformed query paths
also fail safely. Render the canonical JSON-pointer path, effective value/source,
and chronological history: initial application-default (or Go-zero baseline),
base contributions in traversal order, profile, CLI, and recorded domain changes.
Include before/after values for changed or explicitly redefined paths, equal-value
writes, removals, reasons/dependencies, and composite origins. Inputs overridden
before decoding retain policy-validated node values even when they would not fit
the final Go type. Defaults/final/domain snapshots use canonical typed projection;
label those representations rather than claiming every historical input was typed.
Container queries reconstruct the value at each relevant operation; descendant
queries include ancestor replacement/removal and whole-sequence replacements,
without implying element-wise sequence merging. Omitted fields/default retention
must agree with the presence/default contract; do not fabricate a write for every
import that leaves a path alone. Resolution history records the audited net change
per path, not every intermediate Go assignment. Lookup of a secret reference
annotates its effective name as resolved from the process environment; bootstrap
file outcomes come from the separate report and are never guessed.
`WriteExplanation` is a deterministic human renderer over this safe structured
result. Escape control characters in paths, labels, names, and reasons, propagate
writer errors/short writes, and permit partial output only on writer failure.
No unredacted switch or bundled command is provided. For example an application
can call it for `/transcription/concurrency` to show default `4`, an explicitly
imported file's `8`, then a testing profile's `1`, with file line numbers and the
profile as effective source. This does not require automatic `conf.d` discovery.
Historical explanation is implemented. The
[current explanation contract](../config.md#historical-value-explanation) owns its
public records, replay ordering, redaction, rendering, and allocation bounds.
### Module-specific options