Files
commandkit/docs/roadmap/configkit.md

390 lines
22 KiB
Markdown

# 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` | Requires ConfigKit v0.7.1 and PromptKit v0.12.0. It depends on ckconfig's public scope contracts and adapts engine configuration plus named definitions; its ConfigKit scope does not declare comparison. |
| Comparison status | ConfigKit-backed `config diff` is implemented. The [CLI reference](../cli.md#compare-configuration-selections), integration GoDoc, and [implementation record](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/20e19b95694f7fdfd5c342ec4d543152986623dc/docs/roadmap/implementation.md) own current contracts and evidence. PromptKit comparison remains deferred. |
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 now select ConfigKit v0.7.1 and carry normal dependency
metadata for their actual graphs. Consumer Go minimums remain 1.26.0, the selected
release toolchain remains 1.26.8, and PromptKit remains v0.12.0 for this work.
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.