Specify invocation reporting API signatures

This commit is contained in:
2026-10-26 00:10:36 +00:00
parent 976ed89403
commit e951a48902

View File

@@ -7,8 +7,9 @@ OD-2's unified reporting direction, the terminal failure model, and application
ownership of error handling 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. Exact API names, signatures, and representations remain to be
finalized. Implementation has not started.
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.
## Purpose
@@ -158,7 +159,7 @@ 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. Exact API names and signatures remain to be finalized.
error. The selected API below provides failure constructors for this purpose.
Output streamed before the failure remains visible according to its selected
mode; terminal failure does not withdraw earlier bytes. Deferred messages,
@@ -189,7 +190,7 @@ deduplication: separate events may legitimately produce identical messages.
Constructing a failure result attaches explanatory content without emitting or
independently scheduling an Error. Returning that result hands the explanation
to CommandKit for terminal delivery through the invocation's reporting facility.
The concrete API must express this distinction for both simple and structured
The selected API expresses this distinction for both simple and structured
explanations. For example:
| Handler action | Presentation |
@@ -473,26 +474,14 @@ delivery. Do not retain suppressed content. Delivery needs only the deferred
queue, reporting lifecycle state, and bounded delivery-failure information,
alongside the temporary parser state and returned explanations described above.
## Selected consumer reporting API direction
## Selected consumer reporting API
Provide one reporting facility per invocation for substantive output, immediate
messages, and end-of-run diagnostics. The consumer API is chosen for its intended
long-term design; with no current consumers, preserving existing API shapes
carries no weight in this decision.
The facility exposes interfaces appropriate to each need:
| Need | Selected direction | Illustrative shape |
| --- | --- | --- |
| Substantive results | An ordinary `io.Writer`, usable by encoders and native report writers. | `inv.Reporter().Output()` |
| Immediate messages | Categorized methods applying visibility and presentation automatically. | `Info`, `Warn`, `Debug`, `Progress` |
| End-of-run diagnostics | An explicit deferred view sharing the invocation's reporting state. | `report.AtEnd().Warn(message)` |
| Rich upstream diagnostics | Typed submission preserving severity, context, and native rendering. | Exact API remains open. |
These method names illustrate the selected direction; exact names and signatures
remain to be finalized. Progress is streaming output, distinct from deferred
informational summaries. Applications need not implement categories they do not
use or check verbosity before reporting.
The following declarations specify the target API and are not yet implemented.
The reporter owns presentation: output visibility, progress settings, prefixes,
ordering, and delivery bookkeeping. Its views share invocation state, allowing
@@ -501,26 +490,224 @@ be coordinated in one place. Helpers may receive the reporter or a narrow
application-defined interface. Domain libraries can continue returning
diagnostic data for the handler to submit, independently of CommandKit.
The returned outcome owns the operation result: success, failure, or an
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. 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`.
The returned outcome carries terminal explanations, with other retention limited
to the presentation needs described above. Caller-selected destinations belong
at the execution boundary; the handler-facing API routes output through the
reporter instead of requiring a parallel raw-stream API.
Terminal explanations accompany returned outcomes and are delivered through the
same facility. Caller-selected destinations remain at `App.Execute`; handlers
use `Invocation.Reporter()` in place of raw invocation streams.
Immediate methods expose delivery errors and the reporter retains delivery
failure state for automatic finalization, as selected in OD-4. Standard writer
operations remain compatible with `io.Writer`. Exact API names, signatures, and
outcome representations will express these behaviors through the selected
consumer-facing structure.
### Invocation reporter and structured detail
```go
func (inv Invocation) Reporter() *Reporter
type Reporter struct {
// Private invocation state.
}
func (r *Reporter) Output() io.Writer
func (r *Reporter) RenderOutput(write func(io.Writer) error) error
func (r *Reporter) Info(message string, details ...Detail) error
func (r *Reporter) Warn(message string, details ...Detail) error
func (r *Reporter) Debug(message string, details ...Detail) error
func (r *Reporter) Progress(message string, details ...Detail) error
func (r *Reporter) AtEnd() DeferredReporter
type Detail func(io.Writer) error
```
`Output()` supports ordinary writers and encoders. `RenderOutput()` coordinates
an entire rendering operation, captures its rendering error, and prevents other
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.
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.
Optional details follow in order with their formatting preserved. An empty
opening message produces no heading, prefix, or blank opening line, supporting
native renderers that supply a complete, labelled diagnostic:
```go
_ = report.Warn("Using the default profile")
_ = report.Warn("Some configuration settings need attention.", writeNativeWarnings)
_ = report.Warn("", writeNativeWarnings)
```
These are alternative calls; `writeNativeWarnings` represents an
application- or integration-supplied function with signature
`func(io.Writer) error`. No generic logging method or severity enumeration is
needed to submit messages. Applications need not check verbosity before reporting
or implement categories they do not use.
Immediate methods return rendering or delivery errors, and CommandKit records
the corresponding presentation failure even if the caller ignores the error.
A caller can inspect the returned error and end its handler immediately.
### Deferred messages
```go
type DeferredReporter struct {
// Private reference to the same invocation state.
}
func (r DeferredReporter) Info(message string, details ...Detail) error
func (r DeferredReporter) Warn(message string, details ...Detail) error
func (r DeferredReporter) Debug(message string, details ...Detail) error
```
For example:
```go
_ = report.AtEnd().Warn("Three optional files were unavailable.")
```
Copies of the deferred view share invocation state. Its methods return submission
errors, such as attempting to use a closed reporter. Errors encountered during
deferred rendering appear in the final invocation outcome. Progress remains an
immediate streaming operation; substantive output uses the main reporter.
Copy callback lists when accepting a submission. Callers retain ownership of
data captured by rendering callbacks and keep that data valid until presentation.
### Terminal outcomes
Outcome construction requires no invocation state and emits nothing:
```go
type Explanation struct {
Message string
Details []Detail
}
type Outcome struct {
Kind OutcomeKind
Status int
Domain string
Explanation Explanation
Presentation PresentationResult
}
func Success() Outcome
func Failure(message string, details ...Detail) Outcome
func Usage(message string, details ...Detail) Outcome
func Domain(name string, status int) Outcome
func (o Outcome) WithExplanation(message string, details ...Detail) 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.
`OutcomeKind` retains the success, usage, operational, and domain classifications.
`WithExplanation` returns a modified outcome value and replaces its terminal
explanation. Explanations belong to failed outcomes; successful summaries use
Info or substantive output. Ordinary application failure, native failure
rendering, and a custom domain failure respectively can be expressed as:
```go
return commandkit.Failure("Unable to export the requested report.")
```
```go
return commandkit.Failure("", writeNativeFailure)
```
```go
return commandkit.Domain("validation", 3).
WithExplanation("The document failed validation.", writeFindings)
```
The named renderers above have signature `func(io.Writer) error` and preserve the
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.
### Presentation observations and closed reporters
```go
type PresentationResult struct {
Output DeliveryResult
Diagnostics DeliveryResult
}
type DeliveryResult struct {
State DeliveryState
RenderError error
WriteError error
}
type DeliveryState uint8
const (
DeliveryNone DeliveryState = iota
DeliverySuppressed
DeliveryComplete
DeliveryIncomplete
)
var ErrReporterClosed = errors.New("reporter is closed")
```
| State | Meaning |
| --- | --- |
| `DeliveryNone` | No content was submitted for this destination. |
| `DeliverySuppressed` | All submitted content was intentionally suppressed, and rendering succeeded. |
| `DeliveryComplete` | All requested delivery completed; other content may have been suppressed. |
| `DeliveryIncomplete` | Rendering or destination writing failed. |
Retain the first rendering failure and first destination-write failure for each
destination. This provides bounded evidence without collecting an event history.
`Outcome.Presentation` is populated by CommandKit during execution and
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.
### Application example
This target-API handler uses an application-owned `exportData` operation that
returns a JSON-encodable result with a `UsedDefaults` field:
```go
func runExport(inv commandkit.Invocation) commandkit.Outcome {
report := inv.Reporter()
result, err := exportData(inv.Context)
if err != nil {
return commandkit.Failure("Unable to export the requested data.")
}
if result.UsedDefaults {
_ = report.Warn("Some optional settings used defaults.")
}
if err := report.RenderOutput(func(w io.Writer) error {
return json.NewEncoder(w).Encode(result)
}); err != nil {
return commandkit.Failure("Unable to present the export result.")
}
_ = report.AtEnd().Info("Export completed.")
return commandkit.Success()
}
```
Ignored immediate reporting errors are still recorded and can make the final
invocation unsuccessful. The renderer runs in quiet mode against the discard
destination, preserving serialization failures. The deferred summary follows
the selected verbosity policy and describes the completed export operation;
the final invocation outcome also accounts for presentation failures.
## Upstream diagnostic ownership
@@ -561,10 +748,10 @@ rules.
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`.
3. Finalize the unified reporting API and flag contracts, including interactions
with command and flag aliases, following OD-1 through OD-4 above. Replace split
handler submission paths and adapt core and integration callers. Account for
flag collisions and document changed public contracts.
3. Implement the selected unified reporting API and flag contracts, including
interactions with command and flag aliases, following OD-1 through OD-4 above.
Replace split handler submission paths and adapt core and integration callers.
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.
5. Verify classification, flag selection, suppression, terminal explanations,
@@ -593,6 +780,10 @@ rules.
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.
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).
@@ -600,10 +791,8 @@ rules.
the CLI reference, consumer guidance, and release documentation when
implemented. Exact reporting APIs and flag syntax remain API/CLI contracts.
## Remaining design work
## Remaining preparation
OD-1 through OD-4 are resolved. Before implementation, finalize the concrete
reporter and outcome API, including simple and structured terminal explanations,
deferred submission, and delivery-failure observations. Review complete examples
against the selected behavior and reconcile the architectural contracts noted
above.
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
examples into maintained, executable consumers as implementation proceeds.