Validate independent module boundaries

This commit is contained in:
2026-10-11 02:13:16 +00:00
parent d2b5b0f3a8
commit cc574915ee
10 changed files with 61 additions and 2107 deletions

View File

@@ -131,9 +131,9 @@ The maintained consumers compile with their owning module suites and run without
external services:
```sh
go run ./examples/core greet --count=0 --tag=first --tag second
go run ./examples/core workspace greet --count=0 --tag=first --tag second
(cd integrations/ckconfig/examples/simple && go run . config show)
(cd integrations/ckprompt/examples/composed && go run . config validate --scope all --campaign spring)
(cd integrations/ckprompt/examples/composed && go run . config validate --scope all --campaign spring --limit=0)
(cd integrations/ckprompt/examples/composed && go run . promptkit show prompt briefing --campaign spring --format yaml)
(cd integrations/ckprompt/examples/composed && go run . session restore session-42 --campaign spring)
```

View File

@@ -252,3 +252,16 @@ License and notices: [BSD 3-Clause](licenses/BSD-3-Clause.txt) |
[LICENSE](https://go.googlesource.com/mod/+/d3398d06de5fa5c71083d3d1c26f2cda73508e0f/LICENSE).
[Source](https://go.googlesource.com/mod/+/d3398d06de5fa5c71083d3d1c26f2cda73508e0f).
## Go tools
Selected module: `golang.org/x/tools` at `v0.49.0` (ReleaseKit tool graph dependency).
Copyright (c) 2009 The Go Authors. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause.txt) |
[LICENSE](https://go.googlesource.com/tools/+/18332fec72972efbb8ab9881984fec2d8cfc2b58/LICENSE).
[Source](https://go.googlesource.com/tools/+/18332fec72972efbb8ab9881984fec2d8cfc2b58).

View File

@@ -1,18 +1,22 @@
# Implement the optional integration modules and captured registration API
Status: implementation in progress. Stages 1 through 15 are complete; subsequent work follows the ordered roadmap.
Status: implementation complete. Stages 1 through 16 are complete.
Execute the stages in order. Each stage is sized as a separate Luna coding-agent
assignment. This document authorizes no publication or changes to sibling
repositories.
## Scope and authority
Deliver the target in the [module roadmap](modules.md), the exact declarations
and behavior in the [registration API](registration-api.md), and the selected
mechanics in the [registration backend design](registration-backend.md).
Read [development orientation](../development.md) and its applicable policy guide
before implementation. The [architecture policy](../policy/architecture.md) and
accepted ADRs control if a planning instruction conflicts with them.
This report retains the implementation sequence and completion evidence.
Current contracts belong to the [core GoDoc](../../doc.go),
[ckconfig GoDoc](../../integrations/ckconfig/doc.go),
[ckprompt GoDoc](../../integrations/ckprompt/doc.go),
[consumer guide](../consumers/guide.md), [CLI reference](../cli.md),
[internal overview](../internal/overview.md), and
[release procedure](../release.md). Read [development orientation](../development.md)
and its applicable policy guide before related work. The
[architecture policy](../policy/architecture.md) and accepted ADRs control
architecture decisions.
The governing decisions include [optional modules](../adr/0010-optional-integration-modules.md),
[captured declarations](../adr/0011-captured-declarations-and-installation.md),
@@ -51,9 +55,8 @@ Go 1.26.8.
The planning review found one module, integration source and examples in the
root, ReleaseKit v0.3.0 selected as a Go tool, and a root-only release launcher.
The [backend findings](registration-backend.md#baseline-findings) identify the
parallel installation paths, repeated validation, mixed input/state storage, and
help special case that this sprint replaces.
The selected owners and current mechanics are recorded in the
[internal overview](../internal/overview.md) and current package GoDoc.
One factual correction to earlier planning is necessary: core `v0.1.0` is
already published on `origin`. The observed annotated tag object is
@@ -614,7 +617,7 @@ dependency ownership.
2. Rewrite ckconfig's simple example using `AppCommand`, with no dummy input/state
types at its consumer call site. Retain a useful typed application command.
3. Rewrite ckprompt's composed example using the finalized
[three consumer examples](registration-api.md#complete-consumer-registration-examples)
[three consumer examples](../consumers/guide.md)
as API evidence: combined app/promptkit configuration scopes, applicable typed
selectors, scope-aware checks, pointer input isolation, shared app-owned state,
independent named inspection, and `session restore`.
@@ -845,11 +848,20 @@ publication wiring are verified with no compatibility scaffolding.
pending real dependency/sum preparation. The workspace should contain no
unrelated generated files or temporary replacement manifests.
**Completion evidence:** all three module suites and representative consumers
succeed; module/package graph checks prove the requested exclusion boundaries;
current docs and licensing reflect distribution ownership; publication tooling
selects one module through released contracts. Actual publication and upstream
version adoption remain separate from this sprint.
**Completion evidence — complete:** all three module suites pass with the local
workspace, and the five maintained consumer invocations succeed. Workspace-off
tests and three external consumer modules also pass using temporary module files
with local replacements only for unpublished CommandKit modules; package and
module graph checks confirm the intended core, ckconfig, and ckprompt dependency
boundaries. Root tidy is clean, nested manifests were reconciled from temporary
tidy runs, and no future-version checksums or distributed filesystem
replacements were added. Public exports and ownership, installation/input cost
paths, maintained guides, local links, license distribution, release notes, and
the selected-module source-only release wiring were reviewed. Obsolete module,
registration API, and backend planning documents were retired after their current
owners and links were updated. No real publication was attempted. Actual
dependency downloads and checksum preparation remain part of ordered release
preparation after the prerequisite versions are published.
## Release handoff

View File

@@ -1,299 +0,0 @@
# Optional integration modules
Status: module boundary accepted in ADR 0010; consumer API and internal backend
design finalized, with both composition questions resolved. The
[staged implementation plan](implementation.md) is complete; no module extraction
has begun. ReleaseKit v0.4.0 provides module-release support; CommandKit adoption
is pending.
## Purpose and authority
Make CommandKit useful to consumers that use both ConfigKit and PromptKit,
ConfigKit alone, or neither library. Consumers using only CommandKit's core
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
delivery boundaries. The [implementation plan](implementation.md) owns the ordered
steps and validation needed to reach this target. The finalized
[registration API](registration-api.md) owns the
selected declarations, installation semantics, and representative consumer
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.
[ADR 0011](../adr/0011-captured-declarations-and-installation.md) records the
finalized registration and invocation-lifetime decisions. The
[architecture policy](../policy/architecture.md) is aligned with those decisions.
GoDoc, the [CLI reference](../cli.md), and the [consumer guide](../consumers/guide.md) describe the prototype baseline; they do
not make its incidental API shapes compatibility requirements for this sprint.
## Organizing principle: design the final API
Give the existing prototype implementation zero deference when selecting the
final API, as directed for this transition. Core `v0.1.0` has since been published;
preserve that release and prepare the incompatible replacement as a fresh minor
release under the release policy. Publication does not change the selected API
or require compatibility wrappers for this sprint. The goal is
the best, simplest, most coherent API for the accepted responsibilities and
representative consumers, not the smallest diff or a mechanically relocated
prototype. Accepted architectural decisions and explicitly selected behavior
remain requirements; prototype declarations, helpers, bookkeeping, and tests do
not independently establish contracts.
Complete the transition in one sprint. No backward-compatibility wrappers,
deprecation period, or migration bridge is needed. Intermediate commits need not
build or pass tests; do not add temporary machinery merely to make them do so.
Validate the complete target state before release. Reuse existing code where it
serves that design, and refactor, replace, or remove it where it does not.
## Current baseline
At the start of this roadmap, CommandKit has one Go module and one supported
public root package. It requires ConfigKit v0.5.0 and PromptKit v0.12.0 and
declares Go 1.26.0. Its internal Cobra adapter implements routing and parsing.
The root combines core command infrastructure with configuration and PromptKit
inspection. Coupling extends beyond constructors:
- `NewConfigKitScope` accepts ConfigKit results and maps their diagnostics.
- Core `Diagnostic.Severity` currently uses ConfigKit's public severity type;
moving inspection handlers alone will not remove that dependency.
- `SourceReader` returns ConfigKit source records; `ExplanationReader` accepts
ConfigKit paths and returns ConfigKit explanations.
- `PromptKitSuite` callbacks return PromptKit inspection types.
- `NewPromptKitScope` adapts engine configuration into the configuration suite.
- Standard handlers share root-private helpers and state. For example,
configuration registration accesses the application's registry and records
whether the suite has already been installed.
The [internal overview](../internal/overview.md) describes the implemented
inventory. The maintained examples and integration tests currently belong to the
root module. ReleaseKit v0.3.0 is also selected there as a Go tool dependency;
the [release procedure](../release.md) assumes one module and one version tag.
Optional command registration currently controls behavior, not dependencies.
Moving integration code into packages sharing the root `go.mod` would separate
compiled imports but retain integration requirements in the module graph.
Go's [module graph rules](https://go.dev/ref/mod#module-graph-pruning) explain
that distinction.
## Targeted end state
Provide three consumer modules in this repository with dependencies directed
toward the core. Keep the core at `gitea.maximumdirect.net/eric/CommandKit` and
give each integration its own `go.mod`:
| Directory | Module path | Package name |
| --- | --- | --- |
| Repository root | `gitea.maximumdirect.net/eric/CommandKit` | `commandkit` |
| `integrations/ckconfig` | `gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig` | `ckconfig` |
| `integrations/ckprompt` | `gitea.maximumdirect.net/eric/CommandKit/integrations/ckprompt` | `ckprompt` |
The `integrations/` grouping keeps optional modules together as their number
grows; the anticipated three to ten integrations are planning context, not a
limit or delivery commitment. Distinct package names avoid requiring local
aliases when these integrations are imported alongside ConfigKit and PromptKit.
Separate module files establish dependency isolation; directory naming alone
does not. The shared repository requires explicit multi-module testing and
publication orchestration.
| Module | Responsibility | Integration dependencies |
| --- | --- | --- |
| Core | Application construction, command/group registration, typed bindings, dispatch, help/version, common grammar, outcomes, and diagnostics | Neither ConfigKit nor PromptKit |
| ConfigKit integration | Standard configuration family, scope composition, reports, and ConfigKit adapters | Core and ConfigKit; no PromptKit |
| PromptKit integration | Named prompt/profile inspection and the PromptKit configuration-scope adapter | Core, PromptKit, and the ConfigKit integration |
The core remains an opinionated command library with its existing internal
Cobra boundary. The dependency goal is independence from ConfigKit and PromptKit,
not a standard-library-only implementation.
Integrations remain maintained parts of CommandKit. They install commands into
the same application through supported public core operations and follow its
grammar, help, invocation, and outcome conventions. Applications retain domain
commands, acquisition, runtime construction, streams, and process exit.
The selected configuration boundary moves the existing ConfigKit-shaped
contracts with their handlers into the ConfigKit integration. Only ConfigKit
support is needed for now; a generic configuration interface is outside this
migration. The PromptKit integration may depend on the ConfigKit integration,
consistent with PromptKit's own ConfigKit dependency. Document that relationship
in consumer guidance: `ckprompt` depends on `ckconfig`.
This boundary preserves one configuration suite into which
applications compose `app`, `promptkit`, and other supported scopes. PromptKit
configuration inspection and named-definition inspection remain independently
selectable. A module dependency does not automatically install a command family.
Core public signatures and implementation imports contain no ConfigKit or
PromptKit types. Integration-specific types and helpers move to their owning
modules. Do not retain root forwarding wrappers that restore those imports.
Shared helpers stay private unless a concrete cross-module interaction needs a
supported contract; extraction is not a reason to expose every existing helper.
Preserve explicitly selected operator behavior: command vocabulary, selection
and scope semantics, native reports, diagnostics, error identity, outcomes, and
help without provider acquisition. Identify and decide any changes to selected
CLI behavior separately. Do not preserve accidental prototype behavior or Go API
shapes merely because they already exist.
### Selected API and release principles
- 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. 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
wider data model into the core.
- Version the modules independently according to their own changes and the
[release policy](../policy/release.md#versioning-and-compatibility). Do not
require integration version numbers to match either the core or upstream
dependency versions. Record selected dependencies in `go.mod` and explain
relevant upstream adoption in module release notes. Coordinate publication
when changes span modules; independent versions do not imply an exhaustive
compatibility matrix.
### Selected multi-module release approach
Adopt ReleaseKit v0.4.0 with module-release support. This is a planned tooling
upgrade from the current v0.3.0 baseline, not an implemented CommandKit capability.
Use ReleaseKit's public contracts for checking and
publishing one explicitly selected module per invocation; do not build a separate
CommandKit publication engine or temporary compatibility path.
Use one shared launcher with small per-module profiles. Each profile selects its
repository-relative module directory and product identity; each invocation
supplies that module's independently selected semantic version. ReleaseKit owns
module-tag derivation, selected-module validation, repository publication guards,
and publication outcomes. Root releases use unprefixed tags. Nested releases use
their Go module-subdirectory prefixes, and notes belong at
`<module-directory>/docs/releases/<version>.md`. Source and licensing links use
the complete Git tag, while note titles identify the product and semantic version.
Run the selected module's standard suite once with workspace mode disabled and
prepared dependencies. Repository cleanliness, branch, candidate commit, and
endpoint guards remain repository-wide. A root-module release does not implicitly
validate independently released nested modules; composition checks are selected
deliberately where needed. Keep successful source-tag publication as completion,
without CI, hosted delivery, proxy-availability, or installation gates.
CommandKit's preferred shared file inputs are repository-root `COPYRIGHT`,
`LICENSE`, and `docs/oss/attribution.md`, when present, rather than the entire
root `docs/` tree. Use v0.4.0's `validation.shared_files` field to select those
three files explicitly, as documented by ReleaseKit. Shared inputs affect enabled
file checks, not Go suite scope; resolving references does not add surrounding
directories to the inputs.
Keep module selection and profile lookup repository-relative, module-owned
validation settings and product-command execution module-relative, and diagnostic
paths repository-relative. Use the provided repository context for shared scripts.
The released [profile](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/b96262eb0ce9c8f2eb3e948a5841db2b799bc91b/docs/config.md)
and [CLI](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/b96262eb0ce9c8f2eb3e948a5841db2b799bc91b/docs/cli.md)
contracts govern profile fields, command syntax, module context, and shared-file
selection. The implementation plan specifies their adoption; no separate
publication engine is needed.
Nested-module artifact builds, hosted delivery, and batch publication are not
needed for CommandKit's source-only releases.
## Consumer examples and validation target
Three maintained public consumers demonstrate the final API:
| Adoption path | Example home | Required evidence |
| --- | --- | --- |
| Core only | `examples/core` | Typed commands/groups, presence, help/version; no optional-library imports or module requirements |
| ConfigKit only | `integrations/ckconfig/examples/simple` | Focused `AppCommand` convenience and standard config reports; no PromptKit imports or module requirements |
| ConfigKit and PromptKit | `integrations/ckprompt/examples/composed` | Combined configuration scopes, typed applicable selectors, independent scope inputs, shared state, named definition inspection, and an application-owned command |
Tests, fixtures, and benchmarks belong to the module owning their contracts.
Mixed suites are split by responsibility; root tests and examples cannot
reintroduce optional integration dependencies. Public composition checks belong
in the PromptKit integration. Relevant existing coverage is retained or rewritten
for selected behavior rather than incidental prototype API shapes.
Validation addresses package imports and each independently selected module
graph, including transitive and tool requirements. Core-only consumers exclude
both owning libraries and integrations; ConfigKit-only consumers exclude
PromptKit and its integration. Root `go test ./...` alone cannot validate nested
modules. The three modules' suites and representative public consumers provide
composition evidence without an exhaustive historical compatibility matrix.
Local workspace composition supports development; distributed manifests contain
versioned inter-module requirements, without filesystem replacements or committed
workspace files. Workspace-off prepublication checks against local intended
source establish wiring and isolation, not published-version availability.
Released consumers resolve actual independently released versions without
adjacent checkouts or consumer replacements. The implementation plan defines the
prepublication checks and ordered release handoff.
## Documentation and distribution target
Implemented GoDoc owns exact exports and error/lifetime semantics. The consumer
guide and READMEs present the final imports and optional-module selection directly;
maintained examples demonstrate it. The CLI reference retains selected operator
behavior, with registration/ownership prose matching the implemented API. Internal
inventory and contributor navigation describe implemented owners and validation.
No separate downstream prototype migration guide is required.
Each integration distributes a module-local README, project grant, complete
license texts, applicable attribution and required notices, and module-local
release notes. Required licensing material remains available in that module's Go
source distribution rather than only through an outside-module local link. The
existing grant and separately licensed material retain their terms. Repository
attribution can retain factual entries for integration material while module-local
notices identify applicable components.
Modules use independent versions. The planned core replacement is `v0.2.0`;
ckconfig and ckprompt initially use `v0.1.0`. Their dependency requirements express
that relationship explicitly. The already published root `v0.1.0` and its note
remain intact. If a planned version is occupied before preparation, select a
fresh appropriate version under the release policy. Core publication precedes
ckconfig, which precedes ckprompt; actual released dependencies and corresponding
sums are prepared for each candidate. Source-only publication still completes at
successful tag publication, without proxy-availability or installation gates.
The release procedure and CommandKit-specific release-policy adaptations describe
selected-module publication, independent versions, semantic-version note paths,
full-tag links and recovery identity, and the root shared-file selection. Release
profiles and launcher use released ReleaseKit contracts; additional delivery
services and permanent migration gates are outside the target.
## Implementation status
ADR/architecture alignment, public API design, and internal backend design are
complete. Module extraction, code refactoring, maintained consumers, validation,
current documentation, and multi-module tooling adoption are not implemented.
The [16-stage implementation plan](implementation.md) owns the sequence and
completion evidence. Earlier accepted ADR decision text remains intact.
## Scope and follow-up
This work changes packaging and integration boundaries. It does not select a
new backend, concurrent-dispatch contract, dynamic plugin system, or replacement
configuration engine. It does not lower the existing Go minimum.
Keep the PromptKit v0.13.0 and ConfigKit v0.6.0 adoption identifiable as a separate
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.

File diff suppressed because it is too large Load Diff

View File

@@ -1,500 +0,0 @@
# Registration and execution internals
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](implementation.md)
owns the ordered work and completion evidence.
## 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 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
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 |
## 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.
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` (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 |
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.
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
parents. This addition requires no separate registration mechanism.
### 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. Validate bindings used by
generated commands; unused selector bindings in an absent family are ignored,
as clarified in the OQ-1 resolution below. Retained adapter-construction errors
still reach installation.
## 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.
All supplied occurrences undergo conversion, including selectors inapplicable to
the selected scope and values supplied alongside help. Scope filtering governs
value application and required-value checks, not conversion. An earlier malformed
singleton occurrence remains an error even if a later occurrence is valid.
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 the target command's application
or suite input, invoke its validation or execution callbacks, or enforce its
execution-required fields. Suite state factories and providers also remain
uninvoked. The built-in help command may prepare its own private typed input
through the common compiler. 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 selected `ckconfig.WithScopeError` operation described 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.
Include runnable parents with children through `CommandSpec.Children`, applying
the same capture, collision, and placement checks as ordinary groups.
For execution, check wrapped wiring versus input failures, fresh repeatable
collections, pointer-shaped scope inputs with shared state, scope-aware validation,
help without target application/suite factories or providers, and metadata refresh
without callback replay. Check that supplied but inapplicable selectors still
undergo conversion, including on help paths, while the built-in help command can
prepare its own private input through the common compiler.
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.
## Resolved scope-error handoff (OQ-2)
The selected `Scope[I, S]` includes private adapter-construction issues owned by
`ckconfig`. Before the selected addition below, `ckprompt.ConfigScope` could not
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.
**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
copies and narrowing of `Operations`. 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 considered:** 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.
## Resolved unused-binding validation (OQ-1)
**Selected:** empty definition suites contribute no commands, and unused selector
bindings are ignored. Generate commands for the supplied capabilities and validate
the bindings needed by those commands. When neither definition-inspection
capability is enabled, the family contributes no namespace, help entry, or handler.
There is no need to construct validation-only commands or a temporary application
for selector bindings that will not be installed.
This follows the capability-driven assembly model: selecting no capabilities
naturally produces no commands. Rejecting unused bindings would report defects
that cannot affect routing, parsing, or execution. This clarifies the validation
boundary while preserving ADR 0011's selected empty-suite behavior; it does not
promise exhaustive validation of unused metadata.
An explicitly invalid adapter construction remains distinct from an omitted
capability. Retain errors such as a nil loader supplied to an adapter even when
its capabilities are subsequently cleared. Return ordinary `NoCommand()` for an
empty definition suite, attaching any retained construction errors through the
existing `WithError`. Installation checks those errors before skipping absence.
No validation-bearing `NoCommand` overload or public declaration-validation helper
is needed.
The binding rule also applies to configuration selectors unused by any generated
inspection command. Existing configuration-suite semantics remain unchanged:
zero named scopes is an error, while named metadata-only scopes supply the real
`config scopes` discovery command and remain valid. Integration scope identifiers,
applicability references, and enabled-provider wiring retain their own validation;
ignoring unused bindings does not erase retained adapter errors or bypass checks
on commands that are generated.
This construction-time rule does not skip invocation-time conversion of a supplied
flag on an installed command merely because it is inapplicable to the selected
scope. Conversion and scope-specific application remain separate as described
above.
**Alternatives considered:** rejecting empty definition suites could catch an
accidental omission, but would make an empty capability selection an error without
a demonstrated need. Validating all unused bindings, through either an extended
absence factory or an explicit validation helper, would catch latent defects but
add public API and validation machinery for commands that do not exist. Neither
alternative is selected.