From ec0e58efff638d0d4cc2ef20e930a9d477ed2e83 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 8 Nov 2026 13:52:48 +0000 Subject: [PATCH] Plan shared debug binding and reporting mode access --- docs/roadmap/shared-debug-binding.md | 233 +++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 docs/roadmap/shared-debug-binding.md diff --git a/docs/roadmap/shared-debug-binding.md b/docs/roadmap/shared-debug-binding.md new file mode 100644 index 0000000..a2aa256 --- /dev/null +++ b/docs/roadmap/shared-debug-binding.md @@ -0,0 +1,233 @@ +# Shared debug binding and reporting-mode access + +Status: agreed capability direction; not implemented. Public binding details +remain open below. Baseline: `69257066e21c12ea3492237ebee072598adea60c`. + +## Purpose and authority + +Applications should be able to receive the boolean value of CommandKit's +`--debug` flag through normal typed input binding. Applications that need the +resolved reporting mode should also have a read-only invocation API. + +The [Audita request](audita-feature-request.md) motivated this work. Subsequent +discussion selected a broader distinction: application debug behavior and CLI +reporting visibility are separate choices. Audita's original requirement that +capture follow reporting mode is an application policy, not a CommandKit +requirement. This roadmap records the agreed direction from that discussion; +it does not treat every proposal in the original request as accepted. + +The [architecture policy](../policy/architecture.md#ownership-boundaries) and +[ADR 0006](../adr/0006-invocation-reporting.md) already assign CLI mechanics to +core and domain behavior to applications. This work fits those boundaries and +does not require another ADR. Implemented behavior remains documented in +[Go declarations](../../command.go) and the [CLI reference](../cli.md#reporting) +until this work lands. + +## Current baseline + +| Area | Current implementation | Work needed | +| --- | --- | --- | +| Typed binding | `BoolFlag` in [binding.go](../../binding.go) converts supplied occurrences and applies the last boolean with `Supplied[bool]` presence. | Let a binding receive the existing core debug flag's input. | +| Registration | `FlagMeta` and `validateBindings` in [command.go](../../command.go) reserve every reporting name, including `debug`. | Recognize the selected shared-binding form without treating it as an independent competing flag. | +| Parser metadata | [commandmeta](../../internal/commandmeta/metadata.go) separates `CoreFlags` from consumer-indexed `Flags`; [parser.go](../../parser.go) supplies core flags to each command. | Preserve consumer binding identity and indexes while sharing one parser declaration. | +| Backend | [internal/cobra/parse.go](../../internal/cobra/parse.go) registers both lists, records occurrences, and extracts indexed values only for consumer flags. | Supply shared debug values and spellings to the participating binding without duplicate backend registration. | +| Reporting | `reduceReportingOccurrences` in [parser.go](../../parser.go) resolves the mutually exclusive mode from ordered occurrences. | Preserve that reduction independently of the bound debug boolean. | +| Invocation | [execute.go](../../execute.go) applies reporting selection before input preparation and execution. [reporter.go](../../reporter.go) stores the mode privately; [Invocation](../../identity.go) exposes no mode query. | Expose the already resolved selection through a CommandKit-owned public type. | + +Simply permitting the name in registration is insufficient: the backend would +otherwise receive two declarations for `debug`. Existing occurrence capture is +the starting point; applications should not scan or reparse arguments. + +## Targeted end state + +### Bind the debug boolean independently + +An application opts into receiving `--debug` in its command input through a +`Flag[I]` and the existing typed setter model. Exact construction and metadata +rules are subject to OD-1 and OD-2. Commands without such a binding retain the +standard core flag and reporting behavior. + +The ordinary shared binding receives `Supplied[bool]`: omission produces +`Value=false, Set=false`; an explicitly supplied false produces +`Value=false, Set=true`. Repeated debug occurrences follow ordinary boolean +binding semantics: every supplied value must be valid, and the last value wins. +Other reporting flags do not rewrite the bound debug value. + +The following examples appear after a complete command path. They show the +ordinary false-default binding and the existing mode-selection rules together: + +| Arguments | Debug value | Supplied | Resolved reporting mode | +| --- | --- | --- | --- | +| None | false | false | Ordinary | +| `--debug` | true | true | Debug | +| `--debug --quiet` | true | true | Quiet | +| `--quiet --debug` | true | true | Debug | +| `--debug --verbose` | true | true | Verbose | +| `--debug --debug=false` | false | true | Ordinary | +| `--verbose --debug --debug=false` | false | true | Ordinary | +| `--debug --quiet=false` | true | true | Debug | +| `--debug --progress=false` | true | true | Debug | + +Aliases use the same canonical debug input. Tokens consumed as other flag +values or appearing after `--` keep their existing meaning. Invalid reporting +booleans remain usage failures without handler execution. Existing placement, +help, and input-preparation timing continue to apply. + +Application code can use the bound value for diagnostic artifacts even when +quiet mode suppresses ordinary CLI output. Core continues to own reporting +visibility; applications own capture, redaction, file lifecycle, and any other +domain effects. No capture policy is imposed on Audita or other applications. + +### Query the resolved reporting mode + +Add a CommandKit-owned `ReportingMode` type with ordinary, quiet, verbose, and +debug values, and one canonical accessor: + +```go +func (inv Invocation) ReportingMode() ReportingMode +``` + +Use `ReportingModeOrdinary`, `ReportingModeQuiet`, `ReportingModeVerbose`, and +`ReportingModeDebug` as the planned constant names, with ordinary as zero. +The accessor reads the existing selection without output or mutation. The mode +is final when the handler receives the invocation and remains its selection +after execution finishes. A manually constructed zero-value invocation returns +ordinary. Reads are compatible with concurrent reporting on that invocation; +this does not add concurrent dispatch support or extend writer lifetimes. + +An application deliberately following reporting visibility can use: + +```go +captureArtifacts := inv.ReportingMode() == commandkit.ReportingModeDebug +``` + +This reports the selected mode, not whether output was delivered successfully. +Progress and report format remain separate selections. Keep the public type +independent of internal/backend types and avoid a second mutable copy of the +resolved mode. A second accessor on `Reporter` is unnecessary for this scope. + +## Implementation work + +1. **Settle binding construction and metadata.** Resolve OD-1 and OD-2 before + making the registration exception. Document which declaration attaches to + the core flag and how its application metadata is handled. Preserve ordinary + validation for unrelated names and bindings; do not broadly disable reserved + name or collision checks. +2. **Connect shared parsing to typed input.** Update declaration validation, + metadata bridging, and backend value extraction together. Register one + effective debug flag, retain canonical occurrence order and supplied + spellings, and deliver its values through normal input preparation. Keep + reporting reduction based on the same parsed occurrences, including for + commands that have no application debug binding. Reuse current mechanisms; + a general observer registry, alternate parser, or subscription lifecycle is + not needed for this feature. +3. **Preserve binding composition.** Review `CaptureFlags`, `FlagValues.ApplyTo`, + and `ApplySuppliedTo` in [flag_values.go](../../flag_values.go). A shared + binding should participate through the existing identity and presence + machinery. Review `RenameFlag` if a special binding marker is introduced: + renaming to an ordinary application name must not accidentally keep reading + core debug input. Avoid adding separate storage or application callbacks just + to carry this boolean. +4. **Expose the mode query.** Reuse reporter state and its synchronization as + appropriate; document the zero-value, invocation lifetime, and concurrent-read + behavior. Do not expose backend objects or add public mutation APIs. +5. **Update canonical documentation when implemented.** Update binding and + invocation GoDoc, the reserved-name wording in `FlagMeta`, and the + [CLI reference](../cli.md#placement-and-aliases). Add a short example to the + [consumer guide](../consumers/guide.md#invocation-reporting) contrasting a + bound debug boolean with an explicit mode query. Explain application effects + in application help using the metadata choice from OD-2. Record the core API + addition in the applicable release note. Retire this roadmap through the + normal documentation lifecycle when complete. + +This is core work. It does not require ConfigKit or PromptKit API additions or +adoption of Audita's artifact implementation. Binding other core flags, adding +mode setters, or creating a general flag-sharing framework is not part of this +sprint; that scope limit is not a permanent prohibition on later capabilities. + +## Validation and completion checklist + +Follow the [testing policy](../policy/testing.md): test the public behavior and +reuse existing parser and reporting coverage rather than duplicate it wholesale. +The table above supplies representative boundary expectations, not a request +for separate copies of the same cases at every layer. + +- [ ] A registered application command receives the shared debug boolean and + presence through normal typed input preparation, including when quiet or + verbose becomes the resolved reporting mode. +- [ ] The public query returns the selected mode without changing visibility; + cover zero value, retained invocation, and fresh selection on subsequent runs. +- [ ] Focused coverage establishes safe mode reads during existing concurrent + reporter use, without asserting concurrent dispatch support. +- [ ] Exercise aliases, explicit false, repeated values, invalid booleans, and + option-looking operands/values where they establish the new binding boundary. + Preserve existing failure precedence and no-handler-on-parse-failure behavior. +- [ ] A command with both shared debug and ordinary flags receives the correct + values; help and completion describe one effective debug option. Exercise + representative capture/application composition and any affected rename path. +- [ ] Test only the metadata/collision decisions actually selected below; + replace obsolete blanket rejection assertions for the allowed binding form. +- [ ] Run focused binding/parser/reporter tests, then the core suite and aligned + integration suites using the [developer guide](../development.md). The shared + parser change justifies checking that standard integration commands still + register and execute. No live providers or Audita processing tests are needed. +- [ ] GoDoc and consumer guidance distinguish boolean input from reporting + mode, and canonical docs describe the implemented registration exception. + +For this documentation pass, review baseline accuracy, scope, links, and +whitespace only. Implementation and the unchecked items above remain future work. + +## Open Decisions + +### OD-1: How does an application declare the shared binding? + +**Recommendation:** recognize a normal `BoolFlag` with `FlagMeta.Name` set to +`debug` as attachment to the existing core flag. It returns the same `Flag[I]`, +uses `Supplied[bool]`, and occupies the normal command binding list. This most +directly meets the request that using debug should feel like using other flags +and avoids another public constructor. The cost is special handling of a +core-owned name, particularly the metadata rules in OD-2. Do not interpret an +arbitrary string-valued or custom-parser flag named `debug` as equivalent merely +because its name matches. + +**Viable alternative:** add a dedicated constructor such as +`DebugFlag[I](set func(*I, Supplied[bool])) Flag[I]`. This still uses the ordinary +typed binding and composition path, but makes attachment explicit and avoids +exposing conflicting declaration options. It adds public API vocabulary and +makes debug construction different from other booleans. Prefer this if ordinary +`BoolFlag` reuse needs substantial metadata reconciliation or confusing exceptions. + +The example constructor name is illustrative; choose one binding form rather +than adding both entry points without a demonstrated need. + +### OD-2: Which application metadata can accompany a shared flag? + +**Recommendation:** retain the core boolean grammar and false default. Allow +command-local usage text to explain additional application effects alongside +core reporting help, and allow a local short spelling through existing alias +and collision rules. Keep one displayed option. Continue using core boolean +completion. Under the ordinary `BoolFlag` approach, report conflicting default, +requiredness, or completion overrides as declaration errors rather than silently +ignoring them. A dedicated constructor could instead omit those options. These +are proposed rules for the shared attachment only, not accepted new restrictions +on ordinary application flags. + +This preserves consistent debug input across applications and avoids help that +claims a default or requirement different from the shared flag. Its tradeoff is +that a normal-looking declaration would accept fewer metadata choices. + +**Viable alternative:** let application defaults and requiredness affect only +the receiving binding, using ordinary binding semantics, while core reporting +continues to follow explicit occurrences. That gives applications more familiar +flexibility: for example, an application default of true could enable its own +diagnostics without selecting debug reporting. However, omission would no longer +necessarily mean a false bound value, and help would need to distinguish an +application default from the core reporting default. If selected, revise the +omission contract above and explain this distinction explicitly; do not silently +allow local metadata to change core reporting selection. + +**Simpler help alternative:** retain core flag help unchanged and put application +effects in the command description, with short spellings supplied through the +existing `FlagAliasFor` mechanism. This avoids help merging, at the cost of less +convenient flag-local documentation. It combines naturally with the dedicated +constructor in OD-1. There is no need for a general metadata-merging framework.