Link the finalized registration backend design

This commit is contained in:
2026-10-10 23:44:28 +00:00
parent 336d489727
commit bb849ff3e1
3 changed files with 122 additions and 37 deletions

View File

@@ -1,7 +1,9 @@
# Unified command registration API
Status: finalized API design; implementation is pending. The declarations,
consumer examples, and design checks below define the selected target API.
consumer examples, and design checks below define the selected target API,
including the additions and clarifications selected in the finalized backend
design.
No implementation is authorized by this document alone.
## Purpose and authority
@@ -23,7 +25,10 @@ boundaries and convenient integration registration.
captured-declaration model, optional-family namespace lifetime, and separated
input/shared-state lifetimes. The [architecture policy](../policy/architecture.md)
reflects those boundaries. The [module roadmap](modules.md) tracks extraction
work; this API design remains a target rather than implemented behavior.
work. The finalized [backend design](registration-backend.md) owns internal
structure and shared implementation mechanics. Its resolved composition choices
are incorporated below; this API design remains a target rather than implemented
behavior.
Design for the best consumer API without preserving current implementation
signatures or machinery. Existing code can supply evidence and reusable work,
@@ -109,8 +114,10 @@ A failed `New` yields no usable application. A failed `Register` leaves the
existing registry intact. No rollback guarantee spans separate registration calls.
Declaration construction, installation, help, and version do not invoke provider
acquisition or invocation input/context factories. They do not validate files,
credentials, or application configuration. Invalid wiring fails at installation;
acquisition or application/suite input/state factories. Built-in help may prepare
its own private typed input through the common compiler, without initializing the
target command or invoking its validation/execution callbacks. These paths do not
validate files, credentials, or application configuration. Invalid wiring fails at installation;
an upstream error returned by a valid provider remains an invocation result.
An originally nil loader remains invalid wiring even if an adapter would wrap
@@ -171,8 +178,14 @@ The selected empty-suite behavior is:
Use an explicit `commandkit.NoCommand()` declaration for intentional absence.
Core installation skips it. A zero-value `Command` remains invalid. Integration
factories validate or retain wiring errors before representing an empty family
as a no-op; absence must not swallow errors. A no-op does not reserve a namespace.
factories retain adapter-construction errors before representing an empty family
as a no-op. Validate bindings needed by generated commands; unused selector
bindings are ignored when they contribute to no generated inspection command.
Integration-owned scope identifiers, applicability references, and enabled-provider
wiring still undergo validation. Ignoring unused bindings does not erase retained
adapter errors. This is the selected [unused-binding resolution](registration-backend.md#resolved-unused-binding-validation-oq-1).
A no-op does not reserve a namespace, and `NoCommand()` needs no validation-bearing
overload or separate public declaration validator.
## Capture, reuse, and invocation inputs
@@ -273,6 +286,7 @@ type CommandSpec[I any] struct {
Name string
Summary string
Description string
Children []Command
NewInput func() I
Flags []Flag[I]
Arguments []Argument[I]
@@ -367,6 +381,17 @@ installed tree, extract its handlers, or append children after capture. Collecti
in `CommandSpec`, groups, and defaults are captured as appropriate; runtime
closure state and diagnostic writer effects remain caller-owned.
### Runnable parents and captured children
`CommandSpec.Children` combines a typed handler with child declarations. Capture
its child-handle slice and apply the same immediate composition, collision, and
installation placement checks used by `Group`. A runnable parent follows its
handler and operand requirements on bare invocation; help describes the parent
and children under [ADR 0006](../adr/0006-command-grammar.md). `Group` remains the
convenience for a non-runnable parent. Neither form permits mutation or child
append operations after capture. The [backend design](registration-backend.md#immutable-declaration-records)
uses the same captured representation and installation path for both.
### Retained errors, placement, and no-ops
`WithError` returns a new captured declaration retaining a non-nil error alongside
@@ -565,6 +590,8 @@ func FromConfigKit[I, S, T any](
load func(context.Context, Selection, I, S) (*configkit.Result[T], error),
) Scope[I, S]
func WithScopeError[I, S any](scope Scope[I, S], err error) Scope[I, S]
func Command[I, S any](suite Suite[I, S]) commandkit.Command
func AppCommand[T any](
@@ -581,9 +608,11 @@ validation diagnostics at invocation time, preserving current selected reports
and classifications rather than implementing a second configuration engine.
An empty selector scope list applies to all registered scopes; a nonempty list
identifies its applicable registered scopes. Unknown identifiers and reserved
standard-flag collisions are declaration errors. Scope identifiers, capability
selection, defaults, and aggregate availability follow
identifies its applicable registered scopes. Unknown scope identifiers are
declaration errors. Bindings used by generated commands undergo core validation,
including standard-flag collisions; unused selector bindings follow the selected
absence/unused-binding rule above. Scope identifiers, capability selection,
defaults, and aggregate availability follow
[ADR 0009](../adr/0009-configuration-scopes.md). Non-applicable selectors are not
applied to a scope's input; this adds no new unused-selector rejection policy.
`CheckInput` receives the registered name of each requested scope alongside its
@@ -605,6 +634,21 @@ 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.
### Retained scope errors
`ckconfig.WithScopeError` returns a scope retaining a non-nil construction error
alongside earlier causes, leaving the original scope unchanged. A nil error
preserves the scope. Retained causes preserve supported `errors.Is`/`errors.As`
identity across scope copies and narrowing of `Operations`. The family factory
collects those issues into its command declaration before capability selection
can discard them; no acquisition callback runs to discover a construction error.
`ckprompt.ConfigScope` uses this public operation to retain original loader errors
without accessing private `ckconfig` state. It otherwise declares the ordinary
scope fields and capabilities. The [scope-error resolution](registration-backend.md#resolved-scope-error-handoff-oq-2)
records the rationale and rejected alternative; the operation adds no new scope
installation lifecycle.
## Selected PromptKit integration declarations
The `ckprompt` package uses `ckconfig` selections and scope contracts through its
@@ -662,7 +706,9 @@ maps the owning library's public diagnostics into core-owned `Diagnostic` values
Definition providers preserve diagnostics even when a named lookup fails.
Consumers may leave either definition callback nil to omit that capability.
A valid suite with both nil is an explicit no-op after eager declaration validation.
A suite with both callbacks nil contributes no commands and ignores unused
selector bindings. It returns a no-op retaining any adapter-construction errors;
those errors still fail installation.
A nil loader supplied to `FromEngine` is invalid wiring, not missing capability;
setting its callbacks nil does not erase the retained error. For prompt lookup,
the adapter passes the requested version value to PromptKit, with omission using
@@ -706,7 +752,18 @@ succeed. The no-call rows remain unconditional for CommandKit-owned paths.
| Configuration inspection of several scopes | Once per selected scope | Once, shared by selected scopes | Requested scopes, in selected operation order |
| Named definition inspection | Once for the selected command | Once for that invocation | Selected inspection callback |
The factory counts describe application inputs and suite input/state. Built-in
help may prepare its own private input through the common compiler; it still
leaves the target application's and suites' factories, validation/execution
callbacks, and providers uninvoked.
Input conversion and scope/capability preflight use metadata without acquisition.
All supplied occurrences on an installed command undergo conversion, including
selectors inapplicable to the requested scope and values supplied with help.
Scope filtering affects value application and required-value checks, not
conversion; an earlier malformed singleton occurrence remains an error even if a
later one is valid.
For configuration execution, initialize and populate each requested scope input,
then run its `CheckInput`; initialize the shared state before acquisition after
input checks succeed. The input/state callbacks do not run for unselected scopes
@@ -1126,7 +1183,7 @@ of arbitrary effects inside application callbacks.
| --- | --- |
| `ckconfig.Command(ckconfig.Suite[struct{}, struct{}]{})` | Error: no configuration scopes |
| One named scope with empty operations and nil acquisition | Success; `config scopes` describes it, no inspection verbs supplied by that scope |
| Valid definition suite with no callbacks | Explicit absence, no namespace claimed |
| Definition suite with no callbacks and unused selector bindings | Explicit absence, unused bindings ignored; retained adapter errors still fail |
| `NoCommand()` beside ordinary children | Skipped, no placeholder help item |
| `WithError(NoCommand(), sentinel)` | Error retaining sentinel identity |
| Zero `Command{}` at the root or as a child | Declaration error |
@@ -1219,10 +1276,13 @@ The documentation alignment is complete:
| [ADR 0001 status](../adr/0001-command-dispatch-and-inputs.md#status) | Remains accepted, with refinements recorded in ADR 0011 and its decision text preserved |
| [ADR 0010](../adr/0010-optional-integration-modules.md) | Core/integration dependency boundaries remain in effect; ADR 0011 refines registration and supersedes the migration-guide requirement for this unreleased prototype |
| [Architecture policy](../policy/architecture.md) | Uniform installed-family ownership, optional integration facades, and declaration/input boundaries |
| [Module roadmap](modules.md) | Final API selected; documentation alignment completed; extraction and release adoption still pending |
| [Module roadmap](modules.md) | Final API and backend design selected; extraction and release adoption still pending |
| [Backend design](registration-backend.md) | Shared internal pipeline, runnable children, scope-error attachment, unused-binding validation, and both composition-question resolutions |
A staged module implementation plan remains to be written from this finalized
API and the module roadmap. It should specify eager capture, error retention,
neutral bindings, scope isolation, maintained examples, and release adoption.
API, the finalized [backend design](registration-backend.md), and the module
roadmap. Both composition questions are resolved. The plan should specify eager
capture, error retention, neutral bindings, scope isolation, maintained examples,
and release adoption.
The documentation alignment neither implements these changes nor authorizes
implementation by itself.