206 lines
12 KiB
Markdown
206 lines
12 KiB
Markdown
# ADR 0001: Own dispatch through an explicit registry and typed inputs
|
|
|
|
## Status
|
|
|
|
Accepted.
|
|
|
|
## Date
|
|
|
|
2026-10-06
|
|
|
|
## Context
|
|
|
|
Several downstream consumers of the ConfigKit and PromptKit libraries include
|
|
duplicated or near-duplicated code relating to configuration inspection,
|
|
routing, parsing, help, and error handling. However, their command vocabulary
|
|
and syntax have diverged. CommandKit aims to provide a single, unified CLI
|
|
implementation that will reduce duplication, provide consistent vocabulary and
|
|
grammar, and reduce the amount of code each individual application must own.
|
|
|
|
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-first syntax for the standard suite, which provides a clear
|
|
family ownership boundary. The initial standard command families are `config`
|
|
for ConfigKit inspection and validation, and `promptkit` for PromptKit inspection
|
|
and utilities. CommandKit integrates these libraries through their supported public
|
|
semantics.
|
|
|
|
CommandKit accepts custom commands outside its reserved standard families and
|
|
reports registration errors for attempts to replace standard handlers or add
|
|
private operations beneath them. Its input extension mechanism accepts context
|
|
selectors and typed overrides for standard operations.
|
|
|
|
CommandKit accepts caller-supplied context, streams, handlers, and inspection
|
|
providers and returns invocation outcomes. It leaves process termination to the
|
|
caller. Executable startup, signals, runtime construction, schemas, acquisition,
|
|
and domain resolution remain application choices.
|
|
|
|
### Explicit registry and capability installation
|
|
|
|
Each explicitly constructed CommandKit instance owns a registry. Its API accepts
|
|
individual commands and composable groups. Registration uses explicit instance
|
|
state with caller-controlled construction and composition.
|
|
|
|
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. CommandKit assembles standard families from declared provider
|
|
capabilities, installing their supported handlers and metadata together. Each
|
|
family defines its own resource model, supported verbs, and per-operation data
|
|
contracts. Configuration scopes declare their available inspection operations.
|
|
|
|
The API allows supported capabilities to be integrated independently. Adding
|
|
comparison, catalogs, or other inspection features extends the installed surface
|
|
according to the supplied providers. Shared verbs have consistent purposes across
|
|
families, while the available subsets reflect their resources and capabilities.
|
|
Help and discovery expose those subsets. Registration validates the wiring of
|
|
declared capabilities and reports unsupported selections before acquisition.
|
|
|
|
CommandKit registration validates declarations with acquisition callbacks left
|
|
uninvoked. Its construction, help, and version paths use metadata. The selected
|
|
handler requests dependencies when its operation needs them. Invalid configuration
|
|
returned by an acquisition callback is an invocation-time failure. Caller code
|
|
executed before registration and effects inside callbacks remain caller-owned.
|
|
|
|
CommandKit installs handlers for supplied capabilities and describes actual
|
|
availability. Each enabled operation retains its standard meaning and output
|
|
contract. The exact operation inventory and remaining command contracts are
|
|
separate design decisions.
|
|
|
|
### Typed inputs with explicit bindings
|
|
|
|
The public binding contract accepts application-defined input structs for custom
|
|
commands and uses CommandKit-defined inputs for standard handlers. Explicit
|
|
registration binds flags and operands to fields and supplies parsing and help
|
|
metadata. CommandKit delivers parsed typed inputs to registered handlers.
|
|
|
|
Bindings support ordinary typed fields and presence-aware fields where omission
|
|
has a separate meaning. CommandKit records supplied state independently of values
|
|
and defaults, preserving explicitly supplied empty, false, and zero values.
|
|
|
|
CommandKit keeps parsed values, collections, and presence from one invocation out
|
|
of subsequent invocations. Storage allocation and reuse are implementation
|
|
choices. Handler closures and application state retain their own lifecycle;
|
|
concurrent dispatch is a separate contract.
|
|
|
|
The invocation API supplies context and streams separately from CLI inputs.
|
|
CommandKit creates its declarations and bindings from metadata and defers its
|
|
acquisition calls to the selected handler. The binding system accepts domain value
|
|
parsers; domain interpretation and validation belong to registered callbacks.
|
|
|
|
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, prepare independent invocation inputs, apply shared parsing and
|
|
operand checks, then invoke the typed handler. Return its invocation outcome to
|
|
the application.
|
|
|
|
CommandKit classifies shared usage errors and standard-family outcomes. Its
|
|
handler result contract accepts application-specific outcomes and preserves their
|
|
supplied classification. Scriptorium output validation and Notarius artifact
|
|
outcomes, for example, remain domain results. CommandKit translates results from
|
|
explicit classification and emits usage guidance for usage 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.
|
|
|
|
### Contract scope and consumer guidance
|
|
|
|
This decision governs CommandKit's public contracts and implemented behavior.
|
|
Each operation specifies the inputs, result semantics, callback lifetimes, and
|
|
metadata its provider supplies. Consumers choose how to implement those callbacks
|
|
and how to structure loading, execution, startup, and process status mapping.
|
|
|
|
Sharing acquisition and resolution between inspection and execution, and keeping
|
|
acquisition focused on producing inspection views, are recommended practices.
|
|
CommandKit supports those patterns through reusable inputs and lazy provider
|
|
hooks. Its API preserves declared results and coordinates its own calls; arbitrary
|
|
application internals remain application-owned.
|
|
|
|
Cobra isolation applies to CommandKit's exports and internal adapter. Independent
|
|
consumer use of another CLI library remains an application choice. Additional
|
|
families and source models can use capability contracts appropriate to their
|
|
resources, with standard meanings preserved for operations that are installed.
|
|
|
|
## 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 would need to describe its separate parsing and help contract. |
|
|
| 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` for PromptKit | 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. CommandKit's invocation-state contract remains separate from caller-owned object lifetimes; objects are an application option. |
|
|
| 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, independent invocation inputs, cancellation,
|
|
and domain outcomes.
|
|
CommandKit releases document intentional compatibility changes. Coordinated
|
|
application documentation and subprocess-caller updates are recommended migration
|
|
practices.
|
|
|
|
The [architecture policy](../policy/architecture.md) owns normative boundaries.
|
|
[ADR 0002](0002-cobra-backend.md) selects the backend, and
|
|
[ADR 0003](0003-public-facade.md) defines its public/internal boundary.
|