483 lines
30 KiB
Markdown
483 lines
30 KiB
Markdown
# Registration and execution internals
|
|
|
|
Status: proposed internal design for the finalized consumer API; no implementation
|
|
has begun. Two public composition seams identified below need resolution before
|
|
the staged implementation plan is finalized.
|
|
|
|
## Purpose and authority
|
|
|
|
Provide the [registration API](registration-api.md) through one coherent internal
|
|
system. Application commands, optional integration families, and built-in core
|
|
commands should use the same declaration, installation, routing, and execution
|
|
machinery. Command-specific handlers remain different where their responsibilities
|
|
are different.
|
|
|
|
[ADR 0011](../adr/0011-captured-declarations-and-installation.md) owns the accepted
|
|
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.
|
|
|
|
The prototype has no compatibility priority. Replace its parallel paths and
|
|
bookkeeping where they obstruct the selected design. Preserve deliberately
|
|
selected grammar, presence, outcomes, reports, and callback lifetimes. Private
|
|
names below describe responsibilities rather than required exported symbols or a
|
|
mandated file layout. This is design work, not a staged implementation plan.
|
|
|
|
## Baseline findings
|
|
|
|
The following findings come from the current implementation. They identify reuse
|
|
opportunities and structural risks, not measured performance regressions.
|
|
|
|
| Evidence | Pain point | Proposed solution |
|
|
| --- | --- | --- |
|
|
| `New` in [app.go](../../app.go) inserts help/version placeholders; `installBuiltins` in [builtins.go](../../builtins.go) replaces them directly | Built-ins bypass ordinary registry installation and have separate reserved-node bookkeeping | Construct real built-in declarations and install them with caller declarations in one batch |
|
|
| `Register` in [registry.go](../../registry.go) already stages roots before committing | Useful atomic installation exists, but privileged writers can bypass it | Make this the sole registry mutation boundary for every command source |
|
|
| `RegisterConfig` in [config.go](../../config.go) checks the root namespace and `App.configRegistered` before calling `Register` | The configuration installer duplicates collision policy and carries integration state in the core | Integration factories return root-only declarations; core installation decides collisions |
|
|
| `RegisterPromptKit` in [promptkit.go](../../promptkit.go) returns early when both inspection callbacks are nil | An absence shortcut can hide construction or metadata errors | Retain errors before representing absence; installation checks errors before skipping a no-op |
|
|
| `Group`, `validateCommandDeclaration`, and `cloneCommands` in [command.go](../../command.go) recursively validate/copy child trees; registration repeats this | Nested assembly can revisit a subtree at each ancestor and again at installation | Capture immutable child handles and local metadata once; perform contextual installation checks in one traversal |
|
|
| `normalizeConfigSuite` and `normalizePromptKitSuite` repeat flag-name, short-name, duplicate, and wiring rules | Moving integrations into modules could create several competing implementations of core binding policy | Validate generated command bindings in the core; integrations validate only their own suite semantics |
|
|
| `adaptContextSelector` and `contextsForScopes` in [selectors.go](../../selectors.go) apply selectors to mixed context storage, then copy/project it again | Selector storage and shared state have tangled lifetimes; pointer contexts can alias | Capture converted values once; initialize each scope input independently; create shared state after all input checks |
|
|
| Configuration `CheckInput` calls preflight, while `runConfigShow` and `runConfigValidation` select scopes again | Metadata selection and scope setup can be repeated between validation and execution | Carry an invocation-local prepared selection from `CheckInput` into `Run` |
|
|
| `Execute` in [execute.go](../../execute.go) identifies `help` by its path and asserts its private input type | A built-in name controls a separate execution path | Let the help declaration's typed runner receive a private execution context through the common compiler |
|
|
| `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
|
|
|
|
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.
|
|
Use an internal package where it protects an actual dependency boundary.
|
|
|
|
| Owner | Internal responsibility | Does not own |
|
|
| --- | --- | --- |
|
|
| Core declarations | Captured command records, child handles, local declaration errors, root placement, explicit absence | Registry occupancy, provider acquisition, Cobra objects |
|
|
| 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/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 |
|
|
|
|
The neutral metadata package is justified by the existing core/adapter exchange:
|
|
today its neutral structures live inside `internal/cobra`. Move those structures
|
|
as a unit rather than introduce an additional copy of the same DTOs. Only the core
|
|
execution coordinator needs to call the concrete adapter. Declaration and binding
|
|
logic can depend on the neutral shapes without depending on adapter operations.
|
|
This is not a backend-selection interface or a new public package.
|
|
|
|
Integrations use supported public core contracts. Do not solve extraction by
|
|
letting them import core private assembly helpers. Small local selection-flag or
|
|
report-formatting helpers are acceptable; a fourth utility module or a universal
|
|
provider framework is not justified merely by a few similar declarations.
|
|
|
|
## One installation path for every command
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[Application command factories] --> D[Captured core declarations]
|
|
B[Integration family factories] --> D
|
|
C[Core help and version factories] --> D
|
|
D --> V[Shared batch validation and staging]
|
|
V --> R[Instance registry commit]
|
|
R --> M[Cached neutral routing and help metadata]
|
|
M --> S[Fresh invocation adapter session]
|
|
S --> E[Selected typed runner and common outcome delivery]
|
|
```
|
|
|
|
### Construction and later registration
|
|
|
|
Recommend one private `installBatch` operation used by both `New` and
|
|
`App.Register`:
|
|
|
|
1. `New` validates application identity and creates an unpublished empty app.
|
|
2. Core factories construct real help/version declarations, using the same typed
|
|
command compiler as application declarations.
|
|
3. `New` submits built-ins and caller declarations together to `installBatch`.
|
|
4. `App.Register` submits its supplied declarations to that same operation.
|
|
|
|
There are no placeholder commands, direct built-in registry writes, privileged
|
|
installation switches, or separate family installers. The app exists before its
|
|
built-in callbacks are captured, but those callbacks do not execute during
|
|
construction. A failed initial batch exposes no usable app.
|
|
|
|
Help/version names are occupied by real built-in nodes. Optional family names are
|
|
occupied only by installed real roots. The ordinary collision check consequently
|
|
implements reservation without a parallel list of known integration names.
|
|
Root-only placement is captured metadata, not a check for a particular family
|
|
name or the package declaring it.
|
|
|
|
### Batch validation and commit
|
|
|
|
The shared installer should:
|
|
|
|
- Inspect retained errors and zero/absence state. Report attached errors before
|
|
skipping an absent declaration, including absence nested in a group.
|
|
- Traverse real declarations with their installation path; enforce root placement
|
|
and report contextual construction/structural errors. Read captured binding
|
|
validation results rather than invoking parsers or factories.
|
|
- Check real root names against the existing registry and other roots in the
|
|
pending batch. Sibling collisions inside a declaration belong to the same core
|
|
structural-validation rules.
|
|
- Stage additions in a local pending map. Commit only after the entire batch is
|
|
valid. Existing captured nodes need not be cloned to stage new roots.
|
|
- Invalidate derived routing/help metadata once after a successful nonempty
|
|
commit. A failed batch or an entirely absent batch leaves registry and cached
|
|
metadata intact.
|
|
|
|
All mutation is confined to the commit portion. Do not implement atomicity through
|
|
temporary insertion followed by rollback or through copying the whole existing
|
|
registry for every call. This preserves the selected per-call registry guarantee;
|
|
it adds no transaction for callback effects, allocation failures, or concurrency.
|
|
|
|
Preserve retained causes with ordinary wrapping/joining where needed. Do not
|
|
flatten them into strings. The implementation need not promise exhaustive error
|
|
enumeration, stable error text, or a public aggregate-error type.
|
|
|
|
## Capture once; keep installation state separate
|
|
|
|
### Immutable declaration records
|
|
|
|
Recommend an opaque `Command` handle referring to a private captured record.
|
|
That record contains descriptions, child handles, an optional compiled typed
|
|
payload, retained errors, root-only placement, and explicit absence state.
|
|
|
|
Copy caller-owned metadata slices at their ownership boundary, including binding
|
|
lists, child-handle lists, scope operations, and selector applicability. Once
|
|
captured, those records are read-only. `Group` copies its child-handle slice;
|
|
it does not recursively clone already captured child records. `WithError` returns
|
|
a new wrapper/record retaining the existing content and added cause without
|
|
mutating the original handle. `RootGroup` reuses ordinary group capture and adds
|
|
placement metadata. A zero handle remains distinguishable from explicit absence.
|
|
|
|
This permits declaration reuse without copying entire trees at every assembly
|
|
level. It does not clone application values, callbacks, or their object graphs.
|
|
Do not write registry paths, app pointers, converted values, or invocation caches
|
|
into reusable declarations. Core built-ins capture their own app-specific context
|
|
when each app is constructed.
|
|
|
|
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.
|
|
|
|
### Divide validation by responsibility and lifetime
|
|
|
|
Use one implementation of each core metadata rule:
|
|
|
|
- Command/binding constructors capture local metadata and retain local errors.
|
|
- Group capture checks its immediate composition without recursively rerunning
|
|
every child's local binding validator.
|
|
- Installation checks contextual placement and registry/batch collisions while
|
|
reporting retained errors through one tree traversal.
|
|
- Integrations check scope identifiers, applicability references, declared
|
|
operations, adapter issues, and enabled provider wiring. They hand generated
|
|
flags and operands to core command construction for generic binding checks.
|
|
|
|
This prevents ancestor-by-ancestor validation and copying from growing with both
|
|
tree size and nesting depth. Exact allocations remain implementation choices;
|
|
do not add a global intern table, reflection-driven compiler, or mutable
|
|
"validated" cache shared between applications.
|
|
|
|
Core name/short-name/help-reservation rules also check the combined standard and
|
|
selector flags on generated commands. Integration code should not reproduce
|
|
those rules with handwritten reserved-name lists. Error checking for selectors
|
|
in an absent definition family is the unresolved seam described below.
|
|
|
|
## One typed conversion and application mechanism
|
|
|
|
### Prepared typed values
|
|
|
|
Retain the useful separation in the prototype's `prepareInput`: convert raw
|
|
occurrences before initializing input. Refactor its prepared setter closures into
|
|
one private typed prepared-value mechanism usable by ordinary bindings and
|
|
`CaptureFlags`/`FlagValues`.
|
|
|
|
A prepared entry needs original binding identity, supplied state, and a typed
|
|
application closure. The closure captures the converted/default value and applies
|
|
it to initialized input storage. A repeatable binding copies its core-owned slice
|
|
when applying it to another input; it does not clone arbitrary elements.
|
|
|
|
Scalar, boolean, repeated-flag, and operand constructors remain distinct where
|
|
their parsing and cardinality differ. Their execution glue should feed the same
|
|
typed preparation/application machinery, rather than create another conversion
|
|
system for integrations.
|
|
|
|
`CaptureFlags` adapts each original binding to an outer command binding that stores
|
|
the prepared typed entry in `FlagValues`. It does not apply selectors to a shared
|
|
template, initialize suite input, or invoke a parser a second time.
|
|
`FlagValues.ApplyTo` selects the original identities and applies their prepared
|
|
values to the supplied destination.
|
|
|
|
Recommend a private identity token per original binding and an invocation-local
|
|
lookup from token to prepared entry. Handle copies preserve identity; lookalike
|
|
bindings get a different token. A private nonzero-sized token object avoids
|
|
relying on addresses of zero-sized Go objects. No global registry or public ID is
|
|
needed. The lookup belongs to the invocation store, not the reusable binding.
|
|
|
|
### Required values and errors
|
|
|
|
Separate required metadata used for help from the point where execution enforces
|
|
it. Ordinary flags enforce their requirement in ordinary input preparation.
|
|
Captured inner flags enforce it when `ApplyTo` selects applicable bindings. Do not
|
|
erase required metadata or reimplement missing-value policy in each integration.
|
|
|
|
Apply the API's existing `ErrFlagInput`/`ErrFlagWiring` categories through one core
|
|
classification helper. The command's input-validation path should recognize
|
|
wrapped wiring failures as operational; ordinary validation and missing input use
|
|
usage outcomes. An integration may wrap those causes with scope/family context
|
|
without parsing error messages or inventing another outcome protocol.
|
|
|
|
Help still converts supplied occurrences and rejects malformed values through the
|
|
same preparation logic, but does not materialize command/suite input or enforce
|
|
execution-required fields. Presence, singleton repetition, and repeated-value
|
|
semantics remain those of the finalized API and selected grammar.
|
|
|
|
## One compiled execution path, including help
|
|
|
|
Recommend that the private typed command compiler produce a prepared invocation
|
|
closure. After raw conversion, this closure materializes `I`, applies values,
|
|
runs `CheckInput`, and invokes the typed handler. Keep `I` inside that closure
|
|
rather than passing an `any` input through separate factory/check/run calls and
|
|
repeated type assertions. A heterogeneous registry can store the resulting
|
|
non-generic private runner interface/function.
|
|
|
|
The private runner receives the current execution context, containing streams,
|
|
cancellation, the app, and access to the current help/presentation session. For an
|
|
application or integration handler, the compiler passes only the public
|
|
`Invocation` and typed input. Built-in help uses a narrow private typed runner
|
|
adapter to render targets through that same current execution context.
|
|
|
|
That private adapter shares declaration capture, metadata validation, binding
|
|
preparation, registry installation, and outcome presentation with every other
|
|
command. It changes only the final callback's access to core services. Version
|
|
can use ordinary typed handler wiring with captured identity. Do not add a
|
|
second built-in compiler or a dispatcher branch keyed on `path == "help"`.
|
|
|
|
The adapter's local `--help` mechanism is still common syntax machinery, not a
|
|
separately registered application command. Bare root/group help and explicit
|
|
help commands use the same presentation service. Cobra must not add hidden
|
|
command families independently of captured registrations.
|
|
|
|
### Metadata and session lifetime
|
|
|
|
Preserve the existing cache/session split:
|
|
|
|
- Registry changes invalidate one derived neutral routing/help snapshot. Sorting
|
|
and backend metadata projection occur when that snapshot is rebuilt, not on
|
|
every parse or help operation.
|
|
- A fresh mutable adapter session is built for each execution and reused for
|
|
its parsing, help, and usage presentation. Mutable Cobra values are not cached
|
|
across invocations.
|
|
- Give each snapshot a private key-to-runner table. The adapter returns the
|
|
selected neutral command key along with its path/raw values. Core execution
|
|
uses the table associated with that session rather than resolving the same
|
|
path again through `commandAt`.
|
|
- Command keys are meaningful only within their snapshot. Do not expose them,
|
|
depend on persistent numbering, or use a newly rebuilt table to interpret a
|
|
result from an older session.
|
|
- If a callback registers additional commands and subsequent presentation needs
|
|
current metadata, refresh the presentation session on demand. Do not reparse
|
|
argv or rerun factories, validators, or handlers merely to refresh help.
|
|
|
|
This removes duplicated route resolution and preserves useful existing behavior
|
|
without promising concurrent dispatch, registration during another goroutine's
|
|
execution, or a general snapshot-isolation contract.
|
|
|
|
## Integration preparation without parallel execution frameworks
|
|
|
|
### Configuration suite
|
|
|
|
At family construction, capture a private prepared suite containing ordered scope
|
|
records, lookup by scope name, supported operations, discovery metadata, and the
|
|
applicable original selector bindings for each scope. Those lists are a natural
|
|
output of composing scopes and selectors; no separate mutable membership cache
|
|
or public capability framework is needed.
|
|
|
|
Each inspection command uses an invocation-local outer input containing standard
|
|
selectors, `FlagValues[I]`, and its prepared operation selection. Recommend a
|
|
pointer-shaped private outer input initialized by the command's own `NewInput`.
|
|
Its `CheckInput` can populate the prepared selection and `Run` receives that same
|
|
object. This matters because the public `CheckInput` receives its generic input
|
|
by value: assigning a plan to a field in a copied value struct would lose it.
|
|
This outer allocation is distinct from the suite's input and shared-state
|
|
factories and is not performed on help paths.
|
|
|
|
The shared configuration preparation routine should:
|
|
|
|
1. Resolve scopes, operation availability, formats, and applicable operand rules
|
|
from metadata before suite factories or acquisition.
|
|
2. For each selected scope, obtain a fresh `I` through `NewInput` or its zero
|
|
value, apply only applicable captured bindings, and call the scope-aware
|
|
`CheckInput` with scope identity and selection.
|
|
3. Retain the successfully prepared scope/input records in selected order on the
|
|
outer invocation input. On failure, return the supported error and stop;
|
|
later inputs need not be initialized.
|
|
4. In `Run`, after input validation succeeds and execution reaches acquisition,
|
|
obtain shared `S` once through `NewState` or its zero value. Pass that state
|
|
and each prepared input explicitly to the selected provider.
|
|
|
|
Do not recompute selection in every operation's `Run`, put the plan in a shared
|
|
declaration closure, project a shallow copy of one input pointer, or clear
|
|
non-applicable fields after initialization. Non-applicable bindings should leave
|
|
the scope input's own initialized fields untouched.
|
|
|
|
Share this preparation across validate/show/sources/explain. Keep acquisition and
|
|
report loops operation-owned: `show` acquires all selected views before report
|
|
output, while other operations retain their selected timing. A universal
|
|
"acquire everything then render" wrapper would alter behavior unnecessarily.
|
|
Discovery reads captured metadata without initializing suite input or state.
|
|
|
|
`AppCommand` should build the ordinary scope and suite internally and call the
|
|
same family factory. It is convenience assembly, not a second config installer,
|
|
handler set, or execution cache.
|
|
|
|
### PromptKit suite and scope adapter
|
|
|
|
Definition commands use the same core captured bindings, typed runner, and outcome
|
|
delivery. A small private definition-input helper applies the entire selected
|
|
flag set, validates the typed input, and retains it for the runner. It needs no
|
|
configuration-scope projection or inherited mixed-context selector model.
|
|
|
|
Within `ckprompt`, share engine loading/inspection and diagnostic adaptation
|
|
between scope and named-definition adapters where their upstream contract is the
|
|
same. Inspect the original loader before wrapping it. Provider lookup failures
|
|
still retain diagnostics and error identity. Keep report models and native
|
|
configuration views with their proper owners.
|
|
|
|
`ConfigScope` constructs a public `ckconfig.Scope` and delegates configuration
|
|
command generation to `ckconfig`. It must not construct another config family or
|
|
reach into `ckconfig`'s private prepared suite. Its construction-error handoff is
|
|
the second unresolved seam below.
|
|
|
|
### Share mechanics where ownership supports them
|
|
|
|
Core owns outcome classification and diagnostic delivery once; integration
|
|
handlers return public outcomes and diagnostic envelopes. Within an integration,
|
|
share mundane writing/encoding and provider-error helpers where appropriate,
|
|
while preserving each report's actual timing and incomplete-delivery semantics.
|
|
|
|
Some small helpers may remain similar between independent modules. Do not expose
|
|
generic encoders, upstream severity converters, standard-flag getters, or a
|
|
generic pipeline merely to eliminate a few lines. Important duplication to remove
|
|
is competing registration, validation, conversion, acquisition-lifetime, and
|
|
presentation policy—not every structurally similar field assignment.
|
|
|
|
## Efficiency and implementation review evidence
|
|
|
|
The intended cost profile is straightforward:
|
|
|
|
- Capture local metadata once and share read-only subtrees; avoid recursively
|
|
copying/revalidating a child at every ancestor.
|
|
- Stage only added roots, with one contextual tree walk and ordinary name
|
|
lookups; avoid cloning the existing registry or installing commands one by one
|
|
inside a supposedly atomic batch.
|
|
- Derive routing/help metadata once per changed registry state, and use the
|
|
adapter's selected command key rather than repeating route traversal.
|
|
- Convert each supplied occurrence once per invocation. Apply prepared values
|
|
only to selected inputs, copying only core-owned repeated collections.
|
|
- Prepare scope selection once per invocation and shared state once when
|
|
acquisition is reached. No process-global provider cache is introduced.
|
|
|
|
These are algorithmic recommendations, not latency/allocation guarantees. Existing
|
|
benchmarks can inform the implementation sprint; this audit has not run them or
|
|
measured performance. Profile before adding pools, segmented trees, bitsets,
|
|
concurrent registries, or mutable backend reuse.
|
|
|
|
Focused implementation evidence should demonstrate the common path with all
|
|
three command sources: built-ins, application declarations, and integrations.
|
|
Include invalid bindings, conflicting roots in either order, an invalid final
|
|
member of a batch, nested root-only declarations, absence with retained errors,
|
|
source metadata edits after capture, and declaration reuse across apps.
|
|
|
|
For execution, check wrapped wiring versus input failures, fresh repeatable
|
|
collections, pointer-shaped scope inputs with shared state, scope-aware validation,
|
|
help without factories/providers, and metadata refresh without callback replay.
|
|
Evidence for the internal path itself can inspect the shared installer/compiler;
|
|
do not turn a private function name or exact allocation count into a public
|
|
contract. These are review targets for the future implementation, not a request
|
|
to add or run tests during this planning pass.
|
|
|
|
## Open Questions
|
|
|
|
The main internal design can use the existing finalized API. The following two
|
|
seams need an explicit API clarification or small approved extension. Do not
|
|
resolve them with private-module imports, dummy providers, reflection, or hidden
|
|
global state.
|
|
|
|
### OQ-1: Retaining core binding errors when a family is absent
|
|
|
|
The API says suite validation precedes absence and invalid wiring must not be
|
|
swallowed. It does not explicitly define the validation boundary for selector
|
|
bindings when both definition capabilities are absent. If those bindings must
|
|
still be validated, `ckprompt` can retain its own adapter errors, but `Flag`
|
|
validation state is private to the core. An integration can construct a temporary
|
|
command containing its selector bindings, yet the finalized API offers no way to
|
|
transfer that
|
|
command's retained errors to `NoCommand()`. Discarding it loses evidence;
|
|
installing it adds a command that should be absent. Validating through a temporary
|
|
`App` would work mechanically but construct built-ins, routing ownership, and a
|
|
registry merely to inspect metadata.
|
|
|
|
**Recommendation:** approve a narrow extension of the existing absence factory,
|
|
schematically `NoCommand(validation ...Command) Command`. A no-op can retain
|
|
local construction/structural errors from eagerly captured validation declarations
|
|
without installing their commands or claiming their namespaces. The empty
|
|
definition factory builds a metadata-only validation declaration using the same
|
|
generated flag assembly, then returns absence carrying that evidence.
|
|
|
|
This reuses the common declaration validator and keeps one installation-time
|
|
error boundary. Validation sources are captured data, not preparation callbacks;
|
|
they do not acquire providers or introduce registry collision checks for roots
|
|
that will not be installed. Specify the evidence-only behavior explicitly before
|
|
changing the API. Plain `NoCommand()` retains its current meaning.
|
|
|
|
**Alternative:** add a core declaration-validation function returning `error` for
|
|
local metadata/structure, and attach its result through the existing `WithError`.
|
|
That also avoids a temporary app and duplicated flag policy. It adds a public
|
|
validation entry point and a second observation point for errors; ensure it uses
|
|
the same validator rather than evolving independent rules. It is viable if
|
|
explicit validation is useful beyond absent families.
|
|
|
|
**Alternative requiring a contract clarification:** validate only the bindings of
|
|
actual generated commands. An absent family has no installed bindings, so unused
|
|
selectors would not undergo core validation; retained adapter errors still fail.
|
|
This needs no new API and is proportionate to installed behavior, but limits the
|
|
meaning of suite validation before absence to integration metadata and retained
|
|
adapter issues rather than all unused bindings. Choose it deliberately,
|
|
rather than silently making empty capabilities swallow invalid declarations.
|
|
|
|
### OQ-2: Retaining errors in a scope constructed by another integration
|
|
|
|
The selected `Scope[I, S]` includes private adapter-construction issues owned by
|
|
`ckconfig`. `ckprompt.ConfigScope` returns that scope type, but cannot attach its
|
|
original nil-loader error through supported public contracts. An `Acquire == nil`
|
|
check alone is insufficient: narrowing `Operations` to empty makes a scope
|
|
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.
|
|
|
|
**Recommendation:** approve an integration-owned operation, schematically
|
|
`ckconfig.WithScopeError[I, S](scope Scope[I, S], err error) Scope[I, S]`.
|
|
It returns a scope retaining the original issue, preserving supported error
|
|
identity; a nil error preserves the scope. The family factory collects those
|
|
issues into its command declaration before capability selection or absence can
|
|
discard them. `ConfigScope` uses this for its original loader validation and
|
|
otherwise fills the ordinary public scope fields.
|
|
|
|
This mirrors the accepted core retained-error model at the boundary that needs
|
|
it. It does not add scope installation, expose private representations, or move
|
|
PromptKit types into the core. Scope capture still occurs at the existing family
|
|
factory; this operation is evidence attachment rather than a new lifecycle.
|
|
|
|
**Alternative:** give the public scope declaration an explicit construction-error
|
|
field, consumed by the family factory alongside provider metadata. This is simple
|
|
and permits application-created adapters to supply errors directly. It expands
|
|
mutable public representation and requires defining preservation/combination of
|
|
multiple errors; the opaque attachment operation keeps those details private.
|
|
|
|
Neither seam requires a general plugin system. Confirm their resolutions before
|
|
turning this design and the module roadmap into ordered implementation stages.
|