Accept the captured declaration architecture and align documentation
This commit is contained in:
@@ -4,6 +4,10 @@
|
||||
|
||||
Accepted.
|
||||
|
||||
[ADR 0011](0011-captured-declarations-and-installation.md) refines
|
||||
declaration construction, installation, optional-family namespace reservation,
|
||||
and integration input/state lifetimes.
|
||||
|
||||
## Date
|
||||
|
||||
2026-10-06
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
Accepted. Partially supersedes
|
||||
[ADR 0003](0003-public-facade.md) as described below.
|
||||
Partially superseded by [ADR 0011](0011-captured-declarations-and-installation.md)
|
||||
for the consumer-migration consequence during the unreleased prototype
|
||||
transition. It also supplies the registration and invocation-lifetime
|
||||
decisions left open here. The module
|
||||
boundary and remaining principles continue to apply; this record's decision
|
||||
text is retained.
|
||||
|
||||
## Date
|
||||
|
||||
|
||||
187
docs/adr/0011-captured-declarations-and-installation.md
Normal file
187
docs/adr/0011-captured-declarations-and-installation.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user