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

@@ -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.