Accept invocation reporting architecture and clarify error ownership

This commit is contained in:
2026-10-26 01:18:28 +00:00
parent e951a48902
commit e48187d822
3 changed files with 207 additions and 58 deletions

View File

@@ -4,12 +4,14 @@ 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.
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 and presentation-result model below are
also selected. Implementation has not started; architectural contract
reconciliation and maintained implementation examples remain work to complete.
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
not started; implementation, validation, and maintained consumer examples remain.
## Purpose
@@ -25,27 +27,32 @@ domain behavior, and operation outcomes.
## CLI presentation and error-handling ownership
Applications decide how to handle errors from their operations: recover, retry,
Applications decide how to respond to domain-specific errors: 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 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.
Reporting does not require the underlying Go error or undertake its tracking,
recovery, or identity preservation.
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; this conversion already requires little code.
`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.
This boundary governs application error handling; ordinary Go error returns
from CommandKit APIs and writers remain available where appropriate.
Ordinary Go error returns from CommandKit APIs and writers remain available where
appropriate.
## Current baseline
@@ -201,12 +208,12 @@ explanations. For example:
### 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.
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
@@ -436,14 +443,27 @@ 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 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`.
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.
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
@@ -587,6 +607,7 @@ type Outcome struct {
Kind OutcomeKind
Status int
Domain string
Cause error
Explanation Explanation
Presentation PresentationResult
}
@@ -597,11 +618,15 @@ 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` preserves an application-defined classification and 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
@@ -626,10 +651,26 @@ The named renderers above have signature `func(io.Writer) error` and preserve th
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.
`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.
### Presentation observations and closed reporters
@@ -672,7 +713,8 @@ 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.
flush method, transcript accessor, or verbosity checks. Error-conversion helpers
are deferred pending demonstrated value.
### Application example
@@ -719,10 +761,9 @@ 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.
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
@@ -744,7 +785,8 @@ rules.
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.
optional cause transport independent of operator text, 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`.
@@ -771,28 +813,38 @@ rules.
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
immediate write errors and deferred delivery failures follow the outcome table
above, original explanations and causes are retained, 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.
diagnostic destination. Cover usage failure becoming operational failure when
presentation fails, domain classification and non-zero status preservation,
and zero domain status becoming `1`. Verify 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.
Verify `WithCause` preserves classification, status, and explanation, and
`WithExplanation` preserves the cause. Check cause identity through outcome
processing and subsequent presentation failures, including wrapped errors
inspected with `errors.Is` and `errors.As`. Verify causes alone do not produce
messages or change statuses or visibility.
6. Update GoDoc, the CLI reference, consumer guidance, and release documentation
when implemented. Exact reporting APIs and flag syntax remain API/CLI
contracts.
## Remaining preparation
## Architectural preparation completed
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
- [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. Turn the target-API
examples into maintained, executable consumers as implementation proceeds.