Files
commandkit/docs/roadmap/registration-proposal.md

239 lines
13 KiB
Markdown

# Unified command registration proposal
Status: direction agreed; exact API declarations and complete consumer examples
await the next authoring and review pass. No implementation is authorized by this
document alone.
## Purpose and authority
Applications and integrations construct captured command declarations and install
them through the same core API. Installation validates each batch before registry
mutation. Integrations supply complete standard families and focused conveniences
while retaining ownership of their provider semantics.
This document records the selected direction following the
[registration alternatives](registration-alternatives.md). It replaces that
comparison's recommendation of Option A with a refinement of Option C: capture
declarations and any construction errors eagerly, then report errors at
installation. The comparison remains background, not the selected API proposal.
[ADR 0001](../adr/0001-command-dispatch-and-inputs.md) owns explicit registration,
typed inputs, and command ownership.
[ADR 0010](../adr/0010-optional-integration-modules.md) owns optional module
boundaries and convenient integration registration. The
[module roadmap](modules.md) tracks extraction work. Reconcile the namespace
lifetime decision below with the relevant ADR and architecture-policy wording
before implementation; this proposal does not silently rewrite those records.
Design for the best consumer API without preserving current implementation
signatures or machinery. Existing code can supply evidence and reusable work,
but does not constrain the design. This is future API design, not documentation
of implemented behavior.
## Selected consumer model
A consumer can use only the core or add any supported combination of integrations.
Every command reaches the application through core `New` or `Register`, regardless
of whether an application or integration declared it. There is no mandatory
integration interface, application-wide generic input type, or separate
integration installation lifecycle.
The core remains independent of integration modules and their upstream libraries.
Integrations may depend on one another as documented. Such module dependencies
do not install commands or acquire providers. The selected modules remain
`CommandKit`, `CommandKit/integrations/ckconfig`, and
`CommandKit/integrations/ckprompt`, under the existing repository module prefix.
Each application command retains its own typed input and domain handler.
Integration suites retain their own typed context and provider declarations.
Both produce the same opaque core `Command` handle. Consumers need not construct
individual standard handlers, duplicate integration adapters, or expose backend
objects to assemble an application.
## Declaration and installation
### One declaration model
A `Command` is a captured declaration, not proof that the declaration is valid
or installed. Ordinary command, group, scope, and suite adapter construction
does not require a separate error check at each call site.
The intended shape is illustrated below. Names of existing concepts aid discussion;
the complete signatures and fields still need the example pass.
```go
// Core package:
func NewCommand[I any](spec CommandSpec[I]) Command
func Group(name, summary string, children ...Command) Command
func New(identity Identity, commands ...Command) (*App, error)
func (app *App) Register(commands ...Command) error
```
The initial integrations expose complete-family factories using their own suite
types:
```go
// In each initial integration package; Suite belongs to that integration:
func Command[C any](suite Suite[C]) commandkit.Command
```
Future integrations can accept declarations suited to their resources while
returning the same core `Command`; the core does not prescribe a universal suite.
Application-owned command factories follow the same declaration pattern.
Application helpers that genuinely perform other fallible work may still return
errors; the design does not suppress those errors or require all application
functions to be infallible.
### Eager capture, errors reported at installation
Factories capture declaration metadata and callback references and assemble
neutral command descriptions immediately. Validation that can already be done
may occur then, but its errors remain attached to the declaration for installation
to report. Internally fallible assembly can capture an error without returning it
to the consumer at every construction step.
The core needs a small supported public mechanism for integrations to carry
construction errors in opaque declarations. Its exact signature remains to be
specified. Do not substitute an untyped integration payload, root-private access,
or a general preparation callback. The proposed `DeferredGroup` lifecycle from
the comparison is not selected.
`New` and `Register` report retained declaration errors and validate the batch,
including bindings, root placement, and collisions, before registry mutation.
A failed `New` yields no usable application. A failed `Register` leaves the
existing registry intact. No rollback guarantee spans separate registration calls.
Declaration construction, installation, help, and version do not invoke provider
acquisition or invocation input/context factories. They do not validate files,
credentials, or application configuration. Invalid wiring fails at installation;
an upstream error returned by a valid provider remains an invocation result.
An originally nil loader remains invalid wiring even if an adapter would wrap
it in a non-nil function. Integration declarations retain enough information to
detect this. An intentionally absent capability is distinct from an invalid
adapter. Errors identify the affected command family and relevant scope, binding,
or capability and preserve supported wrapped error identity; exact prose is not
selected here.
## Integration composition and conveniences
Configuration scopes are composed before installation into one configuration
suite. Application and engine scopes can share that suite. Named definition
inspection remains independently selectable: installing an engine configuration
scope does not automatically install definition commands, or vice versa.
Keep provider declarations integration-owned. Configuration supports ConfigKit
for now; the core does not acquire a universal configuration or suite interface.
The PromptKit integration can use the ConfigKit integration's selection and scope
contracts through its selected dependency.
Provide the focused `ckconfig.AppCommand(load)` convenience for a context-free
application configuration loader. It declares the standard `app` scope, with a
default description, through the same declaration model as the full suite. It
does not require an unused context argument or `Suite[struct{}]` at the consumer
call site. A loader still receives `context.Context` and configuration selection.
Use the full suite when adding scopes, selectors, custom state, or other metadata.
Do not initially expose integration-specific `Register` or `RegisterApp`
functions alongside declaration factories. Core registration is the installation
path for all consumers. Additional conveniences should address demonstrated
consumer needs rather than mirror every underlying constructor automatically.
## Namespaces and empty declarations
Help and version are reserved by core construction. Optional families claim
their complete root namespace when installed. Before installation, a domain group
may occupy that name; subsequent integration installation then fails with a
collision. The reverse order also rejects a collision. Neither order replaces or
merges an existing group.
Standard-family declarations carry enforceable root-only placement metadata.
Installing them beneath an ordinary group is an assembly error. Use the same
`Command` type with a narrow placement declaration mechanism, whose final shape
remains to be specified. It represents placement, not authentication of the
declaring package. Installed standard families cannot be extended with private
application operations or have their handlers replaced.
The selected empty-suite behavior is:
| Declaration | Installation result |
| --- | --- |
| Configuration suite with no scopes | Declaration error |
| Named configuration scopes with no inspection operations | Install scope discovery with the actual empty capability sets |
| Configuration scopes with supplied operations | Install discovery and supported operations |
| Otherwise valid definition suite with no capabilities | Successful no-op; no namespace or help entry |
| Adapter with a nil loader | Declaration error, even when selected capabilities otherwise appear empty |
Use an explicit `commandkit.NoCommand()` declaration for intentional absence.
Core installation skips it. A zero-value `Command` remains invalid. Integration
factories validate or retain wiring errors before representing an empty family
as a no-op; absence must not swallow errors. A no-op does not reserve a namespace.
## Capture, reuse, and invocation inputs
Capture scope metadata, capability lists, selector applicability, bindings, and
callback references when declarations are constructed. Copy the declaration
collections needed for those semantics. Later edits to the source suite or its
metadata slices do not change a returned command declaration.
Declarations may be installed in multiple applications. Registries maintain
independent installed state, and invocations have independent parsed state.
Application callback closures and objects they reference remain application-owned
and may deliberately be shared. These choices do not promise arbitrary deep
copies or concurrent dispatch.
Use the same typed, presence-aware binding primitives for application commands
and integration inputs. Configuration selectors additionally declare scope
applicability; definition selectors need no configuration-specific wrapper.
Explicit empty, false, and zero values remain distinguishable from omission.
An omitted invocation input/context factory supplies the zero value of its type.
For pointer types that is nil; do not silently allocate arbitrary pointer state
or prohibit a loader that intentionally accepts nil. Applications supply a factory
when initialized state is needed. A supplied factory returning nil retains its
application-owned meaning.
Scope-specific parsed values must stay separate from shared acquisition state.
The value-shaped input with a shared cache pointer in the comparison illustrates
one suitable arrangement. Shallow-copying a shared pointer input and mutating its
selector fields is not sufficient to isolate scope values. The next example pass
must specify this interaction without inventing deep-copy guarantees, banning
pointer contexts, or silently changing factory lifetimes.
## Next example and review pass
The next step is to complete this proposal's declarations and examples, then
review them before implementation. Focus on the following work:
1. Show a complete core-only registration example with a typed application
command and group. Ordinary declaration construction should follow the same
error-reporting pattern as integration factories, rather than hiding an older
pattern in a fallible helper.
2. Show context-free application configuration through `AppCommand`, including
the loader signature and absence of placeholder state or forwarding wrappers.
3. Show full configuration-scope and definition-suite assembly beside application
commands, with all registration-related setup visible. Demonstrate independent
capability selection without importing an integration into the core.
4. Specify the supported mechanisms for retained declaration errors and root-only
placement, plus their interaction with ordinary groups and explicit no-ops.
Show how integration-owned validation feeds core installation without a public
preparation callback or private-state access.
5. Complete the typed selector/override example, including input initialization,
scope filtering, explicit presence, and shared cache lifetime. Explain how
pointer-shaped inputs preserve scope isolation, and align input factory
semantics with application commands.
6. Include focused examples or expected outcomes for invalid wiring, collisions,
nesting a root-only group, empty suites, declaration reuse, and edits to source
metadata after construction. These are design checks, not a mandate for a new
exhaustive test matrix.
7. Reconcile the selected namespace lifetime with the ADR and architecture policy,
and update the module implementation plan once this proposal's examples and
remaining API details have passed review.
The uniform declaration-and-installation pattern is decided. Exact supporting
API names and the unresolved input-isolation mechanics above remain work for the
next pass. This step adds no code, new inspection operations, or release-cadence
decision.