From 7fc425ede3971e1464ff98f3d3095083314c53f1 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 11 Oct 2026 20:35:38 +0000 Subject: [PATCH] Plan the ConfigKit comparison implementation --- docs/roadmap/configkit.md | 10 +- docs/roadmap/implementation.md | 384 +++++++++++++++++++++++++++++++++ 2 files changed, 389 insertions(+), 5 deletions(-) create mode 100644 docs/roadmap/implementation.md diff --git a/docs/roadmap/configkit.md b/docs/roadmap/configkit.md index b64ad78..899ad7c 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 brings the decisions and remaining work together; -a subsequent staged implementation plan will define execution order. +not implemented. This roadmap defines the selected target and remaining gap; +the [implementation plan](implementation.md) owns stages and execution order. ## Purpose and authority @@ -18,8 +18,7 @@ owns scope selection and independent capabilities; [ADR 0011](../adr/0011-captur owns captured declarations, typed input, and shared state, refined for comparison pairs by [ADR 0012](../adr/0012-typed-configuration-comparison-pairs.md). The [architecture policy](../policy/architecture.md) controls ownership and dependency -direction. The current ADR alignment edits remain uncommitted for user -review; this roadmap follows their agreed comparison direction. +direction. The accepted ADRs govern this roadmap's comparison direction. The repository is being developed as a pre-release alpha. Choose the simplest coherent final API without preserving prototype shapes through compatibility @@ -344,7 +343,8 @@ needed operator value for this delivery. ## Work needed from the baseline These work areas close the gap from the current implementation to the selected -end state above. A subsequent staged plan will define their execution order. +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 diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..ed08663 --- /dev/null +++ b/docs/roadmap/implementation.md @@ -0,0 +1,384 @@ +# Implement ConfigKit adoption and configuration comparison + +Status: implementation plan; no implementation stages are complete. + +## Scope and authority + +Follow the stages in order, starting with Stage 1. Each stage is sized for one +Luna implementation assignment. The [feature roadmap](configkit.md) owns the +selected end state, operator behavior, target API, and scope limits. This document +owns the steps to reach that state. Read [development guidance](../development.md) +and the applicable policies before implementation. Accepted ADRs and the +[architecture policy](../policy/architecture.md) control if a conflict is found. + +The baseline inspected for this plan is `f8bb789`. ConfigKit comparison decisions +are accepted in [ADR 0008](../adr/0008-semantic-configuration-diff.md) and +[ADR 0012](../adr/0012-typed-configuration-comparison-pairs.md). ADR 0008 delegates +comparison semantics to the adopted upstream version; it does not freeze those +semantics as separate CommandKit guarantees. Report details belong in the feature +roadmap until implemented, then in the CLI reference. + +This is one sprint against an unreleased prototype. Preserve deliberately selected +contracts, but give existing implementation shapes no compatibility preference. +Intermediate commits need not build. Do not introduce wrappers or extra machinery +to make intermediate states usable. The completed sprint must compile and provide +the complete selected behavior. Core needs no new comparison model or dependency; +reuse its existing typed bindings, captured declarations, and outcomes. + +During implementation, add focused coverage with the stage that owns its behavior, +format changed Go files, and record completion evidence here after each stage. +Commit completed nonempty units under the agent policy. Evidence must describe +actual checks and any limitations, not anticipated success. These development +checks do not create new release gates. This planning pass runs no Go tests and +changes no implementation, manifests, examples, or ADRs. + +## Stage 1: Adopt ConfigKit and establish local validation + +**Outcome:** both integrations select ConfigKit v0.7.1, with a usable development +graph and an honest distinction between local and published dependency evidence. + +1. Read both integration manifests and the pinned ConfigKit release/comparison + contracts linked from the feature roadmap. Update the two direct ConfigKit + requirements from v0.5.0 to v0.7.1. Keep PromptKit v0.12.0 and consumer Go + minimums at 1.26.0. Use the selected Go 1.26.8 development/release toolchain. +2. Preserve an existing ignored `go.work`; ensure it includes the root, ckconfig, + and ckprompt modules. Do not commit workspace files or add filesystem + replacements to distributed manifests. Use released upstream dependencies, + not sibling source replacements. +3. Resolve actual upstream dependencies and sums. ConfigKit's tool requirement + selects ReleaseKit v0.4.1 in integration graphs; retain whatever legitimate + indirect requirements normal resolution needs. Do not change core's declared + ReleaseKit v0.4.0 tool, launcher, or profiles as an incidental upgrade. +4. For unpublished CommandKit requirements, use the disposable module-file + procedure in Stage 9. Generate real external dependency checksums; never + invent sums for unpublished CommandKit versions. Keep normal manifests at + their intended versioned requirements. Reconcile module-local sums from real + dependency downloads, excluding disposable local wiring. +5. Run existing ckconfig and ckprompt suites against the upgraded dependency. + Fix actual upstream API incompatibilities within this scope, if any. Do not + compensate for a test that independently freezes a superseded upstream detail. + +**Completion evidence:** reviewed manifest/sum changes; upgraded integration +graph selections; existing inspection and PromptKit suite results. Record any +unpublished dependency limitation explicitly. + +## Stage 2: Add the comparison capability and captured provider contract + +**Primary files:** `integrations/ckconfig/contracts.go`, `family.go`. + +**Outcome:** scopes can independently declare comparison, without requiring +ordinary acquisition for a comparison-only scope. + +1. Add `Diff Operation = "diff"`. Extend normalization, generated-operation + inventory, and selector-union assembly to recognize it. Append it after the + four existing operations so their relative order stays unchanged. +2. Add `ComparisonSide[I]`, `ComparisonRequest[I]`, and `Scope[I, S].Compare` + exactly as selected under the roadmap's + [provider contract](configkit.md#comparison-provider-and-configkit-adapters). + Keep these exports integration-owned; keep `Request`, `View`, and ordinary `Acquire` focused on + existing operations. Add GoDoc for sides, diagnostics, errors, and lifetimes. +3. Change capture-time wiring checks: declared `Diff` requires `Compare`; + declared validate/show/sources/explain operations require `Acquire`. A scope + declaring both kinds needs both. Metadata-only scopes need neither. + Preserve retained adapter issues even when capability lists are narrowed. +4. Capture the comparison callback reference with the other scope metadata. + Keep existing collection copies, original binding identities, root-only + declaration placement, batch validation, and namespace ownership. Factories + and providers must remain uninvoked during capture and installation. +5. Extend scope discovery and conditional family generation. Generate `diff` + only when a scope supports it; selectors are accepted by that command only + when applicable to a comparison-capable scope. Rely on existing core binding + validation for collisions with generated flags. + +**Completion evidence:** focused installation/discovery checks for comparison-only, +mixed, unsupported, and metadata-only scopes; missing callback and retained nil +loader issues; captured metadata surviving source edits. Execution is wired in +Stages 3 and 6; do not add temporary handlers for intermediate completeness. + +## Stage 3: Prepare two presence-aware selections and typed inputs + +**Primary file:** `integrations/ckconfig/input.go`. + +**Outcome:** the comparison plan contains one selected scope and two independently +prepared inputs, ready for one shared acquisition state. + +1. Add right-side selection storage and expose `--against-config` and + `--against-overlay` only on `diff`. Keep `--config` and `--overlay` as the left + selection. Default the comparison format to YAML; accept explicit YAML and + reject unsupported formats through the existing usage-error path. +2. Resolve right fields independently: if the right `Supplied.Set` is false, + copy the corresponding complete left `Supplied` value; otherwise keep the + right value, including an explicit empty string. Do not interpret an empty + overlay, normalize provider defaults, or require unequal selections. +3. Reuse existing scope/capability preflight. Comparison defaults to `app`, + selects exactly one scope, and rejects `all`, unknown scopes, and scopes + without comparison before suite input/state factories or provider calls. +4. Capture the operation's original selector handles through `CaptureFlags` once. + Prepare left input and then right input with `NewInput` when reached. Apply + only the selected scope's bindings independently through `FlagValues.ApplyTo`. + Call `CheckInput` with the scope name, that side's selection, and its input. + Store both checked sides in the operation plan; leave shared-state creation + to execution after both checks succeed. +5. Preserve nil-factory zero values, caller-owned objects/aliases, copied + core-owned repeated collections, and early failure. Do not clone arbitrary + inputs or add pointer allocation. Preserve existing ordinary-operation paths. + +**Completion evidence:** table-driven selection/presence coverage for omission, +explicit empty overrides, single-field inheritance, and equal selections; +applicable required selectors, explicit false/zero, parser conversion once, and +repeated-value independence. Verify preflight/check failures stop state/provider +work. Help converts supplied values without invoking suite factories/providers. +Do not assert calls that the accepted early-failure contract allows to be skipped. + +## Stage 4: Implement typed native comparison and diagnostic retention + +**Primary file:** `integrations/ckconfig/configkit_adapter.go`. + +**Outcome:** `FromConfigKit` and consequently `AppCommand` support comparison using +their existing typed loader, with native records and diagnostics preserved. + +1. Include `Diff` in `FromConfigKit`'s supported operation list and wire its + comparison callback. Keep the generic `T` inside that adapter; no new view + facet, result assertion, reflection, or private upstream import is needed. +2. Load left and then right with their respective selection/input and the same + supplied state. Check context at ordinary boundaries, stop after a failed + reached load, and do not skip either reached load just because selections + compare equal. A successful callback returning nil is operational failure. +3. Adapt diagnostics from each reached successful result or `LoadError` using + existing conversion. Accumulate left diagnostics before loading right, and + retain both loads' diagnostics before invoking native comparison. Preserve + side identity in comparison diagnostic headings so source IDs from two loads + are not confused. Leave ordinary diagnostic headings unchanged. +4. Call `configkit.Diff(left, right)` and return its native records in + `commandkit.Acquired[[]configkit.Difference]`. On error, return accumulated + diagnostics with a cause preserving supported `errors.Is`/`errors.As` + identity. Add useful scope/side context without making exact error wording + or callback internals a new contract. +5. Reuse small local acquisition/diagnostic helpers only where they simplify + ordinary and paired loading. Keep cache policy and arbitrary callback output + application-owned. Update adapter/convenience GoDoc from four operations. + +**Completion evidence:** direct adapter tests with real ConfigKit loads for +public changes, base-versus-overlay loading, equal effective values, +defaults/resolution, and paired-safe concealed changes. Test right failure +retaining left warnings, wrapped load-error +identity, nil results, and cancellation between loads. Exercise a native +comparison error with a non-nil uninitialized result and retain available +diagnostics. Do not build a global fake comparator just to force an unreachable +branch; handler-level provider errors can carry diagnostics from both sides. + +## Stage 5: Render the selected three-category YAML report + +**Primary new file:** `integrations/ckconfig/diff_report.go`. + +**Outcome:** native safe changed regions become the selected YAML document, +without another equality engine or an untyped value round trip. + +1. Build a private report representation using `go.yaml.in/yaml/v4` nodes. + Initialize ordered `additions`, `changes`, `deletions` entries, with empty + mapping, empty mapping, and empty sequence nodes respectively. Use the + existing two-space encoder convention. No report type needs to be exported. +2. Classify each record solely by native left/right `Present`. Append deletion + paths using `Path.String()`. For additions and changes, decode only the + paired-safe right snapshot YAML to a node and place it in the appropriate + category at the record's semantic path. Preserve scalar tags/value types, + explicit null, empty collections, and whole sequence/structural regions. +3. ConfigKit's public path API exposes canonical JSON Pointer spelling, not + public segment access. Handle the empty spelling as the root before splitting; + `/` identifies one empty mapping key. For non-root pointers, remove the leading + separator, split on `/`, and decode `~1` and `~0` once. Preserve + empty, numeric, dot, percent, slash-containing, and tilde-containing keys. + Do not use filesystem cleaning, URL decoding, or numeric-index guessing. + Native sequence changes are atomic, so report ancestors for descendant + records are mappings. Construct mapping keys as YAML strings. +4. Create category ancestors as needed, inserting nodes in the native traversal + order. A root record assigns its safe node directly to the category; it need + not be a mapping when the safe snapshot is null or wholly masked. Keep the + accepted whole-root ambiguity; add no special root envelope or hidden-value + analysis. Mapping-to-empty changes use the native deletion records. +5. Do not inspect left YAML to derive changes, compare rendered snapshots, + expand atomic regions, infer sensitivity from marker text, append full + configurations, or expose `Redacted`/`HiddenChanged` report fields. Concealed + changes remain included because the native record exists, even when safe + snapshots look equal. Do not claim every masked member changed. +6. Encode the completed document into a buffer before report delivery. Parsing + or encoding failures return an error; no successful report bytes have been + written. Apply ordinary error handling rather than adding a general provider + schema-validation framework or new restrictions on legitimate native records. + +**Completion evidence:** semantic YAML checks for all three categories and the +empty document; nested additions/changes, deletion versus null, empty nested +containers, escaped/empty/numeric keys, and large integers or scalar-looking +strings. Cover atomic sequences, structural replacements, native masks, literal +marker-looking public values, and concealed-only changes. Protect ordered root +keys and types, without freezing incidental emitter spelling or mapping styles. + +## Stage 6: Execute comparison through existing outcomes and delivery + +**Primary files:** `integrations/ckconfig/operations.go`; new `diff.go`. + +**Outcome:** an installed `config diff` works end to end with the selected +preflight, acquisition, reporting, cancellation, and outcome behavior. + +1. Add the operation name/summary and dispatch case. Use the existing execution + path to create shared state once after checked pair preparation. Pass the + invocation context, paired request, and state to the selected `Compare`. + Do not call ordinary `Acquire` for comparison. +2. Copy/retain provider diagnostics through the outcome. On provider/native + comparison failure, return operational failure with supported cause identity + and no successful report. Check context after acquisition and before delivery; + do not promise interruption inside synchronous upstream calls. +3. Render with Stage 5, then use existing checked report delivery. Parsing or + encoding errors are operational failures before report writing. Output errors + or short writes preserve the established delivery error and incomplete-report + behavior. No new exit status, assertion mode, or process termination is added. +4. Successful comparisons return success for public differences, concealed + differences, and no records. Nil/empty returned record slices both represent + an empty successful comparison. Diagnostic stream handling remains core-owned. +5. Update focused public execution tests to assemble real declarations and run + through core/Cobra. Reuse existing test helpers rather than constructing + another dispatcher or private core bridge. + +**Completion evidence:** default/explicit YAML, text rejection, file/overlay +selection inheritance, comparison-only scope, discovery/help availability, and +unsupported scopes with zero provider work. Cover provider diagnostics with an +error, wrapped identity, report preparation failure, cancellation, output/short +writes, and repeated invocations with fresh inputs/state. Existing four operations +and their output defaults must continue to work. + +## Stage 7: Demonstrate correct consumer composition and shared caches + +**Primary owners:** ckconfig's simple example and ckprompt's composed example. + +**Outcome:** maintained consumers demonstrate meaningful comparison while +PromptKit inspection remains independent and usable. + +1. Extend the simple example's application loader to honor a supplied config + file through ConfigKit's public source API, retaining its existing default + configuration when omitted. Add two small local YAML fixtures with different + greetings and a documented `config diff` invocation. Keep `AppCommand` wiring + as the minimal consumer path; do not add a bespoke comparison handler. +2. Repair the composed example's single-result `runCache` before demonstrating + paired loads. Key cached results by the complete selection, including presence, + and the supplied `Campaign` input used below. These fields are comparable; + a small application-owned key and map suffice. This is example guidance, not + a CommandKit cache guarantee or service. +3. Make the composed application loader honor a supplied ConfigKit source; + retain its synthetic default when omitted. Add two local fixtures with valid + existing campaign definitions and different default campaigns. Demonstrate + application-scope comparison through the shared state, and verify the two + source selections produce an actual changed value rather than cache equality. + To demonstrate meaningful typed input, apply explicitly supplied `Campaign` + through ConfigKit's public `Options.Overrides` to the existing + `default_campaign` field, using `configkit.Keys("default_campaign")` and the + safe flag label `--campaign`. Apply only when supplied; do not mutate + `Result.Config` after loading. `Limit` remains an input check rather than a + loading dependency. Explain these example-specific choices and cache identity; + they prescribe no downstream application behavior. +4. Keep `ckprompt.ConfigScope` without `Diff`. Verify comparison selects the + application scope while engine validate/show/sources/explain, named prompt and + profile inspection, and `session restore` still work under PromptKit v0.12.0. + Do not add PromptKit comparison by reaching into its ConfigKit internals. +5. Add small consumer tests for fixture comparisons and cache reuse/separation, + including separation for different supplied campaign values and presence. + Use local sources and synthetic values; no model requests or credentials. + +**Completion evidence:** meaningful simple and composed diff reports, cache +separation for different selections, unchanged representative PromptKit/domain +commands, and a usage failure for unsupported `--scope promptkit` comparison. + +## Stage 8: Reconcile documentation and distributed dependency material + +**Outcome:** current documentation and module notices describe implemented +behavior, while future/deferred work stays clearly separate. + +1. Update ckconfig GoDoc, its README, the CLI reference, consumer guide, + internal overview, relevant example READMEs, and development commands. + Document exact new exports in GoDoc and exact CLI/report semantics in the CLI + owner; other documents summarize and link. Include inheritance/presence, + lifecycle, independent capabilities, native semantics, reduced safe reports, + ordinary null/deletion distinction, accepted root ambiguity, and outcome rules. +2. Move the implemented DIFF-01 portion out of deferred work. Retain unsupported + PromptKit comparison, catalogs, independent per-side domain selectors, and + unrelated deferred ideas. Update this plan's completion evidence and the + feature roadmap's implementation status; do not keep proposals disguised as + current operator/API documentation. +3. Reconcile module-local ConfigKit notices to v0.7.1 and its pinned source commit + from the roadmap. Inspect resolved integration tool graphs for ReleaseKit + v0.4.1 and relevant transitives; update actual versions, roles, pinned links, + and any required license/notice material under the OSS policy. Root notices + describe core's own graph, not the combined workspace's selected versions. +4. Finalize normal manifests/sums using real released upstream dependencies. + Preserve intended CommandKit requirements without local replacement clauses + or fabricated unavailable checksums. Any remaining prepublication limitations + belong in the validation evidence and existing release procedure. +5. Update the still-unpublished integration v0.1.0 release notes to describe + dependency adoption and comparison availability where appropriate. Recheck + tag status first: never revise a published release record. If publication has + occurred since this baseline, prepare a fresh version under the release + policy rather than rewriting history. Do not add a core release solely for + integration behavior, bump PromptKit, publish tags, or run the release launcher. +6. Check local links/fragments, module notice paths, whitespace, and stale claims. + Keep accepted ADR decision text unchanged; implementation/report details do + not require moving them back into ADRs. + +**Completion evidence:** reviewed current owners and notices, correct native +dependency attribution, no stale four-operation convenience claims or text-diff +promises, and normal distributed manifests without local wiring. + +## Stage 9: Validate the complete sprint and finish + +**Outcome:** evidence proves the complete feature end state across all three +module boundaries, without implying publication or new guarantees. + +1. With upstream dependencies prepared, run the deterministic default suites + in the development workspace: + + ```sh + go test ./... + (cd integrations/ckconfig && go test ./...) + (cd integrations/ckprompt && go test ./...) + ``` + + These commands traverse their own modules only. Do not run paid/live calls, + release publication exercises, or add coverage/performance/platform gates. +2. Independently check core with `GOWORK=off GOTOOLCHAIN=local go test -mod=readonly + ./...`. Inspect its workspace-off `go list -m all` and `go list -deps ./...` + output for the established absence of optional integration/owning-library + dependencies and for the unchanged declared maintainer tool selection. +3. Check each integration outside workspace mode. If its normal CommandKit + prerequisites are downloadable, use its normal manifest. Otherwise use a + disposable alternate manifest, keeping its actual ConfigKit/PromptKit versions: + + - Create a temporary directory; copy that integration's `go.mod` to a + `.mod` file there and copy any existing sum file to the matching `.sum`. + - From the integration directory, use `go mod edit -modfile=` + to replace the root CommandKit module with the absolute repository root. + For ckprompt, also replace the ckconfig module with the absolute local + ckconfig directory. Do not replace ConfigKit, PromptKit, or ReleaseKit. + - With `GOWORK=off GOTOOLCHAIN=local`, tidy the alternate file, then run + `go test -modfile= -mod=readonly ./...`, `go list + -modfile= -m all`, and `go list -modfile= -deps + ./...`. Inspect selected versions, package imports, and boundary violations. + - Discard alternate files after review. They establish local-source module + isolation and upgraded external compatibility, not availability of future + CommandKit tags. Never copy their replacements into committed manifests. + +4. Run the maintained simple/composed comparison examples and representative + existing inspection/domain commands. Inspect YAML content, diagnostic streams, + and outcomes. Confirm no comparison is advertised by the PromptKit adapter. +5. Audit every feature-roadmap work area against changed code, tests, current + docs, examples, manifests, and notices. Include both sides' input/check timing, + callback-free discovery, equal-selection loading, warning retention, native + safe values, all report categories, null/deletions, output errors, and ordinary + inspection regressions. Reuse earlier successful evidence for unchanged areas. +6. Record actual commands/results and unresolved external limitations here. + Review the complete diff including new files; check documentation whitespace + and links. Commit intended implementation work under the agent policy and + report remaining unrelated work. Follow existing release preparation later; + this sprint does not publish or wait for module proxy availability. + +**Completion evidence:** all three default suites, independent module graph +reviews (with local-source limits identified when applicable), maintained consumer +results, feature-to-evidence audit, and reviewed final documentation/distribution +changes. No significant product/API decisions remain open in this plan.