892 lines
43 KiB
Markdown
892 lines
43 KiB
Markdown
# Output policy and invocation reporting
|
||
|
||
Status: behavioral decisions selected through OD-4. Message categories, all four
|
||
output modes, uniform treatment of help and version, message prefixes, and
|
||
OD-1's flag grammar and scope (including independent Progress control) are selected.
|
||
OD-2's unified reporting direction, the terminal failure model, and application
|
||
ownership of domain-specific error decisions are selected.
|
||
OD-3's failure presentation and retention boundaries are selected.
|
||
OD-4's delivery lifecycle, write-failure handling, and quiet-mode rendering are
|
||
selected. The consumer API signatures, presentation-result model, provider
|
||
diagnostic boundary, fallback explanations, and rendering callback boundary
|
||
below are also selected. Architectural preparation is complete:
|
||
[ADR 0014](../adr/0014-invocation-reporting.md) is accepted, and the
|
||
[architecture policy](../policy/architecture.md) is aligned. Implementation has
|
||
started. Stages 1–19 are complete; final validation remains. The
|
||
[implementation plan](implementation.md) owns stage order and execution
|
||
instructions.
|
||
|
||
## Purpose
|
||
|
||
Provide consistent handling of `--quiet`, `--verbose`, `--debug`, and
|
||
`--progress` across application commands and optional integrations. Reduce
|
||
repeated output classification, diagnostic presentation, and delivery bookkeeping
|
||
where CommandKit can provide a meaningful benefit.
|
||
|
||
Ordinary Go writers and formatting functions already make application output
|
||
straightforward. Design reporting conveniences around the shared output policy
|
||
and invocation lifecycle. Applications continue to own their report content,
|
||
domain behavior, and operation outcomes.
|
||
|
||
## CLI presentation and error-handling ownership
|
||
|
||
Applications decide how to respond to domain-specific errors: recover, retry,
|
||
continue silently, report a Warning, or end the invocation with a failure.
|
||
CommandKit may provide shared helpers for carrying, inspecting, and presenting
|
||
errors while leaving those decisions with the application. A Go `error` alone
|
||
does not determine a CLI message category. Warnings may describe issues with or
|
||
without an underlying Go error, and handling an error does not require emitting
|
||
a message.
|
||
|
||
CommandKit handles the presentation the application requests: message category,
|
||
visibility, formatting, timing, and delivery. For terminal application failures,
|
||
the application supplies a non-zero outcome and an operator-facing explanation.
|
||
It may also supply an optional Go error cause for the execution caller to inspect.
|
||
CommandKit preserves that cause and its existing `errors.Is`/`errors.As` behavior.
|
||
Supplying a cause alone does not select an outcome, generate an operator-facing
|
||
message, or change visibility.
|
||
|
||
Applications can explicitly present `err.Error()` as a Warning through the
|
||
ordinary message API, illustrated by `report.Warn(err.Error())`. Defer a separate
|
||
`WarnError` convenience helper pending demonstrated value; this conversion
|
||
already requires little code.
|
||
|
||
CommandKit retains responsibility for handling its own errors, including
|
||
routing, parsing, and reporting failures. Integration-owned commands similarly
|
||
adapt their upstream APIs into deliberate command outcomes and presentations.
|
||
Ordinary Go error returns from CommandKit APIs and writers remain available where
|
||
appropriate.
|
||
|
||
## Current baseline
|
||
|
||
[`Streams` and `Invocation`](../../identity.go) expose caller-selected output
|
||
and diagnostic writers. [`Outcome` and `Diagnostic`](../../outcome.go) carry
|
||
operation causes, diagnostic writers, and delivery state. Handlers can return
|
||
`Failure(err)` or attach diagnostics to an outcome for presentation after the
|
||
handler returns. `MessageDiagnostic` currently writes the supplied message with
|
||
a newline; severity does not add a label automatically.
|
||
|
||
CommandKit coordinates final diagnostic delivery and retains delivery failures
|
||
separately from the operation cause. Custom handlers can also write directly to
|
||
the supplied streams and retain responsibility for their output. The existing
|
||
outcome and channel ownership decisions are recorded in
|
||
[ADR 0005](../adr/0005-invocation-outcomes.md).
|
||
|
||
Integration adapters already translate upstream diagnostic records into
|
||
CommandKit diagnostics while preserving native rendering and source context.
|
||
Some adapters wrap multiple records in one diagnostic writer and classify the
|
||
batch by its highest severity. This matters when designing selective visibility.
|
||
|
||
There is currently no shared verbosity policy or invocation reporter API.
|
||
|
||
## Selected output policy
|
||
|
||
Distinguish substantive application output from diagnostic message categories:
|
||
|
||
- **Substantive output (Normal):** The requested report or command result.
|
||
- **Error:** Terminal presentation of an unrecoverable invocation failure,
|
||
resulting in a non-zero status code.
|
||
- **Warning:** Something went wrong or deserves attention, but the invocation
|
||
can still succeed. The application chooses whether to report such an issue.
|
||
- **Info:** Informational context and useful explanation, such as which profile
|
||
was selected or a final operation summary.
|
||
- **Progress:** Streaming updates about work underway, such as items processed.
|
||
- **Debug:** Troubleshooting detail, including intermediate decisions and
|
||
execution-path information.
|
||
|
||
Debug mode adds troubleshooting detail to verbose output:
|
||
|
||
| Output category | `--quiet` | No flag | `--verbose` | `--debug` |
|
||
| --- | --- | --- | --- | --- |
|
||
| Substantive application output | Suppressed | Shown | Shown | Shown |
|
||
| Error | Shown | Shown | Shown | Shown |
|
||
| Warning | Suppressed | Suppressed | Shown | Shown |
|
||
| Info | Suppressed | Suppressed | Shown | Shown |
|
||
| Debug | Suppressed | Suppressed | Suppressed | Shown |
|
||
| Progress, without an explicit setting | Suppressed | Suppressed | Suppressed | Shown |
|
||
|
||
Info belongs in verbose mode; debug includes verbose messages. Normal output
|
||
remains a substantive result category, separate from diagnostic severity.
|
||
The category of a message determines its visibility; stream destination alone
|
||
cannot distinguish a warning from an error.
|
||
|
||
Progress is distinct from Info and is controlled separately as specified below.
|
||
Applications and integrations are not required to generate messages in any
|
||
category. Enabling a category adds no output when there are no messages in it;
|
||
other output remains subject to its own visibility rules.
|
||
|
||
Error presentation follows the terminal failure model below. An application
|
||
may report a recovered problem as a Warning or continue without a message.
|
||
|
||
Apply the same policy to help and version. There are no command-specific
|
||
exceptions for successful output:
|
||
|
||
```text
|
||
app help --quiet # succeeds silently
|
||
app version --quiet # succeeds silently
|
||
```
|
||
|
||
Usage and operational failures still report their errors in quiet mode.
|
||
The feature must distinguish successful output suppression from failure
|
||
diagnostic delivery.
|
||
|
||
### Message prefixes and channels
|
||
|
||
Substantive output uses the result destination, conventionally stdout. Warning,
|
||
Info, Progress, Debug, and terminal explanations use the diagnostic destination,
|
||
conventionally stderr. Callers supply both destinations.
|
||
|
||
Use `WARN:`, `DEBUG:`, and `ERROR:` prefixes for simple warning, debug, and error
|
||
messages respectively. Substantive output and Info messages have no prefix.
|
||
For multiline diagnostics, prefix the opening line and preserve the structured
|
||
detail. Integration adapters coordinate native diagnostic rendering to avoid
|
||
duplicate severity labels or loss of upstream formatting.
|
||
|
||
Usage guidance belongs to the terminal usage explanation and remains visible in
|
||
quiet mode. Successful help output follows the substantive-output policy.
|
||
|
||
Initially, Progress messages are unprefixed streamed lines. The diagnostic
|
||
destination keeps them separate from substantive output such as JSON or YAML.
|
||
Terminal redraw and progress-bar behavior are outside this implementation.
|
||
|
||
### Terminal failure model
|
||
|
||
During execution, applications can produce substantive output, Progress, Info,
|
||
Debug, and Warnings. An unrecoverable failure ends the handler by returning a
|
||
failed outcome. There is no standalone `report.Error(...)` method. Detailed
|
||
error explanations accompany the returned failure and are presented as part of
|
||
that terminal step.
|
||
|
||
CommandKit finalizes reporting, presents the terminal failure explanation, and
|
||
returns the non-zero outcome to the application entry point. It accepts no
|
||
further normal output for that invocation after terminal error presentation.
|
||
The same boundary applies to core routing and parsing failures that prevent
|
||
handler execution. The application entry point retains ownership of process
|
||
exit; CommandKit ends the invocation and returns its outcome.
|
||
|
||
For example, if an export operation fails and the application decides it cannot
|
||
continue, the handler returns a non-zero outcome with an explicit explanation
|
||
such as "Unable to export the requested report." The application can choose to
|
||
include detail from a Go error in that explanation. CommandKit presents the
|
||
supplied explanation without automatically deriving text from an application
|
||
error. The selected API below provides failure constructors for this purpose.
|
||
When a failed outcome supplies no explanation, CommandKit supplies the generic
|
||
fallback defined under terminal outcomes below.
|
||
|
||
Output streamed before the failure remains visible according to its selected
|
||
mode; terminal failure does not withdraw earlier bytes. Deferred messages,
|
||
cleanup writes, and delivery failures must respect the end of normal reporting.
|
||
Their finalization and late-write behavior follow OD-4 below. A terminal
|
||
failure may have multiple explanatory records, all belonging to that failure.
|
||
|
||
### Failure explanations and presentation ownership (OD-3)
|
||
|
||
Make a non-zero outcome with an explicit message the convenient failure path.
|
||
Support structured explanations, including multiple records, through the same
|
||
presentation facility for richer application and integration reports. Neither
|
||
form requires an underlying Go error.
|
||
|
||
The application or integration explicitly identifies the context belonging to
|
||
a terminal explanation. Present that context with the failure in every mode,
|
||
including quiet. Independent warnings and other messages retain their own
|
||
visibility rules. For mixed upstream batches, adapters preserve useful native
|
||
rendering while distinguishing terminal explanatory context from independent
|
||
messages. Core does not infer those relationships from error chains or text.
|
||
|
||
Assign each submitted message one presentation path. Returning a terminal
|
||
explanation schedules its presentation once; core does not append another
|
||
message derived from an application error cause. Native renderers can supply
|
||
already-labelled content without receiving duplicate prefixes. Avoid text-based
|
||
deduplication: separate events may legitimately produce identical messages.
|
||
|
||
Constructing a failure result attaches explanatory content without emitting or
|
||
independently scheduling an Error. Returning that result hands the explanation
|
||
to CommandKit for terminal delivery through the invocation's reporting facility.
|
||
The selected API expresses this distinction for both simple and structured
|
||
explanations. For example:
|
||
|
||
| Handler action | Presentation |
|
||
| --- | --- |
|
||
| Construct a non-zero result with "Unable to export the requested report," then return it. | Construction emits nothing. CommandKit presents the message once during terminal delivery. |
|
||
| Return a failure with native diagnostic records and their source context, submitting independent warnings separately. | CommandKit presents the structured terminal explanation in every mode. Independent warnings follow their own visibility policy. |
|
||
|
||
### Retention boundary (OD-3)
|
||
|
||
CommandKit provides invocation-scoped CLI presentation. Logging and message
|
||
history collection remain application concerns. Keep terminal explanations and
|
||
any supplied cause with the returned outcome, and keep the temporary state needed
|
||
for deferred delivery, parser events awaiting mode selection, and delivery
|
||
bookkeeping. The reporting facility does not collect a transcript or retain other
|
||
emitted or suppressed messages for later inspection. No optional message-history
|
||
facility is planned for this implementation.
|
||
|
||
## Selected flag grammar and scope
|
||
|
||
### Mode selection and resets
|
||
|
||
Model quiet, ordinary, verbose, and debug as mutually exclusive presentation
|
||
modes. Each invocation starts in ordinary mode. Process flag occurrences in
|
||
invocation order; the last enabled mode wins. Repetition does not increment
|
||
verbosity, and debug is selected explicitly.
|
||
|
||
Accept presence and the existing explicit boolean syntax, including
|
||
`--quiet=true` and `--quiet=false`. A false occurrence clears its mode only if
|
||
that mode is currently selected, returning to ordinary mode. It does not restore
|
||
an earlier selection or disable a different mode. Every occurrence is converted;
|
||
invalid boolean values remain usage errors even when a later flag would replace
|
||
the selection.
|
||
|
||
| Flag sequence | Final mode |
|
||
| --- | --- |
|
||
| `--quiet --verbose` | Verbose |
|
||
| `--debug --quiet` | Quiet |
|
||
| `--verbose --verbose` | Verbose |
|
||
| `--verbose --verbose=false` | Ordinary |
|
||
| `--quiet --verbose=false` | Quiet |
|
||
| `--verbose --quiet --quiet=false` | Ordinary |
|
||
| `--quiet=false --debug` | Debug |
|
||
|
||
Keep verbosity a presentation choice: quiet preserves the requested operation's
|
||
semantics and rendering failures. Delivery failures depend on the output actually
|
||
requested; suppressing a destination write can avoid a delivery failure. Apply
|
||
the policy through CommandKit-managed reporting and streams. Output written
|
||
directly to process streams or external destinations remains application-owned.
|
||
|
||
### Progress selection
|
||
|
||
`--progress` enables Progress without enabling Info, Warning, or Debug messages.
|
||
`--progress=false` explicitly disables it. Accept the existing boolean syntax;
|
||
repeated canonical or aliased progress settings use the last value. Retain
|
||
whether a setting was explicitly supplied, separately from its boolean value.
|
||
|
||
Determine effective Progress visibility from the final presentation mode and
|
||
explicit progress setting:
|
||
|
||
1. Quiet mode always suppresses Progress.
|
||
2. Otherwise, an explicit progress setting controls visibility.
|
||
3. Without an explicit setting, debug enables Progress; ordinary and verbose
|
||
modes leave it disabled.
|
||
|
||
An explicit progress setting overrides the mode's default regardless of its
|
||
position relative to mode flags. It does not select or reset the presentation
|
||
mode. Debug includes verbose detail and defaults Progress on; verbose alone
|
||
does not enable Progress.
|
||
|
||
| Flags | Result |
|
||
| --- | --- |
|
||
| `--verbose` | Warning and Info, without Progress. |
|
||
| `--verbose --progress` | Warning, Info, and Progress. |
|
||
| `--debug` | Warning, Info, Debug, and Progress. |
|
||
| `--debug --progress=false` or `--progress=false --debug` | Debug detail without Progress. |
|
||
| `--quiet --progress` or `--progress --quiet` | Only failure diagnostics. |
|
||
| `--debug --verbose` | Verbose detail without Progress, absent an explicit progress setting. |
|
||
|
||
Passing `--progress` to an application that emits no Progress messages adds no
|
||
visible output. It does not suppress that application's substantive results or
|
||
require a progress implementation.
|
||
|
||
### Placement, availability, and aliases
|
||
|
||
Core supplies `--quiet`, `--verbose`, `--debug`, and `--progress` for application
|
||
commands, integrations, the root, canonical groups, and alias-only discovery help.
|
||
Consumers do not register them individually. Preserve the existing placement
|
||
grammar: flags follow the complete command path and may be interspersed with
|
||
operands after that path. For example:
|
||
|
||
```text
|
||
app config show --quiet
|
||
app export destination --verbose
|
||
app help config show --quiet
|
||
app version --quiet
|
||
```
|
||
|
||
Options before or between command-path components are outside this grammar.
|
||
Tokens consumed as values and tokens following `--` retain their ordinary
|
||
meaning.
|
||
|
||
Reserve the four long spellings. Applications may add `-q`, `-v`, or `-d`
|
||
through the alias API; no built-in short spellings are selected. An application
|
||
flag declaration that defines a reserved long name fails installation with a
|
||
collision error. Aliases may target these flags but may not overload canonical
|
||
spellings. Apply the existing collision and atomic installation rules, including
|
||
later registration.
|
||
|
||
Aliased and canonical occurrences participate in the same ordering. With
|
||
`-q → --quiet`, `app export --verbose -q` selects quiet mode. Document the newly
|
||
reserved canonical names as a compatibility change.
|
||
|
||
### Routing and parsing failures
|
||
|
||
Determine the mode through the normal parser before execution or successful
|
||
help/report presentation. Do not add a separate verbosity scanner. This keeps
|
||
token interpretation consistent and avoids reinterpreting values or operands.
|
||
|
||
Routing and parsing failures report their errors regardless of verbosity. For
|
||
example, `app export --quiet --unknown` still reports the unknown flag. Error
|
||
visibility follows the selected output policy independently of how the mode is
|
||
determined; avoiding a separate scanner is an implementation simplification.
|
||
|
||
## CommandKit's own diagnostics
|
||
|
||
Use the shared reporting system for CommandKit's invocation diagnostics so
|
||
parser, integration, and application messages follow the same visibility and
|
||
delivery policy. Installation errors occur before an invocation exists and
|
||
continue to be returned to the application without automatic printing.
|
||
|
||
Supported repetition and last-occurrence overrides are routine decisions.
|
||
Repeated singleton flags, including `--verbose --verbose`, may produce debug
|
||
detail; repetition alone does not warrant a warning. Repeatable flags retain
|
||
their intended accumulation behavior. Add warnings for concrete concerns an
|
||
operator can act on, such as a deprecated spelling if deprecation is introduced.
|
||
Unknown flags, invalid values, and invalid command paths remain invocation
|
||
failures with error diagnostics.
|
||
|
||
Start debug instrumentation with routing, alias resolution, verbosity selection,
|
||
and handler completion. Other useful candidates can be added where they
|
||
explain a consequential decision:
|
||
|
||
| Event | Useful detail |
|
||
| --- | --- |
|
||
| Command-alias resolution | Entered path, intermediate redirects, and final canonical path. |
|
||
| Flag-alias resolution | Entered spelling and canonical flag name. |
|
||
| Scoped alias selection | A command-scoped mapping took precedence over an application-wide mapping. |
|
||
| Verbosity selection | A later occurrence selected a different mode; for `--verbose --debug`, debug wins because it appears last. |
|
||
| Help-route selection | A canonical command or alias-only discovery context was selected. |
|
||
| Repeated singleton flag | A later occurrence replaced an earlier occurrence, without printing their values. |
|
||
| Input preparation | Canonical input validation completed and the handler is starting. |
|
||
| Handler completion | Canonical command and the handler's returned classification and status, before final delivery. |
|
||
|
||
The handler-completion event describes the handler's result. The final returned
|
||
invocation outcome also includes delivery failures and remains authoritative.
|
||
Present this event before terminal failure presentation; do not emit a completion
|
||
message afterward.
|
||
|
||
Use the final selected mode to present earlier parser decisions. Collect the
|
||
relevant events during parsing and apply visibility after mode selection:
|
||
`--verbose --debug` shows the selection explanation, while `--debug --quiet`
|
||
suppresses it. This collection of parser events does not require whole-run
|
||
output buffering.
|
||
|
||
Debug messages should explain operator-relevant decisions independently of
|
||
incidental implementation steps. Use command paths, flag names, and decision
|
||
descriptions rather than raw argument or configuration-value dumps. Upstream
|
||
libraries retain ownership of their diagnostic detail and disclosure rules.
|
||
|
||
## Selected delivery lifecycle and quiet execution (OD-4)
|
||
|
||
### Ordering and finalization
|
||
|
||
Use synchronous delivery. Present immediate messages and substantive writes in
|
||
submission order. After the handler returns:
|
||
|
||
1. Deliver eligible deferred messages in queue order, without sorting by severity.
|
||
2. Finish ordinary reporting, including any handler-completion debug message and
|
||
flushing any CommandKit-owned buffers for ordinary output. Account for failures
|
||
discovered during this delivery before preparing the terminal explanation.
|
||
3. Present any terminal failure explanation and complete its delivery, including
|
||
any CommandKit-owned buffering of that explanation.
|
||
4. Close the invocation's reporting facility and return the final outcome.
|
||
|
||
Deferred messages remain eligible when the operation fails, subject to verbosity.
|
||
Deferral controls timing; applications queue success summaries only after the
|
||
relevant operation succeeds. Ordering governs CommandKit's writes; separate
|
||
stdout and stderr destinations may be buffered or displayed differently by the
|
||
caller.
|
||
|
||
CommandKit finalizes automatically. Consumers do not need a separate flush or
|
||
finish call. Handler cleanup, including Go `defer` functions, runs before the
|
||
handler returns and participates in normal reporting. Applications complete any
|
||
reporting goroutines before returning. After finalization, writes and message
|
||
submissions return a closed-reporter error and emit nothing.
|
||
|
||
Reporter operations within an invocation are safe to call concurrently.
|
||
Serialize access to reporting state and delivery so individual messages do not
|
||
interleave. Concurrent submissions have no predetermined order; deferred messages
|
||
follow the order in which they enter the queue. Multiple writes composing one
|
||
application report still require application coordination if they must remain
|
||
together. This contract covers operations through one invocation's reporter;
|
||
coordination with writes outside that reporter remains caller-owned.
|
||
|
||
Finalize any buffers owned by CommandKit. Caller-supplied writers remain
|
||
caller-owned; CommandKit does not automatically close them or invoke arbitrary
|
||
flushing interfaces.
|
||
|
||
Visibility applies when output is emitted. A verbose or debug run may show
|
||
informational detail before subsequently failing. Progress and substantive
|
||
output may likewise be partially delivered before a failure. Previously written
|
||
bytes cannot be withdrawn. Whole-run buffering solely to make failed runs
|
||
contain only error output is outside this implementation.
|
||
|
||
Keep errors understandable in every mode, including source locations and
|
||
related explanatory context. Unrelated warnings and informational messages
|
||
remain subject to the selected mode. Apply the selected retention boundary
|
||
without accumulating a message history.
|
||
|
||
### Rendering failures, write failures, and execution flow
|
||
|
||
Distinguish who invokes a renderer and whether its destination failed:
|
||
|
||
| Failure source | Responsibility and handling |
|
||
| --- | --- |
|
||
| An encoder or renderer called directly by the application | The application handles its returned error and chooses the outcome and explanation. |
|
||
| A rendering callback invoked by CommandKit | CommandKit handles its returned error as a presentation failure and reflects it in the invocation outcome. |
|
||
| A destination rejects a write | CommandKit records the delivery failure and stops further writes to that destination. |
|
||
|
||
A rendering failure may occur before any write or while the destination remains
|
||
healthy. It does not by itself establish a failed destination. CommandKit can
|
||
explain its rendering failure through a working diagnostic destination. These
|
||
responsibilities also apply when rendering into a discard destination under
|
||
quiet mode.
|
||
|
||
Failure to deliver requested output makes an otherwise successful invocation
|
||
unsuccessful. Apply this rule to substantive output, Warning, Info, Progress,
|
||
Debug, and terminal explanations. Intentional suppression requests no delivery
|
||
and is not a delivery failure.
|
||
|
||
Standard writer operations return write errors normally. Immediate reporting
|
||
methods also expose delivery errors. CommandKit records bounded delivery-failure
|
||
information so an ignored write error cannot yield a successful invocation.
|
||
Deferred delivery failures appear in the final outcome.
|
||
|
||
Apply ADR 0005's outcome rules when presentation fails. For standard outcomes,
|
||
return operational failure with status `1`, including when the original outcome
|
||
was a usage failure. Preserve the original explanation and any supplied cause;
|
||
record the presentation failure separately in `Presentation`.
|
||
|
||
Application-defined domain outcomes retain their domain classification. Preserve
|
||
an already non-zero domain status. If the domain status was `0`, change the
|
||
suggested status to `1` so presentation failure cannot yield a successful
|
||
invocation. The application entry point retains control of process exit.
|
||
|
||
| Outcome before presentation fails | Final outcome |
|
||
| --- | --- |
|
||
| Success, status `0` | Operational failure, status `1`. |
|
||
| Usage failure, status `2` | Operational failure, status `1`. |
|
||
| Operational failure, status `1` | Operational failure, status `1`. |
|
||
| Domain outcome, non-zero status | Same domain classification and status. |
|
||
| Domain outcome, status `0` | Same domain classification, status `1`. |
|
||
|
||
These rules apply to both destination-write failures and rendering failures from
|
||
callbacks invoked by CommandKit. Reporting failures remain CommandKit's own
|
||
error-handling responsibility.
|
||
|
||
Stop writing to a destination after it fails. A substantive-output failure can
|
||
receive a terminal explanation through the diagnostic destination if it still
|
||
works. If diagnostic delivery fails, return that information to the caller.
|
||
Do not redirect diagnostics into substantive output or recursively attempt to
|
||
report the reporting failure.
|
||
|
||
A failed write does not stop application code. The handler can inspect the error
|
||
and return immediately; otherwise, CommandKit records the failure and finalizes
|
||
when the handler returns. CommandKit does not use process exit or panic to
|
||
interrupt execution. Terminal failure presentation ends normal output.
|
||
|
||
### Quiet-mode rendering and suppression
|
||
|
||
Initially, run substantive report generation normally with its managed writer
|
||
discarding output in quiet mode. This preserves report generation, validation,
|
||
and serialization failures while changing presentation. For example, a report
|
||
encoder failure still fails the invocation under `--quiet`; the handler remains
|
||
responsible for handling errors returned by an encoder it calls.
|
||
|
||
Simple suppressed messages need no formatting or destination writes. For richer
|
||
report renderers, initially render into a discard destination. Rendering may be
|
||
skipped later where doing so preserves the command's operation and failure
|
||
semantics.
|
||
|
||
Treat suppression as intentional suppression, never as failed or incomplete
|
||
delivery. Do not retain suppressed content. Delivery needs only the deferred
|
||
queue, reporting lifecycle state, and bounded delivery-failure information,
|
||
alongside the temporary parser state and returned explanations described above.
|
||
|
||
## Selected consumer reporting API
|
||
|
||
Provide one reporting facility per invocation for substantive output, immediate
|
||
messages, and end-of-run diagnostics. The consumer API is chosen for its intended
|
||
long-term design; with no current consumers, preserving existing API shapes
|
||
carries no weight in this decision.
|
||
|
||
The following declarations specify the target API and are not yet implemented.
|
||
|
||
The reporter owns presentation: output visibility, progress settings, prefixes,
|
||
ordering, and delivery bookkeeping. Its views share invocation state, allowing
|
||
already-emitted messages, deferred messages, suppression, and write failures to
|
||
be coordinated in one place. Helpers may receive the reporter or a narrow
|
||
application-defined interface. Domain libraries can continue returning
|
||
diagnostic data for the handler to submit, independently of CommandKit.
|
||
|
||
Use the reporter as the common diagnostic submission path, replacing the earlier
|
||
proposal to split submission between a reporter and `Outcome.Diagnostics`.
|
||
Terminal explanations accompany returned outcomes and are delivered through the
|
||
same facility. Caller-selected destinations remain at `App.Execute`; handlers
|
||
use `Invocation.Reporter()` in place of raw invocation streams.
|
||
|
||
### Invocation reporter and structured detail
|
||
|
||
```go
|
||
func (inv Invocation) Reporter() *Reporter
|
||
|
||
type Reporter struct {
|
||
// Private invocation state.
|
||
}
|
||
|
||
func (r *Reporter) Output() io.Writer
|
||
func (r *Reporter) RenderOutput(write func(io.Writer) error) error
|
||
|
||
func (r *Reporter) Info(message string, details ...Detail) error
|
||
func (r *Reporter) Warn(message string, details ...Detail) error
|
||
func (r *Reporter) Debug(message string, details ...Detail) error
|
||
func (r *Reporter) Progress(message string, details ...Detail) error
|
||
|
||
func (r *Reporter) AtEnd() DeferredReporter
|
||
|
||
type Detail func(io.Writer) error
|
||
```
|
||
|
||
`Output()` supports ordinary writers and encoders. `RenderOutput()` coordinates
|
||
an entire rendering operation, captures its rendering error, and prevents other
|
||
reporter operations from interleaving with it. Rendering callbacks write through
|
||
their supplied writer. Each complete categorized message, including its details,
|
||
is likewise coordinated as one reporter operation.
|
||
|
||
Rendering callbacks use their supplied writer only during the callback. They may
|
||
call rendering helpers that write through it. While a callback is running, it
|
||
must not recursively submit messages, queue deferred messages, start another
|
||
rendering operation, or write through a separate `Output()` handle belonging to
|
||
the same invocation's reporter. Make additional reporter submissions before or
|
||
after the callback. A callback must also avoid waiting for another goroutine to
|
||
submit through that reporter, since the submission may be waiting for the
|
||
callback to finish.
|
||
|
||
This boundary applies to `RenderOutput`, immediate and deferred message details,
|
||
and terminal explanation details. Ordinary concurrent reporter calls remain
|
||
supported and serialized. Keeping each rendering operation together motivates
|
||
the boundary; it does not prescribe a lock implementation or promise runtime
|
||
detection of recursive calls.
|
||
|
||
Message strings are literal; consumers use `fmt.Sprintf` when formatting is
|
||
needed. Initially omit parallel `Infof`, `Warnf`, and similar methods. CommandKit
|
||
formats the opening message with its category's prefix and a terminating newline.
|
||
Optional details follow in order with their formatting preserved. An empty
|
||
opening message produces no heading, prefix, or blank opening line, supporting
|
||
native renderers that supply a complete, labelled diagnostic:
|
||
|
||
```go
|
||
_ = report.Warn("Using the default profile")
|
||
_ = report.Warn("Some configuration settings need attention.", writeNativeWarnings)
|
||
_ = report.Warn("", writeNativeWarnings)
|
||
```
|
||
|
||
These are alternative calls; `writeNativeWarnings` represents an
|
||
application- or integration-supplied function with signature
|
||
`func(io.Writer) error`. No generic logging method or severity enumeration is
|
||
needed to submit messages. Applications need not check verbosity before reporting
|
||
or implement categories they do not use.
|
||
|
||
Immediate methods return rendering or delivery errors, and CommandKit records
|
||
the corresponding presentation failure even if the caller ignores the error.
|
||
A caller can inspect the returned error and end its handler immediately.
|
||
|
||
### Deferred messages
|
||
|
||
```go
|
||
type DeferredReporter struct {
|
||
// Private reference to the same invocation state.
|
||
}
|
||
|
||
func (r DeferredReporter) Info(message string, details ...Detail) error
|
||
func (r DeferredReporter) Warn(message string, details ...Detail) error
|
||
func (r DeferredReporter) Debug(message string, details ...Detail) error
|
||
```
|
||
|
||
For example:
|
||
|
||
```go
|
||
_ = report.AtEnd().Warn("Three optional files were unavailable.")
|
||
```
|
||
|
||
Copies of the deferred view share invocation state. Its methods return submission
|
||
errors, such as attempting to use a closed reporter. Errors encountered during
|
||
deferred rendering appear in the final invocation outcome. Progress remains an
|
||
immediate streaming operation; substantive output uses the main reporter.
|
||
|
||
Copy callback lists when accepting a submission. Callers retain ownership of
|
||
data captured by rendering callbacks and keep that data valid until presentation.
|
||
|
||
### Terminal outcomes
|
||
|
||
Outcome construction requires no invocation state and emits nothing:
|
||
|
||
```go
|
||
type Explanation struct {
|
||
Message string
|
||
Details []Detail
|
||
}
|
||
|
||
type Outcome struct {
|
||
Kind OutcomeKind
|
||
Status int
|
||
Domain string
|
||
Cause error
|
||
Explanation Explanation
|
||
Presentation PresentationResult
|
||
}
|
||
|
||
func Success() Outcome
|
||
func Failure(message string, details ...Detail) Outcome
|
||
func Usage(message string, details ...Detail) Outcome
|
||
func Domain(name string, status int) Outcome
|
||
|
||
func (o Outcome) WithExplanation(message string, details ...Detail) Outcome
|
||
func (o Outcome) WithCause(cause error) Outcome
|
||
```
|
||
|
||
`Success` returns a successful outcome with status `0`. `Failure` produces an
|
||
operational failure with status `1`; `Usage` produces a usage failure with status
|
||
`2`. `Domain` constructs an outcome with the supplied application-defined
|
||
classification and status. Subsequent presentation failures follow OD-4's rules:
|
||
domain classification is preserved, and a zero status becomes `1` if presentation
|
||
fails.
|
||
`OutcomeKind` retains the success, usage, operational, and domain classifications.
|
||
|
||
`WithExplanation` returns a modified outcome value and replaces its terminal
|
||
explanation. Explanations belong to failed outcomes; successful summaries use
|
||
Info or substantive output. Ordinary application failure, native failure
|
||
rendering, and a custom domain failure respectively can be expressed as:
|
||
|
||
```go
|
||
return commandkit.Failure("Unable to export the requested report.")
|
||
```
|
||
|
||
```go
|
||
return commandkit.Failure("", writeNativeFailure)
|
||
```
|
||
|
||
```go
|
||
return commandkit.Domain("validation", 3).
|
||
WithExplanation("The document failed validation.", writeFindings)
|
||
```
|
||
|
||
The named renderers above have signature `func(io.Writer) error` and preserve the
|
||
owning library's safe diagnostic formatting. They run during terminal delivery,
|
||
not when constructing the outcome.
|
||
|
||
Before terminal delivery, supply the explanation `Command failed.` when a
|
||
non-zero outcome has an empty `Explanation.Message` and no non-nil callback in
|
||
`Explanation.Details`. This produces `ERROR: Command failed.` in every mode,
|
||
including quiet, and becomes the explanation retained in the returned outcome.
|
||
Adding the fallback leaves classification, status, and cause unchanged;
|
||
presentation failures still follow OD-4's rules. The fallback is based on the
|
||
failed outcome and absent explanation, without deriving text from `Cause.Error()`.
|
||
|
||
An empty heading with a non-nil detail renderer is an explicitly supplied
|
||
explanation. Trust that renderer to provide its content. Do not buffer or replay
|
||
it to decide whether the explanation is sufficiently informative, and do not
|
||
add the fallback merely because it writes no bytes.
|
||
|
||
| Returned outcome | Terminal presentation |
|
||
| --- | --- |
|
||
| `Failure("")` | Generic fallback, operational classification and status `1`. |
|
||
| `Domain("validation", 3)` | Generic fallback, domain classification and status `3`. |
|
||
| `Failure("").WithCause(err)` | Generic fallback; the supplied cause remains available without automatic rendering. |
|
||
| `Failure("", writeNativeFailure)` | Native explanation without an added generic heading. |
|
||
|
||
`Cause` is optional. `WithCause` returns a modified outcome value, replacing the
|
||
cause without changing the classification, status, or explanation. For example:
|
||
|
||
```go
|
||
return commandkit.Failure("Unable to open the configuration file.").
|
||
WithCause(err)
|
||
```
|
||
|
||
CommandKit presents the supplied explanation and carries the original error
|
||
through to the execution caller, which can inspect it with expressions such as
|
||
`errors.Is(outcome.Cause, fs.ErrNotExist)`. It does not automatically present
|
||
`Cause.Error()` or use the cause to decide success, failure, or message category.
|
||
Providing a cause is independent of choosing the terminal explanation; neither
|
||
is inferred from the other. Warnings do not require supplying or retaining causes.
|
||
|
||
Outcome processing preserves supplied causes, including their existing Go error
|
||
identity, when a subsequent rendering or delivery failure occurs. Presentation
|
||
failures are recorded separately in `Presentation`. The returned outcome retains
|
||
its terminal explanation and optional cause, with other retention limited to the
|
||
presentation needs described above.
|
||
|
||
### Provider-returned diagnostic data
|
||
|
||
Providers return values and diagnostic data without needing a reporter parameter.
|
||
Retain `Acquired[T]` with separate fields for independent messages and failure
|
||
explanations:
|
||
|
||
```go
|
||
type Acquired[T any] struct {
|
||
Value T
|
||
Diagnostics []Diagnostic
|
||
Explanation Explanation
|
||
}
|
||
```
|
||
|
||
`Diagnostics` carries independent, nonterminal messages. `Diagnostic` and
|
||
`Severity` remain passive data types for those messages, with each diagnostic
|
||
carrying its `Severity` and a `Write func(io.Writer) error` renderer. Adapt their
|
||
contracts to the nonterminal reporting categories. Integrations submit the
|
||
records through the corresponding reporter methods, including the deferred view
|
||
where appropriate. Removing `Outcome.Diagnostics` removes a submission path;
|
||
provider-returned records remain available for the integration to submit.
|
||
|
||
`Explanation` carries context to attach when the integration returns a failed
|
||
outcome. Terminal Error content uses this field rather than an independently
|
||
submitted diagnostic. An upstream warning that helps explain the failure may
|
||
belong in the explanation; unrelated warnings remain in `Diagnostics` and follow
|
||
their ordinary visibility rules. Adapters preserve this distinction and useful
|
||
native formatting when translating mixed upstream batches, instead of assigning
|
||
the whole batch its highest severity.
|
||
|
||
Providers may return acquired data alongside a Go error. The owning integration
|
||
chooses the command outcome, attaches any terminal explanation, and decides which
|
||
cause to preserve. Returning diagnostic data does not itself emit messages or
|
||
select the invocation outcome. Diagnostic renderers follow the same callback
|
||
boundary as other details when invoked through the reporter.
|
||
|
||
### Presentation observations and closed reporters
|
||
|
||
```go
|
||
type PresentationResult struct {
|
||
Output DeliveryResult
|
||
Diagnostics DeliveryResult
|
||
}
|
||
|
||
type DeliveryResult struct {
|
||
State DeliveryState
|
||
RenderError error
|
||
WriteError error
|
||
}
|
||
|
||
type DeliveryState uint8
|
||
|
||
const (
|
||
DeliveryNone DeliveryState = iota
|
||
DeliverySuppressed
|
||
DeliveryComplete
|
||
DeliveryIncomplete
|
||
)
|
||
|
||
var ErrReporterClosed = errors.New("reporter is closed")
|
||
```
|
||
|
||
| State | Meaning |
|
||
| --- | --- |
|
||
| `DeliveryNone` | No content was submitted for this destination. |
|
||
| `DeliverySuppressed` | All submitted content was intentionally suppressed, and rendering succeeded. |
|
||
| `DeliveryComplete` | All requested delivery completed; other content may have been suppressed. |
|
||
| `DeliveryIncomplete` | Rendering or destination writing failed. |
|
||
|
||
Retain the first rendering failure and first destination-write failure for each
|
||
destination. This provides bounded evidence without collecting an event history.
|
||
`Outcome.Presentation` is populated by CommandKit during execution and
|
||
finalization; it is observation data, not another submission API.
|
||
|
||
The invocation supplies the reporter. Consumers neither construct nor finalize
|
||
it. After closure, message submissions and writer operations return
|
||
`ErrReporterClosed`, including in quiet mode. The initial API needs no explicit
|
||
flush method, transcript accessor, or verbosity checks. Error-conversion helpers
|
||
are deferred pending demonstrated value.
|
||
|
||
### Application example
|
||
|
||
This target-API handler uses an application-owned `exportData` operation that
|
||
returns a JSON-encodable result with a `UsedDefaults` field:
|
||
|
||
```go
|
||
func runExport(inv commandkit.Invocation) commandkit.Outcome {
|
||
report := inv.Reporter()
|
||
|
||
result, err := exportData(inv.Context)
|
||
if err != nil {
|
||
return commandkit.Failure("Unable to export the requested data.")
|
||
}
|
||
|
||
if result.UsedDefaults {
|
||
_ = report.Warn("Some optional settings used defaults.")
|
||
}
|
||
|
||
if err := report.RenderOutput(func(w io.Writer) error {
|
||
return json.NewEncoder(w).Encode(result)
|
||
}); err != nil {
|
||
return commandkit.Failure("Unable to present the export result.")
|
||
}
|
||
|
||
_ = report.AtEnd().Info("Export completed.")
|
||
return commandkit.Success()
|
||
}
|
||
```
|
||
|
||
Ignored immediate reporting errors are still recorded and can make the final
|
||
invocation unsuccessful. The renderer runs in quiet mode against the discard
|
||
destination, preserving serialization failures. The deferred summary follows
|
||
the selected verbosity policy and describes the completed export operation;
|
||
the final invocation outcome also accounts for presentation failures.
|
||
|
||
## Upstream diagnostic ownership
|
||
|
||
ConfigKit and PromptKit remain authoritative for their diagnostic semantics,
|
||
severity, source context, and disclosure rules. Integration adapters expose
|
||
those capabilities through the shared CommandKit presentation policy.
|
||
|
||
Support consistent reporting of ConfigKit errors and warnings as its adopted
|
||
public API evolves. This roadmap does not assume that a future warning API
|
||
already exists or prescribe its upstream shape. Integration-owned commands
|
||
select the outcome and submit useful native diagnostic detail for presentation.
|
||
Core presents those records under the shared policy. Applications using upstream
|
||
libraries directly choose how to respond to domain-specific errors and which
|
||
outcomes and messages to supply, as for their other operations.
|
||
|
||
An integration may receive both a useful substantive report and an operation
|
||
failure, as PromptKit smoke testing supports. Preserve the report under the
|
||
ordinary substantive-output policy, then present the terminal explanation.
|
||
Quiet mode suppresses the report while retaining the explanation. Having a
|
||
report does not imply a successful outcome, and returning a failure does not
|
||
discard substantive output already prepared for delivery before the terminal step.
|
||
|
||
Debug output follows the same upstream disclosure and safe-rendering contracts.
|
||
Enabling debug does not authorize secret disclosure or bypass owning-library
|
||
rules.
|
||
|
||
## Delivery scope
|
||
|
||
The completed feature includes the selected reporter and outcome API, ordered
|
||
mode/progress selection through normal parsing, shared core presentation, and
|
||
both optional integration migrations. Passive provider diagnostics distinguish
|
||
independent messages from terminal explanations while preserving native evidence
|
||
and rendering. Existing command behavior remains unchanged outside the selected
|
||
reporting policy.
|
||
|
||
Maintained consumers demonstrate ordinary output, immediate/deferred messages,
|
||
silent recovery, explicit warnings, failure after partial output, optional cause
|
||
transport, and structured terminal explanations. Integration consumers include
|
||
native diagnostic context and useful reports returned alongside failures.
|
||
|
||
Behavioral coverage establishes CommandKit-owned parsing, visibility, ordering,
|
||
concurrent reporter coordination, closure, presentation observations, and failure
|
||
handling. It preserves existing alias, input/state, discovery, native-report,
|
||
and upstream-adapter protections without testing upstream domain correctness.
|
||
GoDoc, CLI documentation, consumer guidance, internal documentation, and release
|
||
notes describe the implemented boundary under their canonical ownership.
|
||
|
||
The [implementation plan](implementation.md) defines the concrete migration
|
||
steps, focused validation, and completion criteria. This roadmap defines the
|
||
target behavior and selected policy choices.
|
||
|
||
## Architectural preparation completed
|
||
|
||
- [x] Review and accept [ADR 0014](../adr/0014-invocation-reporting.md) as an
|
||
extension for shared reporting, retaining ADR 0005's outcome and supplied-error
|
||
preservation decisions.
|
||
- [x] Align the [architecture policy](../policy/architecture.md) with the reporting
|
||
ownership model, including application ownership of domain-specific error
|
||
decisions and scope for useful shared error helpers.
|
||
|
||
OD-1 through OD-4 and the consumer API signatures are selected. The target end
|
||
state includes maintained, executable consumers using those APIs; the
|
||
[implementation plan](implementation.md) owns their delivery sequence.
|