Finalize native comparison ADRs and clarify declaration contracts

This commit is contained in:
2026-10-11 18:59:57 +00:00
parent fbee871731
commit 21c2fde8d5
6 changed files with 280 additions and 180 deletions

View File

@@ -71,8 +71,8 @@ schemas for implemented operations.
CommandKit supplies shared handlers with predictable meanings and discovers their
availability from registration. Each enabled operation needs its own data
contract: comparison needs safe snapshots, catalogs need enumeration, and profile
display needs safe execution-target inspection.
contract: comparison needs supported native comparison results, catalogs need
enumeration, and profile display needs safe execution-target inspection.
The [scope model](0009-configuration-scopes.md) and
[comparison policy](0008-semantic-configuration-diff.md) describe related

View File

@@ -55,7 +55,7 @@ A writer failure can leave a partial report and returns operational failure. The
outcome identifies incomplete delivery. Custom handler output remains
application-owned. [ADR 0007](0007-native-yaml-reports.md) covers `config show` report
preparation; [ADR 0008](0008-semantic-configuration-diff.md) defines successful
comparison, including differences and coverage gaps.
comparison outcomes.
## Alternatives considered

View File

@@ -68,6 +68,6 @@ allows a failure in one scope to prevent combined display. Explicit single-scope
display remains available for a narrower view.
Additional structured schemas are compatibility contracts owned by CommandKit.
Native concatenation can use existing writers; semantic comparison separately
requires the safe structured data described in
Native concatenation can use existing writers; semantic comparison uses the owning
library's supported comparison API under
[ADR 0008](0008-semantic-configuration-diff.md).

View File

@@ -1,4 +1,4 @@
# ADR 0008: Compare semantic snapshots with explicit sensitivity gaps
# ADR 0008: Delegate effective configuration comparison to owning libraries
## Status
@@ -6,72 +6,79 @@ Accepted.
## Date
2026-10-06
2026-10-06; revised 2026-10-11.
## Context
Configuration comparison helps operators understand changes in final effective
values across roots and overlays. Formatting, authored file layout, and provenance
identifiers are different properties from effective-value equality. Sensitivity
classification also limits the values available for inspection.
Meaningful comparison requires structured effective snapshots with per-path
sensitivity data from the owning libraries. The comparison policy must preserve
value semantics while making limited coverage visible to operators.
ConfigKit provides a native configuration comparison capability. Exposing it
through CommandKit gives applications a consistent operator interface and reduces
repeated command, selection, and reporting infrastructure. ConfigKit remains the
authority for the meaning and disclosure of its comparison results.
## Decision
For a scope declaring safe comparison support, `config diff` compares two
independently resolved effective configurations. CommandKit checks that capability
before acquisition. Providers supply captured results through the owning
libraries' supported public inspection APIs.
### Comparison ownership
Compare values using the owning library's safe structured inspection semantics,
including defaults and recorded domain resolution. Preserve the distinctions
among absent values, null, empty mappings, and empty sequences. Mappings are
compared recursively; ordered sequences are compared as whole values.
For ConfigKit-backed scopes declaring comparison support, `config diff` exposes
ConfigKit's native comparison API. CommandKit checks capability availability before
acquisition and coordinates selection, acquisition, adaptation, presentation,
and invocation outcomes.
For each path, use the union of the two sides' sensitivity classifications to
protect both before and after presentation. A value classified on either side
receives the corresponding safe treatment in both snapshots. Hidden values
produce explicit comparison gaps with their paths and supported reasons.
Integrations delegate comparison to their owning libraries' supported public
APIs. ConfigKit owns its comparison semantics and disclosure rules. CommandKit
follows the public contract of the ConfigKit version it adopts. When an adopted
upstream revision changes those semantics, the changes flow through to
CommandKit's integration. Upstream remains authoritative as the capability evolves.
Equality conclusions cover the comparable data. A comparison with gaps reports
its limited coverage alongside observed differences. A complete comparison with
an empty difference set supports an equality conclusion for the inspected
selection. Redaction masks are presentation data; classification metadata
determines comparison coverage.
Application-supplied providers retain ownership of their implementation and
loading effects. [ADR 0012](0012-typed-configuration-comparison-pairs.md) governs
paired selections, provider contracts, and typed input/shared-state lifetimes.
Text and YAML reports include differences and coverage gaps. The exact report
schema belongs in the [CLI reference](../cli.md) when implemented. Selection
syntax, inheritance rules, and detailed change records are outside the scope
of this decision.
### Safe disclosure and presentation
A completed comparison returns success whether it finds differences or gaps;
load, comparison, and writer failures use the operational failure classification in
[ADR 0005](0005-invocation-outcomes.md).
Adapt and present native comparison results in accordance with the adopted
upstream API's disclosure contract. The integration carries the upstream results
and relevant metadata through its public provider boundary.
CommandKit owns report presentation and may summarize or transform the returned
safe comparison data for different operator needs. Reports may vary in format,
organization, and detail while respecting safe disclosure and the evidence
provided by the comparison. Each report's documented meaning defines the
conclusions an operator can draw from it.
Report formats, schemas, and rendering choices belong in the
[CLI reference](../cli.md) when implemented. They can evolve independently of
this ownership decision under the [release policy](../policy/release.md).
### Invocation outcomes
A completed comparison returns success regardless of whether differences are
reported. Load, comparison, and writer
failures use the operational failure classification in
[ADR 0005](0005-invocation-outcomes.md). Applications may use comparison results
to make their own domain decisions.
## Alternatives considered
| Alternative | Analysis |
| --- | --- |
| Rendered YAML line comparison | Reuses native display output. Formatting and masking become part of change interpretation. |
| Compare masks as literal strings | Uses visible snapshot bytes directly. Equal masks express equal presentation while hidden values remain uncertain. |
| Use one side's sensitivity policy | Simplifies classification selection. A value's disclosure treatment depends on which snapshot supplies that policy. |
| Fetch secret values for comparison | Gives access to additional equality evidence. Comparison acquires sensitive material and additional confidentiality responsibilities. |
| Element-by-element sequence diff | Gives detailed collection changes. Stable element identity and alignment need another comparison contract. |
| A distinct status for observed differences | Supports assertion-oriented scripts. Inspection completion and change detection share an outcome signal. |
| Compare safe snapshots in CommandKit | Gives CommandKit direct control over comparison, but requires it to maintain equality and sensitivity logic and account for values already withheld from snapshots. Native comparison keeps those responsibilities with the owning library. |
| Compare rendered configuration text | Reuses existing output, but formatting and masking affect the apparent differences. Native comparison supplies evidence grounded in the library's effective-value model. |
| Leave comparison commands entirely to applications | Preserves application flexibility, but repeats selection, acquisition coordination, and presentation infrastructure across consumers. Integration providers retain application control within the common command system. |
| Return a distinct status when differences are found | Supports assertion-oriented scripts, but couples successful inspection with a policy judgment about the result. The selected outcome model leaves that judgment to consumers. |
## Consequences
Operators receive meaningful effective-value changes with explicit uncertainty
where sensitivity limits comparison. Whole-sequence comparison keeps
implementation scope focused at the cost of less detailed collection changes.
CommandKit supplies consistent comparison commands while relying on owning
libraries for comparison semantics and safe disclosure. Dependency adoption
includes adapting the integration and its documentation to upstream changes.
New library integrations
are assessed through their supported comparison contracts; each scope can expose
its available capabilities independently under
[ADR 0009](0009-configuration-scopes.md).
Diff support depends on safe snapshots preserving value semantics and sensitivity.
CommandKit applies this comparison policy to scopes declaring that capability.
Other operations and families can be integrated independently under
[ADR 0004](0004-standard-inspection-surface.md).
Applications can use the successful report as input to domain decisions or future
assertion workflows.
Reports can serve different operator needs using the same native comparison
data. Their level of detail may differ, and sensitivity rules may limit what
can be shown. Maintaining the boundary between comparison evidence and report
presentation allows those reports to evolve while preserving library ownership
and consumer control.

View File

@@ -9,148 +9,140 @@ input/state lifetimes. Partially supersedes
consequence during the unreleased prototype transition, and refines its
convenient composition principle.
Refined by [ADR 0012](0012-typed-configuration-comparison-pairs.md) for
two-selection configuration comparison and its input/state lifetime. The
single-selection lifecycle and other decisions remain in force.
## Date
2026-10-10
## Context
Optional integration modules need to assemble complete standard families through
public core contracts. Applications also declare their own commands and groups.
Different construction and installation paths would add repeated error checks
and registration plumbing, weakening CommandKit's infrastructure-reduction goal.
The current implementation is an unreleased prototype with no downstream
consumers. Its declarations are evidence to evaluate, without compatibility
obligations that would constrain the selected API.
Applications declare domain commands, while optional integrations assemble
standard command families. A common construction and installation model reduces
repeated error checks and registration plumbing. Integrations need public typed
composition contracts to assemble those families while preserving core and
backend boundaries.
Declaring an optional integration is distinct from installing it. Broad references
to reserved standard families in ADR 0001 do not specify when an optional name
becomes reserved. Dependency selection alone should not consume an application
namespace or install commands.
Declaration and installation have different responsibilities. An optional family
claims a namespace when installed; selecting a dependency alone should leave the
application's command surface unchanged. During execution, scope-specific CLI
inputs and application-owned shared state also have distinct lifetimes. Making
those lifetimes explicit avoids accidental aliasing between selected scopes.
Configuration inspection can select several scopes. Those scopes may share an
application-owned cache while receiving different selector values. Combining
selector storage and shared state in one context obscures their distinct
lifetimes and can cause scope-specific inputs to alias. Separate input and state
types make those lifetimes explicit.
Integrations also need to compose typed bindings without accessing private core
parsers and setters or exposing backend structures.
At the time of this decision, the implementation was an unreleased prototype
with no downstream consumers. Its API shapes carried no compatibility obligations
that would constrain the selected design.
## Decision
### Uniform captured declarations
### Declaration capture and reuse
Applications and integrations construct the same opaque core command declarations
and install them through core application construction or registration. Initial
integrations provide complete-family factories and focused conveniences; they do
not add separate integration-specific installation APIs. Suite/provider types
remain integration-owned rather than becoming a universal core provider model.
integrations provide complete-family factories and focused conveniences through
that common installation API. Suite and provider types remain integration-owned.
Factories capture metadata, bindings, capability selections, selector
applicability, and callback references eagerly. They copy the declaration
collections needed so later edits to the source metadata do not change a returned
command. Construction errors remain attached to the declaration and are reported
at installation. There is no public deferred preparation lifecycle or untyped
integration payload.
Factories eagerly capture metadata, bindings, capability selections, selector
applicability, and callback references. They copy the declaration collections
needed to isolate returned commands from later edits to source metadata.
Construction errors remain attached for installation to report. This model uses
eager declarations with public typed composition; a deferred preparation lifecycle
and untyped integration payload are outside the selected API.
Core installation validates the whole batch before registry mutation, including
Declarations may be reused across applications with independent registry and
invocation state. Callback references and captured application objects retain
caller ownership. Reuse provides no deep-copy or concurrent-dispatch guarantee.
Application helpers performing other fallible work may still return errors.
### Installation and retained errors
Core validates each installation batch before registry mutation, checking
retained errors, bindings, placement, and collisions. Failed application
construction returns no usable application; failed later registration leaves the
existing registry intact. This atomicity applies to one installation call, not
separate calls or arbitrary effects in caller code. Report retained errors with
useful command context and preserve supported wrapped error identity without
requiring a particular aggregate-error representation.
construction returns no usable application; failed later registration preserves
the existing registry. Atomicity covers one installation call. Separate calls
and arbitrary callback effects retain their own lifetimes.
An explicit absence declaration contributes no command, help entry, or namespace
claim. Installation checks its attached errors before skipping it. A zero command
declaration remains invalid. Integrations define their own empty-suite semantics:
the initial configuration suite needs at least one named scope, while a valid
definition suite with no capabilities is absent. Metadata-only configuration
scopes remain discoverable. A nil loader supplied to an adapter is invalid wiring;
narrowing its capability list does not erase that error.
Report retained errors with useful command context and preserve supported wrapped
error identity. The aggregate-error representation remains an implementation
choice.
Declarations may be installed in multiple applications with independent registry
and invocation state. Callback references and the objects they capture remain
application-owned; declaration reuse neither deep-copies those objects nor
establishes concurrent-dispatch support. Application helpers performing other
fallible work may still return errors.
An explicit absence declaration contributes no command, help entry, or namespace.
Installation checks attached errors before skipping it. A zero command declaration
remains invalid. Integrations define their own empty-suite semantics: the initial
configuration suite needs at least one named scope, and metadata-only scopes
remain discoverable. A definition suite with no capabilities contributes nothing.
### Optional-family namespace lifetime and placement
Binding validation applies to generated command declarations. Unused selector
bindings in an absent family are ignored; explicitly retained adapter-construction
errors still reach installation. A nil loader supplied to an adapter remains
invalid wiring even if its capabilities are subsequently cleared.
Core construction always reserves help and version. An optional standard family
owns its complete root namespace when a real family declaration is installed.
An import or absent declaration reserves nothing. An application may use that
name while the family is uninstalled, but later installation then fails with a
collision. Application registration after standard-family installation fails in
the same way. Neither order merges groups, replaces handlers, or permits private
application operations beneath an installed standard family.
### Namespace ownership and placement
Standard-family declarations carry root-only placement metadata. Core rejects
their installation below another group. Placement is a public declaration
property, not authentication of the declaring package or a core allowlist of
integration names. A real empty root-only group still claims its namespace and
obeys placement checks; explicit absence does neither.
Core construction reserves help and version. An optional standard family owns
its complete root namespace upon installation. Imports and absent declarations
claim nothing. An application may occupy an uninstalled family name; installing
a conflicting declaration then fails in either registration order. Installation
preserves existing handlers and group ownership. Private application operations
remain outside installed standard families.
### Separate typed input from shared acquisition state
Standard families carry root-only placement metadata, which core enforces when
they are nested beneath another group. Placement is a public declaration property;
its enforcement is independent of package identity or a list of integration names.
A real empty root-only group claims its namespace and obeys placement checks.
Explicit absence contributes no node to place.
### Input and shared-state lifetimes
Integration suites distinguish typed CLI input from typed shared acquisition
state. Configuration inspection initializes and populates one input per selected
scope, applying only applicable selector bindings, and initializes shared state
once for the selected inspection invocation. Named definition inspection uses
one input and one shared state. Providers receive both explicitly.
Configuration input validation receives the selected scope identity together
with its applicable typed input.
state. Ordinary configuration inspection initializes one input per selected scope,
applies its applicable selector bindings, and initializes shared state once for
the inspection invocation. Input validation receives the scope identity and its
typed input. Named definition inspection uses one input and one shared state.
Providers receive both explicitly. ADR 0012 refines this lifecycle for comparison
pairs with two selections of one scope.
Availability preflight and applicable input checks precede shared-state
initialization and acquisition. A failed check stops that path; later input
factories need not run. Construction, installation, help, version, and scope
discovery invoke neither input/state factories nor acquisition providers.
initialization and acquisition. Failures may stop later input factories.
Construction, installation, help, version, and scope discovery leave suite
input/state factories and acquisition providers uninvoked.
An omitted factory supplies its type's zero value, including nil for a pointer.
Applications supply initialization when their accessors or providers need it.
CommandKit does not allocate arbitrary pointer types, clone arbitrary application
graphs, repair aliases returned by factories, or reject a provider that accepts
nil intentionally. Core-owned parsed collections remain independent when applied
to separate inputs. Caller-owned objects and callback effects retain their own
lifetimes.
An omitted factory supplies its type's zero value, including nil pointers.
Applications provide initialization needed by their accessors and providers;
providers may intentionally accept nil. Allocation of application objects,
cloning, and aliases returned by factories remain caller responsibilities.
Core-owned parsed collections stay independent when applied to separate inputs.
Caller-owned objects and callback effects retain their own lifetimes.
Provide a narrow typed core binding-composition contract to capture converted
values and apply a selected binding subset to an initialized input. Preserve
binding identity, defaults, and presence without string-based handler lookup or
exposure of private setters, parser state, or backend objects. Copying a binding
preserves its identity; constructing a lookalike binding does not.
### Typed binding composition
Execution-required checks apply to the selected subset, so an app-only selector
does not become required for an engine-only scope request. Missing required input
is a usage failure. An invalid binding selection or destination is a wiring
failure, classified as operational when encountered at invocation. Those
categories remain identifiable through supported wrapping; assembly-detectable
metadata defects remain installation errors. Both invocation failures stop before
shared-state initialization and acquisition. This composition is part of the
existing typed-input model, not another parser or a raw-argument handler API.
Core provides a narrow typed contract to capture converted values and apply a
selected binding subset to initialized input. It preserves binding identity,
defaults, and presence while keeping private setters, parser state, and backend
objects behind their existing boundaries. A copied binding retains its identity;
a newly constructed lookalike has its own identity.
Execution-required checks apply to the selected subset. Missing required input
is a usage failure; invalid binding selection or destination is a wiring failure
with operational classification at invocation. These categories remain identifiable
through supported wrapping and stop execution before shared-state initialization
and acquisition. Metadata defects detectable during assembly of generated
commands remain installation errors under the validation boundary above.
### Relationship to earlier decisions
No compatibility wrappers or downstream migration guide are required for this
unreleased prototype transition. This replaces
ADR 0010's consumer-migration consequence for this transition only. Changes to
published contracts remain governed by the [release policy](../policy/release.md).
For the unreleased prototype transition covered by this decision, compatibility
wrappers and a downstream migration guide were unnecessary. This exception to
ADR 0010's migration consequence is limited to that transition. Changes to
published contracts follow the [release policy](../policy/release.md).
ADR 0001 continues to govern dispatcher ownership, explicit instance registries,
typed bindings, presence, and application/domain boundaries. This decision
clarifies that optional-family namespace ownership begins upon installation.
It refines the construction and installation mechanics left open there and
distinguishes integration input from shared state.
It does not change the grammar, report formats, supported operation inventory,
or outcome/delivery rules in ADRs 0004 through 0009.
ADR 0010 continues to govern separate core/integration module contracts and
dependency direction. Integration factories return core declarations through
supported public composition contracts. Deliberate exports, private
implementation state, and backend isolation remain governed by
[ADR 0003](0003-public-facade.md) as scoped by ADR 0010. Exact exported signatures
belong in Go declarations and GoDoc when implemented; this record owns the
architectural choices rather than an implementation inventory.
ADR 0001 retains dispatcher, registry, typed-input, and application-ownership
principles. ADR 0010 governs module boundaries and dependency direction, with
[ADR 0003](0003-public-facade.md) governing deliberate exports and backend isolation
within that scope. ADRs 0004 through 0009 retain their operation, grammar, report,
and outcome decisions. Go declarations and GoDoc own exact implemented signatures.
## Alternatives considered
@@ -166,22 +158,15 @@ architectural choices rather than an implementation inventory.
## Consequences
Consumers assemble domain commands and optional families visibly through one
installation boundary. Integrations remain convenient without private core
access. Errors identified during eager assembly are observed at installation,
before provider acquisition.
Consumers assemble domain commands and optional families through one installation
boundary. Eager assembly errors reach that boundary before provider acquisition.
Namespace collisions remain explicit choices for the application to resolve.
Installing an optional family can fail if the application already uses its root
name; the application decides which declaration to install. Installed standard
families retain their ownership and root placement in either registration order.
Separate input and state types make initialization and sharing visible. Focused
conveniences support simple applications, while typed composition gives
integrations the public contracts needed to assemble their families.
Composed suites use two types and explicit initialization when needed. The
context-free application-configuration convenience hides unused suite machinery
for simple consumers. The public typed-composition seam adds a deliberate core
contract while keeping implementation and backend state private.
The [architecture policy](../policy/architecture.md) owns normative boundaries.
Implementation status and sequencing remain in the roadmaps. These choices
govern CommandKit's declarations and calls; application loading, cache design,
startup, domain validation, callback effects, and process exit remain consumer
choices.
The [architecture policy](../policy/architecture.md) owns normative boundaries;
roadmaps own implementation status and sequencing. Application loading, cache
design, startup, domain validation, callback effects, and process exit remain
consumer responsibilities.

View File

@@ -0,0 +1,108 @@
# ADR 0012: Prepare typed selection pairs for configuration comparison
## Status
Accepted. Refines [ADR 0011](0011-captured-declarations-and-installation.md) for
two-selection configuration comparison; its other decisions remain in force.
[ADR 0008](0008-semantic-configuration-diff.md) governs native comparison and
report presentation.
## Date
2026-10-11
## Context
Native effective-configuration comparison needs two completed loads. ConfigKit
requires results of the same configuration type, which an ordinary inspection
view does not expose for pairwise comparison. A generic adapter can retain that
type without introducing result assertions or a universal core provider model.
ADR 0011 separates a selected scope's typed input from shared acquisition state.
A comparison selects the same scope twice, potentially with different roots or
overlays. Reusing one input obscures which selection a provider receives; creating
separate shared state loses the invocation-wide lifetime chosen for inspection.
## Decision
### Selections and provider ownership
Comparison selects one scope and prepares an earlier/left selection and a
later/right selection. Omitted right selection fields inherit the corresponding
left field and supplied state. An explicit right override, including an empty
value, replaces that field. Providers retain interpretation of omitted defaults
and explicit empty values. Applicable domain selector values are shared between
the two sides initially; independent per-side domain selectors may be considered
for a concrete consumer need.
Equal selections are allowed. CommandKit does not skip acquisition merely because
their selection values are equal: caller-owned loading may produce different
effective results. Exact flag spellings belong in the CLI reference when
implemented.
Use a narrow optional comparison provider owned by the integration. Its request
groups each side's selection with its typed input and supplies one shared state
value separately. The provider returns acquired native comparison results
with diagnostics and an error through the existing acquisition convention.
Reuse useful upstream public record types rather than duplicating them solely
to rename fields. Exact exported signatures belong in Go declarations and GoDoc.
The typed ConfigKit adapter invokes the supplied loader for each reached side
and passes the successful typed results to `configkit.Diff`. It retains the
configuration type inside that adapter, without reflection, result type
assertions, private core access, or a comparison-facet framework. Applications
may supply their own pair provider through the same integration contract.
Declared capabilities determine required wiring. A comparison-only scope needs
its comparison provider, without an unrelated ordinary acquisition callback.
Ordinary operations continue to use their ordinary acquisition provider.
Retained adapter construction errors still apply when capabilities are narrowed.
Other scopes and families need not support comparison under
[ADR 0009](0009-configuration-scopes.md).
### Typed input and shared state lifetime
Capture comparison declarations and applicable selector bindings under ADR 0011.
Convert supplied flag values through core once, including on help paths; applying
the values to both inputs does not parse them again.
After availability preflight, initialize each reached side's typed input
separately, apply the same applicable captured selector values, and check that
input with the selected scope identity and that side's selection. Finish both
input checks before initializing shared state once and invoking the pair
provider. Failures may stop later factories or loads. Construction, installation,
help, version, and scope discovery invoke neither input/state factories nor
acquisition/comparison providers.
Existing factory ownership rules remain unchanged: an omitted factory supplies
its type's zero value, including nil pointers. Separate factory calls do not
guarantee distinct caller-owned objects. CommandKit does not allocate arbitrary
pointer types, clone application graphs, repair caller aliases, or supply or
repair application caches. Core-owned parsed collections remain independent
when applied to the two inputs; callback effects remain caller-owned.
## Alternatives considered
| Alternative | Analysis |
| --- | --- |
| Symmetric independently resolved left/right selectors | Makes each side explicit and avoids inheritance. It adds vocabulary and repetition for common base-versus-overlay comparisons. |
| One typed input for both sides | Requires fewer factory calls, but obscures side-specific selection checks and leaves mutation isolation implicit. |
| Acquire a comparison facet with each ordinary view | Shares single-side acquisition across operations, but requires a public compatibility contract between heterogeneous facets and additional handle or assertion machinery. |
| Separate shared state for each side | Isolates acquisition state, but loses the invocation-wide sharing already selected for inspection. Applications can choose isolation within their own pair provider if needed. |
## Consequences
Pair preparation has explicit input and state lifetimes while ordinary inspection
keeps its existing lifecycle. Generic result handling stays inside the integration
adapter; core gains no owning-library dependency or universal comparison model.
Shared state permits application-owned caches. A cache used by a pair provider
should distinguish selections and any input values affecting loading to avoid
reusing the left result for a different right selection. This is integration
guidance, not a CommandKit cache policy or guarantee about caller callbacks.
The [architecture policy](../policy/architecture.md) owns normative boundaries.
ADR 0008 governs delegation to upstream comparison semantics, safe disclosure,
and CommandKit's presentation responsibility. This decision does not add a new
equality engine or strengthen application
loading, object ownership, or concurrency contracts.