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

@@ -2,8 +2,10 @@
Status: module boundary accepted in ADR 0010; implementation planning remains
in progress and no module extraction has begun. The consumer API is finalized
and its ADR/architecture-policy alignment is complete. The next minor ReleaseKit
release will support individual module releases; CommandKit adoption is pending.
and its ADR/architecture-policy alignment is complete. The internal registration
and execution design is finalized, including both composition-question resolutions.
The next minor ReleaseKit release will support individual module releases;
CommandKit adoption is pending.
## Purpose and authority
@@ -15,7 +17,9 @@ module dependencies. ConfigKit-only consumers should not acquire PromptKit.
This roadmap records the targeted end state, selected module layout, and
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.
examples. The finalized [registration backend design](registration-backend.md)
owns internal structure, shared installation/execution mechanics, and the resolved
composition decisions. These documents describe the implementation target.
[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.
@@ -143,6 +147,16 @@ shapes merely because they already exist.
Built-in help/version remain reserved; optional integration roots are owned
when their real family is installed. ADR 0011 and the architecture policy now
record that lifetime; implementation remains pending.
- Use the finalized [internal registration and execution design](registration-backend.md).
Built-ins, application commands, and integration families share one
[installation path](registration-backend.md#one-installation-path-for-every-command)
and typed compiler. Captured metadata and invocation state have distinct owners;
the backend document defines the capture, binding, routing, and presentation
mechanics without adding another integration installation API.
- Apply the resolved [unused-binding validation boundary](registration-backend.md#resolved-unused-binding-validation-oq-1)
and [scope-error handoff](registration-backend.md#resolved-scope-error-handoff-oq-2).
The registration API incorporates the selected runnable-parent and scope-error
attachment declarations. No absence-validation overload is selected.
- 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
@@ -205,10 +219,13 @@ with refinements recorded in ADR 0011. ADR 0010's consumer-migration consequence
is superseded for the unreleased prototype transition. The architecture policy
reflects the selected core/integration facades, installed-family ownership, and
scope-aware configuration input validation. Earlier accepted decision text remains
intact.
intact. The internal backend design is also finalized; its runnable-child,
scope-error attachment, and unused-binding decisions are incorporated into the
API target. Both composition questions are resolved.
- Use the finalized [registration API](registration-api.md) and selected module
layout as the implementation target before moving public symbols.
layout together with the [backend design](registration-backend.md) as the
implementation target before moving public symbols.
- 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. Implement final exports and ownership from the
@@ -218,7 +235,8 @@ intact.
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,
- Prepare a staged implementation plan from this roadmap, the finalized API,
and the finalized [backend design](registration-backend.md),
preserving the unreleased-prototype organizing principle above.
Completion: the dependency direction, final API, and registration approach
@@ -227,14 +245,21 @@ are concrete enough to implement without inventing a general plugin framework.
### 2. Extract the integrations and isolate the core
- Establish the selected module layout, preserving the existing root import
path for the core.
path for the core. Use the backend document's [internal ownership boundaries](registration-backend.md#selected-internal-ownership)
when organizing private declarations, bindings, registry, execution, neutral
metadata, and the Cobra adapter.
- Move configuration declarations, registration, handlers, reports, and
ConfigKit adapters together. Move PromptKit declarations, handlers, reports,
and its configuration-scope adapter into the PromptKit integration.
- Replace access to root-private registry state and helpers with the smallest
supported interactions needed by those integrations. Preserve duplicate
registration handling and standard-family ownership; audit these boundaries
rather than assuming package relocation preserves them automatically.
- Use the shared installer and typed compiler for every command source, including
built-in help/version. Replace direct registry writes, duplicated family
collision checks, and integration-specific core state with the selected
captured-declaration path. Integration factories use public core contracts,
and `ckprompt` uses the selected scope-error attachment contract in `ckconfig`.
- Follow the backend document's [integration preparation design](registration-backend.md#integration-preparation-without-parallel-execution-frameworks)
for selected inputs and shared state. Preserve each operation's acquisition
and report timing while removing repeated scope selection and mixed-context
projection.
- Implement core outcomes, diagnostics, bindings, and command declarations using
the finalized API. Replace ConfigKit severity in core diagnostics
with the selected CommandKit-owned type. Place report encoding and upstream

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.

View File

@@ -1,8 +1,8 @@
# Registration and execution internals
Status: proposed internal design for the finalized consumer API; no implementation
has begun. Runnable-command children and scope-error attachment are selected API
additions to incorporate into the registration API document. Both composition
Status: finalized internal design; no implementation has begun. Runnable-command
children, scope-error attachment, and the unused-binding validation resolution are
incorporated into the [registration API](registration-api.md). Both composition
questions below are resolved; the staged implementation plan remains to be written.
## Purpose and authority
@@ -18,8 +18,9 @@ capture, installation, namespace, and input/state decisions.
[ADR 0010](../adr/0010-optional-integration-modules.md) owns module boundaries, and
the [architecture policy](../policy/architecture.md) governs implementation.
The [module roadmap](modules.md) owns the broader extraction and release work.
This document proposes internal mechanics and identifies gaps; it neither changes
the finalized API silently nor establishes additional consumer guarantees.
This document defines selected internal mechanics and records the resolved
composition decisions. Public declarations and consumer examples belong in the
registration API document; these changes remain unimplemented.
The prototype has no compatibility priority. Replace its parallel paths and
bookkeeping where they obstruct the selected design. Preserve deliberately
@@ -46,7 +47,7 @@ opportunities and structural risks, not measured performance regressions.
| `bridgeTree` in [parser.go](../../parser.go) caches neutral metadata; [internal/cobra/parse.go](../../internal/cobra/parse.go) creates a session per invocation | The useful metadata/session split should survive the refactor | Share captured metadata, refresh it after registry changes, and keep mutable backend values invocation-local |
| `commandAt` traverses names again after the adapter has already resolved a command | Duplicate route lookup can become another source of dispatch disagreement | Return a private command key from the adapter and resolve the captured runner directly |
## Proposed internal ownership
## Selected internal ownership
Keep most cohesive core machinery private in the root package. A facade does not
require forwarding-only files or duplicate representations for every public type.
@@ -58,7 +59,7 @@ Use an internal package where it protects an actual dependency boundary.
| Core bindings | Binding identity, typed conversion, presence/defaults, converted-value application, binding metadata validation | Scope names, ConfigKit/PromptKit types, application object cloning |
| Core registry | Validate/stage/commit installation batches and invalidate derived metadata | Integration-specific installed flags, command factories, report rendering |
| Core execution | Invoke the selected compiled runner, share the current presentation context, normalize/present outcomes | Application startup, provider schemas, process exit |
| `internal/commandmeta` (proposed) | Narrow backend-neutral routing/help DTOs, private command keys, parse result shapes | Public contracts, typed handlers, registry policy, upstream library types |
| `internal/commandmeta` (planned) | Narrow backend-neutral routing/help DTOs, private command keys, parse result shapes | Public contracts, typed handlers, registry policy, upstream library types |
| `internal/cobra` | Translate neutral metadata into one invocation session; route, collect raw occurrences, render help | Typed value conversion, declaration validation, family ownership, acquisitions |
| `ckconfig` private implementation | Capture scopes, compile applicable selectors, prepare selected inputs, acquire views, perform each configuration operation | Core registry state or private binding access |
| `ckprompt` private implementation | Capture definition providers, adapt engine inspection, prepare definition input/state, render definition reports | A second dispatcher/parser or configuration-suite installation |
@@ -164,9 +165,8 @@ Keep payload and children independent in the private model. Do not introduce a
new prohibition on runnable commands with children merely because separate leaf
and group constructors are convenient.
The selected public addition is `Children []Command` on `CommandSpec[I]`. The
registration API currently has no way to combine a handler with children; the
private model alone cannot expose the behavior described by
The selected public addition is [children on runnable commands](registration-api.md#runnable-parents-and-captured-children)
through `CommandSpec.Children`. It exposes the behavior described by
[ADR 0006](../adr/0006-command-grammar.md). Capture the child-handle slice using the
same ownership and immediate composition checks as `Group`, and enforce the same
placement rules at installation. `Group` remains a convenience for non-runnable
@@ -434,8 +434,8 @@ metadata-only, while the original invalid loader must remain an error. Wrapping
the nil loader in a non-nil acquisition closure would incorrectly defer it to
invocation. A core `WithError` only accepts a `Command`, not a scope.
**Selected:** add the integration-owned operation
`ckconfig.WithScopeError[I, S](scope Scope[I, S], err error) Scope[I, S]`.
**Selected:** use the integration-owned [retained scope-error operation](registration-api.md#retained-scope-errors),
`ckconfig.WithScopeError`, declared in the registration API document.
It returns a scope retaining the original issue, preserving supported error
identity. Repeated attachments preserve earlier causes and leave the original
scope unchanged; a nil error preserves the scope. Retained causes survive scope