239 lines
13 KiB
Markdown
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.
|