Files
ConfigKit/docs/roadmap/diff.md

200 lines
10 KiB
Markdown

# Effective Configuration Diff Roadmap
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
ConfigKit compares the canonical effective inspection values retained by two
successful loads and returns structured differences. Consumers decide how to
render those differences: a changes table, a tree, or a changed-region diff are
possible presentations. ConfigKit owns comparison and safe value disclosure,
while applications own presentation and labels.
Both configurations have completed the normal loading lifecycle, including
typed decoding, resolution, secret lookup, and validation. Invalid input on
either side produces the existing load failure and diagnostics, with no usable
diff. Comparing successful results does not rerun callbacks, reopen sources, or
resolve credentials again. Later mutation of caller-owned `Result.Config` does
not change the frozen values being compared.
The comparison is over retained inspection values, not every value accessible
through the returned Go configuration. In particular, typed secret references
contribute their configured environment names, not resolved credential bytes.
The motivating workflows are:
- Load `app.conf.bak` and `app.conf`, then inspect effective differences.
- Load a base configuration and separately load it with a selected overlay,
then inspect the overlay's effective impact, including subsequent resolution.
An overlay alone is not the right side of the second workflow: omitted overlay
keys retain base values. Both workflows use successful, independent loads;
applications control their definitions, options, and external inputs. Using the
same definition and controlled inputs makes comparisons easier to interpret.
Sharing a Go type does not prove equal defaults or callbacks, and comparison
does not require callback-function identity checks.
## Selected public contract
### Entry point and records
The target entry point is:
```go
Diff[T any](left, right *Result[T]) ([]Difference, error)
```
It compares results of the same configuration type. Nil or uninitialized results
produce an error rather than an empty successful diff. Valid results with equal
effective values produce no difference records. Loading remains a separate
operation; this API does not aggregate load failures or orchestrate callbacks.
The root-owned `Difference` record contains:
- `Path Path`: the semantic path of the changed region;
- `Left ValueSnapshot`: the safe value before the change;
- `Right ValueSnapshot`: the safe value after the change; and
- `HiddenChanged bool`: whether at least one actual change in the region is
withheld by diff redaction, as defined below.
Both sides reuse the existing `ValueSnapshot` representation: `Present`,
`Redacted`, and deterministic `YAML`. Absence is `Present=false` with empty
YAML; explicit null is present and renders as `null`. Presence identifies
additions and removals. A record with both sides present describes a replacement
at its reported path. Record membership establishes a change even when both
safe snapshots render identically.
Paths and records are detached. Consumer mutation does not affect subsequent
comparisons or either result's existing inspection state. The public facade
exposes no internal document or parser types.
### Equality, granularity, and ordering
Compare the underlying canonical effective inspection values before redaction.
Equal values produce no differences even if source locations, explicit-input
status, or loading histories differ. An explicit zero and an omitted field
defaulting to zero therefore compare equally. Presence and provenance remain
separately inspectable through each result's existing APIs. Original comments,
formatting, and mapping declaration order do not participate in equality.
Retain the existing comparator's granularity:
- Mappings compare recursively. Additions and removals report the affected key;
changes beneath mappings report the changed descendant region.
- Sequences compare atomically: any change reports the sequence path.
- Scalar, cross-kind, and null/container replacements report the affected path.
Sequence comparison introduces no element alignment or public correspondence
between old and new indices. Records are ordered by canonical pointer spelling,
matching the existing comparator. Paths retain semantic key/index intent and
proper escaping, including numeric mapping keys.
### Cross-configuration redaction
Apply the applicable sensitivity from both loads before returning either side's
value. A classification on either side protects both snapshots. Include
historical and dependency-derived classification, classified ancestors and
descendants, and paths absent from one effective tree.
Where the two shapes permit corresponding sensitive regions to be identified,
mask those regions while retaining public sibling values. Do not treat matching
pointer text alone as proof of matching mapping-key versus sequence-index shape.
When a structural replacement prevents safe correspondence for a classified
region, mask the complete reported replacement region on both present sides.
For example, replacing a mapping containing a classified descendant with a
scalar masks the whole mapping/scalar replacement, rather than trying to apply
the old descendant path to the scalar. This fallback also covers incompatible
mapping/sequence shapes within an atomic replacement. An absent side remains
absent; redaction does not fabricate a value for it.
`ValueSnapshot.Redacted` describes masking, not change. `HiddenChanged` is true
when underlying value or presence differences occur in a region withheld by the
combined redaction policy. It is false when only public content changes and all
masked regions retain equal values and presence. This includes atomic sequence
records: an unchanged masked member does not alone set the flag. The complete
replacement fallback sets the flag because the changed region itself is
withheld. The flag does not identify individual changed children, counts, or
sequence correspondence, and does not assert that every redacted child changed.
For a wholly masked changed value, record membership and `HiddenChanged` let a
consumer show `[REDACTED]` on the left and `[CHANGED]` on the right. These
strings are presentation choices, not configuration values or the encoding of
change state. Literal marker-looking input remains ordinary input; applications
use record metadata rather than interpreting YAML marker text.
Errors and returned records disclose neither classified values nor credential
bytes.
### Credential boundary and renderer context
Compare classified values and configured secret reference names retained in the
effective inspection snapshots. Rotation of a resolved credential behind an
unchanged reference is outside this comparison. Diff does not read mutable
`Result.Config`, reread the environment, or introduce credential retention or
credential-derived comparison tokens.
Return changed regions only. The records do not include paired full-document
context or a structured dynamic value tree, and do not alone supply unchanged
context for unified hunks. Calling `WriteEffective` independently on each side
is not a substitute for cross-protected context because each call applies only
that result's sensitivity. Consumers can render the returned safe regions
without reconstructing unredacted context.
## Scope and architectural fit
This extends ConfigKit's existing snapshot-based inspection responsibilities.
Applications can compare completed configurations without duplicating structural
comparison, presence handling, or safe value disclosure. Loading and composition
rules remain unchanged.
The feature does not include input-text comparison, invalid-document inspection,
patch application, generating a reconstructing overlay, fingerprints, automatic
reload, resolved-credential comparison, cross-type comparison, or a built-in
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.
## Implementation references
- [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
mapping additions, removals, and replacements, ignores mapping order, treats
sequences atomically, and sorts changes by pointer spelling. It ignores
provenance and sensitivity metadata.
- Results retain private frozen inspection state. Existing
[inspection types](../../inspection.go) include `ValueSnapshot`, with
presence, redaction, and deterministic YAML. Explanation records describe
one load's history rather than a comparison between loads.
- [Secret classification](../config.md#secret-classification-and-effective-rendering)
includes historical classification and dependency propagation. Existing
snapshots apply one load's sensitivity; diff requires protection from both.
- [Typed secret references](../config.md#typed-secret-references) retain reference
names in inspection, not resolved credential bytes.
- The [public facade](../internal/overview.md) is root-owned and delegates to
the engine. The new operation and records follow that same boundary.
## Implementation outcome
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.
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.