Files
commandkit/docs/adr/0001-command-dispatch-and-inputs.md

180 lines
11 KiB
Markdown

# ADR 0001: Own dispatch through an explicit registry and typed inputs
## Status
Accepted.
## Date
2026-10-06
## Context
Audita, Narratio, Notarius, and Scriptorium repeat configuration inspection,
routing, parsing, help, and error handling. Their command vocabulary and syntax
have diverged. CommandKit aims to provide one implementation of standard
inspection, consistent vocabulary and mechanics, and less application
infrastructure. Application migrations are expected to make breaking changes.
Applications have genuine domain commands, such as Narratio's `session restore`,
that remain outside the standard suite. Merely forwarding standard families from
separate application dispatchers would leave custom-command mechanics and root
help dependent on each application's implementation.
Input handling also needs a common boundary. Raw argv handlers or string-based
flag retrieval leave repeated parsing and input assembly with applications.
Presence matters for configuration overrides: omitted values differ from
explicitly supplied empty strings, false, and zero.
## Decision
### Ownership and namespace structure
CommandKit owns the dispatcher, shared parsing and presence tracking, help,
syntax diagnostics, and standard-family handlers and outcomes. Applications own
custom command names, domain meaning, validation, effects, artifacts, and
operational outcomes.
Use namespace/resource-first syntax for the standard suite. The `config` family
and an installed `promptkit` family belong to CommandKit. PromptKit definition
inspection uses the single `promptkit` namespace; root `prompt` and `profile`
names remain available to applications. This provides a clear family ownership
boundary without spreading a namespace across root verbs.
Applications register commands outside reserved families. They cannot replace
standard handlers or add private operations underneath a reserved family.
Standard-family context selectors and typed overrides use the defined input
extension mechanism rather than introducing application-owned operations there.
Applications retain `main`, signal handling, context creation, stream selection,
runtime construction, and final process exit. CommandKit returns invocation
outcomes and does not terminate the process. Configuration schemas, acquisition,
domain resolution and validation, and provider construction remain application
integration responsibilities. ConfigKit and PromptKit retain their own
configuration, provenance, safe inspection, and native rendering semantics.
### Explicit registry and capability installation
Each explicitly constructed application instance owns a registry. Applications
register individual commands and composable groups. There is no package-global
registry or import-time `init` registration.
Definitions provide paths, descriptions, flags and defaults, repetition and
presence metadata, operand requirements, and execution handlers. One definition
supplies routing, parsing, and help. Duplicate paths and attempts to replace
reserved commands return registration errors; order does not select a winner.
Help and version are installed automatically using supplied application identity
and build metadata. Configuration and PromptKit families are installed when the
application supplies their providers. Supplying a capability installs its complete
standard family, not a collection requiring manual operation-by-operation wiring.
Registering providers does not execute them. Construction may diagnose missing
or invalid wiring without reading configuration, resolving credentials, or
building engines. Invalid configuration is an invocation-time failure. Command
declarations, help, and version remain available independently of application
initialization. The selected handler acquires dependencies when it needs them.
Do not install placeholder commands. Omitting the configuration provider does
not make a partial integration a complete core adopter. PromptKit is optional;
its selected family is installed as a complete capability. The CLI roadmap owns
the proposed command inventory and the remaining choices for that standard.
### Typed inputs with explicit bindings
Use application-defined input structs for custom commands and CommandKit-defined
inputs for standard handlers. Explicit registration binds flags and operands to
their input fields. The declarations also supply parsing and help metadata.
Handlers do not retrieve primitive values by flag name or parse the complete argv.
Use ordinary typed fields where omission has no separate meaning. Where it does,
keep supplied/not-supplied state beside the value in a presence-aware field.
Defaults do not mark input as explicitly supplied. Preserve explicit empty,
false, and zero values.
Create fresh input storage per invocation. Registered commands must not capture
one mutable input permanently or retain prior values, collections, or presence.
This decision addresses lifetime without establishing concurrent dispatch.
Supply context and stream access separately from CLI inputs. Input declarations
and binding setup must not construct execution dependencies. Applications perform
domain validation and may interpret parsed domain values or supply a custom value
parser through the binding system.
The public factory, binding, registration, and handler signatures and presence
type names remain API design choices. No illustrative declaration establishes
an exact public API.
### Outcomes and domain behavior
Resolve the command and handle help/version before loading application state.
For execution, allocate fresh inputs, apply shared parsing and operand checks,
then invoke the typed handler. Return its invocation outcome to the application.
Shared usage errors and standard-family outcomes belong to CommandKit. Custom
handlers must communicate application-specific outcomes without silent generic
reclassification. For example, Scriptorium output validation and Notarius artifact
outcomes remain application concerns. Do not classify domain errors by matching
message strings or print usage unconditionally for runtime failures.
Common mechanics do not require a common success-report format for custom
commands. The application owns its artifact and operational output contracts.
Exact result/error interfaces and unresolved grammar choices are separate design
work, not approvals implied by this decision.
## Alternatives considered
### Dispatcher and ownership
| Alternative | Analysis |
| --- | --- |
| Application-owned dispatcher forwarding standard families | Small integration surface and convenient migration, but custom syntax, root help, and errors still need cross-dispatcher coordination; removes less infrastructure. |
| Standard definitions mounted into an application's framework | Reuses routing infrastructure, but application configuration can preserve inconsistent behavior outside mounted families. Internal framework reuse remains compatible with this decision. |
| Shared metadata/help with separate routing | Unifies discovery but adds synchronization among metadata, routing, and parsing. One dispatcher avoids it. |
| Both full dispatch and embeddable family handlers | Serves established external CLI architectures but adds integration surfaces and maintenance. Add embedding only for a concrete need. |
| Raw-argument handlers for custom commands | Eases migration but leaves parsing and help responsibilities with applications. A future transition adapter needs explicit limitations and is not full adoption. |
| Universal application runtime framework | Could absorb startup and operational services, but expands beyond the demonstrated CLI boundary and domain ownership. |
| Verb-first standard paths | Groups actions but distributes namespace ownership among individual routes. Namespace/resource-first paths keep family ownership clear. |
| Reserve root `prompt` and `profile` | Provides short resource paths but consumes application namespaces. A single `promptkit` family gives the library one ownership boundary. |
### Registration
| Alternative | Analysis |
| --- | --- |
| Declarative tree passed to construction | Makes assembly visible together; larger applications still need group composition. May become a convenience over the same registry. |
| Constructor options for every command/group | Compact setup, but excessive options can obscure commands versus application settings. Option conventions remain API design work. |
| Global registry populated by `init` | Less entry-point code, but hidden import effects, shared mutable state, and unclear ownership. Explicit instances avoid those costs. |
### Handler inputs
| Alternative | Analysis |
| --- | --- |
| Parsed-value accessor object | Uniform handlers, but string-based lookup, runtime type errors, and repeated assembly remain. |
| Individual typed parameters | Clear for small commands; grows into long signatures and adapter machinery. Typed structs scale more naturally. |
| Reflection and struct tags | Compact declarations, but adds a tag language, field-discovery rules, and runtime declaration validation. Explicit bindings keep declarations visible. |
| Typed command objects | Can organize complex handlers; must separate fresh input from dependency and execution lifetimes. Applications may use objects without making them mandatory. |
| Generated bindings/adapters | Can combine compact authoring and typing, but adds tooling and generated-file maintenance without demonstrated need. |
| Raw arguments | Simple routing integration, but returns common parsing and help to applications. |
## Consequences
One command system removes repeated routing, parser setup, help assembly, and
shared error plumbing as well as standard inspection handlers. Domain handlers
and their declarations remain recognizable application code.
The public registration surface is larger than family delegation, and explicit
bindings require connecting declarations to the correct fields. Those costs
support consistent mechanics without requiring reflection or generated code.
Shared parsing defects can affect every adopter. Consumer and backend integration
evidence must protect selected semantics, including ownership conflicts, help
without configuration, presence, fresh input, cancellation, and domain outcomes.
Intentional migration changes require coordinated application documentation and
subprocess-consumer updates.
The [architecture policy](../policy/architecture.md) owns normative boundaries.
The [dispatcher roadmap](../roadmap/02-dispatch.md) owns implementation status and
pending API design. The [CLI roadmap](../roadmap/01-cli-plan.md) retains command
proposals and their approval status. [ADR 0002](0002-cobra-backend.md) selects the
backend, and [ADR 0003](0003-public-facade.md) defines its public/internal boundary.