Record the unified command registration direction
This commit is contained in:
238
docs/roadmap/registration-proposal.md
Normal file
238
docs/roadmap/registration-proposal.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user