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

@@ -0,0 +1,79 @@
# ADR 0014: Share command-line presentation within each invocation
## Status
Accepted. Extends [ADR 0005](0005-invocation-outcomes.md) with shared reporting
for each invocation. Its outcome classifications, standard status meanings,
output channels, error-cause preservation, and application ownership of process
exit remain in force.
## Date
2026-10-25
## Context
Applications need consistent control over command results, messages shown during
a run, and messages shown at its end. Sharing that control reduces repeated
output code across applications and integrations.
An application decides what an error means for its work. It may recover silently,
show a warning, or end the invocation. CommandKit needs the chosen outcome and
messages to present that decision to the operator.
## Decision
CommandKit core provides one reporting facility for each invocation. Application
commands, integration commands, and core commands use it for results, immediate
messages, and deferred messages. Core applies shared rules for visibility,
formatting, and delivery, and completes reporting when the invocation ends.
Callers choose the output destinations.
Applications decide how to respond to domain-specific errors and choose their
outcomes and messages. CommandKit may provide shared helpers for carrying,
inspecting, and presenting errors while leaving those decisions with the
application.
Applications supply explicit outcomes and explanations for failures. They may
also supply a Go error cause. CommandKit keeps that error unchanged for the caller
to inspect. Supplying a cause alone does not select an outcome or generate an
operator-facing message. CommandKit remains responsible for handling its own
errors, including routing, parsing, and presentation failures.
Integrations adapt upstream results and diagnostics into command outcomes and
messages. The upstream libraries retain ownership of their meanings and rules
for safe disclosure.
An Error message explains a failed invocation with a non-zero status. Its
explanation is presented at the end of the invocation, after normal reporting
finishes. The returned outcome carries that explanation. The application entry
point decides how to exit the process.
CommandKit reports rendering and delivery failures separately from the command's
explanation. A presentation failure produces an operational failure for standard
outcomes under ADR 0005. Application-defined domain classifications are preserved,
along with an already non-zero domain status. A successful domain outcome receives
a non-zero status if presentation fails. The original explanation and any supplied
cause remain available to the caller. Intentionally suppressed output is not a
delivery failure.
Reporting keeps the state needed to deliver messages and return the invocation
outcome. Logging and message history remain application concerns.
## Alternatives considered
| Alternative | Analysis |
| --- | --- |
| Let each application manage all presentation | Gives applications full control, but repeats visibility and delivery code across commands and integrations. |
| Have core decide whether domain-specific errors require recovery, a warning, or failure | Reduces some caller code, but gives core decisions that depend on application behavior. |
| Use separate facilities for results, immediate messages, and deferred messages | Lets each facility serve one purpose, but spreads visibility and final delivery across several paths. |
## Consequences
Applications and integrations share presentation behavior while choosing their
own results and messages. Core handles reporting failures consistently and keeps
them visible in the returned outcome.
GoDoc and the [CLI reference](../cli.md) define the implemented API, flags,
message formats, and delivery details. Those details can evolve within this
ownership model.

View File

@@ -2,7 +2,7 @@
**Base Policy Version:** 1.3
**Last Updated**: 2026-10-11
**Last Updated**: 2026-10-26
**Upstream Source:** [architecture.md](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/policy/architecture.md)
@@ -75,12 +75,6 @@ Its documentation recommends the same grammar and vocabulary for application-own
commands, including those outside its dispatcher, under
[ADR 0006](../adr/0006-command-grammar.md).
CommandKit preserves application-supplied outcome classifications and supported
error identity. It assigns responsibility for its diagnostics, avoids duplicate
presentation through its own paths, and reports write failures. Caller callbacks
retain control of their own output. [ADR 0005](../adr/0005-invocation-outcomes.md)
describes the outcome and delivery model.
Use explicit instance construction and composable command registration, not
package-global or import-time registries. Applications and integrations supply
captured canonical command declarations through the core installation API.
@@ -127,6 +121,30 @@ inspection/execution loading and focused acquisition callbacks are recommended
integration practices. Independent consumer use of other CLI frameworks remains
application-owned.
## Invocation Reporting And Outcomes
Use one reporting facility per invocation for command results, immediate messages,
and deferred messages. Core owns shared visibility, formatting, delivery, and
finalization. Applications and integrations choose their report content and
messages; callers choose the destinations. [ADR 0014](../adr/0014-invocation-reporting.md)
extends the outcome and delivery model in [ADR 0005](../adr/0005-invocation-outcomes.md).
Applications decide how to respond to domain-specific errors. CommandKit may
provide shared helpers for carrying, inspecting, and presenting errors while
leaving those decisions with the application. Supplying an error cause alone
does not select an outcome or generate an operator-facing message. CommandKit
handles its own errors, including routing, parsing, and presentation failures.
Preserve application-defined domain classifications and supplied error identity.
Report rendering and delivery failures separately from the command's explanation
and cause, applying the outcome rules in ADR 0005 and ADR 0014. Intentionally
suppressed output is not a delivery failure. Present terminal failure explanations
after normal reporting finishes, avoid duplicate presentation through core's own
paths, and retain the explanation and any supplied cause in the returned outcome.
Keep reporting state scoped to the invocation's presentation and returned outcome.
Logging and message history remain application concerns.
## Handler Inputs And Invocation State
Handlers receive typed input structs with explicit flag and operand bindings.
@@ -215,8 +233,8 @@ transforming arbitrary application objects.
Standard contracts such as `context.Context` and `io.Writer` remain appropriate.
Upstream public types may be selected as deliberate dependencies in the
integrations that own them; that permission does not justify optional library
dependencies in the core. Core diagnostics use a CommandKit-owned severity type,
and integrations convert upstream diagnostics while preserving selected outcome
dependencies in the core. Core owns shared presentation categories,
and integrations adapt upstream diagnostics while preserving selected outcome
and error semantics. Application-owned input types remain application-owned.
Expose narrow core composition contracts for demonstrated integration needs,
without exporting private assembly or backend structures.

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.