From 2ebdfb539be05c7cfb8479e4b7632ed4aa006c48 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 9 Oct 2026 12:42:17 +0000 Subject: [PATCH] Document and audit effective configuration diffs --- README.md | 6 ++- docs/config.md | 40 +++++++++++++++ docs/consumers/README.md | 21 ++++++++ docs/development.md | 2 + docs/internal/overview.md | 14 +++-- docs/policy/architecture.md | 7 +++ docs/roadmap/deferred.md | 8 ++- docs/roadmap/diff.md | 94 ++++++++-------------------------- docs/roadmap/implementation.md | 23 +++++++-- 9 files changed, 133 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 3b65e80..a647522 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,11 @@ secrets-directory bootstrap is available as a separate startup operation. Explic CLI override records apply above overlays. Value explanation is available through detached structured records and deterministic redacted output. Built-in `extends` relationships let a mapping inherit from a named sibling during the -same load, retaining explicit presence and source/derivation history. +same load, retaining explicit presence and source/derivation history. The root +`Diff` API compares frozen effective values from two successful same-type loads; +the [configuration contract](docs/config.md#effective-configuration-diffs) and +[consumer guide](docs/consumers/README.md#effective-configuration-diffs) describe +its safe changed-region records and maintained workflows. ```go type Config struct { diff --git a/docs/config.md b/docs/config.md index c4cd2ec..c2b79dc 100644 --- a/docs/config.md +++ b/docs/config.md @@ -423,6 +423,46 @@ an explicit `LoadSecretsDir` bootstrap, then use `WriteDiagnostics`, ConfigKit does not infer bootstrap outcomes from a secret reference and does not bundle these independent reports into a command framework. +## Effective configuration diffs + +`configkit.Diff(left, right)` compares two completed successful loads of the same +configuration type. `left` is the earlier state and `right` is the later state. +Applications load both sides with the sources and options they choose; to inspect +an overlay's impact, load the same base once without the overlay and once with it +selected. The [consumer guide](consumers/README.md#effective-configuration-diffs) +links to maintained examples. The public API returns safe changed-region records; +applications own their presentation. + +Comparison uses the frozen canonical effective inspection values, not mutable +`Result.Config`, source text, provenance, or history. It does not reopen sources, +reread environment variables, or rerun defaults or callbacks. Equal effective +values produce no records even when sources, explicit-input status, defaults, or +resolution histories differ; for example, explicit zero and defaulted zero are +equal. Mappings compare recursively, sequences compare atomically, and scalar, +kind, or null/container replacements report their affected region. Records use +the existing comparator's canonical pointer ordering. `Present=false` with empty +YAML means absence; explicit null is present and renders as `null`. + +Before either side is returned, sensitivity from both loads is applied to both +snapshots, including historical and dependency-derived classification. A +classification on either side protects the corresponding values on both sides. +`Redacted` reports masking, while `HiddenChanged` reports an actual concealed +value or presence change; it is not implied merely by redaction. A structural +replacement that prevents safe correspondence masks the complete reported +region, and an absent side remains absent. Record membership establishes that a +change occurred even when safe snapshots render identically. Literal +marker-looking input is ordinary input; consumers use the record fields rather +than interpreting marker text. + +Typed secret references contribute their configured environment names to the +frozen comparison, never resolved credential bytes. Credential rotation behind +an unchanged reference is therefore not detected. The records contain changed +regions only, not paired full-document context; independently calling +`WriteEffective` on each result does not create cross-protected context. Nil or +uninitialized results return an error with no records. A failed `Load` retains +its normal nil-result/`LoadError` behavior, which callers handle before invoking +`Diff`. + ## Typed secret references Credential-bearing fields use the opaque `Secret` type and store an environment diff --git a/docs/consumers/README.md b/docs/consumers/README.md index 40aa957..3f2ed6f 100644 --- a/docs/consumers/README.md +++ b/docs/consumers/README.md @@ -18,6 +18,7 @@ sanitized shapes using only ConfigKit and the Go standard library: | Bundled file | Simple native `fs.FS` source through ordinary `Load` | Selection and lifetime of the application-owned embedded filesystem | | Layered catalog | Embedded defaults with an import, a mapping underlay, an independent host main file, explicit false/zero input, and source-relative resource access | Domain validation and opening/interpreting the template resource | | Named presets | One-load sibling inheritance, explicit false, nested destination resolution, and source/derivation inspection | Choosing and executing a preset | +| Effective configuration diff | Same-type frozen effective comparisons for two selected files or base-versus-overlay impact | Source/options selection and consumer-owned safe presentation | The YAML under [`testdata`](../../examples/consumers/testdata) is loaded by the executable examples and ordinary tests. It contains no private application data, @@ -51,6 +52,25 @@ both without redefining their syntax. ConfigKit overlays are configuration overl they do not select or replace an application's LLM, model, pipeline, report, or execution profile concepts. +## Effective configuration diffs + +[`CompareFiles`](../../examples/consumers/diff.go) loads two caller-selected files +with one application definition, handles each load error, calls `configkit.Diff`, +and presents the safe records through consumer-owned `FormatDifferences`. +[`CompareRolloutOverlay`](../../examples/consumers/diff.go) loads the same base +once without an overlay and once with the selected overlay, so omitted lower +priority values remain present. Its resolver records a derived endpoint URL after +overlay application, and its nested typed secret reference is classified before +the resulting records are presented. + +The maintained [behavior tests and executable examples](../../examples/consumers/diff_test.go) +use temporary files and disposable secret values. Presenters should use +`ValueSnapshot.Present` for absence, record membership for change, and +`HiddenChanged` for a withheld change indicator. Do not parse the redaction marker +or add separately rendered full contexts that lack combined sensitivity. The +[configuration contract](../config.md#effective-configuration-diffs) owns shared +equality, granularity, and disclosure semantics. + ## Named sibling inheritance [`LoadPresets`](../../examples/consumers/inheritance.go) loads the maintained @@ -168,6 +188,7 @@ focused behavioral owner: | `LoadSecretsDir`, bootstrap reporting, typed lookup, CLI explanation | `Example_weatherStartup` | | Native filesystem, grouped underlays, programmatic mapping, retained resource context | `Example_nativeFilesystem`, `Example_layeredCatalog`, and their focused consumer tests | | Named sibling inheritance, presence, and derivation | `Example_inheritedPresets` and `TestPresetCatalogOneLoadAndDerivation` | +| Effective configuration diffs and consumer-owned presentation | `ExampleDiff`, `ExampleCompareFiles`, and `ExampleCompareRolloutOverlay` | | Results, metadata, diagnostics, effective output, and writer failures | Focused root-package load, provenance, rendering, and explanation tests | The package-level import-boundary test covers the consumer examples too: supported diff --git a/docs/development.md b/docs/development.md index 5c27a11..5c1445b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -54,6 +54,7 @@ Accepted decisions: - [Secret references and explicit bootstrap](adr/0004-secret-references-and-explicit-bootstrap.md) - [Explicit overrides and safe explanation](adr/0005-explicit-overrides-and-safe-explanation.md) - [Keep the public facade root-owned](adr/0006-public-facade.md) +- [Compare frozen effective configuration snapshots](adr/0010-effective-configuration-diff.md) For cross-cutting changes, follow every applicable row. Completed implementation and remediation plans are preserved in Git history at `cb5b435`; use current @@ -161,6 +162,7 @@ code changes. Use the component overview to find the corresponding implementatio | Paths, metadata, provenance | [Paths](../internal/engine/path_test.go), [metadata](../internal/engine/metadata_test.go), [provenance](../internal/engine/provenance_test.go) | `go test ./internal/engine -run 'Path\|Metadata\|Provenance\|Queries'` | | Passive resource contexts | [Engine contexts](../internal/engine/resource_context_test.go), [public contexts](../facade_resource_context_test.go) | `go test ./internal/engine -run 'Resource'` and `go test . -run 'SourceContext'` | | Secrets, bootstrap, redaction, explanation | [References](../internal/engine/secret_reference_test.go), [bootstrap](../internal/engine/secrets_test.go), [rendering](../internal/engine/render_test.go), [explanation](../internal/engine/explain_test.go) | `go test ./internal/engine -run 'Secret\|Bootstrap\|Effective\|Explain\|Explanation\|History\|Sensitivity'` | +| Effective configuration diff | [Engine comparison and masking tests](../internal/engine/diff_test.go), [engine operation tests](../internal/engine/diff_operation_test.go), [root facade tests](../facade_diff_test.go), and [consumer examples](../examples/consumers/diff_test.go) | `go test ./internal/engine -run 'Diff|Explain|History|Secret|Independent'`, `go test . -run 'Diff|FacadeInspection|PublicFacadeBoundary|DependencyImportBoundary'`, `go test ./examples/consumers`, and `go test . -run '^ExampleDiff$'` | | Consumer examples and migration fixtures | [Consumer tests](../examples/consumers/consumers_test.go), [native and layered examples](../examples/consumers/layered_test.go), [API examples](../example_test.go) | `go test ./examples/consumers` and `go test . -run '^Example'` | | Assembly and layered scaling evidence | [Assembly benchmarks](../internal/engine/assembly_bench_test.go), [layered benchmarks](../internal/engine/underlay_bench_test.go) | `go test ./internal/engine -run '^$' -bench '^BenchmarkUnderlay' -benchmem`; repeat with `^BenchmarkCompose` and `^BenchmarkOverlay` | | Inheritance scaling evidence | [Expansion benchmarks](../internal/engine/inheritance_bench_test.go) | `go test ./internal/engine -run '^$' -bench '^BenchmarkInheritance' -benchmem` | diff --git a/docs/internal/overview.md b/docs/internal/overview.md index 73c8e82..0bc2408 100644 --- a/docs/internal/overview.md +++ b/docs/internal/overview.md @@ -12,7 +12,8 @@ Implemented component owners: sibling inheritance before typed decoding. It also owns schema inspection, lifecycle coordination, callback auditing, provenance/history, passive source-context retention, secret resolution and - bootstrap, and safe inspection rendering. It never imports the root package. + bootstrap, safe inspection rendering, and effective-value comparison over + frozen inspection snapshots. It never imports the root package. - Root `Source`, `Filesystem`, `Input`, `Underlay`, `Path`, `Secret`, records, lifecycle callbacks, results, and bootstrap reports are root-defined wrappers or records. Callback wrappers pass only live engine context during a callback; @@ -21,6 +22,9 @@ Implemented component owners: - Root conversion files explicitly cross the path, record, callback, secret, and error boundaries. There are no public aliases, anonymous engine embeddings, or public engine bridge methods. +- Root `Diff` and `Difference` provide the public comparison boundary and + convert detached engine records to root-owned paths and snapshots; consumer + examples own presentation of the safe changed regions. - `internal/document` owns the parser-independent ordered tree and cloning. `internal/yaml` is the sole upstream YAML adapter and parser/dumper boundary. - Engine mechanism tests own source, composition, inheritance, decoding, @@ -37,9 +41,11 @@ these constraints and rejects exported aliases, fields, or signatures involving internal types. The [configuration contract](../config.md) owns implemented YAML and lifecycle -semantics. The [consumer adoption guide](../consumers/README.md) owns application -guidance. The [architecture policy](../policy/architecture.md) owns contributor -boundaries, and [ADR 0006](../adr/0006-public-facade.md) records the facade +semantics, including [effective configuration diffs](../config.md#effective-configuration-diffs). +The [consumer adoption guide](../consumers/README.md) owns application guidance. +The [architecture policy](../policy/architecture.md) owns contributor +boundaries; [ADR 0006](../adr/0006-public-facade.md) records the facade decision +and [ADR 0010](../adr/0010-effective-configuration-diff.md) records the diff decision. See the [development guide](../development.md) for validation commands and [release procedure](../release.md) for source-candidate gates. [ADR 0009](../adr/0009-sibling-inheritance.md) records why inheritance is a built-in diff --git a/docs/policy/architecture.md b/docs/policy/architecture.md index 8ddade0..82d686f 100644 --- a/docs/policy/architecture.md +++ b/docs/policy/architecture.md @@ -201,6 +201,13 @@ See [ADR 0005](../adr/0005-explicit-overrides-and-safe-explanation.md). Detailed syntax and operation tables belong to the [current configuration contract](../config.md). +The frozen inspection boundary also supports comparing completed same-type +results without reopening sources or rerunning the load lifecycle. The +[configuration contract](../config.md#effective-configuration-diffs) owns +comparison and disclosure behavior, the [component overview](../internal/overview.md) +owns implementation responsibility, and [ADR 0010](../adr/0010-effective-configuration-diff.md) +records the accepted decision. + ## Ownership And Confidentiality Returned configuration and metadata must not share mutable state across loads. diff --git a/docs/roadmap/deferred.md b/docs/roadmap/deferred.md index e0d2db0..e2c1b81 100644 --- a/docs/roadmap/deferred.md +++ b/docs/roadmap/deferred.md @@ -23,12 +23,18 @@ The following remain deliberately excluded: - automatic schema migrations; - hot reload, runtime resource replacement, and remote resource validation; - a bundled CLI framework or application commands; and -- configuration diffs and fingerprints. +- configuration fingerprints. Applications may build their own validate, effective-value, source, bootstrap, and explanation commands around the library. Broadening these boundaries requires concrete consumer evidence, compatibility review, and an explicit design change. +Effective configuration diffs are implemented over frozen successful-load +inspections; their current comparison and disclosure contract is in the +[configuration contract](../config.md#effective-configuration-diffs), with +consumer workflows in the [adoption guide](../consumers/README.md#effective-configuration-diffs). +Fingerprints remain deferred. + Built-in sibling inheritance using `extends` is implemented as described in the [configuration contract](../config.md#sibling-inheritance). It is distinct from inheritance between selected overlay files, which remains excluded above. diff --git a/docs/roadmap/diff.md b/docs/roadmap/diff.md index e9ab84f..c0a3224 100644 --- a/docs/roadmap/diff.md +++ b/docs/roadmap/diff.md @@ -1,9 +1,9 @@ # Effective Configuration Diff Roadmap -Status: design agreed; implementation has not started. This document owns the -targeted end state, selected contract, and work needed from the current baseline. -The [implementation plan](implementation.md) owns execution order. No diff API -is available yet. +Status: implemented. This document preserves the selected contract and scope; +the [configuration contract](../config.md#effective-configuration-diffs) owns +current behavior, and the [implementation record](implementation.md) preserves +the completed sequence and validation evidence. ## Targeted end state @@ -158,11 +158,10 @@ renderer. A diff may describe removals that existing overlay semantics cannot express. These boundaries define this feature's selected scope, not intrinsic correctness restrictions on every possible future extension. -## Current baseline +## Implementation references -- [Deferred scope](deferred.md) currently excludes configuration diffs and - fingerprints. This design reconsiders only the diff boundary; fingerprints - and unrelated exclusions remain deferred. +- [Deferred scope](deferred.md) continues to exclude fingerprints and unrelated + items; effective configuration diffs are implemented. - The [configuration contract](../config.md#typed-projection-and-ownership) describes canonical typed projection and private structural comparison. [The existing comparator](../../internal/engine/document_compare.go) reports @@ -181,69 +180,20 @@ correctness restrictions on every possible future extension. - The [public facade](../internal/overview.md) is root-owned and delegates to the engine. The new operation and records follow that same boundary. -## Work needed from the baseline +## Implementation outcome -### Architecture and scope documentation +The accepted inspection boundary is recorded in +[ADR 0010](../adr/0010-effective-configuration-diff.md), and the deferred +inventory retains fingerprints while recognizing implemented diffs. The engine +owns canonical comparison and paired disclosure in +[internal/engine/diff.go](../../internal/engine/diff.go); the root facade exposes +detached records in [diff.go](../../diff.go). The configuration +contract, consumer guidance, component overview, architecture policy, and +development guide now link to their respective behavior and implementation +owners. -Record the inspection boundary extension in an ADR and reconcile the diff -exclusion in `deferred.md`, retaining fingerprints and unrelated exclusions. -Keep planned behavior here until implementation lands. The staged implementation -plan should reference the selected contract above rather than reopen it. - -### Private comparison and disclosure - -Add an engine operation over two successful frozen inspections, reusing the -existing comparator where its semantics match this contract. Sharing helpers -must preserve resolution and validation auditing behavior. - -Implement cross-configuration masking, structural-replacement fallback, and -`HiddenChanged`. Compare canonical values rather than serialized redacted -output, caller-mutated configuration values, or historical replay. Construct -the needed safe snapshots without replaying every path's full explanation -history. Keep comparison and disclosure distinct so masking does not erase -change detection. - -### Root API and consumer guidance - -Add root-owned records, the comparison entry point, and explicit conversion -across the engine boundary. Document direction, equality, failures, ownership, -secret behavior, and the credential limitation beside declarations. - -Add executable examples for loading both files and handling each load failure, -and for comparing a base result with a base-plus-overlay result. Include -resolution and sensitive nested values, demonstrating consumer-owned formatting -and changed-secret indicators with disposable data. - -### Behavioral validation and completion - -Use engine tests for comparison and combined sensitivity, root tests for the -facade and ownership, and consumer examples for the complete workflows. Cover -meaningful risks without duplicating all existing loading tests: - -- equality despite mapping order, provenance, or explicit-input differences; - canonical equality after defaults and resolution; -- addition/removal versus explicit null, empty containers, scalar and kind - replacements, atomic sequence changes, and escaped or numeric mapping keys; -- deterministic ordering and reported subtree granularity; -- changed and unchanged sensitive values, classification on only one side, - removed secrets, classified containers, and mixed public/sensitive changes; -- cross-kind replacement of a classified descendant, incompatible key/index - shapes, and complete-region masking without changing absence semantics; -- hidden changes with identical safe YAML, public-only changes alongside - unchanged masked values with `HiddenChanged=false`, and literal marker-like - input; -- changed secret reference names and unchanged names with credential rotation, - using controlled environment state; -- invalid left/right loads, unavailable results, caller mutation after loading, - and returned-record mutation isolation. - -Use disposable canaries to check non-disclosure, including failure paths. Follow -the focused, everyday, ownership-race, and documentation checks in the -[development guide](../development.md). New performance benchmarks are not -required for this feature. - -Update the configuration contract, consumer guidance, and component overview -when their behavior lands. Keep symbol details in Go declaration documentation. -Review compatibility and release notes under the release policy; publication is -separate from feature completion. Completion means the selected contract is -implemented, checks pass, and maintained examples and documentation agree. +Validation and coverage are recorded in the completed +[implementation plan](implementation.md). The implementation compares frozen +effective values, protects both sides with combined sensitivity, retains the +selected credential boundary, and provides executable file and overlay +workflows. Publication and release preparation remain separate. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 02e87c2..08210e2 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,8 +1,9 @@ # Implement effective configuration diffs -Status: planned. Audience: a Luna coding agent implementing one stage per prompt, -in numerical order. The [diff roadmap](diff.md) owns the agreed target contract -and scope. This document owns implementation steps and completion evidence. +Status: complete. The [diff roadmap](diff.md) owns the agreed target contract and +scope. This document preserves the implementation sequence and completion +evidence; completed planning records retire during separately authorized release +preparation. Planning baseline: `e82b1c90543a6ab92138ee8c0c2a4cd4daea8af0`. No diff API exists at this baseline. This plan adds comparison of successful @@ -385,7 +386,7 @@ redaction marker; no full-context rendering supplements the diff records. ## Stage 10 — Reconcile documentation and audit the complete feature -Status: planned. Scope: current-behavior documentation, integration audit, and +Status: complete. Scope: current-behavior documentation, integration audit, and completion evidence; no release publication. 1. Add an effective-diff section to `docs/config.md` for shared equality, @@ -417,6 +418,20 @@ completion evidence; no release publication. Done: the agreed feature is implemented, validated, and documented without stronger unrelated guarantees or changes to the publication contract. +Evidence: the planned feature baseline was `e82b1c90543a6ab92138ee8c0c2a4cd4daea8af0`; +the reviewed implementation through consumer examples is commit `a37e689`, +following the accepted decision at `3697a51` and public facade at `8ae542b`. +The complete baseline-to-implementation diff was reviewed against this plan and +the selected contract. `go test ./internal/engine -run 'Diff|Explain|History|Secret|Independent'`, +`go test . -run 'Diff|FacadeInspection|PublicFacadeBoundary|DependencyImportBoundary'`, +`go test ./examples/consumers`, `go test . -run '^ExampleDiff$'`, the local +documentation-link test, and `./scripts/check.sh` passed on +`go1.26.8 linux/amd64`. `GOTOOLCHAIN=local GOPROXY=off go test -race ./...` also passed. +The feature does not compare resolved credential bytes or supply paired full- +document rendering context; credential rotation behind the same reference is +not reported. Fingerprints remain deferred, no performance claim is made, and +release publication remains separate. + ## Open Questions None required for this scope. The roadmap's four choices are approved; the