Finalize the registration API roadmap
This commit is contained in:
@@ -12,8 +12,9 @@ should acquire neither ConfigKit nor PromptKit through CommandKit's package or
|
||||
module dependencies. ConfigKit-only consumers should not acquire PromptKit.
|
||||
|
||||
This roadmap records the targeted end state, selected module layout, and
|
||||
migration work. Recommendations under [Open Decisions](#open-decisions) remain
|
||||
proposals. It does not describe these changes as implemented.
|
||||
migration work. The finalized [registration API](registration-api.md) owns the
|
||||
selected declarations, installation semantics, and representative consumer
|
||||
examples. Neither document describes these changes as implemented.
|
||||
[ADR 0010](../adr/0010-optional-integration-modules.md) owns the accepted module
|
||||
boundary and convenient registration principles; it partially supersedes
|
||||
[ADR 0003](../adr/0003-public-facade.md), whose remaining principles still apply.
|
||||
@@ -130,11 +131,16 @@ shapes merely because they already exist.
|
||||
|
||||
### Selected API and release principles
|
||||
|
||||
- Design convenient integration registration around representative consumers.
|
||||
Reuse public command/group registration when it fits the final design. Add a
|
||||
narrow core capability only where a concrete responsibility requires it; do
|
||||
not introduce a general family framework or preserve private bookkeeping for
|
||||
its own sake. Exact signatures remain implementation-planning work.
|
||||
- Use the finalized [uniform registration API](registration-api.md#declaration-and-installation).
|
||||
Applications and integrations produce captured core command declarations and
|
||||
install them through core `New` or `App.Register`; construction errors are
|
||||
retained for installation-time validation. Integration factories provide
|
||||
complete families and focused conveniences. The API document owns exact
|
||||
signatures and the [input/shared-state separation](registration-api.md#why-inputs-and-shared-state-have-separate-types).
|
||||
- Follow the selected [namespace lifetime and reconciliation requirements](registration-api.md#namespace-reconciliation-and-implementation-handoff).
|
||||
Built-in help/version remain reserved; optional integration roots are owned
|
||||
when their real family is installed. Complete the identified durable-document
|
||||
alignment before implementing that lifetime.
|
||||
- Core diagnostics use a small CommandKit-owned severity type. Upstream
|
||||
diagnostic conversion belongs in the integrations, preserving selected
|
||||
classification, delivery, and error semantics without copying ConfigKit's
|
||||
@@ -188,25 +194,27 @@ needed for CommandKit's source-only releases.
|
||||
|
||||
## Work sequence
|
||||
|
||||
### 1. Align policy and settle implementation boundaries
|
||||
### 1. Align policy and prepare the selected API for implementation
|
||||
|
||||
Completed: ADR 0010 is accepted and ADR 0003 is marked partially superseded,
|
||||
with its decision text preserved. The partial status follows the explicitly
|
||||
authorized exception to the documentation policy for this decision.
|
||||
|
||||
- Resolve the remaining choices under Open Decisions before moving public
|
||||
symbols, using the selected module paths and package names above.
|
||||
- Align the architecture policy with ADR 0010 and review other
|
||||
ADRs for affected ownership statements without reopening unrelated decisions.
|
||||
- Use the finalized [registration API](registration-api.md) and selected module
|
||||
layout as the implementation target before moving public symbols.
|
||||
- Align the architecture policy with ADR 0010 and complete the registration
|
||||
API document's namespace reconciliation. Review other ADRs for affected
|
||||
ownership statements without reopening unrelated decisions.
|
||||
- Inventory root exports, imports, private helper use, and family registration
|
||||
behavior. Use the inventory as evidence of responsibilities and coupling, not
|
||||
as a list of APIs to preserve. Select final exports and ownership from consumer
|
||||
needs; no compatibility mapping for the unpublished prototype is required.
|
||||
- Sketch three representative consumers: core only, ConfigKit only, and both
|
||||
libraries. Confirm that combined configuration scopes remain straightforward
|
||||
and that registration meets ADR 0010's convenience principle without replacing
|
||||
removed boilerplate with new plumbing. Use these examples to evaluate the
|
||||
registration API; no fixed call count or particular options pattern is selected.
|
||||
as a list of APIs to preserve. Implement final exports and ownership from the
|
||||
selected API and consumer needs; no compatibility mapping for the unpublished prototype is required.
|
||||
- Use the API document's [three representative consumers](registration-api.md#complete-consumer-registration-examples):
|
||||
core only, ConfigKit only, and both libraries. They define the target wiring,
|
||||
including combined configuration scopes and independent definition inspection;
|
||||
port them into maintained examples as implementation lands.
|
||||
- Prepare a staged implementation plan from this roadmap and the finalized API,
|
||||
preserving the unreleased-prototype organizing principle above.
|
||||
|
||||
Completion: the dependency direction, final API, and registration approach
|
||||
are concrete enough to implement without inventing a general plugin framework.
|
||||
@@ -307,22 +315,3 @@ change after extraction unless an implementation dependency justifies combining
|
||||
them. Catalogs and requirement discovery are subsequent integration features.
|
||||
Semantic comparison retains the upstream-contract dependency recorded in
|
||||
[deferred work](deferred.md). None is needed to demonstrate module isolation.
|
||||
|
||||
## Open Decisions
|
||||
|
||||
### Exact registration API
|
||||
|
||||
The design principle is selected: convenient registration through the simplest
|
||||
coherent public core contracts, without preserving the prototype. Exact
|
||||
signatures and any additional core capability remain to be settled through the
|
||||
three representative consumer sketches and an ownership/collision review.
|
||||
|
||||
**Recommendation:** integrations construct their standard groups and install them
|
||||
through ordinary public registration, adding a narrow capability only if the
|
||||
selected ownership or atomicity behavior cannot be expressed coherently. Remove
|
||||
redundant private flags and checks rather than reproducing them publicly.
|
||||
|
||||
**Alternative:** an explicit public family declaration and installation API can
|
||||
represent ownership and collision handling if command groups cannot adequately
|
||||
express those responsibilities. It adds a lasting abstraction, so select it only
|
||||
for a demonstrated consumer or core responsibility, not to imitate current state.
|
||||
|
||||
@@ -1,868 +0,0 @@
|
||||
# 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 evaluated below, with a focused convenience function recommended for Option A.
|
||||
The generic sketches remain visible for comparison; they are not the recommended
|
||||
shortest setup for that case.
|
||||
|
||||
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. The concrete
|
||||
`DeferredGroup` proposal below makes that cost visible; 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.
|
||||
|
||||
The accepted ownership rule is clear for installed standard families: application
|
||||
registration cannot replace them or add private operations underneath them.
|
||||
[ADR 0001](../adr/0001-command-dispatch-and-inputs.md) also refers to reserved
|
||||
standard families, while the [architecture policy](../policy/architecture.md)
|
||||
explicitly describes an installed `promptkit` family. Neither provides a uniform,
|
||||
explicit lifetime rule for every uninstalled optional family. That interpretation
|
||||
must be decided deliberately rather than inferred from prototype flags.
|
||||
|
||||
**Recommendation:** help/version are reserved by core construction; an optional
|
||||
standard family claims its complete root namespace when installed. Before that,
|
||||
a domain group can occupy the name, but a subsequent integration installation
|
||||
then fails with a collision. Neither registration order can replace a winner.
|
||||
This avoids a core registry of every possible future integration namespace and
|
||||
does not prohibit core-only applications from using an otherwise absent family
|
||||
name. It preserves ownership throughout an installed family's lifetime.
|
||||
|
||||
**Alternative:** reserve the known standard names before integration installation.
|
||||
This catches domain misuse earlier but constrains applications that never install
|
||||
those integrations and requires the core to recognize the reserved names and an
|
||||
installation distinction. If selected, a narrow standard-group constructor can
|
||||
mark integration declarations; do not expose private registry state.
|
||||
|
||||
Before implementation, align the roadmap, policy, and ADR interpretation with
|
||||
the selected lifetime rule. If it changes an accepted decision, record that
|
||||
change through the ADR process; this mockup does not authorize weakening it.
|
||||
|
||||
Option A assembles and registers its group at the root internally. B and C expose
|
||||
handles, so they additionally need enforceable root placement. For B, a proposed
|
||||
`commandkit.RootGroup(name, summary, children...) (Command, error)` returns an
|
||||
opaque group declaration that normal `New`/`Register` accepts at the root and
|
||||
rejects as a child of another group. It adds one public core constructor for a
|
||||
concrete placement need, without exposing its handlers or creating a second
|
||||
`Family` type. C's deferred constructor carries the same root placement metadata.
|
||||
Moving a returned standard declaration under `tools`, for example, is an assembly
|
||||
error rather than silently producing `app tools config show`.
|
||||
|
||||
This public distinction represents placement, not proof of a caller's package
|
||||
identity. Do not invent package-origin authentication. An explicit `Family` type
|
||||
remains viable if a concrete responsibility eventually warrants a separate
|
||||
model, but it is not needed merely to relocate packages.
|
||||
|
||||
## 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. For B and C, the proposal below
|
||||
makes capture happen when a declaration factory is called; installation does not
|
||||
read mutable caller-owned declaration slices.
|
||||
|
||||
The empty-suite proposal below distinguishes a configuration suite with no
|
||||
scopes from a definition suite with no capabilities. B and C use an explicit
|
||||
no-op declaration for the latter; an invalid zero-value command remains an error.
|
||||
|
||||
## 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 | Ordinary groups/registration under the recommended reservation rule | Root-placement declaration and explicit no-op | Deferred preparation, root placement, and explicit no-op |
|
||||
| Dependency isolation and lazy acquisition | Supported | Supported | Supported |
|
||||
|
||||
## Recommendation
|
||||
|
||||
Prefer **Option A: integration-owned `Register` functions with typed suites and
|
||||
declaration-only adapters, plus the focused `RegisterApp` convenience below**.
|
||||
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.
|
||||
Adopt the proposed installed-family reservation rule only after its deliberate
|
||||
policy/ADR reconciliation. With that rule, A needs no extra core family or
|
||||
preparation abstraction. Add only capabilities serving a selected responsibility.
|
||||
|
||||
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.
|
||||
|
||||
## Additional design work and proposed resolutions
|
||||
|
||||
These proposals address the requested follow-up areas. They are recommendations
|
||||
for review, not new accepted contracts. Select their semantics before producing a
|
||||
decision-complete implementation plan.
|
||||
|
||||
### 1. Registration choice and the full setup cost
|
||||
|
||||
Retain the ranking **A, C, B**. All three generic sketches include real adapter,
|
||||
scope, and suite assembly. A's common case improves further with the focused
|
||||
entry point below. C has the shortest mixed declaration expression but needs a
|
||||
new public preparation contract, root-placement handling, and no-op representation.
|
||||
B exposes early validation and batch composition at the cost of more intermediate
|
||||
handles and error checks. None needs a transaction spanning separate calls.
|
||||
|
||||
### 2. A context-free application configuration entry point
|
||||
|
||||
**Recommendation for A:** add a single focused convenience function:
|
||||
|
||||
```go
|
||||
// In ckconfig:
|
||||
func RegisterApp[T any](
|
||||
app *commandkit.App,
|
||||
load func(context.Context, Selection) (*configkit.Result[T], error),
|
||||
) error
|
||||
```
|
||||
|
||||
Complete registration call site, assuming `greet` is already declared:
|
||||
|
||||
```go
|
||||
app, err := commandkit.New(identity, greet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ckconfig.RegisterApp(app, loadAppConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
Here `loadAppConfig` accepts only `context.Context` and `ckconfig.Selection`.
|
||||
`RegisterApp` adapts it internally, declares the standard `app` scope, and uses
|
||||
the same validation and installation path as `Register`. A nil original loader
|
||||
remains a declaration error. The default scope summary can be a plain application
|
||||
configuration description; callers needing a different summary, additional
|
||||
scopes, selectors, or state use `Register` with a typed suite.
|
||||
|
||||
This is an alternative entry point for a demonstrated common case, not an options
|
||||
language or a second scope model. It does not install a suite to which more
|
||||
scopes are appended later. An application needing composition selects the full
|
||||
suite entry point from the start.
|
||||
|
||||
**Alternative:** keep only generic `Register`. A context-free loader can use the
|
||||
following explicit wrapper and declaration, with no additional export:
|
||||
|
||||
```go
|
||||
if err := ckconfig.Register(app, ckconfig.Suite[struct{}]{
|
||||
Scopes: []ckconfig.Scope[struct{}]{
|
||||
ckconfig.FromConfigKit("app", "Application settings.",
|
||||
func(ctx context.Context, selection ckconfig.Selection, _ struct{}) (*configkit.Result[AppConfig], error) {
|
||||
return loadAppConfig(ctx, selection)
|
||||
}),
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
That is viable but makes simple consumers repeat plumbing unrelated to their
|
||||
application. Prefer the one focused export; do not automatically add analogous
|
||||
conveniences for every provider or family. If B or C is chosen, evaluate an
|
||||
`AppCommand` factory for this same use case instead of adding A's registration
|
||||
entry point to a factory-based design.
|
||||
|
||||
### 3. Empty suites and intentional no-op declarations
|
||||
|
||||
**Recommendation:** distinguish absence of resources from absence of an optional
|
||||
family's operations, and validate supplied declarations before treating them as
|
||||
empty.
|
||||
|
||||
| Declaration | Proposed result |
|
||||
| --- | --- |
|
||||
| Config suite with no scopes | Declaration error: there is no configuration resource to inspect |
|
||||
| Named config scopes with no inspection operations | Install `config scopes`; report those scopes and their actual empty capability sets |
|
||||
| Config scopes with some supplied operations | Install discovery and the operations supplied by at least one scope |
|
||||
| Definition suite with no capabilities and otherwise valid wiring | Successful no-op; no `promptkit` namespace or help entry installed |
|
||||
| Adapter constructed with a nil loader | Declaration error, even if its wrappers or selected capabilities would otherwise look empty |
|
||||
|
||||
Not calling a registration function remains the simplest way to omit a family.
|
||||
The no-op case supports capability-driven assembly without requiring a special
|
||||
conditional in every consuming application. It does not reserve an absent
|
||||
namespace under the proposed installed-family rule.
|
||||
|
||||
For B and C only, add an explicit proposed `commandkit.NoCommand() Command`
|
||||
representation. Core `New`/`Register` skips it. A zero-value `Command` remains
|
||||
invalid. B returns the explicit no-op after validating an empty definition suite;
|
||||
C's preparation returns it after validation. Invalid wiring is never swallowed
|
||||
as a no-op. A needs neither a public no-op handle nor that extra export.
|
||||
|
||||
**Alternatives:** omit all empty suites successfully, or install configuration
|
||||
discovery even with no scopes. Both avoid the configuration error, but may hide
|
||||
missing resource wiring or expose a namespace with nothing to discover. Requiring
|
||||
at least one definition capability is also viable, but adds conditional assembly
|
||||
for an intentionally optional family. These are product choices, not necessities
|
||||
inherited from the prototype.
|
||||
|
||||
### 4. Namespace reservation and placement
|
||||
|
||||
Prefer the installed-family lifetime rule described above. It protects complete
|
||||
standard groups while keeping unused names available to core-only consumers.
|
||||
Confirm or revise the relevant policy/ADR interpretation explicitly before
|
||||
implementation. Pre-installation reservation remains a viable alternative; its
|
||||
cost and required installation distinction are described above.
|
||||
|
||||
For B and C, enforce standard-root placement in the core declaration metadata,
|
||||
not only in documentation. An ordinary wrapper group must reject a standard
|
||||
root-only child during assembly/registration. A keeps standard handles private
|
||||
and performs its complete root registration inside the integration function.
|
||||
|
||||
### 5. Declaration capture, installation, and reuse
|
||||
|
||||
**Recommendation:** A captures registration metadata when `Register` is called;
|
||||
B and C capture it when their `Command` factory is called. Capture includes
|
||||
scope names and descriptions, capability lists, selector applicability, bindings,
|
||||
and callback references. Copy declaration collections and nested metadata
|
||||
collections needed for these semantics, rather than retaining the caller's
|
||||
mutable slices. Application callback closures and the state they reference stay
|
||||
application-owned; this is not a deep-copy guarantee for arbitrary values.
|
||||
|
||||
After `declaration := ckconfig.Command(suite)`, changing `suite.Scopes` or a scope's
|
||||
operation list does not change that declaration. C's deferred preparation reads
|
||||
its captured declaration, not the later contents of `suite`. Invalid recorded
|
||||
wiring also stays attached to that captured declaration.
|
||||
|
||||
B and C declarations may be installed in more than one application. Each registry
|
||||
captures its own installed metadata and each invocation receives its own parsed
|
||||
state. Callback references may remain shared, and consumer closures may share
|
||||
state deliberately. This does not promise concurrent dispatch or make caller
|
||||
state safe for concurrent use. A can likewise receive the same suite in separate
|
||||
registration calls; each call captures the suite as it exists at that time.
|
||||
|
||||
**Alternative:** C could capture at installation. That permits editing a suite
|
||||
through retained references until installation but makes a command handle's
|
||||
meaning depend on subsequent mutations and installation timing. Factory-time
|
||||
capture provides a clearer declaration model without freezing application state.
|
||||
|
||||
### 6. Declaration validation and Option C preparation
|
||||
|
||||
**Recommendation:** declaration-only adapters retain the original loader or a
|
||||
private construction issue in their owning integration declarations. Validators
|
||||
inspect that information before using generated wrappers. A non-nil wrapper must
|
||||
not make an originally nil loader appear valid. No public invalid-loader flag,
|
||||
root-private accessor, or provider invocation is needed.
|
||||
|
||||
For example, `ckprompt.FromEngine(nilLoader)` records invalid wiring; it is not
|
||||
an empty suite. A deliberately absent capability is an absent callback in a
|
||||
manually composed, otherwise valid suite, or an explicitly supported capability
|
||||
selection. A zero definition suite expresses intentional absence. An upstream
|
||||
configuration or named lookup error from a valid callback remains an invocation
|
||||
result; it is not a registration error.
|
||||
|
||||
Errors identify the family and, where applicable, the scope, capability, or flag.
|
||||
For example, `config scope "app": loader is nil` and
|
||||
`promptkit: engine loader is nil` are illustrative diagnostic content, not exact
|
||||
string contracts. Preserve wrapped error identity where a fallible declaration
|
||||
operation supplies an error. Validate scope uniqueness, supported capability
|
||||
wiring, selector declarations, and reserved-flag collisions from metadata.
|
||||
Integrations validate their own provider and scope metadata; core command/binding
|
||||
construction validates opaque binding handles and command/flag conflicts. Wrap
|
||||
those errors with the integration context rather than inspecting private flag
|
||||
fields or duplicating parser validation. This does not require a new public
|
||||
registry inspection or binding bridge just for declaration validation.
|
||||
Do not validate files, credentials, or application configuration during assembly.
|
||||
|
||||
If C is selected, its concrete proposed public preparation mechanism is:
|
||||
|
||||
```go
|
||||
// In commandkit; needed only for Option C:
|
||||
func DeferredGroup(
|
||||
name, summary string,
|
||||
prepare func() (Command, error),
|
||||
) Command
|
||||
```
|
||||
|
||||
This is a root-only deferred command declaration, not a startup hook. Its opaque
|
||||
handle carries root identity and the preparation callback. Integration factories
|
||||
capture the typed suite and supply their own metadata-only preparation function:
|
||||
|
||||
```go
|
||||
func Command[C any](suite Suite[C]) commandkit.Command {
|
||||
captured := captureSuiteDeclaration(suite) // Integration-private metadata copy.
|
||||
return commandkit.DeferredGroup("config", "Inspect configuration.",
|
||||
func() (commandkit.Command, error) {
|
||||
// Validate captured wiring and assemble the complete standard group.
|
||||
// No acquisition callback or NewContext callback is invoked here.
|
||||
return prepareConfigGroup(captured)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
`captureSuiteDeclaration` and `prepareConfigGroup` identify integration-owned
|
||||
responsibilities; they are not additional public exports. Capturing metadata can
|
||||
record an invalid declaration without returning an error. Preparation reports
|
||||
that error at installation.
|
||||
|
||||
Core `New`/`Register` prepares the entire supplied batch before mutation, checks
|
||||
resolved declarations and placement against each other and the existing registry,
|
||||
and commits only after success. A nil preparation callback or a prepared group's
|
||||
root differing from its declared root is an assembly error. A deliberate no-op
|
||||
is permitted; malformed declarations are not. Failure leaves the registry intact,
|
||||
but cannot undo arbitrary effects inside a caller-supplied callback. Standard
|
||||
integration preparation functions themselves are responsible for remaining
|
||||
metadata-only. Registering the same declaration in another app may prepare it
|
||||
again; no cross-application preparation cache or exactly-once promise is needed.
|
||||
|
||||
**Alternative for C:** store a captured wiring error and an already assembled
|
||||
neutral group in a command declaration, reporting the error only at installation.
|
||||
This can avoid a general preparation callback if all necessary assembly can be
|
||||
completed without error-returning work. Its viability depends on the final core
|
||||
constructors; it must not discard validation failures or expose an untyped payload.
|
||||
C's callback form is the concrete mockup here, not a requirement to choose C.
|
||||
|
||||
### 7. Typed customization and absent context factories
|
||||
|
||||
**Recommendation:** an omitted `NewContext` supplies the zero value of `C` for
|
||||
the selected invocation. For a struct, that is a fresh zero-valued struct; for
|
||||
`*RunState`, it is nil. Do not silently allocate arbitrary pointer contexts or
|
||||
reject every pointer-context declaration. A loader may intentionally accept nil.
|
||||
Applications whose binding callbacks or loaders require allocated state supply
|
||||
`NewContext`. A supplied factory returning nil has the same caller-owned meaning;
|
||||
this is not a promise to inspect or repair arbitrary callback state.
|
||||
|
||||
The following realistic A sketch uses a value input with a shared cache pointer.
|
||||
All integration assembly is visible. `strconv.Atoi`, the string parser, and core
|
||||
bindings are illustrative public binding primitives; their final names do not
|
||||
constrain the registration choice.
|
||||
|
||||
```go
|
||||
type RunInput struct {
|
||||
Campaign commandkit.Supplied[string]
|
||||
Limit commandkit.Supplied[int]
|
||||
Cache *RunCache
|
||||
}
|
||||
|
||||
campaignFlag := commandkit.ValueFlag[RunInput, string](
|
||||
commandkit.FlagMeta{Name: "campaign", Usage: "Select campaign inputs."},
|
||||
nil,
|
||||
func(raw string) (string, error) { return raw, nil },
|
||||
func(input *RunInput, value commandkit.Supplied[string]) {
|
||||
input.Campaign = value
|
||||
},
|
||||
)
|
||||
limitFlag := commandkit.ValueFlag[RunInput, int](
|
||||
commandkit.FlagMeta{Name: "limit", Usage: "Override the application's item limit."},
|
||||
nil, strconv.Atoi,
|
||||
func(input *RunInput, value commandkit.Supplied[int]) {
|
||||
input.Limit = value
|
||||
},
|
||||
)
|
||||
newInput := func() RunInput {
|
||||
return RunInput{Cache: newRunCache()}
|
||||
}
|
||||
|
||||
app, err := commandkit.New(identity, sessionGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ckconfig.Register(app, ckconfig.Suite[RunInput]{
|
||||
NewContext: newInput,
|
||||
Selectors: []ckconfig.Selector[RunInput]{
|
||||
{Binding: campaignFlag, Scopes: []string{"app", "promptkit"}},
|
||||
{Binding: limitFlag, Scopes: []string{"app"}},
|
||||
},
|
||||
Scopes: []ckconfig.Scope[RunInput]{
|
||||
ckconfig.FromConfigKit("app", "Application settings.", loadAppConfig),
|
||||
ckprompt.ConfigScope(loadEngine),
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
definitions := ckprompt.FromEngine(loadEngine)
|
||||
definitions.NewContext = newInput
|
||||
definitions.Selectors = []commandkit.Flag[RunInput]{campaignFlag}
|
||||
if err := ckprompt.Register(app, definitions); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
Configuration selectors carry configuration-scope applicability. Definition
|
||||
selectors are plain typed flags for that family's input; they do not need a
|
||||
list containing a fictitious configuration scope. Each loader in this sketch
|
||||
receives `RunInput`; registration and help call neither loader nor `newInput`.
|
||||
|
||||
For `app config show --scope all --campaign spring --limit=0`, both scopes receive
|
||||
the campaign selector, but only the `app` scope receives the limit override.
|
||||
Its loader checks `input.Limit.Set` before applying `input.Limit.Value`; explicit
|
||||
zero overrides an inherited limit, while omission leaves it unchanged. An explicit
|
||||
`--campaign=` similarly remains supplied with an empty value. The bindings retain
|
||||
presence rather than inferring it from nonzero values.
|
||||
|
||||
Scope filtering uses binding metadata so a selector not applicable to a scope
|
||||
is not copied into that scope's input. It does not erase shared acquisition state
|
||||
such as `Cache`, or require deep-copying application state. Selecting only a scope
|
||||
to which a supplied selector does not apply does not add a new rejection rule:
|
||||
the selector simply is not delivered to that scope. If a stricter unused-selector
|
||||
policy is desired, decide it separately rather than inventing it in registration.
|
||||
|
||||
For `app promptkit show profile briefing --campaign spring`, definition inspection
|
||||
receives the campaign input and fresh invocation state; it does not receive the
|
||||
configuration-only limit flag or invoke the application configuration scope.
|
||||
|
||||
**Alternative:** require a context factory for every stateful suite. That makes
|
||||
initialization explicit but adds boilerplate for value inputs that work correctly
|
||||
with zero values. Prefer the zero-value default plus an explicit factory when
|
||||
actual application state needs one.
|
||||
|
||||
## Decisions still requiring approval
|
||||
|
||||
The requested design work is now concrete, but these proposals remain unapproved:
|
||||
|
||||
1. Choose A with `RegisterApp`, C, or B.
|
||||
2. Confirm empty configuration errors, discovery-only named scopes, and empty
|
||||
definition no-ops; B/C additionally need explicit no-op declarations.
|
||||
3. Confirm installed-family namespace reservation and reconcile policy/ADR wording,
|
||||
or choose pre-installation reservation and its narrow core distinction.
|
||||
4. Confirm declaration capture timing and reuse, and the proposed separation from
|
||||
caller-owned state and concurrent-dispatch guarantees.
|
||||
5. Confirm the zero-value context default and the typed customization contracts.
|
||||
|
||||
If C is chosen, additionally select its preparation mechanism; if B is chosen,
|
||||
select the root-placement constructor. Those extra exports are conditional and
|
||||
are not additions to the recommended A API.
|
||||
@@ -1,9 +1,8 @@
|
||||
# Unified command registration proposal
|
||||
# Unified command registration API
|
||||
|
||||
Status: direction agreed; declarations, consumer examples, and design checks are
|
||||
prepared for final review. Supporting API names and input/state separation below
|
||||
are proposed details of that direction. No implementation is authorized by this
|
||||
document alone.
|
||||
Status: finalized API design; implementation is pending. The declarations,
|
||||
consumer examples, and design checks below define the selected target API.
|
||||
No implementation is authorized by this document alone.
|
||||
|
||||
## Purpose and authority
|
||||
|
||||
@@ -12,11 +11,9 @@ them through the same core API. Installation validates each batch before registr
|
||||
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.
|
||||
This document defines the selected registration API: capture declarations and
|
||||
any construction errors eagerly, then report errors at installation. The
|
||||
completed alternatives comparison is retained in Git history.
|
||||
|
||||
[ADR 0001](../adr/0001-command-dispatch-and-inputs.md) owns explicit registration,
|
||||
typed inputs, and command ownership.
|
||||
@@ -24,7 +21,7 @@ typed inputs, and command ownership.
|
||||
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.
|
||||
before implementation; this API design 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,
|
||||
@@ -60,7 +57,8 @@ or installed. Ordinary command, group, scope, and suite adapter construction
|
||||
does not require a separate error check at each call site.
|
||||
|
||||
The uniform construction shape is below. The complete declarations and examples
|
||||
later in this document provide the supporting fields and semantics for review.
|
||||
later in this document provide the supporting fields and semantics for
|
||||
implementation.
|
||||
|
||||
```go
|
||||
// Core package:
|
||||
@@ -80,7 +78,7 @@ func Command[I, S any](suite Suite[I, S]) commandkit.Command
|
||||
```
|
||||
|
||||
Here `I` is the suite's CLI input and `S` is its shared invocation state. This
|
||||
proposed separation resolves the pointer-isolation issue without a clone hook or
|
||||
selected separation resolves the pointer-isolation issue without a clone hook or
|
||||
an application-wide type. Future integrations can accept declarations suited to
|
||||
their resources while returning the same core `Command`; the core does not
|
||||
prescribe a universal suite.
|
||||
@@ -98,11 +96,10 @@ may occur then, but its errors remain attached to the declaration for installati
|
||||
to report. Internally fallible assembly can capture an error without returning it
|
||||
to the consumer at every construction step.
|
||||
|
||||
Use the proposed `WithError(command, err)` core operation to retain integration
|
||||
Use the selected `WithError(command, err)` core operation to retain integration
|
||||
construction errors in opaque declarations. Its complete signature and no-op
|
||||
interaction are specified below. 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.
|
||||
root-private access, or a deferred preparation lifecycle.
|
||||
|
||||
`New` and `Register` report retained declaration errors and validate the batch,
|
||||
including bindings, root placement, and collisions, before registry mutation.
|
||||
@@ -155,7 +152,7 @@ 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 the proposed `RootGroup` constructor specified below. It
|
||||
`Command` type with the selected `RootGroup` constructor specified below. It
|
||||
represents placement, not authentication of the declaring package. Installed
|
||||
standard families cannot be extended with private application operations or have
|
||||
their handlers replaced.
|
||||
@@ -200,14 +197,14 @@ factory when initialized state is needed. A supplied factory returning nil retai
|
||||
its application-owned meaning.
|
||||
|
||||
Scope-specific parsed values stay separate from shared acquisition state through
|
||||
the proposed `Suite[I, S]`: `NewInput` initializes each selected scope's input,
|
||||
the selected `Suite[I, S]`: `NewInput` initializes each selected scope's input,
|
||||
while `NewState` initializes `S` once for the selected inspection invocation.
|
||||
Providers receive both explicitly. Pointer inputs are independently initialized,
|
||||
not shallow copies of a single pointer. The detailed lifetime table below makes
|
||||
this change from the comparison's mixed context model explicit; it does not
|
||||
require arbitrary deep copies or prohibit pointer inputs or shared pointer state.
|
||||
|
||||
## Proposed core declarations
|
||||
## Selected core declarations
|
||||
|
||||
The following declarations specify the public registration and binding surface
|
||||
used by these examples. Opaque types retain unexported representation; these are
|
||||
@@ -485,7 +482,7 @@ inspection applies the whole captured flag set once. This explains the public
|
||||
seam; private input structs and the exact assembly algorithm remain implementation
|
||||
choices.
|
||||
|
||||
## Proposed configuration integration declarations
|
||||
## Selected configuration integration declarations
|
||||
|
||||
The `ckconfig` package owns the following contracts. Only the four implemented
|
||||
inspection operations appear here; comparison remains deferred. `config scopes`
|
||||
@@ -606,7 +603,7 @@ arguments nor demands a forwarding wrapper from the caller. A nil loader becomes
|
||||
a retained `config`/`app` declaration error. The full suite is used for composition;
|
||||
there is no incremental scope append operation after installation.
|
||||
|
||||
## Proposed PromptKit integration declarations
|
||||
## Selected PromptKit integration declarations
|
||||
|
||||
The `ckprompt` package uses `ckconfig` selections and scope contracts through its
|
||||
selected module dependency. Definition selectors are plain typed flags; they do
|
||||
@@ -674,10 +671,10 @@ custom inspection callback; this does not redefine upstream version semantics.
|
||||
|
||||
### Why inputs and shared state have separate types
|
||||
|
||||
The comparison used one context type holding both selector fields and a cache.
|
||||
Combining selector fields and a cache in one context type obscures their lifetimes.
|
||||
A shared pointer to that object cannot isolate selector fields between scopes.
|
||||
Cloning arbitrary application graphs would add a promise CommandKit cannot make.
|
||||
The proposed `Suite[I, S]` makes the actual two responsibilities explicit:
|
||||
The selected `Suite[I, S]` makes the actual two responsibilities explicit:
|
||||
|
||||
- `I`: per-scope/per-command CLI input, initialized and populated independently.
|
||||
- `S`: shared acquisition state for one selected inspection invocation.
|
||||
@@ -685,8 +682,8 @@ The proposed `Suite[I, S]` makes the actual two responsibilities explicit:
|
||||
There is no public clone hook and no reflection-based allocation of pointer inputs.
|
||||
Two type parameters cost some declaration syntax in composed consumers, but make
|
||||
sharing deliberate and lifetimes predictable. `AppCommand` hides both for the
|
||||
simple case. This is a supporting API detail proposed for final review, not a
|
||||
change to unified construction or the independently owned suite principle.
|
||||
simple case. This selected separation supports unified construction and
|
||||
independently owned suites.
|
||||
|
||||
### Factory timing
|
||||
|
||||
@@ -735,7 +732,7 @@ an invocation cache. No concurrent-dispatch guarantee follows from these rules.
|
||||
|
||||
## Complete consumer registration examples
|
||||
|
||||
These examples target the proposed APIs, not today's prototype. Each block is a
|
||||
These examples target the selected APIs, not today's prototype. Each block is a
|
||||
complete consumer assembly function with imports and input declarations. Domain
|
||||
loaders and domain execution are supplied as typed function parameters where
|
||||
appropriate: their bodies are application behavior, not hidden registration
|
||||
@@ -1059,7 +1056,7 @@ plus discovery. It does not invent comparison or catalog providers.
|
||||
|
||||
## Focused design checks and expected outcomes
|
||||
|
||||
These checks specify observable design results for review, not a requirement to
|
||||
These checks specify observable target behavior, not a requirement to
|
||||
add an exhaustive new test matrix. They use the declarations above; `identity`
|
||||
has a valid name. Declaration errors are returned Go errors from installation,
|
||||
not invocation status codes. Provider failures and ordinary input errors retain
|
||||
@@ -1215,40 +1212,15 @@ This clarifies an ambiguity rather than assuming that earlier broad reservation
|
||||
wording already specifies the same lifetime. The durable documents need deliberate
|
||||
alignment before implementation:
|
||||
|
||||
| Owner | Required alignment after final proposal review |
|
||||
| Owner | Required alignment before implementation |
|
||||
| --- | --- |
|
||||
| ADR 0001 | Clarify the reservation lifetime through the ADR change process if its accepted wording is interpreted as unconditional reservation |
|
||||
| ADR 0010 | Retain core/integration dependency and convenient-composition boundaries; exact factories now return captured declarations |
|
||||
| Architecture policy | State installed-family ownership uniformly for config and promptkit; align its public facade rules with ADR 0010 |
|
||||
| Module roadmap | Replace the open registration alternative with this selected uniform model and reviewed supporting signatures |
|
||||
| Staged module implementation plan | Specify eager capture, error retention, neutral bindings, scope isolation, examples, and release adoption using the reviewed API |
|
||||
| Staged module implementation plan | Specify eager capture, error retention, neutral bindings, scope isolation, examples, and release adoption using this finalized API |
|
||||
|
||||
A new or superseding ADR can record the lifetime clarification where needed;
|
||||
accepted decision text is not revised silently. This authoring pass updates only
|
||||
this proposal, leaving policy/ADR reconciliation and the staged plan for the
|
||||
explicit post-review handoff requested above. It does not claim those documents
|
||||
are already aligned or authorize implementation.
|
||||
|
||||
## Review checklist
|
||||
|
||||
The requested declaration/example work is now present:
|
||||
|
||||
1. Core-only typed command/group construction with a single installation boundary.
|
||||
2. Context-free `AppCommand` with the complete loader signature and no dummy state.
|
||||
3. Visible composed suite assembly, application command, and independent capability
|
||||
selection.
|
||||
4. Concrete `WithError`, `RootGroup`, and `NoCommand` contracts, with no delayed
|
||||
preparation or private-state access.
|
||||
5. Typed selector composition with stable binding identity, scope-aware validation,
|
||||
usage/wiring error categories, distinct pointer inputs, and one shared
|
||||
invocation cache through `NewState`, with normal and failure-path timing.
|
||||
6. Focused expected outcomes for invalid wiring, collisions, placement, emptiness,
|
||||
capture, and reuse.
|
||||
7. An explicit durable-document reconciliation and post-review implementation-plan
|
||||
handoff, without prematurely editing accepted records or implementing code.
|
||||
|
||||
Final review should particularly consider the proposed `Suite[I, S]` separation
|
||||
and the narrow `CaptureFlags`/`FlagValues` composition seam. These are now concrete
|
||||
supporting API choices rather than unresolved isolation mechanics. The uniform
|
||||
declaration model is selected; these names and details await the user's final
|
||||
review before implementation.
|
||||
accepted decision text is not revised silently. The [module roadmap](modules.md)
|
||||
now references this finalized API. Policy/ADR reconciliation and a staged
|
||||
implementation plan remain pending; this document does not claim that those
|
||||
records are already aligned or authorize implementation.
|
||||
Reference in New Issue
Block a user