799 lines
40 KiB
Markdown
799 lines
40 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 error handling 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 and presentation-result model below are
|
|
also selected. Implementation has not started; architectural contract
|
|
reconciliation and maintained implementation examples remain work to complete.
|
|
|
|
## 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 handle errors from their operations: recover, retry,
|
|
continue silently, report a Warning, or end the invocation with a failure.
|
|
A Go `error` 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.
|
|
Reporting does not require the underlying Go error or undertake its tracking,
|
|
recovery, or identity preservation.
|
|
|
|
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; 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.
|
|
This boundary governs application error handling; 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.
|
|
|
|
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 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.
|
|
|
|
If the application returned success, a delivery failure produces an operational
|
|
failure. If the application already returned failure, preserve its classification,
|
|
status, and explanation, and include the delivery failure separately. Reporting
|
|
failures belong to CommandKit's own error-handling responsibility.
|
|
|
|
Apply the same outcome rule to rendering failures from callbacks invoked by
|
|
CommandKit. In particular, a usage failure retains its usage classification and
|
|
status when subsequent presentation fails.
|
|
|
|
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.
|
|
|
|
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
|
|
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
|
|
```
|
|
|
|
`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` preserves an application-defined classification and status.
|
|
`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.
|
|
|
|
The target outcome has no application `Cause` field. Applications supply their
|
|
chosen explanation; CommandKit records its own presentation failures separately.
|
|
The returned outcome retains the terminal explanation, with other retention
|
|
limited to the presentation needs described above.
|
|
|
|
### 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, verbosity checks, or error-conversion helpers.
|
|
|
|
### 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; it does not infer an
|
|
application's recovery decisions or classify arbitrary Go errors. Applications
|
|
using upstream libraries directly retain the same choice over outcomes and
|
|
messages 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.
|
|
|
|
## Work needed
|
|
|
|
1. Specify the selected behavior in examples covering ordinary application output,
|
|
immediate and deferred diagnostics, failure after partial output, help and
|
|
version, and integration diagnostics in every mode. Include applications that
|
|
emit no Progress messages and the distinction between Info and Progress.
|
|
Cover silent recovery, an explicit Warning with or without a Go error, and a
|
|
terminal failure with an explicit explanation and no Go error cause.
|
|
Include simple and structured failure construction without early emission,
|
|
and an integration returning both a substantive report and a failure.
|
|
2. Review current diagnostic batching, parsing, help, outcome presentation, and
|
|
writer ownership to identify the smallest shared changes. Include invocation
|
|
failures that occur before a handler receives its `Invocation`.
|
|
3. Implement the selected unified reporting API and flag contracts, including
|
|
interactions with command and flag aliases, following OD-1 through OD-4 above.
|
|
Replace split handler submission paths and adapt core and integration callers.
|
|
Account for flag collisions and document changed public contracts.
|
|
4. Implement the selected behavior across core and the integrations. Preserve
|
|
upstream rendering and semantics while applying the chosen visibility policy.
|
|
5. Verify classification, flag selection, suppression, terminal explanations,
|
|
ordering, duplicate prevention, terminal Error/non-zero-outcome consistency, debug
|
|
visibility, and write-failure handling with focused behavioral tests and
|
|
maintained consumer examples. Cover mode resets, mixed canonical/alias
|
|
ordering, help and discovery contexts, and core parser events presented
|
|
according to the final mode. Cover explicit progress overrides, debug's
|
|
progress default, quiet suppression, progress aliases, message prefixes,
|
|
and preservation of native multiline diagnostics. Cover explicit explanatory
|
|
context in quiet mode, independent warnings in mixed batches, already-labelled
|
|
native content, and distinct submissions with identical text. Verify the end
|
|
of normal reporting after terminal error presentation, including deferred
|
|
and late writes under the selected OD-4 behavior. Cover immediate and deferred
|
|
ordering on success and failure, handler cleanup, automatic finalization,
|
|
flushing ordinary buffers before terminal delivery, handler-completion debug
|
|
placement, and ownership of caller-supplied writers. Verify concurrent reporter
|
|
calls protect shared state and keep individual messages together, without
|
|
requiring a predetermined order among concurrent submissions. Verify ignored
|
|
immediate write errors and deferred delivery failures affect the final outcome,
|
|
existing failures retain their classification and status, and failed destinations
|
|
receive no further writes. Cover diagnostic-destination failure without
|
|
fallback into substantive output, quiet-mode rendering failures, and
|
|
suppression without destination writes or incomplete-delivery status.
|
|
Distinguish renderer failures from destination failures, including callbacks
|
|
that fail without writing and continued terminal delivery through a healthy
|
|
diagnostic destination. Cover usage-status preservation on presentation
|
|
failure, all category destinations, and usage guidance under quiet mode.
|
|
Verify the selected API's empty-heading native rendering, ordered details,
|
|
deferred submission errors, replacement by `WithExplanation`, and delivery
|
|
states for each destination. Check bounded first-error observations, rendering
|
|
through `RenderOutput`, and `ErrReporterClosed` for retained handles in quiet mode.
|
|
6. Before changing architectural contracts, reconcile the selected error-handling
|
|
boundary with ADR 0005's existing outcome and error-identity commitments and
|
|
the corresponding commitments in the [architecture policy](../policy/architecture.md).
|
|
Record architectural changes in a new ADR and align the policy. Update GoDoc,
|
|
the CLI reference, consumer guidance, and release documentation when
|
|
implemented. Exact reporting APIs and flag syntax remain API/CLI contracts.
|
|
|
|
## Remaining preparation
|
|
|
|
OD-1 through OD-4 and the consumer API signatures are selected. Reconcile the
|
|
architectural contracts noted above before changing them, and turn the target-API
|
|
examples into maintained, executable consumers as implementation proceeds.
|