439 lines
25 KiB
Markdown
439 lines
25 KiB
Markdown
# Adopt ConfigKit v0.7.1 and expose native configuration comparison
|
|
|
|
Status: proposed feature roadmap; dependency adoption and `config diff` are not
|
|
implemented. The targeted delivery includes both, as requested by the user.
|
|
This document defines the selected end state; a subsequent staged
|
|
implementation plan will define execution order.
|
|
|
|
## 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 current ADR alignment edits remain uncommitted for user
|
|
review; this roadmap follows their agreed 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.5.0. `Scope`, `Suite`, `FromConfigKit`, and `AppCommand` expose scopes/validate/show/sources/explain. No comparison operation or provider exists. |
|
|
| `integrations/ckprompt` | Directly requires ConfigKit v0.5.0 and PromptKit v0.12.0. Depends on ckconfig's public scope contracts and adapts engine configuration plus named definitions. |
|
|
| Deferred comparison | [DIFF-01](deferred.md#diff-01-semantic-configuration-comparison) records the target. Native semantics, selection syntax (OD-1), provider representation (OD-2), and the YAML-only report (OD-3) are accepted. |
|
|
|
|
The existing ConfigKit adapter acquires a typed `*configkit.Result[T]` and exposes
|
|
its effective, source, and explanation facets through `View`. That generic
|
|
adapter is a useful place to retain typed results for comparison. `View` alone
|
|
does not supply two results of the same `T` to `configkit.Diff`.
|
|
|
|
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. The review found no apparent
|
|
adapter migration requirement for existing operations; that is static review
|
|
evidence, not a completed upgrade validation. 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).
|
|
|
|
## Targeted end state
|
|
|
|
### Dependencies and module boundaries
|
|
|
|
Both integration modules 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.
|
|
|
|
### Ownership
|
|
|
|
| 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. |
|
|
|
|
### ConfigKit-backed comparison
|
|
|
|
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
|
|
are supported through ordinary application loading choices. Exact side selection
|
|
and inheritance rules are accepted in OD-1 below.
|
|
|
|
ConfigKit-backed adapters use the same supplied typed loader for existing
|
|
inspection and both comparison selections. They delegate equality and paired
|
|
disclosure to `configkit.Diff`; no rendered-YAML comparison or assertion on
|
|
mutable `Result.Config` is introduced. Convenience wiring remains simple:
|
|
`AppCommand` and `FromConfigKit` should provide native comparison alongside their
|
|
existing supported operations, with ordinary capability narrowing available.
|
|
The provider representation is accepted in OD-2.
|
|
|
|
Declaration capture, metadata discovery, and help leave suite factories and
|
|
acquisition/comparison callbacks uninvoked. Invocation preparation uses original
|
|
typed binding identities and converted values, preserving explicit empty, false,
|
|
and zero selections. Supplied values are converted through core once, including
|
|
help paths; applying a value to both comparison inputs does not parse it twice.
|
|
Input/state timing for the pair is part of OD-2, rather than an implicit extension
|
|
of the existing one-selection lifecycle.
|
|
|
|
### 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 default report deliberately presents a reduced view under
|
|
OD-3 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.
|
|
|
|
### Reports, diagnostics, and outcomes
|
|
|
|
Default `config diff` to one YAML document with `additions`, `changes`, and
|
|
`deletions`, as selected in OD-3. Use existing output streams. YAML is the only
|
|
supported report format, with explicit `--format yaml` accepted. The
|
|
view distinguishes deletion from null but omits separate redaction and concealed-
|
|
change metadata. Move the implemented report contract to the CLI reference.
|
|
ADR 0008 records this reduced view while retaining native metadata at the
|
|
provider boundary. Other commands retain their existing format defaults.
|
|
|
|
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.
|
|
|
|
## Work needed from the baseline
|
|
|
|
These are work areas, not numbered implementation stages. Use the accepted
|
|
choices below to write the staged implementation plan.
|
|
|
|
- **Dependency adoption:** upgrade both integration requirements, reconcile sums
|
|
and indirect/tool dependencies, and assess PromptKit v0.12.0 against the newly
|
|
selected ConfigKit version. Preserve distributed manifests without sibling
|
|
replacements or committed workspace files.
|
|
- **Capability and provider API:** extend ckconfig's operation/capture/validation
|
|
model for the selected comparison provider. Preserve retained adapter errors,
|
|
capability narrowing, metadata-only scopes, and validation only of bindings
|
|
used by generated commands. Do not require an ordinary acquisition provider
|
|
for a comparison-only scope if its selected pair provider is sufficient.
|
|
- **Typed preparation:** capture comparison selection metadata and applicable
|
|
selector lists once. Prepare/check the selected pair through core bindings,
|
|
with state and callback timing from the chosen lifecycle. Keep caller-owned
|
|
objects and aliases under existing ownership rules.
|
|
- **Native adaptation:** extend the typed ConfigKit adapter/convenience factory
|
|
to load the selected pair and call `configkit.Diff`. Reuse diagnostic adaptation
|
|
and ordinary error handling without introducing a generic provider framework,
|
|
global cache, or untyped result transport.
|
|
- **Command and reports:** generate the conditional `config diff` declaration,
|
|
integrate it with discovery/help, and render the chosen report contract using
|
|
native safe values. Preserve existing operation/report behavior.
|
|
- **Documentation and distribution:** update GoDoc, CLI/consumer guidance,
|
|
maintained examples, affected module notices and pinned ConfigKit links, and
|
|
relevant release notes as implementation lands. Include a maintained comparison
|
|
example that loads genuinely different selections through shared state, with
|
|
cache keys accounting for the selection and typed-input values used by its
|
|
loader. Review transitive tool notices
|
|
for the actual graph. Move DIFF-01's implemented portion out of deferred work;
|
|
retain unresolved future PromptKit comparison and unrelated proposals.
|
|
- **Validation evidence:** run each affected module's deterministic offline suite
|
|
and representative consumers. Check module/package boundaries independently
|
|
with workspace mode off. If CommandKit prerequisite versions remain unpublished,
|
|
use disposable alternate module files for local-source wiring evidence, as
|
|
described in the [development guide](../development.md#documentation-validation).
|
|
Do not fabricate future checksums or turn migration checks into permanent gates.
|
|
|
|
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 open choices may be silently converted into
|
|
contracts by implementation. Publication follows the existing release procedure
|
|
and is not performed by this planning pass.
|
|
|
|
## Selected report design (OD-3)
|
|
|
|
**Selected:** the default operator-facing output of `config diff` is a single YAML
|
|
document with three primary keys: `additions`, `changes`, and `deletions`.
|
|
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.
|
|
|
|
**Accepted edge-case treatment:** 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. Ambiguity
|
|
in that case is acceptable to the user and is not a planning blocker. Native
|
|
comparison records retain their distinctions; ordinary field-level null values
|
|
and deletions retain the selected reporting rules.
|
|
|
|
**Accepted format and empty-category treatment:** YAML is the only report format
|
|
for this delivery; a separate plain text report adds no needed operator value.
|
|
Always include all three keys in the order `additions`, `changes`, `deletions`.
|
|
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.
|
|
|
|
ADR 0008 records this selected presentation. Selection syntax and provider shape
|
|
are accepted below. No significant open decisions remain for this roadmap.
|
|
|
|
**Alternatives considered:** a full difference envelope with left/right snapshots
|
|
and explicit presence, redaction, and hidden-change fields preserves all native
|
|
metadata, but is more verbose than the chosen operator view. Embedding parsed
|
|
values in that envelope avoids YAML strings but retains the record-oriented
|
|
layout. A right-side-only view without deletion paths cannot represent removals
|
|
unambiguously. The selected three-category document balances configuration-shaped
|
|
output with explicit removal reporting.
|
|
|
|
Omitting empty categories would shorten the report but require consumers to
|
|
handle missing keys. A separate text report could label changes by path or group
|
|
them into sections, but YAML already provides readable operator output; another
|
|
renderer and presentation contract are unnecessary for this delivery.
|
|
|
|
## Accepted Decisions
|
|
|
|
### OD-1: How operators select the two sides
|
|
|
|
**Accepted:** 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 show the accepted target syntax, not current commands:
|
|
|
|
```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.
|
|
Shared domain selector values are the initial scope;
|
|
independent per-side domain selectors can be considered with a concrete consumer
|
|
need, without precluding them as future API work.
|
|
|
|
**Alternative considered:** expose symmetric `--left-config`/`--right-config` and
|
|
`--left-overlay`/`--right-overlay` selections, resolving each side independently.
|
|
This makes two independent selections explicit and avoids inheritance rules. It
|
|
adds vocabulary beside existing configuration selectors and repeats flags for
|
|
the common single-base overlay workflow. It remains viable if symmetry is valued
|
|
more than reuse of familiar inspection syntax.
|
|
|
|
### OD-2: Comparison provider and input/state lifetime
|
|
|
|
**Accepted:** add a narrow optional scope comparison callback receiving
|
|
the pair's selections, separately prepared left/right typed inputs, and one
|
|
shared state value. Group each side's selection and input in a request value to
|
|
avoid several positional left/right arguments. Return
|
|
`commandkit.Acquired[[]configkit.Difference]` plus an error, following the existing
|
|
acquisition convention. Reuse upstream public record types rather than duplicate
|
|
them solely to rename fields. The selected target shape in ckconfig is:
|
|
|
|
```go
|
|
type ComparisonSide[I any] struct {
|
|
Selection Selection
|
|
Input I
|
|
}
|
|
|
|
type ComparisonRequest[I any] struct {
|
|
Left ComparisonSide[I]
|
|
Right ComparisonSide[I]
|
|
}
|
|
|
|
// Accepted target field on Scope[I, S]:
|
|
// Compare func(context.Context, ComparisonRequest[I], S) (commandkit.Acquired[[]configkit.Difference], error)
|
|
```
|
|
|
|
These names and the callback shape are selected for implementation; they are not
|
|
implemented exports. ADR 0012 records paired provider ownership and input/state
|
|
lifetimes; this roadmap owns the target declarations until the Go API is implemented.
|
|
|
|
The typed `FromConfigKit` adapter can retain its `T`, invoke the supplied loader
|
|
for each reached side, and call `configkit.Diff`. 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.
|
|
|
|
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. Help/discovery remain free of these calls.
|
|
ADR 0012 records this two-selection specialization of ADR 0011's suite lifecycle,
|
|
and the architecture policy reflects it. Document the exact implemented API
|
|
contract in Go declarations and GoDoc when it lands.
|
|
|
|
This keeps generic result handling inside the owning adapter and preserves
|
|
typed binding composition 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. Demonstrate correct keys in the maintained example;
|
|
CommandKit neither supplies nor repairs application caches. Cache policy and
|
|
factory objects remain caller-owned.
|
|
|
|
**Alternative considered:** acquire a comparison-capable facet with each ordinary `View`,
|
|
then compare the two acquired facets. This shares single-side acquisition between
|
|
all operations and may suit providers already retaining typed results behind a
|
|
stable comparison handle. It requires a public way to establish compatibility
|
|
between two heterogeneous facets, and can lead to type assertions or extra
|
|
opaque-handle machinery. Consider it if representative non-ConfigKit consumers
|
|
demonstrate enough benefit to justify that additional contract.
|