diff --git a/docs/roadmap/registration-proposal.md b/docs/roadmap/registration-proposal.md new file mode 100644 index 0000000..086f123 --- /dev/null +++ b/docs/roadmap/registration-proposal.md @@ -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.