Accept invocation reporting architecture and clarify error ownership
This commit is contained in:
79
docs/adr/0014-invocation-reporting.md
Normal file
79
docs/adr/0014-invocation-reporting.md
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user