173 lines
10 KiB
Markdown
173 lines
10 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.
|
|
|
|
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
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
### 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 through
|
|
that common installation API. Suite and provider types remain integration-owned.
|
|
|
|
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.
|
|
|
|
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 preserves
|
|
the existing registry. Atomicity covers one installation call. Separate calls
|
|
and arbitrary callback effects retain their own lifetimes.
|
|
|
|
Report retained errors with useful command context and preserve supported wrapped
|
|
error identity. The aggregate-error representation remains an implementation
|
|
choice.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
### Namespace ownership and placement
|
|
|
|
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.
|
|
|
|
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. 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. 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 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.
|
|
|
|
### Typed binding composition
|
|
|
|
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
|
|
|
|
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 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
|
|
|
|
| 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 through one installation
|
|
boundary. Eager assembly errors reach that boundary before provider acquisition.
|
|
Namespace collisions remain explicit choices for the application to resolve.
|
|
|
|
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.
|
|
|
|
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.
|