Document invocation reporting contracts

This commit is contained in:
2026-10-26 03:57:11 +00:00
parent a6c0fb5b0d
commit 96aa6b5ca0
15 changed files with 345 additions and 71 deletions

View File

@@ -8,13 +8,17 @@ modules, so applications choose which upstream libraries they add.
The core captures reusable command declarations, validates installation batches
before changing an application's registry, converts typed inputs before
constructing them, and returns outcomes without exiting the process. Its
[consumer guide](docs/consumers/guide.md) covers declaration and invocation.
[consumer guide](docs/consumers/guide.md) covers declaration, invocation
reporting, and migration. Each handler receives one invocation reporter for
substantive output, categorized messages, and terminal explanations; `Execute`
returns delivery observations with the outcome. The [CLI reference](docs/cli.md)
defines the shared visibility policy.
## Modules
| Module | Package | Provides |
| --- | --- | --- |
| `gitea.maximumdirect.net/eric/CommandKit` | `commandkit` | Typed commands, bindings, execution, outcomes, help, and version. |
| `gitea.maximumdirect.net/eric/CommandKit` | `commandkit` | Typed commands, bindings, execution reporting and outcomes, help, and version. |
| `gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig` | `ckconfig` | ConfigKit-backed `config` scopes, including configuration source evidence. |
| `gitea.maximumdirect.net/eric/CommandKit/integrations/ckprompt` | `ckprompt` | PromptKit engine configuration/comparison, five catalogs, named inspection, requirements discovery, concrete request validation, and native smoke tests. |

3
doc.go
View File

@@ -1,6 +1,7 @@
// Package commandkit provides the core instance-owned CLI dispatcher, typed
// command declarations, application-owned command and flag aliases, input
// bindings, and invocation outcomes. New and App.Register accept canonical
// bindings, invocation-scoped reporting, and outcomes with separate operation
// causes and presentation observations. New and App.Register accept canonical
// Command declarations and RoutingRule declarations together. Optional
// integration modules provide ConfigKit and PromptKit-specific commands;
// importing those modules does not install their commands.

View File

@@ -66,6 +66,93 @@ version: dev
revision: ""
```
## Reporting
CommandKit applies one shared reporting policy to application handlers,
integrations, help, version, and CommandKit's own invocation diagnostics. The
caller passes result and diagnostic writers to `App.Execute`; substantive
results go to `Streams.Out`, and categorized messages, terminal explanations,
and usage guidance go to `Streams.Err`.
### Modes and visibility
Each invocation starts in ordinary mode. `--quiet`, `--verbose`, and `--debug`
are mutually exclusive; the last enabled occurrence selects the mode. They
accept presence or an explicit boolean value. A `false` occurrence clears the
selected mode only when it names the current mode, returning to ordinary mode;
it does not restore an earlier selection. Invalid boolean values are usage
errors, even if a later flag would select another mode.
| Category | `--quiet` | No mode flag | `--verbose` | `--debug` |
| --- | --- | --- | --- | --- |
| Substantive result | Suppressed | Shown | Shown | Shown |
| Terminal failure | Shown | Shown | Shown | Shown |
| Warning | Suppressed | Suppressed | Shown | Shown |
| Info | Suppressed | Suppressed | Shown | Shown |
| Debug | Suppressed | Suppressed | Suppressed | Shown |
| Progress, unless enabled explicitly | Suppressed | Suppressed | Suppressed | Shown |
`--progress` independently enables Progress; `--progress=false` disables it.
Both accept the existing boolean syntax, and the last occurrence wins. An
explicit setting overrides the mode default, except quiet always suppresses
Progress. Without an explicit setting, debug enables Progress; ordinary and
verbose modes leave it disabled. Progress does not enable Info, Warning, or
Debug.
### Placement and aliases
Reporting flags are available on application commands, the root, canonical
groups, help, version, and alias-only discovery help. Place them after the
complete command path; they may be interspersed with operands after that path.
For example:
```text
example export --quiet
example config show --verbose
example promptkit test all --debug --progress=false
example help config show --quiet
example version --quiet
```
Reporting flags do not go before or between command-path components. Tokens
consumed as values and tokens after `--` retain their normal meaning.
The reserved long spellings are `--quiet`, `--verbose`, `--debug`, and
`--progress`; application declarations cannot reuse them. There are no built-in
short spellings. Applications may define aliases such as `-q → --quiet`,
`-v → --verbose`, and `-d → --debug`; canonical and aliased occurrences share
the same ordering. Thus `example export --verbose -q` selects quiet mode.
### Channels and messages
Substantive reports use the result destination. Warnings, Info, Progress,
Debug, terminal explanations, and usage guidance use the diagnostic destination.
Simple Warning, Debug, and terminal failure messages begin with `WARN:`,
`DEBUG:`, and `ERROR:` respectively. Info, Progress, and substantive results
have no prefix. For multiline messages, the prefix appears on the opening line;
native structured details keep their own formatting without duplicate labels.
Progress is streamed as unprefixed lines; terminal redraw and progress bars are
not provided. A failed outcome with no message or detail uses the fallback
`ERROR: Command failed.`.
Quiet mode suppresses successful help and version output, as it does other
substantive results. Usage guidance and routing, parsing, and operational
failures remain visible in every mode. A recovered problem is reported only if
the application chooses to submit a Warning; an error cause by itself does not
create a message.
### Presentation failures
CommandKit continues rendering substantive output under quiet mode through a
discard destination. This suppresses writes to the caller's result writer while
still detecting report preparation, encoding, or rendering failures. An
intentional suppression is not a delivery failure. A failure while rendering or
writing requested content is retained in the returned `Outcome` and makes an
otherwise successful or usage invocation operational with status `1`. A domain
classification and its nonzero status are preserved; a zero-status domain
outcome becomes status `1` when presentation fails. Earlier bytes already
written remain visible.
## Registered command syntax
Options are parsed after the complete command path and may be interspersed with
@@ -223,8 +310,8 @@ interrupt the native value. Existing native markers and leading comments are
retained; documents without a marker receive one. Scope labels containing line
control characters are quoted to keep them on one comment line. A provider
failure leaves the output stream empty. A rendering or output failure may leave
an aggregate prefix, and the outcome reports
incomplete report delivery.
an aggregate prefix; `Outcome.Presentation.Output` records incomplete delivery
and the destination or rendering error.
### List configuration sources
@@ -390,8 +477,9 @@ again.
The complete report is prepared before writing. Report preparation failures and
cancellation observed before report delivery starts leave CommandKit's report
output empty. Once writing starts, CommandKit does not interrupt the supplied
writer or roll back delivered bytes. A write error or short write uses the
existing incomplete-report delivery outcome and can leave partial output.
writer or roll back delivered bytes. A write error or short write is recorded
as incomplete delivery in `Outcome.Presentation.Output` and can leave partial
output.
## PromptKit definition inspection
@@ -607,8 +695,9 @@ execution. See PromptKit's
Builder, engine-loading, and native validation errors use the operational path
(status `1`) with supported error identity and acquired diagnostics retained.
Malformed syntax, missing operands, and unsupported formats use usage status
`2`. Successful diagnostics and report-delivery failures follow the common
delivery contract.
`2`. Successful diagnostics use the common reporting policy. Report delivery
failures are recorded in `Outcome.Presentation.Output`, separately from the
operation cause.
### PromptKit catalogs
@@ -802,8 +891,8 @@ outcome. Operation errors use operational status `1`; aggregate runs continue
after individual errors, retain their reports and ordered error text, and join
causes for supported `errors.Is`/`errors.As` access. Cancellation stops later
operations and preserves results already acquired. Report encoding or delivery
failures use the common incomplete-report delivery outcome and retain operation
causes where applicable. Syntax, operand, unsupported format, and conflicting
failures are recorded in `Outcome.Presentation.Output`, separately from
operation causes. Syntax, operand, unsupported format, and conflicting
namespace selection are usage status `2`.
Native configuration, selection, and diagnostic meanings remain owned by
@@ -816,4 +905,9 @@ and [engine API](https://gitea.maximumdirect.net/eric/promptkit/src/commit/9178f
CommandKit usage failures use status `2`; operational failures use status `1`.
Successful operations use status `0`. Application handlers may return a domain
classification and status of their choice, which `Execute` preserves. The caller
chooses how to map an outcome to process exit.
chooses how to map an outcome to process exit. A rendering or delivery failure
changes a successful or usage outcome to operational status `1`. A domain
outcome keeps its classification and nonzero status; a zero-status domain
outcome becomes `1` if presentation fails. The returned `Outcome.Presentation`
records output and diagnostic delivery observations separately from its
operation `Cause`.

View File

@@ -64,7 +64,9 @@ func Greeting() commandkit.Command {
),
},
Run: func(invocation commandkit.Invocation, input GreetInput) commandkit.Outcome {
_, _ = fmt.Fprintln(invocation.Streams.Out, input.Count.Value)
if _, err := fmt.Fprintln(invocation.Reporter().Output(), input.Count.Value); err != nil {
return commandkit.Failure("Could not write the greeting.").WithCause(err)
}
return commandkit.Success()
},
})
@@ -85,6 +87,49 @@ values convert before input construction. A command's `NewInput` creates input
only when execution reaches it; help does not construct caller input or run
provider callbacks.
## Invocation reporting
`Invocation.Reporter()` supplies the reporter for the current handler. Use
`Output()` or `RenderOutput()` for substantive results; use `Warn`, `Info`,
`Progress`, and `Debug` for categorized messages. Queue a summary with
`AtEnd().Info` when it belongs after the handler returns. The
[CLI reference](../cli.md#reporting) owns flag grammar, visibility, prefixes,
and status behavior.
Return terminal text with `Failure(message, details...)`, `Usage(message,
details...)`, or `WithExplanation` on a domain outcome. Attach an optional Go
error separately with `WithCause`. A cause is preserved for inspection, but it
does not choose the outcome or become user-facing text. Rendering callbacks
write to their supplied writer only while the callback runs; they must not
submit another message or start another rendering operation through the same
reporter.
```go
Run: func(invocation commandkit.Invocation, input GreetInput) commandkit.Outcome {
if _, err := fmt.Fprintln(invocation.Reporter().Output(), input.Count.Value); err != nil {
return commandkit.Failure("Could not write the greeting.").WithCause(err)
}
return commandkit.Success()
}
```
After `App.Execute`, inspect the returned `Outcome`. Its `Cause` retains the
operation error; `Presentation.Output` and `Presentation.Diagnostics` report
delivery state and the first rendering or write errors for their destinations.
CommandKit does not retain a message transcript. Reporter operations for one
invocation are safe concurrently, but concurrent submission order is not
specified. The caller owns the writers passed to `Execute`; CommandKit does not
close them.
### Migrating from the previous reporting API
| Previous API | Current API |
| --- | --- |
| `invocation.Streams.Out` / `.Err` | `invocation.Reporter().Output()` and categorized reporter methods. `Streams` remains the argument to `App.Execute`. |
| `Failure(err)` / `Usage(err)` | `Failure("operator-facing explanation").WithCause(err)` / `Usage("usage explanation").WithCause(err)`. Choose the explanation deliberately; the cause alone is not printed. |
| `Outcome.Diagnostics` or terminal `SeverityError` | Submit independent messages through reporter methods. Put failure context in `Explanation`; provider `Acquired.Diagnostics` remains passive data for the owning integration to submit. |
| `ReportIncomplete`, `DiagnosticDelivery`, or `DeliveryError` | Inspect `Outcome.Presentation.Output` and `.Diagnostics`, including each `DeliveryResult.State`, `RenderError`, and `WriteError`. |
## Application-owned aliases
Pass commands and aliases together as declarations. Integration factories can
@@ -257,10 +302,11 @@ application-owned `session restore` command.
## Outcomes and ownership
Core classifies usage and operational failures while preserving application
domain outcomes and supported error identity. Report and diagnostic delivery
errors remain inspectable separately from the operation cause. The caller owns
process startup, signal handling, and exit mapping; `Execute` never exits the
process.
domain outcomes and supported error identity. Applications choose domain
classification and status. A terminal explanation is presented separately from
the optional operation cause, while report and diagnostic delivery observations
are available in `Outcome.Presentation`. The caller owns process startup,
signal handling, and exit mapping; `Execute` never exits the process.
Go declarations and GoDoc own exact exported contracts. ConfigKit owns
configuration decoding, validation, and provenance. PromptKit owns prompt

View File

@@ -2,8 +2,9 @@
CommandKit is distributed as a core Go module and two optional integration
modules. Core owns captured declarations, typed input preparation, installation,
dispatch, outcomes, help, and version. `integrations/ckconfig` owns ConfigKit
scope adaptation and reports; `integrations/ckprompt` owns PromptKit engine,
dispatch, invocation reporting and outcomes, help, and version.
`integrations/ckconfig` owns ConfigKit scope adaptation and reports;
`integrations/ckprompt` owns PromptKit engine,
definition inspection, requirements discovery, and concrete request validation.
The maintained [consumer guide](consumers/guide.md) links to the three maintained
consumers.
@@ -53,6 +54,7 @@ For application-owned aliases, use the
| Task | Read |
| --- | --- |
| Implemented CLI behavior | [CLI reference](cli.md) |
| Invocation reporting | [CLI reporting contract](cli.md#reporting), [consumer guide](consumers/guide.md#invocation-reporting), core GoDoc, [reporting roadmap](roadmap/reporting.md), and [ADR 0014](adr/0014-invocation-reporting.md) |
| Application-owned aliases | [CLI reference](cli.md#application-owned-aliases), [Go declarations and GoDoc](../command.go), [consumer guide](consumers/guide.md#application-owned-aliases), and [ADR 0013](adr/0013-application-alias-routing.md) |
| Public API usage and standard-suite wiring | [Consumer guide](consumers/guide.md), [ckconfig package](../integrations/ckconfig/doc.go), [ckprompt package](../integrations/ckprompt/doc.go), and applicable ADRs |
| Dispatcher, registration, or typed inputs | [Command declarations](../command.go), [bindings](../binding.go), [invocation](../execute.go), and [ADR 0001](adr/0001-command-dispatch-and-inputs.md), refined by [ADR 0011](adr/0011-captured-declarations-and-installation.md) |

View File

@@ -10,7 +10,8 @@ this page inventories the implemented owners.
The root facade owns captured canonical command declarations, application-owned
routing rules, binding conversion, invocation coordination, the per-App
registries, standard help/version commands, and outcome/diagnostic delivery.
registries, standard help/version commands, invocation reporting, and outcome
finalization.
Its dependency graph has no ConfigKit or PromptKit integration dependency.
Applications provide typed callbacks and install routing rules alongside the
complete canonical command tree through the same batch API.
@@ -25,6 +26,30 @@ successful registration invalidates the snapshot for a later invocation. The
root package owns app input and callback execution, not provider configuration
semantics.
Each execution creates one reporter backed by the caller's result and diagnostic
writers. The Cobra adapter returns parsed values, flag spellings, and reporting
flag occurrences. Core reduces those occurrences in entered order after route
resolution, applies the final mode and progress selection, and presents any
collected parser events through that selection. The selected mode therefore
applies to command execution, help, and version; routing and parsing failures
remain visible in every mode.
The reporter owns invocation-scoped visibility, message formatting, managed
report rendering, delivery observations, and finalization. It presents queued
messages after the handler returns, then a failed outcome's explanation, and
closes its handles before `Execute` returns. It records rendering and write
errors separately for result and diagnostic destinations; the returned
`Outcome.Presentation` exposes those observations independently from the
operation cause. The reporter does not retain a transcript and does not close
or flush caller-owned writers.
Internally, one reporting-state mutex guards visibility, queued messages,
destination state, and delivery errors while serializing reporter operations
and writes. Rendering callbacks execute within that coordination boundary, so
they must not recursively submit or render through the same reporter. This is
an implementation detail supporting the callback contract; the mutex does not
coordinate writes made outside the reporter.
## `internal/alias` and `internal/commandpath`
`internal/commandpath` provides shared path parsing and token operations used by

View File

@@ -2,9 +2,11 @@
## Summary
CommandKit v0.3.0 adds application-owned command and flag aliases. This
pre-1.0 minor release also updates the installation declaration type and
Invocation struct shape; review the migration guidance before upgrading.
CommandKit v0.3.0 adds application-owned command and flag aliases and shared
invocation reporting. This pre-1.0 minor release also updates the installation
declaration type and Invocation struct shape, replaces the error-first outcome
and diagnostic-delivery APIs, and reserves reporting flag names; review the
migration guidance before upgrading.
## Changes
@@ -17,6 +19,14 @@ Invocation struct shape; review the migration guidance before upgrading.
`EnteredPath` and `CanonicalPath`.
- Interpret a canonical short value flag such as `-c=` as an explicitly
supplied empty value, consistent with `--color=` and `-c ""`.
- Obtain an invocation-scoped `Reporter` from each handler's `Invocation` for
substantive output, Warning/Info/Progress/Debug messages, deferred messages,
and managed report rendering. The shared `--quiet`, `--verbose`, `--debug`,
and `--progress` flags apply across core and integration commands.
- Return terminal explanation text with `Failure`, `Usage`, or
`Outcome.WithExplanation`; attach an optional operation error separately with
`Outcome.WithCause`. Returned `Outcome.Presentation` records output and
diagnostic delivery observations without changing the operation cause.
## Compatibility and migration
@@ -29,10 +39,26 @@ values whose parameter type is `...Command`, including wrappers assigned from
`New` or `Register`, must update their signature to `...Declaration`.
Adding private route fields to `Invocation` means positional struct literals no
longer compile. Use keyed fields, for example
`commandkit.Invocation{Context: ctx, Streams: streams}`, when constructing an
Invocation outside command execution. CommandKit populates the route fields for
executed handlers.
longer compile. `Invocation` no longer contains `Streams`; `App.Execute` supplies
the reporter and route paths to handlers. `Streams` remains the argument to
`App.Execute`, where the caller owns the result and diagnostic writers.
Handlers that used `invocation.Streams.Out` or `.Err` should use
`invocation.Reporter().Output()` or the categorized reporting methods. Replace
`Failure(err)` and `Usage(err)` with an operator-facing explanation and an
optional `.WithCause(err)`; causes alone are not rendered. Replace
`Outcome.Diagnostics` submissions and terminal `SeverityError` records with
reporter messages and an `Outcome` explanation. Provider-returned
`Acquired.Diagnostics` remain passive data for the owning integration to
submit. Replace `ReportIncomplete`, `DiagnosticDelivery`, and `DeliveryError`
with `Outcome.Presentation.Output` and `.Diagnostics`, whose `DeliveryResult`
contains a state plus first render and write errors.
The long flags `--quiet`, `--verbose`, `--debug`, and `--progress` are reserved
for CommandKit reporting and cannot be declared by applications. Application
aliases may target them. See the current [CLI reporting contract](../cli.md#reporting)
and [consumer migration guide](../consumers/guide.md#invocation-reporting) for
their behavior and API examples.
The consumer minimum remains Go 1.26.0 and the maintainer release toolchain
remains Go 1.26.8. Core v0.3.0 must be published before preparing dependent

View File

@@ -1,6 +1,6 @@
# Invocation reporting implementation plan
Status: Stages 1–18 complete. Stage 19 is next.
Status: Stages 1–19 complete. Stage 20 is next.
## Objective and authority
@@ -719,7 +719,7 @@ on either optional integration module.
## Stage 19: Update canonical documentation and compatibility guidance
Status: pending.
Status: complete.
1. Update public Go declarations/GoDoc for exact reporter, outcome, provider,
lifecycle, cause, and presentation-observation contracts. Describe callback
@@ -749,6 +749,15 @@ Validation: review prose against implemented behavior, check local links and
fragments, and run the Markdown whitespace checks from the developer guide.
Current contract documentation must no longer present the removed APIs as valid.
Completion: updated exported GoDoc, the CLI reference, consumer migration
guidance, README, integration package documentation, developer guide, internal
overview, and the unpublished core v0.3.0 note. Current contracts direct callers
to the invocation reporter and keep operation causes separate from explanations
and presentation observations. Reviewed the prose against the implementation
and selected reporting policy; `go doc` checks, local Markdown link and fragment
checks, and both Markdown whitespace checks pass. Historical integration release
notes, accepted ADRs, and deferred-work records remain unchanged.
## Stage 20: Validate the complete end state and finish
Status: pending.

View File

@@ -12,8 +12,8 @@ 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.
The [implementation plan](implementation.md) owns stage order and execution
started. Stages 1–19 are complete; final validation remains. The
[implementation plan](implementation.md) owns stage order and execution
instructions.
## Purpose

View File

@@ -14,10 +14,11 @@ import (
// Execute parses and dispatches one invocation. args excludes the executable
// name. Supplied values are converted before command input construction; help
// does not construct caller inputs or run acquisition callbacks. CommandKit
// writes diagnostics to streams.Err and leaves process exit to the caller. A
// nil context returns an operational failure, and nil streams are replaced with
// io.Discard.
// does not construct caller inputs or run acquisition callbacks. It applies the
// parsed reporting selection, finalizes invocation presentation, and returns
// the resulting Outcome without exiting the process. Supplied streams remain
// caller-owned; nil destinations are replaced with io.Discard. A nil context
// returns an operational failure.
func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outcome {
current := newExecution(a, ctx, streams)
if a == nil {

View File

@@ -18,8 +18,11 @@ type Identity struct {
Revision string
}
// Streams contains the output destinations associated with a command
// invocation.
// Streams contains the caller-owned output destinations associated with an
// invocation. Out receives substantive results; Err receives messages,
// terminal explanations, usage guidance, and CommandKit diagnostics. Nil
// destinations are replaced with io.Discard. CommandKit does not close or flush
// supplied writers.
type Streams struct {
// Out receives help and command reports.
Out io.Writer
@@ -37,10 +40,11 @@ func normalizeStreams(streams Streams) Streams {
return streams
}
// Invocation provides execution context and reporting to a command handler.
// For command aliases, EnteredPath and CanonicalPath expose the route the
// caller used and the selected canonical command path. Both omit the
// executable name, flags, and operands.
// Invocation provides execution context and invocation-scoped reporting to a
// command handler. Execute finalizes its reporter before returning. For command
// aliases, EnteredPath and CanonicalPath expose the route the caller used and
// the selected canonical command path. Both omit the executable name, flags,
// and operands.
type Invocation struct {
// Context carries caller cancellation and request-scoped values.
Context context.Context
@@ -50,7 +54,8 @@ type Invocation struct {
canonicalPath string
}
// Reporter returns the reporting facility for this invocation.
// Reporter returns the reporting facility supplied for this invocation. A
// manually constructed Invocation has no reporter.
func (inv Invocation) Reporter() *Reporter { return inv.reporter }
// EnteredPath returns the command path used to invoke the selected command,

View File

@@ -3,5 +3,7 @@
// through the core commandkit package. Applications can install CommandKit
// RoutingRule declarations alongside it through commandkit.New or
// App.Register. Source reports preserve ConfigKit's destination, placement,
// and version-check evidence; importing ckconfig alone installs no commands.
// and version-check evidence. Invocation reporting applies CommandKit's shared
// visibility and delivery policy; provider warnings remain independent of
// failure explanations. Importing ckconfig alone installs no commands.
package ckconfig

View File

@@ -4,5 +4,7 @@
// canonical commandkit.Command declarations. Applications can install
// commandkit.RoutingRule aliases alongside them through commandkit.New or
// App.Register. Importing ckprompt installs neither family; callers compose its
// declarations with core and ckconfig explicitly.
// declarations with core and ckconfig explicitly. Provider diagnostics remain
// passive until the owning command submits them through the invocation
// reporter, separately from any terminal explanation.
package ckprompt

View File

@@ -6,7 +6,8 @@ import (
"strings"
)
// Severity classifies a nonterminal diagnostic.
// Severity classifies a nonterminal provider diagnostic. Terminal failure
// context belongs in an Outcome explanation instead.
type Severity string
const (
@@ -48,13 +49,18 @@ const (
DeliveryIncomplete
)
// ErrReporterClosed is returned when a reporting handle is used after invocation finalization.
// ErrReporterClosed is returned when a reporting handle is used after
// invocation finalization.
var ErrReporterClosed = errors.New("reporter is closed")
// Detail renders optional content to the supplied writer.
// Detail renders optional content to the writer supplied by the reporter.
// Use that writer only during the callback. A callback must not submit another
// message, queue deferred work, or start another rendering operation through
// the same invocation's reporter.
type Detail func(io.Writer) error
// Explanation contains terminal content for a failed outcome.
// Explanation contains terminal content for a failed outcome. CommandKit
// presents it during invocation finalization, including in quiet mode.
type Explanation struct {
// Message is the literal opening text.
Message string
@@ -62,7 +68,9 @@ type Explanation struct {
Details []Detail
}
// PresentationResult records delivery observations for the two output channels.
// PresentationResult records output and diagnostic delivery observations
// populated by CommandKit during invocation finalization. It is not a message
// submission path or a transcript.
type PresentationResult struct {
// Output records delivery to the result destination.
Output DeliveryResult
@@ -71,6 +79,9 @@ type PresentationResult struct {
}
// DeliveryResult records rendering and writing errors for one destination.
// The state distinguishes no submitted content, intentionally suppressed
// content, completed delivery, and incomplete delivery. RenderError and
// WriteError retain the first error of their kind for that destination.
type DeliveryResult struct {
// State summarizes content delivery to the destination.
State DeliveryState
@@ -80,7 +91,9 @@ type DeliveryResult struct {
WriteError error
}
// Diagnostic is passive nonterminal message data returned by a provider.
// Diagnostic is passive nonterminal message data returned by a provider. It
// does not emit output or select an invocation outcome; the owning integration
// submits it through the invocation reporter.
//
// Write renders the diagnostic to a supplied writer when the owning integration
// submits it through the invocation reporter. A nil Write function has no detail.
@@ -91,7 +104,9 @@ type Diagnostic struct {
Write Detail
}
// Acquired pairs a provider value with independent diagnostics and failure context.
// Acquired pairs a provider value with independent diagnostics and failure
// context. The owning command chooses how to submit each diagnostic and whether
// to attach the explanation to a failed outcome.
type Acquired[T any] struct {
// Value is the value returned by the acquisition callback.
Value T
@@ -101,11 +116,16 @@ type Acquired[T any] struct {
Explanation Explanation
}
// Outcome is the classified result of an operation.
// Outcome is the classified result of an operation. Constructing or returning
// an outcome does not itself write output; Execute presents its explanation
// during finalization.
//
// Cause records an optional operation error without selecting the outcome or
// generating operator-facing text. Presentation records delivery observations
// separately. Invocation coordination treats a zero Outcome as success with status 0.
// separately. Invocation coordination treats a zero Outcome as success with
// status 0. Presentation failures change a success or usage outcome to
// operational status 1; a domain outcome keeps its classification and nonzero
// status, while a zero domain status becomes 1.
type Outcome struct {
// Kind classifies the result.
Kind OutcomeKind
@@ -126,28 +146,38 @@ func Success() Outcome {
return Outcome{Kind: OutcomeSuccess}
}
// Failure returns an operational outcome with status 1 and a terminal explanation.
// Failure returns an operational outcome with status 1 and a terminal
// explanation. The message and details are presented once when the outcome is
// returned from an invocation; an optional operation error can be attached with
// WithCause and is not rendered automatically.
func Failure(message string, details ...Detail) Outcome {
return Outcome{Kind: OutcomeOperational, Status: 1, Explanation: Explanation{Message: message, Details: cloneDetails(details)}}
}
// Usage returns a usage outcome with status 2 and a terminal explanation.
// Usage returns a usage outcome with status 2 and a terminal explanation. The
// message and details are presented when the outcome is returned from an
// invocation; an optional cause is preserved separately with WithCause.
func Usage(message string, details ...Detail) Outcome {
return Outcome{Kind: OutcomeUsage, Status: 2, Explanation: Explanation{Message: message, Details: cloneDetails(details)}}
}
// Domain returns an application-domain outcome with the supplied name and status.
// Domain returns an application-domain outcome with the supplied name and
// status. The application owns the classification and status decision.
func Domain(name string, status int) Outcome {
return Outcome{Kind: OutcomeDomain, Status: status, Domain: name}
}
// WithExplanation returns a copy of the outcome with the supplied terminal explanation.
// WithExplanation returns a copy of the outcome with the supplied terminal
// explanation, replacing any existing explanation. CommandKit presents it once
// during finalization, including in quiet mode.
func (o Outcome) WithExplanation(message string, details ...Detail) Outcome {
o.Explanation = Explanation{Message: message, Details: cloneDetails(details)}
return o
}
// WithCause returns a copy of the outcome with the supplied optional cause.
// WithCause returns a copy of the outcome with the supplied optional operation
// cause. The cause is preserved for caller inspection and does not select an
// outcome or generate operator-facing text.
func (o Outcome) WithCause(cause error) Outcome {
o.Cause = cause
return o

View File

@@ -60,16 +60,24 @@ type deferredMessage struct {
details []Detail
}
// Reporter coordinates presentation and delivery for one invocation.
// Reporter coordinates presentation and delivery for one invocation. The
// invocation supplies it; consumers do not construct or finalize it. Reporting
// operations on one reporter are safe concurrently, although concurrent
// submissions have no predetermined order. Rendering callbacks must not
// reenter the same reporter, and their supplied writer is valid only while the
// callback runs.
//
// The invocation supplies the reporter. Consumers use Output for substantive
// command results and the reporting methods for categorized messages.
// Consumers use Output for substantive command results and the reporting
// methods for categorized messages. Delivery and rendering failures are
// retained for Outcome.Presentation even when a caller ignores an immediate
// method's returned error.
type Reporter struct {
state *reportingState
}
// DeferredReporter queues messages for delivery during invocation finalization.
// Copies share the same invocation reporting state.
// Copies share the same invocation reporting state. Submissions made after
// finalization return ErrReporterClosed.
type DeferredReporter struct {
state *reportingState
}
@@ -126,7 +134,10 @@ func (r *Reporter) applySelection(selection commandmeta.ReportingSelection) {
state.progressSet = selection.ProgressSet
}
// Output returns a managed writer for substantive command results.
// Output returns a managed writer for substantive command results. Writes are
// serialized with other operations through this reporter, and failures are
// recorded in Outcome.Presentation. The writer returns ErrReporterClosed after
// invocation finalization.
func (r *Reporter) Output() io.Writer {
if r == nil || r.state == nil {
return managedOutputWriter{}
@@ -142,6 +153,10 @@ func (w managedOutputWriter) Write(p []byte) (int, error) {
}
// RenderOutput coordinates a complete substantive report rendering operation.
// The callback receives a writer usable only during the callback and must not
// reenter this reporter. In quiet mode, the callback still runs against a
// discard destination so rendering failures are retained without writing to
// the caller's output destination.
func (r *Reporter) RenderOutput(write func(io.Writer) error) error {
if r == nil || r.state == nil {
return ErrReporterClosed
@@ -168,27 +183,33 @@ func (r *Reporter) RenderOutput(write func(io.Writer) error) error {
return finishRenderLocked(state, destinationOutput, renderErr, writer.writeError)
}
// Info submits an informational message.
// Info submits an informational message to the diagnostic destination. Its
// visibility follows the selected reporting mode.
func (r *Reporter) Info(message string, details ...Detail) error {
return r.submitMessage(categoryInfo, message, details)
}
// Warn submits a warning message.
// Warn submits a warning message to the diagnostic destination. Its visibility
// follows the selected reporting mode.
func (r *Reporter) Warn(message string, details ...Detail) error {
return r.submitMessage(categoryWarning, message, details)
}
// Debug submits a debug message.
// Debug submits a debug message to the diagnostic destination. Its visibility
// follows the selected reporting mode.
func (r *Reporter) Debug(message string, details ...Detail) error {
return r.submitMessage(categoryDebug, message, details)
}
// Progress submits a progress message.
// Progress submits a progress message to the diagnostic destination. Its
// visibility follows the selected mode and explicit progress setting.
func (r *Reporter) Progress(message string, details ...Detail) error {
return r.submitMessage(categoryProgress, message, details)
}
// AtEnd returns a view for messages delivered after the handler returns.
// AtEnd returns a view for messages delivered after the handler returns and
// before terminal explanation delivery. The deferred view shares this
// invocation's state.
func (r *Reporter) AtEnd() DeferredReporter {
if r == nil {
return DeferredReporter{}
@@ -196,17 +217,23 @@ func (r *Reporter) AtEnd() DeferredReporter {
return DeferredReporter{state: r.state}
}
// Info queues an informational message for invocation finalization.
// Info queues an informational message for invocation finalization. The
// submission error is returned immediately; rendering and delivery errors are
// reflected in the final outcome.
func (r DeferredReporter) Info(message string, details ...Detail) error {
return r.enqueue(categoryInfo, message, details)
}
// Warn queues a warning message for invocation finalization.
// Warn queues a warning message for invocation finalization. The submission
// error is returned immediately; rendering and delivery errors are reflected in
// the final outcome.
func (r DeferredReporter) Warn(message string, details ...Detail) error {
return r.enqueue(categoryWarning, message, details)
}
// Debug queues a debug message for invocation finalization.
// Debug queues a debug message for invocation finalization. The submission
// error is returned immediately; rendering and delivery errors are reflected in
// the final outcome.
func (r DeferredReporter) Debug(message string, details ...Detail) error {
return r.enqueue(categoryDebug, message, details)
}