# Adopt ConfigKit v0.7.1 and expose native configuration comparison Status: complete. ConfigKit v0.7.1 adoption and ConfigKit-backed `config diff`, including distribution reconciliation and final validation, are implemented. The [implementation record](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/20e19b95694f7fdfd5c342ec4d543152986623dc/docs/roadmap/implementation.md) records completion evidence; release publication remains a separate process. ## Purpose and authority Adopt ConfigKit's current loading and inspection improvements and expose its native effective-configuration comparison through CommandKit. Operators should be able to compare two selections of one configuration scope, including the effect of a selected overlay, without applications writing duplicate CLI or comparison infrastructure. The revised [ADR 0008](../adr/0008-semantic-configuration-diff.md) owns native comparison and paired safe disclosure. [ADR 0009](../adr/0009-configuration-scopes.md) owns scope selection and independent capabilities; [ADR 0011](../adr/0011-captured-declarations-and-installation.md) owns captured declarations, typed input, and shared state, refined for comparison pairs by [ADR 0012](../adr/0012-typed-configuration-comparison-pairs.md). The [architecture policy](../policy/architecture.md) controls ownership and dependency direction. The accepted ADRs govern this roadmap's comparison direction. The repository is being developed as a pre-release alpha. Choose the simplest coherent final API without preserving prototype shapes through compatibility wrappers. Existing deliberately selected contracts still control; changes to published releases follow the [release policy](../policy/release.md). This work consumes ConfigKit's supported public API. No upstream extension is required for the selected comparison goal. ConfigKit owns comparison and safe disclosure; CommandKit owns selection, coordination, adaptation, and presentation. ## Current baseline | Owner | Current state | | --- | --- | | Core module | Go 1.26.0; captured commands, typed bindings, dispatch, outcomes, help, and version. No ConfigKit dependency. ReleaseKit v0.4.0 is the declared maintainer tool. | | `integrations/ckconfig` | Requires ConfigKit v0.7.1. `Scope`, `Suite`, `FromConfigKit`, and `AppCommand` expose independently declared validate/show/sources/explain/diff capabilities. | | `integrations/ckprompt` | The later PromptKit adoption selects PromptKit v0.14.0 and ConfigKit v0.7.1. It adapts engine configuration and exposes native comparison, prompt/profile catalogs, and named definitions through ckconfig and core contracts. | | Comparison status | ConfigKit-backed `config diff` and PromptKit engine-scope comparison are implemented. The [CLI reference](../cli.md#compare-configuration-selections) and [PromptKit adoption record](promptkit.md) describe current behavior; the [implementation record](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/20e19b95694f7fdfd5c342ec4d543152986623dc/docs/roadmap/implementation.md) records ConfigKit completion. | The ConfigKit adapter acquires typed `*configkit.Result[T]` values and exposes effective, source, and explanation facets through `View` for ordinary operations. Its separate comparison callback retains `T`, loads both prepared selections, and returns the native records from `configkit.Diff` without routing through `View`. ConfigKit v0.7.1 identifies source commit `6fffde4af8f31f99d3ee295c426eb3234cb0fb77`. Relative to the selected v0.5.0: - v0.6.0 reduces repeated loading, resolution, metadata, and inspection work. Its Go 1.26.0 minimum is already met by CommandKit. - v0.7.0 adds native frozen effective-value comparison. - v0.7.1 changes the upstream maintainer tool to ReleaseKit v0.4.1, with no runtime/API change from v0.7.0. The YAML dependency remains `go.yaml.in/yaml/v4 v4.0.0-rc.6`. Existing public loading and inspection signatures are unchanged, and the integration suites pass with the adopted dependency. See the pinned [release notes](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/6fffde4af8f31f99d3ee295c426eb3234cb0fb77/docs/releases/v0.7.1.md) and [comparison contract](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/6fffde4af8f31f99d3ee295c426eb3234cb0fb77/docs/config.md#effective-configuration-diffs). ## Dependency adoption and ownership Both integration modules select ConfigKit v0.7.1 and carry normal dependency metadata for their actual graphs. Consumer Go minimums remain 1.26.0, and the selected release toolchain remains 1.26.8. At completion of this ConfigKit work, ckprompt still selected PromptKit v0.12.0; the later PromptKit adoption is tracked in [promptkit.md](promptkit.md). Core continues to have no ConfigKit or optional-integration dependency. ConfigKit's declared tool requirement brings ReleaseKit v0.4.1 into the integration module graphs. Reconcile that selection through normal Go module resolution and accurate attribution. A combined development workspace can also select that version. This does not by itself change the core module's separately declared release tool or authorize release-launcher changes; assess core through its own workspace-off graph. Independent module versions remain independent of upstream ConfigKit's version. Release preparation uses appropriate unused versions under the existing policy, without moving tags or claiming unpublished dependencies are available. ### Responsibility boundaries | Owner | Target responsibility | | --- | --- | | ConfigKit | Frozen effective-value equality, changed-region selection/order, sensitivity union, paired safe snapshots, hidden-change detection, and load semantics. | | ckconfig | Comparison capability and provider contracts; typed ConfigKit adapter; `config diff` input preparation, acquisition coordination, reports, and upstream diagnostic adaptation. | | Core | Existing declaration installation, flag conversion/presence, help, dispatch, cancellation context, and outcome/delivery mechanics. No comparison model or engine. | | ckprompt | Continue existing integration behavior with the upgraded shared dependency and ckconfig API. Do not claim native PromptKit comparison support through this upgrade. | | Consuming application | Configuration definitions, acquisition callback internals, loader/cache choices, domain interpretation, startup, and process exit. | ## Operator behavior ### Availability and scope selection The `config` family exposes `diff` when at least one scope declares the capability. Help and `config scopes` report actual availability. A scope can support comparison independently of other operations; enabling comparison elsewhere does not require every scope to support it. Unsupported selections fail as usage errors before acquisition. `config diff` selects one scope, defaults to `app`, and does not accept `--scope all`, as selected by ADR 0009. It compares earlier/left and later/right effective configurations. Both file comparison and base-versus-overlay comparison use the providers' ordinary application-loading contracts. ### Left and right selections Use existing `--config` and `--overlay` for the left side and add `--against-config` and `--against-overlay` overrides for the right side. Omitted right fields inherit the left field, including its supplied state; explicit right values, including an empty string, replace it. Use the scope's applicable typed domain selectors on both sides. This supports two files and base-versus-overlay comparison while reusing familiar syntax and preserving provider-owned defaults. Allow equal selections; they can produce a legitimate empty diff without an invented requirement that an override be supplied. Do not short-circuit acquisition merely because the selections are equal: caller-owned loading may produce different effective results. For example, these are current invocations: ```text app config diff --config before.yml --against-config after.yml app config diff --config base.yml --against-overlay candidate app config diff --config base.yml --overlay current --against-overlay "" ``` Right-side inheritance is field-by-field. Overriding only the configuration still inherits the left overlay. For either selection field: | Left field | Right override | Effective right field | | --- | --- | --- | | Omitted | Omitted | Omitted, preserving provider defaults | | Supplied value | Omitted | Same value and supplied state as left | | Explicit empty string | Omitted | Explicit empty string, still supplied | | Any value/state | Supplied value | Right value, supplied | | Any value/state | Explicit empty string | Empty string, supplied | The provider continues to interpret an explicitly empty overlay through its ordinary loading contract; CommandKit does not define empty as disabling overlays. Domain selector values are shared between sides in this delivery; independent per-side domain selectors can be considered for a concrete consumer need. ### Comparison meaning and disclosure Retain ConfigKit's native changed-region records at the provider boundary, including semantic paths, left/right `Present` and `Redacted` states, safe YAML values, and `HiddenChanged`. The YAML report deliberately presents a reduced view rather than exposing all native metadata. The native contract owns detailed comparison semantics; the important operator consequences are: - Changes reflect frozen effective load values, including defaults and recorded domain resolution. Provenance or source-layout differences alone are not effective changes. - Compare final values, not source presence alone. Omitted and explicit-null inputs may resolve equally for a typed field; an absent map key and a present null-valued key can remain distinct in the final configuration. Nil and empty containers also remain distinct. Mappings compare recursively; sequences report atomic changed regions. - Both sides receive the union of sensitivity from both loads. Structural replacements can mask an entire region. - A difference can exist even when its safe left/right YAML is identical. `HiddenChanged` reports an actual concealed change; redaction alone does not. - An empty successful diff supports equality within ConfigKit's inspected value model. Typed secret references compare by configured name; credential rotation behind an unchanged reference is outside that comparison. Reports use safe changed regions without inventing a coverage-gap inventory, decoding redaction-marker text as metadata, identifying concealed members beyond upstream disclosure, or adding separately rendered full configurations as if they had received paired protection. Full paired document context and a sensitivity audit are separate potential capabilities, not prerequisites here. ### YAML report `config diff` emits one YAML document through the existing output streams. YAML is the only report format for this delivery; explicit `--format yaml` is accepted. Always include the three keys `additions`, `changes`, and `deletions` in that order. Additions and changes preserve configuration hierarchy and contain only safe right-side values; deletions are canonical JSON Pointer paths. For example: ```yaml additions: server: health_check: true changes: server: port: 9090 logging: destination: null deletions: - /server/legacy_timeout ``` Classify each native difference by its presence fields: absent-left/present-right is an addition, present-left/absent-right is a deletion, and present on both sides is a change. Explicit null remains a value; do not encode deletion as null. Changed sequences remain whole sequences, and native structural replacements remain whole reported regions. Do not derive a second set of changes by comparing rendered values or expanding atomic regions. Assemble additions and changes from ConfigKit's paired-safe right snapshots, preserving YAML value types and semantic path components. Parsing and assembly serve presentation only. Do not read unredacted configuration values or append independently rendered full configurations. The result is a partial inspection view, not a complete configuration or an automatically applicable patch. Omit separate `Redacted` and `HiddenChanged` metadata from this default view. Every reported region changed, but a masked value within it need not have changed: a public field in an atomic sequence may change while a secret remains unchanged. A concealed-only change still appears in its native reported region even if the safe snapshots are identical. Preserve native masks without interpreting marker text; a literal public string resembling `[REDACTED]` remains an ordinary value. The default report intentionally does not expose every distinction available in the native records. A detailed metadata format can be considered separately if needed; it is not required for this delivery. When a nonempty root mapping becomes an empty mapping, report the native removed-key paths under `deletions`. The reduced report need not add special metadata or a separate envelope solely to distinguish exceptional whole-root empty/null transitions from an empty comparison. That ambiguity is an accepted limit of this reduced report. Native comparison records retain their distinctions; ordinary field-level null values and deletions retain the selected reporting rules. Empty additions and changes render as `{}`; empty deletions render as `[]`. An empty comparison therefore produces: ```yaml additions: {} changes: {} deletions: [] ``` This stable shape supports automated consumption without missing-key handling and retains the accepted tolerance for exceptional whole-root ambiguity. Other commands keep their existing formats. ### Diagnostics and outcomes Successful comparisons return success whether they find differences, concealed changes, or none. No assertion status or separate exit code is added. Load and comparison failures retain operational classification and supported error identity; selection/input errors retain usage classification. Output failures use existing delivery and incomplete-report semantics. The ConfigKit adapter retains diagnostics from the acquired results, including warnings and any load failure encountered. Present them through existing core diagnostic delivery; preserve meaningful left/right context when needed to identify the failing selection without losing wrapped error identity. Retain left-side warnings if the right load fails, and diagnostics from both successful loads if native comparison fails. Finish acquisition and native comparison before writing a successful diff report. Cancellation is checked at ordinary operation boundaries; do not promise interruption inside a synchronous upstream call. ## Consumer API and execution lifecycle ### Comparison provider and ConfigKit adapters `ckconfig.Scope[I, S]` exposes a narrow optional comparison callback. Its request groups each side's selection with its separately prepared typed input and supplies one shared state value separately. Return `commandkit.Acquired[[]configkit.Difference]` plus an error, following the existing acquisition convention. It reuses upstream public record types rather than duplicating them solely to rename fields. The current API shape is: ```go type ComparisonSide[I any] struct { Selection Selection Input I } type ComparisonRequest[I any] struct { Left ComparisonSide[I] Right ComparisonSide[I] } // Current field on Scope[I, S]: // Compare func(context.Context, ComparisonRequest[I], S) (commandkit.Acquired[[]configkit.Difference], error) ``` These are the implemented exports in `integrations/ckconfig/contracts.go`. ADR 0012 records paired provider ownership and input/state lifetimes; GoDoc owns the exact public declarations. `AppCommand` and `FromConfigKit` provide native comparison alongside their existing supported operations, with ordinary capability narrowing available. They use the same supplied typed loader for ordinary inspection and both comparison sides, retaining `T` inside the adapter and passing successful results to `configkit.Diff`. Do not compare rendered YAML or mutable `Result.Config`. Existing single-view acquisition remains responsible for ordinary operations. A comparison-only scope can supply the pair callback without an unrelated `Acquire` callback. Declared capabilities determine which callbacks must be wired. ### Input preparation and acquisition lifecycle Use original typed binding identities and converted values, preserving explicit empty, false, and zero selections. Convert supplied values through core once, including help paths; applying values to both inputs does not parse them twice. For a reached comparison, initialize each selection's `I` separately, apply the same applicable captured selector values, and check it with that side's `Selection`. Finish both input checks before initializing `S` once and invoking the pair provider. Omitted factories still supply zero values, including nil pointers; separate calls do not guarantee distinct caller objects. Later factories or loads need not run after failure. Declaration construction, installation, help, version, and scope discovery leave input/state factories and acquisition/comparison callbacks uninvoked. ADR 0012 records this two-selection specialization of ADR 0011's suite lifecycle, and the architecture policy reflects it. The exact API contract is in the Go declarations and GoDoc. ### Application-owned state and caches Generic result handling stays inside the owning adapter, without reflection, private-core access, or result type assertions. Shared state supports consumer caches whose keys distinguish the effective selections and any typed-input values that affect loading. A cache that stores only one configuration can incorrectly reuse the left result for the right and report equality. The composed example demonstrates selection- and input-aware keys; CommandKit neither supplies nor repairs application caches. Cache policy and factory objects remain caller-owned. ## Design tradeoffs The chosen selection flags reuse existing configuration vocabulary and reduce repetition for base-versus-overlay comparison. Symmetric `--left-config` / `--right-config` and `--left-overlay` / `--right-overlay` flags would make each side independent but add vocabulary and repetition. Independent per-side domain selectors remain possible future work for a concrete consumer need. A comparison-capable facet on each ordinary `View` could share acquisition across operations, but would require a compatibility contract between heterogeneous facets and additional handle or type-assertion machinery. The typed pair provider keeps both results inside their owning adapter and avoids that extra framework. A full difference envelope could expose both snapshots and all native metadata, using YAML strings or parsed values. It would be more verbose than the selected operator view. The three-category report retains configuration-shaped right-side values while representing removals explicitly. Omitting empty categories would shorten output but require missing-key handling; a separate text renderer adds no needed operator value for this delivery. ## Implementation record Dependency adoption, capability and typed-pair preparation, native adaptation, YAML reporting, end-to-end execution, and both maintained comparison examples are implemented. Current API and operator contracts belong to GoDoc, the [CLI reference](../cli.md#compare-configuration-selections), the [consumer guide](../consumers/guide.md#configkit-scopes), and the example READMEs. The [implementation record](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/20e19b95694f7fdfd5c342ec4d543152986623dc/docs/roadmap/implementation.md) records stage-by-stage changes and completed validation, including [distribution reconciliation](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/20e19b95694f7fdfd5c342ec4d543152986623dc/docs/roadmap/implementation.md#stage-12-reconcile-manifests-notices-and-pending-integration-release-notes) and [final sprint validation](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/20e19b95694f7fdfd5c342ec4d543152986623dc/docs/roadmap/implementation.md#stage-13-validate-the-complete-sprint-and-finish). The isolated integration checks used local replacements for unpublished CommandKit dependencies. They establish compatibility with local CommandKit sources and released external dependencies, not availability of the intended CommandKit module tags. No release was published as part of the sprint; publication follows the [release procedure](../release.md). ### Focused behavioral evidence Focused comparison coverage should protect the CommandKit boundary: capability availability before callbacks, side selection/presence, chosen input/state lifetime, native public and concealed changes, absent versus null values, and empty successful comparisons. Include representative load/comparison errors, diagnostics, cancellation, and output failure. Specifically cover left-side warnings followed by a right-side load failure, and diagnostics from both loads followed by a comparison failure. Verify that selection-aware caching does not return the left result for the right selection. Include the three report categories, explicit null versus deletion, concealed-only changes, and an unchanged masked value inside a changed sequence. Existing inspection and composed PromptKit behavior must continue to work. Reuse native collaborators and test CommandKit's adaptation/reporting; do not duplicate ConfigKit's entire comparator suite or impose performance thresholds. ## Scope limits and completion This work delivers ConfigKit adoption and ConfigKit-backed `config diff`. PromptKit upgrade/comparison/catalogs, JSON reports, per-element sequence matching, resolved credential comparison, full paired document context, assertion mode, provider caching, and core backend changes are outside this target. Completion means upgraded integration dependencies, usable native comparison through declared scopes and convenience wiring, selected public API/CLI/report contracts in their current owners, relevant maintained examples, accurate notices, and sufficient boundary validation. No significant open decisions remain. Implementation must not silently add contracts beyond this selected scope. Publication follows the existing release procedure and is not performed by this planning pass.