188 lines
11 KiB
Markdown
188 lines
11 KiB
Markdown
# ADR 0011: Capture command declarations and install optional families explicitly
|
|
|
|
## Status
|
|
|
|
Accepted. Refines [ADR 0001](0001-command-dispatch-and-inputs.md) for declaration
|
|
construction, installation, optional-family namespace reservation, and integration
|
|
input/state lifetimes. Partially supersedes
|
|
[ADR 0010](0010-optional-integration-modules.md) only for its consumer-migration
|
|
consequence during the unreleased prototype transition, and refines its
|
|
convenient composition principle.
|
|
|
|
## 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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
## Decision
|
|
|
|
### Uniform captured declarations
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
Core installation validates the whole batch before registry mutation, including
|
|
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
### Optional-family namespace lifetime and placement
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
### Separate typed input from shared acquisition state
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
### 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).
|
|
|
|
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.
|
|
|
|
## Alternatives considered
|
|
|
|
| Alternative | Analysis |
|
|
| --- | --- |
|
|
| Fallible constructors plus integration-specific registration helpers | Makes each construction failure immediate, but creates several assembly conventions and repeated consumer checks. Captured errors keep one installation boundary. |
|
|
| Deferred preparation callbacks | Allows installation to construct a family, but adds lifecycle and error timing beyond eager capture. No demonstrated assembly need requires it. |
|
|
| Reserve all known optional family names at core construction | Prevents later name conflicts, but consumes namespaces for integrations the application has not selected. Installed-family ownership keeps conflicts explicit without that restriction. |
|
|
| Separate family/plugin registration framework | Could encode ownership and composition, but a captured command with root-only placement and batch validation expresses the required behavior without another installation model. |
|
|
| One shared input/state object | Uses fewer type parameters and can share a cache through references in separate input values, but leaves the distinct lifetimes implicit and scope-input isolation to application conventions. Explicit input and state types make those lifetimes visible. |
|
|
| Clone hooks or automatic pointer allocation | Can hide input construction, but adds object-graph and initialization responsibilities that belong to the application. Explicit factories keep those choices visible. |
|
|
| Public binding getters/setters or an untyped value bag | Enables integration assembly, but exposes private mechanics or returns typed-input assembly to string lookup. Narrow typed composition addresses the actual need. |
|
|
|
|
## 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.
|
|
|
|
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.
|
|
|
|
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.
|