# Registration API alternatives Status: design mockup for discussion; none of the alternatives is implemented or selected by this document. ## Purpose and authority Settle the public registration shape for the optional modules described in [the module roadmap](modules.md), before finalizing its implementation plan. [ADR 0001](../adr/0001-command-dispatch-and-inputs.md) selects explicit instance registration, typed inputs, and standard-family ownership. [ADR 0010](../adr/0010-optional-integration-modules.md) places optional handlers, providers, and adapters in their owning modules and requires convenient family registration through supported core contracts. Design for the best, simplest, most coherent final API. CommandKit is an unreleased prototype with no consumers; its signatures and private machinery receive zero deference. Existing code supplies evidence of responsibilities and can be reused where suitable. Accepted decisions remain authoritative. The Go fragments below illustrate proposed consumer APIs. They are not runnable examples, published contracts, or instructions to implement these names exactly. Application loaders, domain command constructors, imports, and execution code are omitted where unrelated to registration. No new inspection operations are proposed here; deferred capabilities remain deferred. ## Common requirements All alternatives must provide: - A core-only consumer importing neither integration nor its upstream library. - Integration-supplied standard commands and adapters; applications supply acquisition and domain context without constructing each standard handler. - One `config` suite containing application, PromptKit, and other supported configuration scopes. Definition inspection remains independently selectable. - Registration and help that leave acquisition callbacks uninvoked. - Typed, presence-aware context flags and overrides, with invocation-local state. - Registration errors for collisions and attempts to modify installed standard families. A failed registration operation must not partially install its inputs. - No public Cobra types, root-private registry access, import-time installation, or ConfigKit/PromptKit dependencies in the core. The ownership boundary is the complete installed group. There is no public incremental operation for appending application commands underneath an installed standard family, or replacing its handlers. Configuration composition happens before installation through integration-owned scope declarations. ## Shared provider and scope mockup The alternatives differ in how declarations reach the application registry. They can use the same integration-owned provider declarations: | Proposed declaration | Owner | Role | | --- | --- | --- | | `Scope[C]`, `Suite[C]`, `Selection` | `ckconfig` | Configuration capabilities, scope composition, and config/overlay inputs | | `FromConfigKit[C, T]` | `ckconfig` | Adapt an application ConfigKit loader into a configuration scope | | `ConfigScope[C]` | `ckprompt` | Adapt a PromptKit engine loader into a `ckconfig.Scope[C]` | | `Suite[C]`, `FromEngine[C]` | `ckprompt` | Definition inspection providers and their engine adapter | | Typed bindings, diagnostics, command declarations | `commandkit` | Shared command infrastructure, without upstream-library types | These short names are proposed for consumer readability, rather than preserving the prototype's root-package names. The two `Suite` types belong to distinct integration packages and describe distinct resources. No universal suite, provider, or integration interface is needed. The illustrative adapter signatures are: ```go // In ckconfig: func FromConfigKit[C, T any]( name, summary string, load func(context.Context, Selection, C) (*configkit.Result[T], error), ) Scope[C] // In ckprompt: func ConfigScope[C any]( load func(context.Context, ckconfig.Selection, C) (*promptkit.Engine, error), ) ckconfig.Scope[C] func FromEngine[C any]( load func(context.Context, ckconfig.Selection, C) (*promptkit.Engine, error), ) Suite[C] ``` Using `ckconfig.Selection` for the PromptKit adapters describes their existing configuration-selection inputs. It does not move that integration-specific type back into the core. `ckprompt` already has the selected dependency on `ckconfig`. These adapters construct declarations without acquiring providers or returning errors. Option A validates the complete suite in `Register`; Option B validates it in the complete-group factory; Option C validates it during core installation. All validate before registry mutation. Invalid wiring, including a nil loader, must remain detectable in the declaration rather than becoming an invocation-time failure inside an otherwise non-nil wrapper. Invalid configuration returned by a valid loader remains an invocation-time failure. This consolidates declaration errors at a useful assembly boundary. It does not require every future constructor to be infallible: constructors that perform genuinely fallible work may still return errors. The existing adapter constructors only reject nil loaders at construction, so they do not demonstrate that need. Adapters supply appropriate supported operations. Provider declarations can also describe supported subsets through typed callbacks, so callers need not enable every available inspection operation. Advanced composition does not require exposing individual standard command constructors. Suites allow application context and explicitly bound selector flags where needed. Configuration selectors may identify the scopes to which they apply; definition selectors belong to their own suite. Share core binding primitives, without forcing PromptKit definition providers to use a configuration-specific selector wrapper. The exact selector constructors are a separate part of the typed-input design, not a reason to add a registration framework. ### Consumer assumptions The simple ConfigKit examples have no domain selectors, overrides, or application state. Under the current generic sketch, their loader accepts an unused `struct{}` context argument and their suite uses `struct{}` with no `NewContext` callback. This makes the remaining type plumbing visible rather than hiding it behind an `AppInput` type or helper. Whether to offer a context-free loader form is still open; these examples do not settle that API. In the composed examples, both loaders accept `*RunState` and all provider and suite assembly is shown inline. The illustrative `newRunState` callback is invoked by the selected handler, not during assembly. Application state can hold an invocation-local cache shared by the selected configuration scopes. Separate families do not acquire state together merely because both are registered. Keeping the engine scope while omitting definition registration enables only PromptKit configuration inspection; keeping definitions while omitting that scope enables only definition inspection. Importing `ckprompt` enables neither. `greet` and `sessionGroup` represent already constructed application-owned commands, whose construction is identical across the alternatives. Their domain implementation and the loaders' bodies are omitted; no integration assembly or error handling is hidden in a `makeInspectionSuites` helper. ## Option A: integration convenience functions install complete groups ### Proposed public shape ```go // Existing core concepts, evaluated for their final shape: func New(identity Identity, commands ...Command) (*App, error) func (app *App) Register(commands ...Command) error // In the integration packages: func Register[C any](app *commandkit.App, suite Suite[C]) error ``` Each integration's `Register` validates its declaration, assembles its complete standard group privately, and installs that group through supported public core registration. Applications do not receive individual standard handlers or need to declare standard paths. The function name is qualified by its owning package: `ckconfig.Register` or `ckprompt.Register`. ### Core-only consumer ```go greet, err := makeGreetCommand() // Application-owned typed command. if err != nil { return err } app, err := commandkit.New(identity, greet) if err != nil { return err } // Execute using caller-owned context and streams. ``` Only `commandkit` is imported for command infrastructure. Help/version are provided by construction; there is no inspection-provider setup. ### ConfigKit-only consumer ```go app, err := commandkit.New(identity, greet) if err != nil { return err } if err := ckconfig.Register(app, ckconfig.Suite[struct{}]{ Scopes: []ckconfig.Scope[struct{}]{ ckconfig.FromConfigKit("app", "Application settings.", loadAppConfig), }, }); err != nil { return err } ``` This consumer imports `commandkit`, `ckconfig`, and the ConfigKit types used by its loader, with no `ckprompt` or PromptKit dependency. ### Composed consumer ```go app, err := commandkit.New(identity, sessionGroup) if err != nil { return err } if err := ckconfig.Register(app, ckconfig.Suite[*RunState]{ NewContext: newRunState, Scopes: []ckconfig.Scope[*RunState]{ ckconfig.FromConfigKit("app", "Application settings.", loadAppConfig), ckprompt.ConfigScope(loadEngine), }, }); err != nil { return err } definitions := ckprompt.FromEngine(loadEngine) definitions.NewContext = newRunState if err := ckprompt.Register(app, definitions); err != nil { return err } ``` The configuration suite installs `config` once with both scopes. Definition registration independently installs the supplied `promptkit` operations. `session restore` remains an application-owned command inside `sessionGroup`. ### Advantages - Expresses the consumer task directly: install this integration's standard commands using these providers. - Keeps standard assembly behind the integration facade and avoids exposing a family-builder API solely to support extraction. - Works with ordinary core command/group concepts, without a new integration object, callback protocol, or application-wide options language. - Provider validation and installation are coordinated within one call, without separate error checks for the declaration-only adapter constructors. - Each module can evolve its provider declarations independently. ### Costs and limits - Several integrations require several registration calls and error checks. - Atomicity covers each call, not the entire application setup. If PromptKit registration fails after configuration succeeds, the app contains the earlier registration. The application can discard that unfinished app; no rollback framework is required for this assembly pattern. - Standard group construction is not independently available to consumers. Tests exercise it through registration and the public application unless a concrete composition need later justifies another surface. ## Option B: integrations return complete command declarations ### Proposed public shape Keep the same core construction and registration concepts, but expose integration factories instead of installation functions: ```go // In the integration packages: func Command[C any](suite Suite[C]) (commandkit.Command, error) ``` `ckconfig.Command` returns the complete `config` group; `ckprompt.Command` returns the supplied definition-inspection group. The returned core handle has private implementation state. It does not expose child mutation or standard handlers. Factory validation and group construction leave acquisition callbacks uninvoked. Adapter declarations use the shared error-free constructors; suite wiring errors are reported by `Command`, while registry collisions and placement errors are reported by `New` or `Register`. ### Core-only consumer The core-only sketch from Option A is unchanged. This design adds no required integration dependency or configuration to core construction. ### ConfigKit-only consumer ```go configCommand, err := ckconfig.Command(ckconfig.Suite[struct{}]{ Scopes: []ckconfig.Scope[struct{}]{ ckconfig.FromConfigKit("app", "Application settings.", loadAppConfig), }, }) if err != nil { return err } app, err := commandkit.New(identity, greet, configCommand) if err != nil { return err } ``` ### Composed consumer ```go configCommand, err := ckconfig.Command(ckconfig.Suite[*RunState]{ NewContext: newRunState, Scopes: []ckconfig.Scope[*RunState]{ ckconfig.FromConfigKit("app", "Application settings.", loadAppConfig), ckprompt.ConfigScope(loadEngine), }, }) if err != nil { return err } definitions := ckprompt.FromEngine(loadEngine) definitions.NewContext = newRunState promptCommand, err := ckprompt.Command(definitions) if err != nil { return err } app, err := commandkit.New(identity, configCommand, promptCommand, sessionGroup, ) if err != nil { return err } ``` Alternatively, an existing app can receive the complete declarations in one `app.Register(configCommand, promptCommand, sessionGroup)` call. Declaration validation happens before registry mutation, so a collision rejects the whole batch. No guarantee extends across separate registration calls. ### Advantages - Uses one declaration model for both standard and domain commands. - Supports batch registration without new core transaction machinery. - Applications can assemble a collection of commands before constructing an app. - Factories can be reused in larger application assembly code without receiving an app or modifying its registry. ### Costs and limits - The common task requires constructing and holding additional command handles before installation, with separate factory and registry errors. - The public factory makes standard-group composition part of the consumer API. Documentation must explain that these groups occupy their standard root namespaces; arbitrary nesting must not redefine standard command paths. - Returning an ordinary command handle alone does not settle reservation rules. The registry must still recognize any selected standard-namespace restrictions rather than assume every valid command may be installed at any location. ## Option C: complete declarations validated during installation ### Proposed public shape This variant of Option B defers suite validation and fallible group assembly to core construction or registration: ```go // In the integration packages: func Command[C any](suite Suite[C]) commandkit.Command ``` A returned command is an uninstalled, potentially invalid declaration. `New` or `Register` prepares and validates the complete batch before committing it. The core needs a supported mechanism for integration-owned validation and assembly without importing integration types or exposing backend machinery. Its exact shape remains open; the shorter consumer syntax does not make that work disappear. Preparation uses only declarations and metadata. It does not acquire providers or call application context factories. Installation errors include both invalid suite wiring and registry conflicts. For `New`, a failure yields no usable app; for `Register`, it leaves the prior registry intact. ### Core-only consumer The core-only sketch from Option A remains available. Option C does not require changing every domain command constructor to defer its own validation. ### ConfigKit-only consumer ```go app, err := commandkit.New(identity, greet, ckconfig.Command(ckconfig.Suite[struct{}]{ Scopes: []ckconfig.Scope[struct{}]{ ckconfig.FromConfigKit("app", "Application settings.", loadAppConfig), }, }), ) if err != nil { return err } ``` ### Composed consumer ```go definitions := ckprompt.FromEngine(loadEngine) definitions.NewContext = newRunState app, err := commandkit.New(identity, ckconfig.Command(ckconfig.Suite[*RunState]{ NewContext: newRunState, Scopes: []ckconfig.Scope[*RunState]{ ckconfig.FromConfigKit("app", "Application settings.", loadAppConfig), ckprompt.ConfigScope(loadEngine), }, }), ckprompt.Command(definitions), sessionGroup, ) if err != nil { return err } ``` An existing app can instead receive these declarations in one `Register` call. No guarantee extends across separate calls. ### Advantages - Retains Option B's collection and batch-composition capabilities. - Removes intermediate command handles and complete-group factory error checks. - Reports invalid wiring and installation conflicts at one installation boundary. ### Costs and limits - A command handle no longer implies that its integration declaration has passed validation; consumers must install it to obtain that result. - The core must support deferred integration validation and assembly while retaining batch atomicity and dependency independence. - Standard-root placement and empty declarations still need explicit semantics, as in Option B. - Declaration capture and reuse need care: deferring work must not accidentally let later suite mutations change installed commands or share invocation state. ## Ownership, collisions, and the core boundary None of the alternatives needs to expose registry maps, an installed-config boolean, individual handler constructors, or backend objects. Ordinary registration can reject an occupied root path and commit a fully validated batch without merging children. That protects installed-family ownership for all three designs. Evaluate any additional reservation requirements against the accepted ownership decision, not prototype flags or incidental checks. If a reserved namespace must be unavailable to ordinary application declarations before its integration is installed, group immutability and duplicate checks alone do not express that rule. A narrow core installation distinction may then be justified. All designs can accommodate it, and the final proposal should identify the exact rule it serves before selecting its public signature. Do not invent package-origin authentication or a general plugin framework to solve registration ownership. An explicit `Family` handle with separate creation and installation is also possible. It would distinguish standard families from domain groups, but adds a second public declaration model. Adopt it only if a concrete reservation or composition responsibility requires that distinction; module extraction alone does not. This is a conditional extension to the alternatives, not a prerequisite. ## Validation and declaration ownership The following timing distinguishes the proposals; none shifts declaration errors to command execution: | Boundary | Option A | Option B | Option C | | --- | --- | --- | --- | | Scope/provider adapter | Record declaration | Record declaration | Record declaration | | Integration suite validation | Integration `Register` | Integration `Command` | Core `New`/`Register` through integration-owned preparation | | Registry and placement checks | Core registration | Core `New`/`Register` | Core `New`/`Register` | | Provider acquisition | Selected handler | Selected handler | Selected handler | Recommended ownership behavior: successful installation captures the declaration metadata it needs, so later edits to suite fields or declaration slices do not change installed commands. Application callbacks and the runtime objects they capture retain their documented application-owned lifecycle; this is not a promise to deep-copy arbitrary application state. The precise capture boundary for B and C, before installation, remains an open decision below. Empty declarations also need an explicit choice. A suite with no definition capabilities could reasonably install nothing, but B and C then need a way to represent that result without registering an empty standard namespace or treating every invalid zero-value command as a no-op. An empty configuration suite may have different semantics because scope discovery is itself an operation. These cases remain open rather than inheriting a choice from illustrative code. ## Comparison | Consideration | Option A: `Register` | Option B: validated `Command` | Option C: deferred `Command` | | --- | --- | --- | --- | | Typical consumer task | Supply suite and install family | Supply suite, receive validated group, then install | Supply suite as a declaration to core installation | | Integration exports for registration | One convenience function | One complete-group factory | One deferred-declaration factory | | Suite errors reported at | Integration registration | Group construction | Core installation | | Domain commands | Ordinary core declarations | Same | Same; infallible constructors not required | | Atomicity | Each integration registration call | Each core registration batch | Each core registration batch, including preparation | | Standard assembly visible to callers | Integration-owned | Complete group handle exposed | Deferred group handle exposed | | Pre-assembly of mixed command collections | Separate domain declarations and family calls | Directly supported | Directly supported | | Additional core mechanism | Only for demonstrated ownership needs | Same | Deferred preparation, plus any ownership mechanism | | Dependency isolation and lazy acquisition | Supported | Supported | Supported | ## Recommendation Prefer **Option A: integration-owned `Register` functions with typed suites and declaration-only adapters**. Rank Option C second and Option B third. The representative consumers benefit from installing standard behavior directly; they do not demonstrate a need to collect standard command handles or roll back all family registrations together. Per-call atomicity and discarding a failed application setup are sufficient for these sketches. Use ordinary group construction and registry collision handling wherever they satisfy the selected ownership rules. Remove redundant prototype bookkeeping. Resolve the reservation distinction above before finalizing core signatures; add only the capability its actual responsibility requires. Do not ship both `Register` and `Command` merely to cover every possible assembly style. Options B and C are credible alternatives if pre-assembly or batch installation becomes a demonstrated consumer requirement. C reduces consumer error-handling boilerplate but adds deferred preparation to the core contract; B offers earlier validation with more explicit assembly steps. All preserve the same module and capability boundaries. Once this proposal is accepted, update the owning module roadmap with the chosen public registration shape and reservation semantics. Its staged implementation plan can then specify exact declarations and validation without asking the coding agent to invent a family framework. Prototype-compatible names, intermediate buildability, and mechanical file relocation are not selection criteria. ## Remaining decisions and incomplete areas 1. **Select the registration alternative.** A remains the recommendation; C and B remain proposals. Evaluate the full setup shown above rather than counting only the final installation calls. No registration shape is selected here. 2. **Settle the context-free case.** The simple examples still require `Suite[struct{}]`, a scope slice, and an unused loader argument. Decide whether that is acceptable or whether a focused convenience entry point should accept a loader without application context. Show its complete call site and avoid creating a parallel configuration language just to remove a few lines. 3. **Specify empty-suite behavior.** Decide independently for configuration and definition suites whether zero capabilities mean no installation, discovery only, or a declaration error. If selecting B or C, define the representation of an intentional no-op and distinguish it from an invalid command. 4. **Resolve pre-installation namespace reservations.** Identify the exact accepted rule for names before an integration is installed, reconcile ADR and architecture-policy language where necessary, and distinguish it from protecting an already installed family. For B or C, also specify how standard root placement is retained when declarations are passed through ordinary group APIs. Add a core distinction only for the selected rule. 5. **Finalize declaration capture and reuse.** Confirm the proposed separation of captured metadata and application-owned callback state. For B and C, decide whether capture happens at factory invocation or installation, and whether the returned declaration can be installed in multiple applications. Do not infer concurrent-dispatch guarantees from declaration reuse. 6. **Complete validation details.** Define how declaration-only adapters retain invalid wiring for the eventual validation boundary, how errors identify the affected family or scope, and how nil loaders differ from deliberately absent capabilities. If selecting C, specify the public preparation mechanism and show that it validates the whole batch before registry mutation without invoking providers or application context factories. 7. **Exercise typed customization.** Add one realistic selector/override example showing explicit presence and scope applicability. Specify absent `NewContext` behavior, particularly for pointer context types, and keep the configuration and definition selector contracts appropriately distinct. These details should support the chosen registration model without becoming a universal integration framework.