From e48187d8226a68be52a676a1ddf7d1be965d52ee Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 26 Oct 2026 01:18:28 +0000 Subject: [PATCH] Accept invocation reporting architecture and clarify error ownership --- docs/adr/0014-invocation-reporting.md | 79 ++++++++++++++ docs/policy/architecture.md | 36 +++++-- docs/roadmap/reporting.md | 150 +++++++++++++++++--------- 3 files changed, 207 insertions(+), 58 deletions(-) create mode 100644 docs/adr/0014-invocation-reporting.md diff --git a/docs/adr/0014-invocation-reporting.md b/docs/adr/0014-invocation-reporting.md new file mode 100644 index 0000000..7b02ef1 --- /dev/null +++ b/docs/adr/0014-invocation-reporting.md @@ -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. diff --git a/docs/policy/architecture.md b/docs/policy/architecture.md index 2b58dd4..7c87ae5 100644 --- a/docs/policy/architecture.md +++ b/docs/policy/architecture.md @@ -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. diff --git a/docs/roadmap/reporting.md b/docs/roadmap/reporting.md index 169f84c..24ed52c 100644 --- a/docs/roadmap/reporting.md +++ b/docs/roadmap/reporting.md @@ -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.