Clarify provider diagnostics and reporting edge cases

This commit is contained in:
2026-10-26 01:25:11 +00:00
parent e48187d822
commit 1d29cdd029

View File

@@ -7,8 +7,9 @@ OD-2's unified reporting direction, the terminal failure model, and application
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. Architectural preparation is complete:
selected. The consumer API signatures, presentation-result model, provider
diagnostic boundary, fallback explanations, and rendering callback boundary
below are 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.
@@ -167,6 +168,8 @@ 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 selected API below provides failure constructors for this purpose.
When a failed outcome supplies no explanation, CommandKit supplies the generic
fallback defined under terminal outcomes below.
Output streamed before the failure remains visible according to its selected
mode; terminal failure does not withdraw earlier bytes. Deferred messages,
@@ -544,6 +547,21 @@ reporter operations from interleaving with it. Rendering callbacks write through
their supplied writer. Each complete categorized message, including its details,
is likewise coordinated as one reporter operation.
Rendering callbacks use their supplied writer only during the callback. They may
call rendering helpers that write through it. While a callback is running, it
must not recursively submit messages, queue deferred messages, start another
rendering operation, or write through a separate `Output()` handle belonging to
the same invocation's reporter. Make additional reporter submissions before or
after the callback. A callback must also avoid waiting for another goroutine to
submit through that reporter, since the submission may be waiting for the
callback to finish.
This boundary applies to `RenderOutput`, immediate and deferred message details,
and terminal explanation details. Ordinary concurrent reporter calls remain
supported and serialized. Keeping each rendering operation together motivates
the boundary; it does not prescribe a lock implementation or promise runtime
detection of recursive calls.
Message strings are literal; consumers use `fmt.Sprintf` when formatting is
needed. Initially omit parallel `Infof`, `Warnf`, and similar methods. CommandKit
formats the opening message with its category's prefix and a terminating newline.
@@ -651,6 +669,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.
Before terminal delivery, supply the explanation `Command failed.` when a
non-zero outcome has an empty `Explanation.Message` and no non-nil callback in
`Explanation.Details`. This produces `ERROR: Command failed.` in every mode,
including quiet, and becomes the explanation retained in the returned outcome.
Adding the fallback leaves classification, status, and cause unchanged;
presentation failures still follow OD-4's rules. The fallback is based on the
failed outcome and absent explanation, without deriving text from `Cause.Error()`.
An empty heading with a non-nil detail renderer is an explicitly supplied
explanation. Trust that renderer to provide its content. Do not buffer or replay
it to decide whether the explanation is sufficiently informative, and do not
add the fallback merely because it writes no bytes.
| Returned outcome | Terminal presentation |
| --- | --- |
| `Failure("")` | Generic fallback, operational classification and status `1`. |
| `Domain("validation", 3)` | Generic fallback, domain classification and status `3`. |
| `Failure("").WithCause(err)` | Generic fallback; the supplied cause remains available without automatic rendering. |
| `Failure("", writeNativeFailure)` | Native explanation without an added generic heading. |
`Cause` is optional. `WithCause` returns a modified outcome value, replacing the
cause without changing the classification, status, or explanation. For example:
@@ -672,6 +710,42 @@ 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.
### Provider-returned diagnostic data
Providers return values and diagnostic data without needing a reporter parameter.
Retain `Acquired[T]` with separate fields for independent messages and failure
explanations:
```go
type Acquired[T any] struct {
Value T
Diagnostics []Diagnostic
Explanation Explanation
}
```
`Diagnostics` carries independent, nonterminal messages. `Diagnostic` and
`Severity` remain passive data types for those messages, with each diagnostic
carrying its `Severity` and a `Write func(io.Writer) error` renderer. Adapt their
contracts to the nonterminal reporting categories. Integrations submit the
records through the corresponding reporter methods, including the deferred view
where appropriate. Removing `Outcome.Diagnostics` removes a submission path;
provider-returned records remain available for the integration to submit.
`Explanation` carries context to attach when the integration returns a failed
outcome. Terminal Error content uses this field rather than an independently
submitted diagnostic. An upstream warning that helps explain the failure may
belong in the explanation; unrelated warnings remain in `Diagnostics` and follow
their ordinary visibility rules. Adapters preserve this distinction and useful
native formatting when translating mixed upstream batches, instead of assigning
the whole batch its highest severity.
Providers may return acquired data alongside a Go error. The owning integration
chooses the command outcome, attaches any terminal explanation, and decides which
cause to preserve. Returning diagnostic data does not itself emit messages or
select the invocation outcome. Diagnostic renderers follow the same callback
boundary as other details when invoked through the reporter.
### Presentation observations and closed reporters
```go
@@ -786,7 +860,9 @@ rules.
terminal failure with an explicit explanation and no Go error cause.
Include simple and structured failure construction without early emission,
optional cause transport independent of operator text, and an integration
returning both a substantive report and a failure.
returning both a substantive report and a failure. Include provider-returned
independent warnings and terminal context, unexplained failures receiving the
fallback, and rendering helpers using only the supplied callback writer.
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`.
@@ -796,6 +872,10 @@ rules.
Account for flag collisions and document changed public contracts.
4. Implement the selected behavior across core and the integrations. Preserve
upstream rendering and semantics while applying the chosen visibility policy.
Adapt provider-returned `Acquired[T]`, `Diagnostic`, and `Severity` contracts
to independent nonterminal messages and separate terminal explanations.
Replace whole-batch severity promotion with deliberate separation of failure
context and unrelated messages. Providers remain independent of the reporter.
5. Verify classification, flag selection, suppression, terminal explanations,
ordering, duplicate prevention, terminal Error/non-zero-outcome consistency, debug
visibility, and write-failure handling with focused behavioral tests and
@@ -833,6 +913,19 @@ rules.
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.
Verify provider-returned data remains unpresented until submitted by the
integration, including data returned alongside an error. Cover mixed batches
where terminal context remains visible in quiet mode and unrelated warnings
remain suppressed. Verify generic fallback presentation and retention for
failed outcomes with no message or non-nil detail renderer, with and without
a cause. Check unchanged classifications, statuses, and causes when fallback
delivery succeeds, and existing presentation-failure rules when it fails.
Cover native details with an empty heading, including a renderer that writes
no bytes, without adding a fallback or replaying callbacks to inspect content.
Exercise callback helpers using the supplied writer, messages submitted before
and after rendering, and ordinary concurrent submissions serialized around a
complete rendering operation. Document the callback boundary without requiring
runtime detection of recursive reporter use.
6. Update GoDoc, the CLI reference, consumer guidance, and release documentation
when implemented. Exact reporting APIs and flag syntax remain API/CLI
contracts.