From 8b46e12da7168db00b7b6722fd5080c5ffe64968 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 11 Oct 2026 22:00:22 +0000 Subject: [PATCH] Document configuration comparison behavior --- docs/cli.md | 57 +++++++++++ docs/consumers/guide.md | 15 +-- docs/development.md | 2 + docs/internal/overview.md | 9 +- docs/roadmap/configkit.md | 104 +++++++-------------- docs/roadmap/deferred.md | 31 +++--- docs/roadmap/implementation.md | 19 +++- integrations/ckconfig/README.md | 9 +- integrations/ckconfig/configkit_adapter.go | 7 +- integrations/ckconfig/contracts.go | 30 +++--- integrations/ckconfig/family.go | 10 +- 11 files changed, 170 insertions(+), 123 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index dbce178..9619590 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -230,6 +230,63 @@ environment-variable name; effective and historical secret fragments remain redacted. Empty location, inheritance, history, reason, and dependency lists are serialized as `[]`. +### Compare configuration selections + +`config diff` compares two independently loaded selections for one scope that +declares comparison. The default scope is `app`; `--scope all` is unsupported. +The PromptKit engine scope does not currently declare comparison, so selecting +`--scope promptkit` is a usage error. +`--config` and `--overlay` select the left side. `--against-config` and +`--against-overlay` independently override the right side. When a right-side +field is omitted, it inherits the corresponding left value and supplied state; +an explicit empty string overrides it and remains supplied. Equal selections are +allowed and both reached sides are loaded. + +YAML is the only format and the default. Explicit `--format yaml` is accepted; +`text` and other formats are usage errors. For example: + +```text +example config diff --config before.yml --against-config after.yml +example config diff --config base.yml --against-overlay candidate +example config diff --config base.yml --overlay current --against-overlay "" +``` + +ConfigKit compares frozen effective typed values after defaults and resolution; +source or provenance differences alone do not create a change. The report is one +YAML document with `additions`, `changes`, and `deletions` in that order. +Additions and changes preserve semantic paths and contain safe right-side values. +Deletions contain canonical JSON Pointer paths. Explicit null is a value; it is +not a deletion. Changed sequences and native structural replacement regions stay +atomic. + +ConfigKit pairs sensitivity across both loads. A concealed change can therefore +appear even when its two safe snapshots are equal. The report omits separate +redaction and hidden-change metadata: a reported masked region changed, but that +does not mean every member inside it changed. Marker-looking public strings stay +ordinary YAML values. An empty comparison succeeds with this shape: + +```yaml +additions: {} +changes: {} +deletions: [] +``` + +The reduced report is a partial safe view, not a reloadable configuration or an +automatically applicable patch. Exceptional whole-root empty/null transitions +can be ambiguous because the report has no root envelope; ordinary field-level +null values and deletions retain their distinction. + +Successful comparisons return status `0`, including concealed or empty results. +Unsupported scopes and format errors are usage errors; input checks retain the +existing usage/operational classification. Load and native comparison failures +are operational and preserve supported error identity. Comparison +diagnostics identify their left/right side and source legend. The adapter loads +left before right, retains left diagnostics if right loading fails, and gathers +both loads' diagnostics before native comparison. The complete report is +prepared before writing. Cancellation and preparation failures leave report +output empty; an output error or short write uses the existing incomplete-report +delivery outcome. + ## PromptKit definition inspection Applications can install prompt and profile inspection independently with diff --git a/docs/consumers/guide.md b/docs/consumers/guide.md index d02ba93..c3f1c4a 100644 --- a/docs/consumers/guide.md +++ b/docs/consumers/guide.md @@ -103,18 +103,21 @@ values. Use `ckconfig.Command(ckconfig.Suite[I, S]{...})` when the application needs multiple named scopes, selector flags, scope-aware input checks, or shared -acquisition state. Input is initialized separately for each selected scope; -state is shared by selected acquisitions. Factory results remain caller-owned, -so separate initialization calls do not guarantee distinct objects. See +acquisition state or a custom comparison provider. A declared `Diff` capability +uses `Scope.Compare`; `ckconfig.FromConfigKit` and `AppCommand` provide the +ConfigKit-backed implementation. Each comparison side gets its own initialized +and checked input, while state is shared by the invocation. Factory results +remain caller-owned, so separate initialization calls do not guarantee distinct +objects. See [ckconfig.Suite's GoDoc](../../integrations/ckconfig/contracts.go) for factory timing, omitted-factory zero values (including nil pointers), and ownership. The application owns the state type and what it caches. `ckconfig.FromConfigKit` adapts a typed ConfigKit loader without calling it during construction. The `config` family provides scope discovery, validation, native effective YAML, -source inventory, and path explanation for the capabilities declared by its -scopes. The [CLI reference](../cli.md#configuration-scopes) owns operator -syntax and report details. +source inventory, path explanation, and ConfigKit-backed comparison for the +capabilities declared by its scopes. The [CLI reference](../cli.md#configuration-scopes) +owns operator syntax and report details. ## PromptKit configuration and definitions diff --git a/docs/development.md b/docs/development.md index fa69617..8a5db52 100644 --- a/docs/development.md +++ b/docs/development.md @@ -134,7 +134,9 @@ external services: ```sh go run ./examples/core workspace greet --count=0 --tag=first --tag second (cd integrations/ckconfig/examples/simple && go run . config show) +(cd integrations/ckconfig/examples/simple && go run . config diff --config fixtures/before.yml --against-config fixtures/after.yml) (cd integrations/ckprompt/examples/composed && go run . config validate --scope all --campaign spring --limit=0) +(cd integrations/ckprompt/examples/composed && go run . config diff --scope app --config fixtures/before.yml --against-config fixtures/after.yml) (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) ``` diff --git a/docs/internal/overview.md b/docs/internal/overview.md index 699f932..9bee449 100644 --- a/docs/internal/overview.md +++ b/docs/internal/overview.md @@ -40,10 +40,11 @@ Cobra and pflag imports remain inside this package. ## Optional modules `integrations/ckconfig` owns ConfigKit adaptation, scope capture, the standard -`config` command family, and native validate/show/sources/explain reports. It -depends on the public core facade and ConfigKit; it does not depend on PromptKit. -Each selected scope receives an independently initialized input, while one -suite-owned state value is shared by selected acquisitions in that invocation. +`config` command family, and native validate/show/sources/explain/diff behavior. +It depends on the public core facade and ConfigKit; it does not depend on +PromptKit. Each selected scope or comparison side receives an independently +initialized input, while one suite-owned state value is shared by an invocation's +acquisitions or comparison callback. `integrations/ckprompt` owns PromptKit engine loading, configuration-scope adaptation, and optional named prompt/profile inspection. It uses public core diff --git a/docs/roadmap/configkit.md b/docs/roadmap/configkit.md index 899ad7c..22c66e2 100644 --- a/docs/roadmap/configkit.md +++ b/docs/roadmap/configkit.md @@ -1,8 +1,8 @@ # Adopt ConfigKit v0.7.1 and expose native configuration comparison -Status: end-state decisions selected; dependency adoption and `config diff` are -not implemented. This roadmap defines the selected target and remaining gap; -the [implementation plan](implementation.md) owns stages and execution order. +Status: ConfigKit v0.7.1 adoption and ConfigKit-backed `config diff` are +implemented. Distribution reconciliation and final validation remain in the +[implementation plan](implementation.md), which records the evidence and order. ## Purpose and authority @@ -34,14 +34,15 @@ disclosure; CommandKit owns selection, coordination, adaptation, and presentatio | Owner | Current state | | --- | --- | | Core module | Go 1.26.0; captured commands, typed bindings, dispatch, outcomes, help, and version. No ConfigKit dependency. ReleaseKit v0.4.0 is the declared maintainer tool. | -| `integrations/ckconfig` | Requires ConfigKit v0.5.0. `Scope`, `Suite`, `FromConfigKit`, and `AppCommand` expose scopes/validate/show/sources/explain. No comparison operation or provider exists. | -| `integrations/ckprompt` | Directly requires ConfigKit v0.5.0 and PromptKit v0.12.0. Depends on ckconfig's public scope contracts and adapts engine configuration plus named definitions. | -| Deferred comparison | [DIFF-01](deferred.md#diff-01-semantic-configuration-comparison) records the target. Native semantics, selection syntax, provider representation, and the YAML-only report are selected; implementation remains pending. | +| `integrations/ckconfig` | Requires ConfigKit v0.7.1. `Scope`, `Suite`, `FromConfigKit`, and `AppCommand` expose independently declared validate/show/sources/explain/diff capabilities. | +| `integrations/ckprompt` | Requires ConfigKit v0.7.1 and PromptKit v0.12.0. It depends on ckconfig's public scope contracts and adapts engine configuration plus named definitions; its ConfigKit scope does not declare comparison. | +| Comparison status | ConfigKit-backed `config diff` is implemented. The [CLI reference](../cli.md#compare-configuration-selections), integration GoDoc, and [implementation record](implementation.md) own current contracts and evidence. PromptKit comparison remains deferred. | -The existing ConfigKit adapter acquires a typed `*configkit.Result[T]` and exposes -its effective, source, and explanation facets through `View`. That generic -adapter is a useful place to retain typed results for comparison. `View` alone -does not supply two results of the same `T` to `configkit.Diff`. +The ConfigKit adapter acquires typed `*configkit.Result[T]` values and exposes +effective, source, and explanation facets through `View` for ordinary operations. +Its separate comparison callback retains `T`, loads both prepared selections, +and returns the native records from `configkit.Diff` without routing through +`View`. ConfigKit v0.7.1 identifies source commit `6fffde4af8f31f99d3ee295c426eb3234cb0fb77`. Relative to the selected v0.5.0: @@ -53,15 +54,14 @@ ConfigKit v0.7.1 identifies source commit runtime/API change from v0.7.0. The YAML dependency remains `go.yaml.in/yaml/v4 v4.0.0-rc.6`. Existing public -loading and inspection signatures are unchanged. The review found no apparent -adapter migration requirement for existing operations; that is static review -evidence, not a completed upgrade validation. See the pinned +loading and inspection signatures are unchanged, and the integration suites pass +with the adopted dependency. See the pinned [release notes](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/6fffde4af8f31f99d3ee295c426eb3234cb0fb77/docs/releases/v0.7.1.md) and [comparison contract](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/6fffde4af8f31f99d3ee295c426eb3234cb0fb77/docs/config.md#effective-configuration-diffs). ## Dependency adoption and ownership -Both integration modules select ConfigKit v0.7.1 and carry normal dependency +Both integration modules now select ConfigKit v0.7.1 and carry normal dependency metadata for their actual graphs. Consumer Go minimums remain 1.26.0, the selected release toolchain remains 1.26.8, and PromptKit remains v0.12.0 for this work. Core continues to have no ConfigKit or optional-integration dependency. @@ -115,7 +115,7 @@ empty diff without an invented requirement that an override be supplied. Do not short-circuit acquisition merely because the selections are equal: caller-owned loading may produce different effective results. -For example, these show the accepted target syntax, not current commands: +For example, these are current invocations: ```text app config diff --config before.yml --against-config after.yml @@ -255,12 +255,12 @@ boundaries; do not promise interruption inside a synchronous upstream call. ### Comparison provider and ConfigKit adapters -Add a narrow optional comparison callback to `ckconfig.Scope[I, S]`. Its request +`ckconfig.Scope[I, S]` exposes a narrow optional comparison callback. Its request groups each side's selection with its separately prepared typed input and supplies one shared state value separately. Return `commandkit.Acquired[[]configkit.Difference]` plus an error, following the existing -acquisition convention. Reuse upstream public record types rather than duplicate -them solely to rename fields. The selected target shape in ckconfig is: +acquisition convention. It reuses upstream public record types rather than +duplicating them solely to rename fields. The current API shape is: ```go type ComparisonSide[I any] struct { @@ -273,13 +273,13 @@ type ComparisonRequest[I any] struct { Right ComparisonSide[I] } -// Accepted target field on Scope[I, S]: +// Current field on Scope[I, S]: // Compare func(context.Context, ComparisonRequest[I], S) (commandkit.Acquired[[]configkit.Difference], error) ``` -These names and the callback shape are selected for implementation; they are not -implemented exports. ADR 0012 records paired provider ownership and input/state -lifetimes; this roadmap owns the target declarations until the Go API is implemented. +These are the implemented exports in `integrations/ckconfig/contracts.go`. +ADR 0012 records paired provider ownership and input/state lifetimes; GoDoc owns +the exact public declarations. `AppCommand` and `FromConfigKit` provide native comparison alongside their existing supported operations, with ordinary capability narrowing available. They use the @@ -306,19 +306,18 @@ or loads need not run after failure. Declaration construction, installation, hel version, and scope discovery leave input/state factories and acquisition/comparison callbacks uninvoked. ADR 0012 records this two-selection specialization of ADR 0011's suite lifecycle, -and the architecture policy reflects it. Document the exact implemented API -contract in Go declarations and GoDoc when it lands. +and the architecture policy reflects it. The exact API contract is in the Go +declarations and GoDoc. ### Application-owned state and caches Generic result handling stays inside the owning adapter, without reflection, private-core access, or result type assertions. Shared state supports consumer caches whose keys distinguish the effective selections and any typed-input values -that affect loading. A cache -that stores only one configuration can incorrectly reuse the left result for the -right and report equality. Demonstrate correct keys in the maintained example; -CommandKit neither supplies nor repairs application caches. Cache policy and -factory objects remain caller-owned. +that affect loading. A cache that stores only one configuration can incorrectly +reuse the left result for the right and report equality. The composed example +demonstrates selection- and input-aware keys; CommandKit neither supplies nor +repairs application caches. Cache policy and factory objects remain caller-owned. ## Design tradeoffs @@ -340,46 +339,15 @@ values while representing removals explicitly. Omitting empty categories would shorten output but require missing-key handling; a separate text renderer adds no needed operator value for this delivery. -## Work needed from the baseline +## Implementation record -These work areas close the gap from the current implementation to the selected -end state above. The [implementation plan](implementation.md) defines their -execution order and completion evidence. - -- **Dependency adoption:** upgrade both integration requirements, reconcile sums - and indirect/tool dependencies, and assess PromptKit v0.12.0 against the newly - selected ConfigKit version. Preserve distributed manifests without sibling - replacements or committed workspace files. -- **Capability and provider API:** extend ckconfig's operation/capture/validation - model for the selected comparison provider. Preserve retained adapter errors, - capability narrowing, metadata-only scopes, and validation only of bindings - used by generated commands. Do not require an ordinary acquisition provider - for a comparison-only scope if its selected pair provider is sufficient. -- **Typed preparation:** capture comparison selection metadata and applicable - selector lists once. Prepare/check the selected pair through core bindings, - with state and callback timing from the chosen lifecycle. Keep caller-owned - objects and aliases under existing ownership rules. -- **Native adaptation:** extend the typed ConfigKit adapter/convenience factory - to load the selected pair and call `configkit.Diff`. Reuse diagnostic adaptation - and ordinary error handling without introducing a generic provider framework, - global cache, or untyped result transport. -- **Command and reports:** generate the conditional `config diff` declaration, - integrate it with discovery/help, and render the chosen report contract using - native safe values. Preserve existing operation/report behavior. -- **Documentation and distribution:** update GoDoc, CLI/consumer guidance, - maintained examples, affected module notices and pinned ConfigKit links, and - relevant release notes as implementation lands. Include a maintained comparison - example that loads genuinely different selections through shared state, with - cache keys accounting for the selection and typed-input values used by its - loader. Review transitive tool notices for the actual graph. Move DIFF-01's - implemented portion out of deferred work; retain unresolved future PromptKit - comparison and unrelated proposals. -- **Validation evidence:** run each affected module's deterministic offline suite - and representative consumers. Check module/package boundaries independently - with workspace mode off. If CommandKit prerequisite versions remain unpublished, - use disposable alternate module files for local-source wiring evidence, as - described in the [development guide](../development.md#documentation-validation). - Do not fabricate future checksums or turn migration checks into permanent gates. +Dependency adoption, capability and typed-pair preparation, native adaptation, +YAML reporting, end-to-end execution, and both maintained comparison examples +are implemented. Current API and operator contracts belong to GoDoc, the +[CLI reference](../cli.md#compare-configuration-selections), the +[consumer guide](../consumers/guide.md#configkit-scopes), and the example +READMEs. The [implementation plan](implementation.md) records stage-by-stage +changes and validation; distribution reconciliation and final validation remain. ### Focused behavioral evidence diff --git a/docs/roadmap/deferred.md b/docs/roadmap/deferred.md index 4cb8f12..a0376d4 100644 --- a/docs/roadmap/deferred.md +++ b/docs/roadmap/deferred.md @@ -18,29 +18,20 @@ an idea here does not select its syntax, API, priority, or delivery date. ## Deferred capability targets -### DIFF-01: semantic configuration comparison +### PromptKit configuration comparison -Expose comparison of two independently resolved selections of one scope using -owning-library public comparison APIs. [ADR 0008](../adr/0008-semantic-configuration-diff.md) -owns native comparison, paired safe disclosure, and the reduced default report. -The target includes a YAML-only report of safe changed regions; completed -comparisons succeed whether or not they find differences. +ConfigKit-backed `config diff` is implemented by `ckconfig` for scopes that +declare that capability. `ckprompt.ConfigScope` does not declare comparison. +Reassess PromptKit's supported public comparison capabilities before planning a +PromptKit comparison. Do not reach into ConfigKit internals or infer comparison +semantics from rendered YAML. -The initial review of ConfigKit v0.5.0 and PromptKit v0.12.0 did not find the -public comparison support needed for that target. ConfigKit v0.7.0 introduced -`configkit.Diff`, retained in v0.7.1, which supplies native comparison and paired -safe changed-region snapshots. CommandKit still uses ConfigKit v0.5.0; dependency -adoption and comparison delivery are targeted by the -[ConfigKit roadmap](configkit.md). Assess PromptKit's supported -public comparison capabilities separately. Rendered YAML and sibling internals -are not substitutes for the owning library's public comparison contract. +### Comparison extensions -The ConfigKit roadmap records accepted selection syntax and the paired provider -API, with lifecycle recorded in -[ADR 0012](../adr/0012-typed-configuration-comparison-pairs.md). The selected report -shape is recorded in that roadmap. Independent per-side domain selectors and -an optional assertion mode remain outside that delivery. Add public types and -handlers with their implementation rather than exposing placeholders. +Independent per-side domain selectors and an optional assertion mode remain +unselected extensions to the delivered ConfigKit comparison. Consider them only +for a concrete consumer need and a deliberate API/behavior decision under the +relevant ADRs. ### CAT-01: PromptKit catalogs diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 88633dc..c4e4243 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Implement ConfigKit adoption and configuration comparison -Status: implementation in progress; Stages 1–10 are complete. +Status: implementation in progress; Stages 1–11 are complete. ## Scope and authority @@ -545,9 +545,20 @@ canonical owners, while future/deferred work stays clearly separate. symbols. Keep accepted ADR decision text unchanged; presentation details remain in CLI/API owners rather than returning to ADRs. -**Completion evidence:** current operator/API/consumer owners agree with the -implemented behavior; no stale four-operation convenience claims, text-diff -promises, or deferred markers for delivered ConfigKit comparison. +**Completion evidence — complete:** ckconfig GoDoc now describes all five +operations, paired input lifetimes, shared state, native records, diagnostics, +and supported error identity. The ckconfig README, CLI reference, consumer guide, +internal overview, development examples, and maintained example READMEs describe +current ConfigKit comparison behavior. The CLI owner covers independent right +inheritance and supplied presence, native effective-value semantics, reduced +safe YAML categories, null/deletion, concealed regions, empty output, root +ambiguity, diagnostics, and outcome delivery. The ConfigKit feature roadmap now +records implementation status; the deferred record no longer carries ConfigKit +comparison as pending work and retains PromptKit comparison, independent per-side +domain selectors, catalogs, and other unselected ideas. The accepted ADRs were +left unchanged. Local Markdown targets/fragments and copied commands were +reviewed; a local link check and `git diff --check -- '*.md'` passed. `go doc` +was inspected for the exported comparison API. ## Stage 12: Reconcile manifests, notices, and pending integration release notes diff --git a/integrations/ckconfig/README.md b/integrations/ckconfig/README.md index d500a24..ebec781 100644 --- a/integrations/ckconfig/README.md +++ b/integrations/ckconfig/README.md @@ -24,9 +24,12 @@ app, err := commandkit.New(identity, ckconfig.AppCommand(loadAppConfig)) Use `ckconfig.Command(ckconfig.Suite[Input, State]{...})` to define named scopes, selectors, input checks, and state shared by the selected acquisitions -in a config invocation. `ckconfig.FromConfigKit` adapts a typed ConfigKit loader. -The `config` command family exposes scope discovery and the validate, show, -sources, and explain operations supported by the declared scopes. +or comparison in a config invocation. `ckconfig.FromConfigKit` adapts a typed +ConfigKit loader. The `config` command family exposes scope discovery and the +validate, show, sources, explain, and diff operations supported by the declared +scopes. Comparison returns ConfigKit's native records to the YAML report path; +the [CLI reference](https://gitea.maximumdirect.net/eric/CommandKit/src/branch/main/docs/cli.md#compare-configuration-selections) +owns its operator details. The maintained [simple example](examples/simple) shows a complete consumer. The [CommandKit CLI reference](https://gitea.maximumdirect.net/eric/CommandKit/src/branch/main/docs/cli.md#configuration-scopes) diff --git a/integrations/ckconfig/configkit_adapter.go b/integrations/ckconfig/configkit_adapter.go index 0f9decf..5bef93f 100644 --- a/integrations/ckconfig/configkit_adapter.go +++ b/integrations/ckconfig/configkit_adapter.go @@ -14,9 +14,10 @@ import ( // FromConfigKit adapts a typed ConfigKit loader to all five native // configuration operations. The loader receives the operation's explicit // Selection, initialized input, and shared invocation state, and runs only when -// an installed operation selects this scope. Construction and help do not call -// it. A nil original loader is retained as a construction issue, even if callers -// later narrow the scope's operation list. +// an installed operation selects this scope. Comparison invokes it separately +// for each reached side. Construction and help do not call it. A nil original +// loader is retained as a construction issue, even if callers later narrow the +// scope's operation list. func FromConfigKit[I, S, T any]( name, summary string, load func(context.Context, Selection, I, S) (*configkit.Result[T], error), diff --git a/integrations/ckconfig/contracts.go b/integrations/ckconfig/contracts.go index 36e843a..7507805 100644 --- a/integrations/ckconfig/contracts.go +++ b/integrations/ckconfig/contracts.go @@ -17,7 +17,7 @@ type Selection struct { Overlay commandkit.Supplied[string] } -// Operation identifies a supported configuration inspection operation. +// Operation identifies a supported standard configuration operation. type Operation string const ( @@ -42,7 +42,8 @@ type Request struct { } // ComparisonSide groups one configuration selection with its independently -// prepared typed input. +// initialized and checked typed input. The supplied-state fields of Selection +// are preserved for the scope's loader. type ComparisonSide[I any] struct { // Selection carries the explicit config and overlay inputs for this side. Selection Selection @@ -51,7 +52,8 @@ type ComparisonSide[I any] struct { } // ComparisonRequest carries the earlier and later selections supplied to a -// scope's comparison callback. +// scope's comparison callback. Each side has a separately prepared input; the +// callback receives shared invocation state separately. type ComparisonRequest[I any] struct { // Left is the earlier configuration selection. Left ComparisonSide[I] @@ -99,7 +101,9 @@ type Scope[I, S any] struct { // invocation. Acquire func(context.Context, Request, I, S) (commandkit.Acquired[View], error) // Compare compares two selections for this scope using one shared state. - // It is required when Diff is declared in Operations. + // Its Acquired result carries native difference records and diagnostics; + // returned errors retain their supported cause identity. It is required when + // Diff is declared in Operations. Compare func(context.Context, ComparisonRequest[I], S) (commandkit.Acquired[[]configkit.Difference], error) issues []error @@ -116,24 +120,28 @@ type Selector[I any] struct { // Suite declares the typed input, shared state, selectors, and configuration // scopes used to assemble configuration commands. // -// Input initialization is separate for each selected scope; state is shared -// by that invocation's acquisitions. Factory results and callback effects remain +// Input initialization is separate for each selected ordinary scope and each +// reached comparison side; state is shared by the invocation's acquisitions or +// comparison callback. Factory results and callback effects remain // caller-owned: Command does not deep-copy arbitrary objects or repair aliases // returned by factories. Core-owned repeated flag collections are copied when // applied to separate inputs. Factories are not invoked during construction, // installation, help, version, or scope discovery, and need not run after failure. type Suite[I, S any] struct { - // NewInput is called once for each selected scope reached after operation - // preflight, before applying its selectors and calling CheckInput. + // NewInput is called once for each selected ordinary scope, or once for each + // reached side of a comparison, after operation preflight and before applying + // selectors and calling CheckInput. // If nil, input is the zero value of I, including nil for pointer types. NewInput func() I - // NewState is called once when acquisition is reached, after all selected - // inputs pass their checks. The result is shared by selected acquisitions. + // NewState is called once when provider execution is reached, after all + // selected inputs pass their checks. The result is shared by selected + // acquisitions or the comparison callback. // If nil, state is the zero value of S, including nil for pointer types. NewState func() S // Selectors declares typed flags and their applicable scopes. Selectors []Selector[I] - // CheckInput validates one initialized scope input after applicable values are applied. + // CheckInput validates one initialized scope input after applicable values + // are applied. Comparison sides are checked separately with their selection. CheckInput func(scope string, selection Selection, input I) error // Scopes declares the configuration scopes assembled by Command. Scopes []Scope[I, S] diff --git a/integrations/ckconfig/family.go b/integrations/ckconfig/family.go index 9dbedee..a6444e3 100644 --- a/integrations/ckconfig/family.go +++ b/integrations/ckconfig/family.go @@ -43,9 +43,10 @@ type preparedSuite[I, S any] struct { // Command eagerly captures a configuration suite as a root-only config family. // It copies suite slices, retains callback references, and returns a reusable // declaration; installation does not invoke factories or providers. During an -// inspection, it initializes input separately for each selected scope and -// validates those inputs before acquisition. Suite documents factory timing, -// zero values, shared state, and caller ownership of returned objects. +// inspection, it initializes input separately for each selected scope. A +// comparison initializes and checks both sides before creating shared state +// and calling the pair provider. Suite documents factory timing, zero values, +// shared state, and caller ownership of returned objects. func Command[I, S any](suite Suite[I, S]) commandkit.Command { prepared := captureSuite(suite) if len(prepared.scopes) == 0 { @@ -67,7 +68,8 @@ func Command[I, S any](suite Suite[I, S]) commandkit.Command { // AppCommand builds the state-free, input-free convenience suite for application // configuration. The application consumer supplies only its ConfigKit loader, -// which runs only for a selected operation after input preflight. +// which runs for ordinary operations and separately for each reached comparison +// side after input preflight. func AppCommand[T any](load func(context.Context, Selection) (*configkit.Result[T], error)) commandkit.Command { var adapterLoad func(context.Context, Selection, struct{}, struct{}) (*configkit.Result[T], error) if load != nil {