Add redacted effective rendering

This commit is contained in:
2026-09-06 21:21:11 +00:00
parent 26cece6d53
commit 828fdd38eb
14 changed files with 662 additions and 62 deletions

View File

@@ -1,9 +1,9 @@
# Configuration Input Contract
This document owns ConfigKit's currently implemented source-input, YAML syntax,
schema, typed-decoding, loading, metadata, and ownership contracts. The parser,
owned document representation, and decoder remain private behind the public loading
lifecycle.
schema, typed-decoding, loading, metadata, redaction, rendering, and ownership
contracts. The parser, owned document representation, and decoder remain private
behind the public loading lifecycle.
## Sources
@@ -42,7 +42,7 @@ The current lifecycle is:
validate options → parse the selected source → traverse/compose enabled imports
→ build schema → check the optional application version → apply a selected profile
→ create and clone defaults → strictly decode → recorded application resolution
→ semantic validation → snapshot configuration and metadata
→ semantic validation → classify secrets → snapshot configuration and metadata
```
`File`, `Bytes`, and `Reader` each contribute the first base source with deterministic
@@ -53,17 +53,18 @@ missing file remains an error; it is never interpreted as no input.
The current `Options` boundary accepts a defaults factory, `VersionRule`, resolver,
semantic validator, explicit path and glob imports, one selected profile, and
filesystem context. Overrides and dynamic secret classification return
`options.unavailable` because their lifecycle stages are not implemented. `NoSource`
rejects imports, active profiles, and filesystem context as invalid combinations.
Input containing a reserved `_configkit` mapping key is rejected while imports are
disabled.
filesystem context. It also accepts static secret tags and a dynamic path predicate.
Overrides return `options.unavailable` because their lifecycle stage is not
implemented. `NoSource` rejects imports, active profiles, and filesystem context as
invalid combinations. Input containing a reserved `_configkit` mapping key is
rejected while imports are disabled.
Every source, options, schema, version, defaults, decode, resolution, or validation
failure returns a nil result and a `*LoadError`. Successful results contain caller-owned `Config`, detached
`Metadata`, a defensive source list, and defensive nonfatal diagnostics. Mutating
the returned configuration or inspection records cannot alter ConfigKit's private
snapshot. ConfigKit performs no logging and exposes no effective writer yet.
snapshot. ConfigKit performs no logging. `Result.WriteEffective` exposes only the
frozen redacted projection.
### Application schema versions
@@ -88,8 +89,7 @@ available source coordinates are retained. `History` records the Go-zero or appl
default baseline followed by exact base or profile definitions, including
equal-valued input. Profile replacement removes displaced descendants from effective
metadata while retaining their base definition and the parent replacement operation
in history.
All metadata queries and returned nested records are detached.
in history. All metadata queries and returned nested records are detached.
History is chronological: a path can have a synthetic default record, its exact base
definition, a profile replacement, and a recorded resolution operation. A descendant
@@ -108,6 +108,42 @@ operation. Reasons and dependency paths retain first occurrence order with dupli
removed. Source records remain ordered as selected source, depth-first imports, then
the selected profile.
### Secret classification and effective rendering
Secret classification combines `configkit:"secret"` field tags with the optional
pure `Definition.Secret(Path) bool` predicate. A tagged or predicate-classified
container makes all descendants sensitive. ConfigKit evaluates final and historical
paths, so replacing or deleting a definition does not erase its classification. The
predicate can add sensitivity but cannot declassify a tagged path.
Recorded resolution dependencies propagate sensitivity to their derived target.
Propagation reaches a fixed point, so record/map iteration order does not affect the
result and dependency cycles terminate. A dependency on a classified descendant or
on a container that contains one is treated conservatively as sensitive. Applications
must classify secret-producing transformations whose dependencies are not recorded.
`Result.WriteEffective` writes the frozen resolved snapshot as deterministic redacted
YAML. Mappings sort by decoded key string, sequences retain order, indentation is two
spaces with noncompact sequence indentation, line endings are LF, the document has no
start or end marker, and output ends with a newline. Nil pointers/maps/slices remain
`null`; non-nil empty maps and sequences remain `{}` and `[]`. A secret root is
replaced by the quoted string `'[REDACTED]'`. Redacted output is intended for
inspection and is not promised to be reloadable.
Rendering uses only a safe adapter tree built from the already completed canonical
typed projection. It does not invoke application YAML marshal hooks or text codecs;
all text-codec projection has succeeded before `Load` returns. Caller mutation of
`Result.Config` cannot change later output. Writer failures, including short writes,
are returned and may leave partial redacted output; internal dumper failures are
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.
### Recorded application resolution
An optional `Definition.Resolve` callback runs after successful typed decoding. It
@@ -356,7 +392,8 @@ or pointer-to-struct, or designates at most one string-keyed inline map for othe
unmatched names. Ambiguous names, named inline fields, multiple inline maps, and
unsupported or malformed tag options are schema errors. Embedded fields are ordinary
named fields unless explicitly inlined. `configkit:"secret"` is the only current
ConfigKit field tag and records sensitivity for later lifecycle work.
ConfigKit field tag and marks its bound field or container for secret classification
and redacted inspection.
Schema path traversal uses these same bindings. Explicit key segments address
mappings, explicit index segments address sequences, and parsed numeric pointer