Add safe historical configuration explanations
This commit is contained in:
@@ -141,10 +141,60 @@ translated to a fixed safe error. There is no unredacted rendering option and
|
||||
ConfigKit performs no implicit logging.
|
||||
|
||||
`Result.Config` is the explicit trusted boundary and contains ordinary typed values,
|
||||
including secrets. Metadata, histories, diagnostics, and load errors contain no raw
|
||||
configuration values. Applications must still keep source labels, resolution
|
||||
reasons, and diagnostic messages value-free because ConfigKit cannot infer and
|
||||
sanitize secrets embedded in arbitrary application prose.
|
||||
including secrets. Metadata records, diagnostics, and load errors contain no
|
||||
configuration values. Explanation snapshots deliberately expose non-secret
|
||||
configuration history after applying the redaction contract below. Applications
|
||||
must still keep source labels, resolution reasons, and diagnostic messages value-free
|
||||
because ConfigKit cannot infer and sanitize secrets embedded in arbitrary application
|
||||
prose.
|
||||
|
||||
### Historical-value explanation
|
||||
|
||||
`Result.Explain(path)` returns a detached `Explanation` for any known effective or
|
||||
historical path. Its effective value and each chronological before/after value are
|
||||
`ValueSnapshot` records containing deterministic YAML text rather than mutable Go or
|
||||
parser values. `Present=false` with empty YAML means absence or deletion; explicit
|
||||
null is present and renders as `null`. The history starts with the canonical typed
|
||||
application-default or Go-zero baseline, then includes only relevant base sources in
|
||||
traversal order, the selected profile, supplied CLI records, and audited net
|
||||
resolution operations. Explicit equal writes remain visible.
|
||||
|
||||
Base, profile, and CLI history preserves the policy-validated input representation,
|
||||
including a superseded value that could not have decoded into the final Go field.
|
||||
Baseline, effective, and resolution values use canonical typed projection. Mapping
|
||||
queries reconstruct each source or override contribution, while sequences remain
|
||||
atomic: a descendant history identifies its presence before and after the containing
|
||||
sequence replacement without implying element-wise merging. Removed paths remain
|
||||
explainable; unknown and malformed paths return false.
|
||||
|
||||
Every snapshot applies the same historical tag, predicate, and recorded-dependency
|
||||
sensitivity closure as effective rendering before YAML is exposed. A redacted scalar
|
||||
is the quoted marker `'[REDACTED]'`, and a container snapshot has `Redacted=true` if
|
||||
it contains a masked descendant. Typed `Secret` snapshots contain only the configured
|
||||
environment name. `SecretReference` reports that name only for a successfully
|
||||
resolved effective reference, and is omitted when its path is classified. Credential
|
||||
values are never retained in explanation history.
|
||||
|
||||
`Result.WriteExplanation(output, path)` renders the safe structured result with the
|
||||
canonical JSON-pointer path, canonical-versus-input representation labels, effective
|
||||
source, composite locations, chronological values, resolution reasons and
|
||||
dependencies, and successful secret-reference lookup attribution. Control characters
|
||||
in paths, source identifiers and labels, reasons, dependencies, and reference names
|
||||
are escaped. An unknown path returns a fixed safe error before writing anything;
|
||||
writer errors and short writes are returned and may leave partial output. There is no
|
||||
unredacted mode.
|
||||
|
||||
Retained history consists of immutable source applications and audited subtree
|
||||
deltas, rather than a full tree copy per field. Shared nodes count once toward a
|
||||
4,000,000-node retained-history limit; exceeding it fails loading with
|
||||
`explain.limit_exceeded`. Replaying one requested path is performed on demand, and
|
||||
the returned YAML strings and nested records cannot mutate later explanations.
|
||||
|
||||
Application commands should render the separate `SecretsReport` immediately after
|
||||
an explicit `LoadSecretsDir` bootstrap, then use `WriteDiagnostics`,
|
||||
`WriteEffective`, or `WriteExplanation` on the later load result as appropriate.
|
||||
ConfigKit does not infer bootstrap outcomes from a secret reference and does not
|
||||
bundle these independent reports into a command framework.
|
||||
|
||||
## Typed secret references
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ composition, selected profile overlays, typed defaults, schema-version checks,
|
||||
explicit CLI overrides, recorded application resolution, semantic validation,
|
||||
layered metadata, secret
|
||||
classification, typed environment references, explicit secrets bootstrap, and
|
||||
redacted effective rendering. The
|
||||
redacted effective rendering and historical value explanation. The
|
||||
[component overview](internal/overview.md) owns the implementation inventory.
|
||||
|
||||
## Initial orientation
|
||||
|
||||
@@ -39,14 +39,16 @@ module and package documentation boundary. Its implemented public primitives are
|
||||
- explicit bounded startup environment bootstrap and safe partial reporting in
|
||||
`secrets.go`;
|
||||
- fixed-point tag/predicate/dependency sensitivity classification in `secret.go`; and
|
||||
- frozen-snapshot redaction and canonical effective output in `render.go`, with the
|
||||
- frozen-snapshot redaction and canonical effective output in `render.go`;
|
||||
- bounded immutable input applications, safe historical replay, detached explanation
|
||||
records, and deterministic human rendering in `explain.go`, with the
|
||||
only upstream dumper integration isolated in `internal/yaml/write.go`.
|
||||
|
||||
The [configuration contract](../config.md) owns implemented input, schema, decoding,
|
||||
loading lifecycle, path imports, composition, profile/CLI overlay, version checking,
|
||||
recorded resolution, typed secret references/bootstrap, semantic validation,
|
||||
metadata, secret classification, redacted effective rendering, and ownership
|
||||
behavior.
|
||||
metadata, secret classification, redacted effective rendering, historical
|
||||
explanation, and ownership behavior.
|
||||
|
||||
The source-extraction inventory records the application foundations assigned to
|
||||
future implementation work:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user