From ec29d658b7f4039f357e982d1cf7553955040cfa Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 24 Oct 2026 01:50:38 +0000 Subject: [PATCH] Clarify application error handling and reporting ownership --- docs/roadmap/reporting.md | 103 ++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 33 deletions(-) diff --git a/docs/roadmap/reporting.md b/docs/roadmap/reporting.md index 1377483..9b09525 100644 --- a/docs/roadmap/reporting.md +++ b/docs/roadmap/reporting.md @@ -3,7 +3,8 @@ Status: initial planning. 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 and the terminal failure model are selected. +OD-2's unified reporting direction, the terminal failure model, and application +ownership of error handling are selected. Exact API signatures, failure explanation details, retention, and delivery mechanics remain open. Implementation has not started. @@ -19,6 +20,30 @@ 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. + +A convenience helper that explicitly presents `err.Error()` as a Warning could +fit this boundary. Such a helper would only convert the supplied error to a +message at the application's request; its inclusion and signature remain open. + +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 @@ -49,7 +74,7 @@ Distinguish substantive application output from diagnostic message categories: - **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. A recovered problem is a warning. + 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. @@ -77,9 +102,8 @@ 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. Problems from -which the invocation recovers are warnings, even when an attempted operation -failed internally. +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: @@ -120,15 +144,12 @@ 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, a handler returns immediately on an unrecoverable failure: - -```go -result, err := performOperation() -if err != nil { - return commandkit.Failure(err) -} -// Continue normal reporting only after successful operation. -``` +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 exact failure-construction API remains open under OD-3. Output streamed before the failure remains visible according to its selected mode; terminal failure does not withdraw earlier bytes. Deferred messages, @@ -295,9 +316,9 @@ output is not part of the proposed direction. Keep errors understandable in every mode, including source locations and related explanatory context. Unrelated warnings and informational messages -remain subject to the selected mode. Retain suppressed diagnostics for -programmatic inspection rather than equating suppression with loss of evidence. -The exact representation of retained and delivered diagnostics remains open. +remain subject to the selected mode. Whether to retain submitted messages for +programmatic inspection, including suppressed messages, remains open under +OD-3. This concerns presentation records, not application error tracking. ## Selected consumer reporting API direction @@ -332,7 +353,8 @@ application domain classification. CommandKit presents a failed outcome through the same reporting facility as the terminal step. Error explanations belong to that returned failure, including rich upstream explanations. Typed diagnostic submission must preserve this relationship instead of permitting independently -emitted Error messages during normal execution. +emitted Error messages during normal execution. Application failures carry +explicit status and presentation content; they do not require a Go error cause. Use the reporter as the common diagnostic submission path, replacing the earlier proposal to split submission between a reporter and `Outcome.Diagnostics`. @@ -354,9 +376,12 @@ 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. Preserve useful native -diagnostic detail and supported error identity, and coordinate returned errors -with diagnostics that already explain them. +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. Debug output follows the same upstream disclosure and safe-rendering contracts. Enabling debug does not authorize secret disclosure or bypass owning-library @@ -368,6 +393,8 @@ rules. 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. 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`. @@ -388,23 +415,33 @@ rules. reporting after terminal error presentation, including deferred and late writes under the selected OD-4 behavior. 6. Update GoDoc, the CLI reference, consumer guidance, and release documentation - when implemented. Review whether any resulting architectural change needs an - ADR; exact reporting APIs and flag syntax remain API/CLI contracts. + when implemented. Reconcile the selected error-handling boundary with + ADR 0005's existing outcome and error-identity commitments before changing + those contracts. Record any resulting architectural change in a new ADR; + exact reporting APIs and flag syntax remain API/CLI contracts. ## Open Decisions ### OD-3: Failure presentation and diagnostic retention -The terminal failure model is selected above. How are detailed explanations -attached to the returned failure, including application domain failures? How -are recovered upstream problems represented as warnings while preserving their -native context? Which contextual records belong to a terminal error explanation? -How are mixed upstream -diagnostic batches filtered while preserving useful native rendering? What is -retained when presentation is suppressed, and how is duplicate presentation -prevented without hiding unrelated failures? -How do adapters apply the selected prefixes to mixed or already-labelled native -diagnostics? What presentation should streaming Progress use? +The terminal failure model and application error-handling boundary are selected +above. The remaining questions concern explicit presentation content: + +- How does a handler supply a non-zero outcome with a simple explanation or + multiple structured explanatory records, including application domain failures? +- How do callers and integration adapters identify the context belonging to a + terminal explanation, and present mixed diagnostic batches while preserving + useful native rendering and applying visibility to independent messages? +- Which submitted messages, if any, are retained for programmatic inspection, + including when suppressed? How is that observation exposed separately from + message submission? +- How is duplicate presentation avoided, and how do adapters apply prefixes to + mixed or already-labelled native diagnostics? +- What presentation should streaming Progress use? Is a convenience helper for + explicitly presenting `err.Error()` as a Warning useful enough to include? + +Application error classification, recovery, and cause tracking are outside +these questions. Delivery failures produced by reporting remain under OD-4. ### OD-4: Delivery lifecycle and quiet execution