Add redacted effective rendering
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -4,8 +4,8 @@ This is the contributor entry point for ConfigKit. Read the task-specific owners
|
||||
below before making changes. The repository is in early implementation: its public
|
||||
loader currently covers strict selected sources, explicit path/glob import graphs,
|
||||
composition, selected profile overlays, typed defaults, schema-version checks,
|
||||
recorded application resolution,
|
||||
semantic validation, and metadata. The
|
||||
recorded application resolution, semantic validation, layered metadata, secret
|
||||
classification, and redacted effective rendering. The
|
||||
[component overview](internal/overview.md) owns the implementation inventory.
|
||||
|
||||
## Initial orientation
|
||||
|
||||
@@ -200,6 +200,18 @@ snapshot agreement, defensive nested copies, and concurrent load isolation. Valu
|
||||
rendering, redaction, explanation, commands, generated attribution, diffs, and
|
||||
digests remain assigned to their later owning work.
|
||||
|
||||
Effective-rendering implementation record (2026-09-06): ConfigKit adapts Narratio's
|
||||
deterministic effective-output and side-effect-free inspection cases in
|
||||
`classifySecretPaths`, `redactedDocument`, `Result.WriteEffective`, and the private
|
||||
`internal/yaml.Write` adapter. Tests cover stable sorted mappings, preserved sequence
|
||||
order, explicit indentation/line/document formatting, tagged containers, dynamic
|
||||
current and historical paths, fixed-point dependency cycles, nil/empty distinctions,
|
||||
resolved values, text-scalar snapshot reuse, application YAML-hook isolation,
|
||||
non-disclosure through failures and record formatting, caller mutation, and writer
|
||||
failures. Unlike Narratio's marshal/reparse path, ConfigKit renders only its owned
|
||||
frozen tree and substitutes redaction before the upstream dumper boundary. Historical
|
||||
value explanation and secret references remain assigned to later work.
|
||||
|
||||
### Defaults, presence, and domain resolution
|
||||
|
||||
Assigned across schema, decoding, resolution, validation, and consumer examples:
|
||||
|
||||
@@ -30,11 +30,15 @@ module and package documentation boundary. Its implemented public primitives are
|
||||
explicitness queries, helper verification, structural mutation audit, coalesced
|
||||
intent, private before/after snapshots, and resolution metadata integration; and
|
||||
- post-resolution schema-aware report collection and validator mutation detection in
|
||||
`validation.go`, completing the public callback lifecycle after composed input.
|
||||
`validation.go`, completing the public callback lifecycle after composed input;
|
||||
- fixed-point tag/predicate/dependency sensitivity classification in `secret.go`; and
|
||||
- frozen-snapshot redaction and canonical effective output in `render.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 overlay, version checking,
|
||||
recorded resolution, semantic validation, metadata, and ownership behavior.
|
||||
recorded resolution, semantic validation, metadata, secret classification, redacted
|
||||
effective rendering, and ownership behavior.
|
||||
|
||||
The source-extraction inventory records the application foundations assigned to
|
||||
future implementation work:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Status and ownership
|
||||
|
||||
Status: implementation in progress; Stages 1–14 are complete and later stages are
|
||||
Status: implementation in progress; Stages 1–15 are complete and later stages are
|
||||
pending.
|
||||
|
||||
Audience: a `gpt-5.6-terra` coding agent implementing one stage per prompt, in
|
||||
@@ -1092,7 +1092,36 @@ Acceptance:
|
||||
|
||||
## Stage 15 — Implement secret classification and effective rendering
|
||||
|
||||
Status: pending.
|
||||
Status: complete.
|
||||
|
||||
Completion evidence (2026-09-06):
|
||||
|
||||
- Removed the dynamic-secret gate and combined descriptor `configkit:"secret"` roots
|
||||
with a pure application path predicate over final and historical paths. Tagged
|
||||
containers cover descendants, replaced/deleted definitions stay classified, and
|
||||
resolution dependencies propagate sensitivity to a fixed point, including
|
||||
container dependencies and cycles independent of record-map iteration order.
|
||||
- Added `Result.WriteEffective` over the frozen resolved document. It clones and
|
||||
redacts before crossing the adapter boundary, sorts mappings by decoded key,
|
||||
preserves sequence order and nil/empty distinctions, and never invokes application
|
||||
YAML hooks or text codecs during rendering. Caller mutation cannot change output;
|
||||
there is no unredacted path or implicit logging.
|
||||
- Added a private v4 dumper adapter with explicit two-space/noncompact-sequence
|
||||
indentation, unlimited line width, Unicode, unique keys, block collections,
|
||||
single-quote preference, LF endings, one document, and disabled start/end markers.
|
||||
Secret roots render as the quoted string `'[REDACTED]'`; writer errors and short
|
||||
writes propagate while internal dumper errors collapse to a fixed safe error.
|
||||
- Tests cover tagged containers, dynamic current and overwritten paths, dependency
|
||||
cycles, container dependencies, custom text scalars, application marshal-hook
|
||||
isolation, resolved values, sorted/stable YAML, sequence order, nil/empty forms,
|
||||
snapshot mutation isolation, writer failures, and canary non-disclosure through
|
||||
parsing, decoding, returned hook errors, warnings, metadata, and ordinary/Go-syntax
|
||||
record formatting. The
|
||||
[current redaction contract](../config.md#secret-classification-and-effective-rendering)
|
||||
owns the behavior. Historical-value explanation and secret references/bootstrap
|
||||
remain later work. 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.
|
||||
|
||||
Read: feature [redaction](initial.md#provenance-diagnostics-and-redaction),
|
||||
[secret predicate](initial.md#schema-checking-and-secret-classification), result API.
|
||||
|
||||
@@ -470,7 +470,8 @@ The [current application schema contract](../config.md#application-schema-bindin
|
||||
owns root/type support, field and tag binding, inlining, codec pairing, and
|
||||
schema-aware path rules. The [current defaults and typed-decoding contract](../config.md#defaults-and-typed-decoding)
|
||||
owns strict conversions, container application, presence, safe failures, and initial
|
||||
origins. Secret handling and rendering remain target work below.
|
||||
origins. Secret classification and effective rendering are owned by the
|
||||
[current redaction contract](../config.md#secret-classification-and-effective-rendering).
|
||||
|
||||
## Recorded resolution and validation
|
||||
|
||||
@@ -479,7 +480,7 @@ Application-owned recorded resolution is implemented. The
|
||||
explicitness queries, helpers and manual records, structural mutation auditing,
|
||||
coalescing, diagnostics, origins, history, and snapshot ownership. Layered input
|
||||
history is owned by the [current metadata contract](../config.md#layered-metadata);
|
||||
redacted explanation and secret dependency propagation remain target work.
|
||||
redacted historical-value explanation remains target work.
|
||||
|
||||
Canonical typed projection, detached cloning, structural comparison, and publishing
|
||||
independently captured resolved results are implemented. The
|
||||
@@ -499,7 +500,9 @@ Safe diagnostic/provenance value records, canonical diagnostic rendering, and
|
||||
Default, base, profile, and resolution records are attached to the loading lifecycle;
|
||||
their source ordering, composite/synthetic origins, replacement history, and detached
|
||||
queries are owned by the [current metadata contract](../config.md#layered-metadata).
|
||||
Safe historical values, redaction, and rendering remain target work below.
|
||||
Secret classification and effective rendering are owned by the
|
||||
[current redaction contract](../config.md#secret-classification-and-effective-rendering).
|
||||
Safe historical values and explanation remain target work below.
|
||||
|
||||
Diagnostics have stable codes, severity, semantic path when available, a safe
|
||||
message, primary location, and related locations. Sort deterministically by
|
||||
@@ -520,20 +523,6 @@ Applications likewise must supply value-free validation messages and resolution
|
||||
reasons. ConfigKit cannot sanitize secrets embedded in arbitrary application
|
||||
prose reliably.
|
||||
|
||||
Use `configkit:"secret"` on fields and an application path-classification hook
|
||||
for dynamic entries. A secret container makes descendants secret. Secret
|
||||
classification applies conservatively across current and historical definitions
|
||||
of a path: replacing a secret does not declassify its history. It propagates from
|
||||
recorded secret dependencies to derived outputs. Applications remain responsible
|
||||
for marking secret-producing transformations whose dependencies are not recorded.
|
||||
|
||||
Provide source reporting and redacted effective rendering in the initial target.
|
||||
Render from a canonical projection of the final typed snapshot, including zero
|
||||
values and null/empty distinctions supported by the schema. Redacted output is
|
||||
for inspection and is not promised to be reloadable. ConfigKit renderers must
|
||||
not expose raw secrets; returning typed configuration to the application is an
|
||||
intentional trusted boundary. ConfigKit does not log configuration on its own.
|
||||
|
||||
Provide full safe historical-value explanation in the initial release, as defined
|
||||
[below](#historical-value-explanation). Defer config diffs and fingerprints. A future fingerprint design
|
||||
must specify canonicalization, secret handling, and disclosure risks before
|
||||
@@ -568,9 +557,9 @@ and verification against consumer models.
|
||||
|
||||
`Load`, `Definition`, `Options`, `FilesystemContext`, `Override`, `VersionRule`,
|
||||
`Version`, `Validation`, and `Result` are declared. The current loader supports the
|
||||
callback and path-import subset described by the [current contract](../config.md);
|
||||
later override, secret, and rendering behavior below remains target work and
|
||||
unavailable options fail explicitly rather than being ignored.
|
||||
implemented boundary described by the [current contract](../config.md). Explicit
|
||||
overrides, secret references/bootstrap, and historical-value explanation remain
|
||||
target work; unavailable options fail explicitly rather than being ignored.
|
||||
|
||||
```go
|
||||
func Load[T any](source Source, options ...Options[T]) (*Result[T], error)
|
||||
@@ -863,8 +852,9 @@ potentially sensitive configured values.
|
||||
`VersionRule`, `Version`, and the version-checking use of `Validation` are
|
||||
implemented. The [current version contract](../config.md#application-schema-versions)
|
||||
owns absent/null/string handling, report locations, warnings, failure behavior, and
|
||||
raw-error suppression. Secret classification and later composed-source placement
|
||||
checks remain target work.
|
||||
raw-error suppression. The
|
||||
[current redaction contract](../config.md#secret-classification-and-effective-rendering)
|
||||
owns secret classification.
|
||||
|
||||
```go
|
||||
type VersionRule struct {
|
||||
@@ -918,19 +908,15 @@ versions remain strings under ConfigKit's convention; adopting consumers with
|
||||
integer versions must migrate their syntax rather than enable a second scalar
|
||||
policy.
|
||||
|
||||
`Definition.Secret` is an optional pure path predicate for dynamic entries,
|
||||
combined with secret field tags. It can add sensitivity but cannot override a
|
||||
tag to declassify a value. It is applied to historical as well as final paths.
|
||||
Recorded dependencies propagate sensitivity under the existing redaction rules;
|
||||
applications own secret-producing transformations not described by those records.
|
||||
`Definition.Secret` and `configkit:"secret"` behavior are implemented and owned by
|
||||
the [current redaction contract](../config.md#secret-classification-and-effective-rendering).
|
||||
|
||||
### Results, errors, and inspection
|
||||
|
||||
`Result.Config`, `Metadata`, `Sources`, and `Diagnostics`, together with
|
||||
`LoadError` and `WriteDiagnostics`, are implemented at the current loading boundary.
|
||||
`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. Effective rendering and richer layered inspection
|
||||
below remain target work.
|
||||
comments own their behavior. Historical-value explanation below remains target work.
|
||||
|
||||
```go
|
||||
type Result[T any] struct {
|
||||
@@ -961,12 +947,9 @@ locations as described [above](#provenance-diagnostics-and-redaction). No raw
|
||||
parser error or unrestricted secret-bearing history is exposed. No automatic
|
||||
logging occurs. Inspection methods returning slices provide defensive copies.
|
||||
|
||||
`Config` is caller-owned typed data. Metadata and rendering use an independently
|
||||
captured final snapshot, so later caller mutation cannot rewrite the recorded
|
||||
result. `WriteEffective` emits deterministic redacted YAML for inspection, with
|
||||
no unredacted switch. It returns output errors rather than a new configuration validation result.
|
||||
Custom projection failures must already have been rejected before a load
|
||||
succeeds. Output can be partial on writer failure.
|
||||
Caller-owned configuration, the frozen result snapshot, secret classification, and
|
||||
`WriteEffective` behavior are owned by the
|
||||
[current redaction contract](../config.md#secret-classification-and-effective-rendering).
|
||||
|
||||
An application can report errors and inspect a successful load without rebuilding
|
||||
diagnostic formatting. This example uses `errors` and `io` and the pipeline
|
||||
|
||||
Reference in New Issue
Block a user