Migrate PromptKit inspection reports to the reporter

This commit is contained in:
2026-10-26 03:30:19 +00:00
parent 37dfea5b25
commit 7fc89768ef
3 changed files with 204 additions and 102 deletions

View File

@@ -1,6 +1,6 @@
# Invocation reporting implementation plan
Status: Stages 1–13 complete. Stage 14 is next.
Status: Stages 1–14 complete. Stage 15 is next.
## Objective and authority
@@ -535,7 +535,7 @@ adapter files; Stage 20 will run the assembled suite.
## Stage 14: Migrate PromptKit listing and inspection commands
Status: pending.
Status: complete.
Primary file: [command.go](../../integrations/ckprompt/command.go) and its
catalog, definition, and requirements report helpers/tests.
@@ -557,6 +557,16 @@ Validation: migrate existing report/selection tests; add representative
successful quiet/verbose/debug execution and native acquisition failure with an
independent warning. Reuse core reporting coverage for universal mechanics.
PromptKit catalog listing, definition inspection, and requirements inspection
now submit provider messages through the reporter and render complete reports
inside managed output. Acquisition failures use native terminal context when
available and preserve the original cause. Tests cover independent warning and
terminal-context delivery, successful ordinary/quiet/verbose/debug execution,
quiet encoding failure, cancellation, and result delivery observations. The
ckprompt suite cannot run yet because `validation_command.go` and
`smoke_command.go` still use the legacy reporting API scheduled for Stage 15;
Stage 20 will validate the assembled module.
## Stage 15: Migrate request validation and smoke commands
Status: pending.

View File

@@ -185,23 +185,23 @@ func newCatalogListCommand[I, S any](suite Suite[I, S], name string) commandkit.
Run: func(invocation commandkit.Invocation, input *catalogInput[I]) commandkit.Outcome {
switch name {
case "prompts":
return runCatalogList(invocation, suite, input, suite.ListPrompts, encodePromptCatalog)
return runCatalogList(invocation, suite, input, name, suite.ListPrompts, encodePromptCatalog)
case "profiles":
return runCatalogList(invocation, suite, input, suite.ListProfiles, encodeProfileCatalog)
return runCatalogList(invocation, suite, input, name, suite.ListProfiles, encodeProfileCatalog)
case "backends":
return runCatalogList(invocation, suite, input, suite.ListBackends, encodeProfileCatalog)
return runCatalogList(invocation, suite, input, name, suite.ListBackends, encodeProfileCatalog)
case "namespaces":
list := func(ctx context.Context, request CatalogRequest, value I, state S) (commandkit.Acquired[[]string], error) {
return suite.ListNamespaces(ctx, NamespaceRequest{Selection: request.Selection, Kinds: input.kinds}, value, state)
}
return runCatalogList(invocation, suite, input, list, encodeProfileCatalog)
return runCatalogList(invocation, suite, input, name, list, encodeProfileCatalog)
case "collections":
list := func(ctx context.Context, request CatalogRequest, value I, state S) (commandkit.Acquired[[]promptkit.CollectionInfo], error) {
return suite.ListCollections(ctx, request.Selection, value, state)
}
return runCatalogList(invocation, suite, input, list, encodeCollectionCatalog)
return runCatalogList(invocation, suite, input, name, list, encodeCollectionCatalog)
default:
return definitionFailure(fmt.Errorf("unknown PromptKit catalog %q", name), nil)
return definitionFailure("Unable to list the selected PromptKit catalog.", commandkit.Explanation{}, fmt.Errorf("unknown PromptKit catalog %q", name))
}
},
})
@@ -385,12 +385,13 @@ func runCatalogList[T any, I, S any](
invocation commandkit.Invocation,
suite Suite[I, S],
input *catalogInput[I],
catalog string,
list func(context.Context, CatalogRequest, I, S) (commandkit.Acquired[T], error),
encode func(T) ([]byte, error),
) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, nil)
return definitionFailure("PromptKit catalog listing was canceled.", commandkit.Explanation{}, err)
}
}
var state S
@@ -399,30 +400,35 @@ func runCatalogList[T any, I, S any](
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, nil)
return definitionFailure("PromptKit catalog listing was canceled.", commandkit.Explanation{}, err)
}
}
acquired, err := list(invocation.Context, CatalogRequest{Selection: input.selection, Filter: input.filter}, input.selected, state)
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
if err != nil {
return definitionFailure(err, acquired.Diagnostics)
return definitionFailure(fmt.Sprintf("Unable to list PromptKit %s.", catalog), acquired.Explanation, err)
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, acquired.Diagnostics)
return definitionFailure("PromptKit catalog listing was canceled.", acquired.Explanation, err)
}
}
output, err := encode(acquired.Value)
if err != nil {
return definitionFailure(err, acquired.Diagnostics)
}
return writePromptReport(invocation, output, acquired.Diagnostics)
_ = invocation.Reporter().RenderOutput(func(writer io.Writer) error {
output, err := encode(acquired.Value)
if err != nil {
return err
}
_, err = writer.Write(output)
return err
})
return commandkit.Success()
}
func runDefinitionInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *definitionInput[I], prompt bool) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, nil)
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
}
}
var state S
@@ -431,41 +437,43 @@ func runDefinitionInspection[I, S any](invocation commandkit.Invocation, suite S
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, nil)
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
}
}
if prompt {
request := PromptRequest{Selection: input.selection, ID: input.id}
acquired, err := suite.InspectPrompt(invocation.Context, request, input.selected, state)
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
if err != nil {
return definitionFailure(err, acquired.Diagnostics)
return definitionFailure(fmt.Sprintf("Unable to inspect PromptKit prompt %q.", input.id), acquired.Explanation, err)
}
if acquired.Value == nil {
return definitionFailure(errors.New("PromptKit prompt inspection returned nil"), acquired.Diagnostics)
return definitionFailure("PromptKit prompt inspection returned no result.", acquired.Explanation, nil)
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, acquired.Diagnostics)
return definitionFailure(fmt.Sprintf("Inspection of PromptKit prompt %q was canceled.", input.id), acquired.Explanation, err)
}
}
return renderPromptReport(invocation, promptReportFromInspection(acquired.Value), input.format, acquired.Diagnostics)
return renderPromptReport(invocation, acquired.Value, input.format)
}
request := ProfileRequest{Selection: input.selection, ID: input.id}
acquired, err := suite.InspectProfile(invocation.Context, request, input.selected, state)
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
if err != nil {
return definitionFailure(err, acquired.Diagnostics)
return definitionFailure(fmt.Sprintf("Unable to inspect PromptKit profile %q.", input.id), acquired.Explanation, err)
}
if acquired.Value == nil {
return definitionFailure(errors.New("PromptKit profile inspection returned nil"), acquired.Diagnostics)
return definitionFailure("PromptKit profile inspection returned no result.", acquired.Explanation, nil)
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, acquired.Diagnostics)
return definitionFailure(fmt.Sprintf("Inspection of PromptKit profile %q was canceled.", input.id), acquired.Explanation, err)
}
}
return renderProfileReport(invocation, profileReportFromInspection(acquired.Value), input.format, acquired.Diagnostics)
return renderProfileReport(invocation, acquired.Value, input.format)
}
type promptInputReport struct {
@@ -624,7 +632,7 @@ func profileReportFromInspection(inspection *promptkit.ProfileInspection) profil
func runRequirementsInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *requirementsInput[I]) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, nil)
return definitionFailure("PromptKit requirements inspection was canceled.", commandkit.Explanation{}, err)
}
}
var state S
@@ -633,56 +641,56 @@ func runRequirementsInspection[I, S any](invocation commandkit.Invocation, suite
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, nil)
return definitionFailure("PromptKit requirements inspection was canceled.", commandkit.Explanation{}, err)
}
}
request := RequirementsRequest{Selection: input.selection, Inspection: input.inspection}
acquired, err := suite.InspectRequirements(invocation.Context, request, input.selected, state)
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
if err != nil {
return definitionFailure(err, acquired.Diagnostics)
return definitionFailure(fmt.Sprintf("Unable to inspect requirements for PromptKit prompt %q.", input.inspection.PromptID), acquired.Explanation, err)
}
if acquired.Value == nil {
return definitionFailure(errors.New("PromptKit requirements inspection returned nil"), acquired.Diagnostics)
return definitionFailure("PromptKit requirements inspection returned no result.", acquired.Explanation, nil)
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, acquired.Diagnostics)
return definitionFailure(fmt.Sprintf("Requirements inspection for PromptKit prompt %q was canceled.", input.inspection.PromptID), acquired.Explanation, err)
}
}
output, err := encodePromptYAMLBytes(requirementsReportFromInspection(acquired.Value))
if err != nil {
return definitionFailure(err, acquired.Diagnostics)
}
return writePromptReport(invocation, output, acquired.Diagnostics)
_ = invocation.Reporter().RenderOutput(func(writer io.Writer) error {
return encodePromptYAML(writer, requirementsReportFromInspection(acquired.Value))
})
return commandkit.Success()
}
func renderPromptReport(invocation commandkit.Invocation, report promptInspectionReport, format string, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
var output bytes.Buffer
if format == "yaml" {
if err := encodePromptYAML(&output, report); err != nil {
return definitionFailure(err, diagnostics)
func renderPromptReport(invocation commandkit.Invocation, inspection *promptkit.PromptInspection, format string) commandkit.Outcome {
_ = invocation.Reporter().RenderOutput(func(writer io.Writer) error {
report := promptReportFromInspection(inspection)
if format == "yaml" {
return encodePromptYAML(writer, report)
}
} else {
output.WriteString(formatPromptText(report))
}
return writePromptReport(invocation, output.Bytes(), diagnostics)
_, err := io.WriteString(writer, formatPromptText(report))
return err
})
return commandkit.Success()
}
func renderProfileReport(invocation commandkit.Invocation, report profileInspectionReport, format string, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
var output bytes.Buffer
if format == "yaml" {
if err := encodePromptYAML(&output, report); err != nil {
return definitionFailure(err, diagnostics)
func renderProfileReport(invocation commandkit.Invocation, inspection *promptkit.ProfileInspection, format string) commandkit.Outcome {
_ = invocation.Reporter().RenderOutput(func(writer io.Writer) error {
report := profileReportFromInspection(inspection)
if format == "yaml" {
return encodePromptYAML(writer, report)
}
} else {
text, err := formatProfileText(report)
if err != nil {
return definitionFailure(err, diagnostics)
return err
}
output.WriteString(text)
}
return writePromptReport(invocation, output.Bytes(), diagnostics)
_, err = io.WriteString(writer, text)
return err
})
return commandkit.Success()
}
func formatPromptText(report promptInspectionReport) string {
@@ -771,30 +779,33 @@ func encodePromptYAMLBytes(value any) ([]byte, error) {
return output.Bytes(), nil
}
func writePromptReport(invocation commandkit.Invocation, output []byte, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, diagnostics)
func queuePromptDiagnostics(reporter *commandkit.Reporter, diagnostics []commandkit.Diagnostic) {
deferred := reporter.AtEnd()
for _, diagnostic := range diagnostics {
if diagnostic.Write == nil {
continue
}
switch diagnostic.Severity {
case commandkit.SeverityInfo:
_ = deferred.Info("", diagnostic.Write)
case commandkit.SeverityDebug:
_ = deferred.Debug("", diagnostic.Write)
case commandkit.SeverityProgress:
_ = reporter.Progress("", diagnostic.Write)
default:
_ = deferred.Warn("", diagnostic.Write)
}
}
written, err := invocation.Streams.Out.Write(output)
if err == nil && written != len(output) {
err = io.ErrShortWrite
}
if err != nil {
outcome := definitionFailure(err, diagnostics)
outcome.ReportIncomplete = true
outcome.DeliveryError = err
return outcome
}
outcome := commandkit.Success()
outcome.Diagnostics = diagnostics
return outcome
}
func definitionFailure(cause error, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
outcome := commandkit.Failure(cause)
outcome.Diagnostics = diagnostics
func definitionFailure(message string, explanation commandkit.Explanation, cause error) commandkit.Outcome {
outcome := commandkit.Failure(message)
if explanation.Message != "" || len(explanation.Details) > 0 {
outcome = outcome.WithExplanation(explanation.Message, explanation.Details...)
}
if cause != nil {
outcome = outcome.WithCause(cause)
}
return outcome
}

View File

@@ -701,41 +701,39 @@ func TestCatalogRejectsOperandsAndUnavailableSelectors(t *testing.T) {
}
}
func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) {
diagnostic := commandkit.Diagnostic{Severity: commandkit.SeverityWarning, Write: func(writer io.Writer) error {
_, err := io.WriteString(writer, "catalog warning\n")
return err
}}
hasCatalogWarning := func(diagnostics []commandkit.Diagnostic) bool {
for _, got := range diagnostics {
if got.Severity != diagnostic.Severity || got.Write == nil {
continue
}
var output bytes.Buffer
if got.Write(&output) == nil && output.String() == "catalog warning\n" {
return true
}
}
return false
}
func TestCatalogFailureReportsWarningAndNativeExplanationSeparately(t *testing.T) {
diagnostic := commandkit.MessageDiagnostic(commandkit.SeverityWarning, "catalog warning")
providerErr := errors.New("catalog provider failed")
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
return commandkit.Acquired[[]promptkit.PromptIdentity]{Diagnostics: []commandkit.Diagnostic{diagnostic}}, providerErr
return commandkit.Acquired[[]promptkit.PromptIdentity]{
Diagnostics: []commandkit.Diagnostic{diagnostic},
Explanation: commandkit.Explanation{Details: []commandkit.Detail{func(writer io.Writer) error {
_, err := io.WriteString(writer, "PromptKit lookup context\n")
return err
}}},
}, providerErr
}}))
outcome := app.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, providerErr) || !hasCatalogWarning(outcome.Diagnostics) {
var output, diagnosticOutput bytes.Buffer
outcome := app.Execute(context.Background(), []string{"--verbose", "promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: &diagnosticOutput})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, providerErr) {
t.Fatalf("provider failure outcome = %#v", outcome)
}
warningAt := strings.Index(diagnosticOutput.String(), "catalog warning")
explanationAt := strings.Index(diagnosticOutput.String(), "PromptKit lookup context")
if warningAt < 0 || explanationAt < 0 || warningAt >= explanationAt || strings.Count(diagnosticOutput.String(), "catalog warning") != 1 {
t.Fatalf("warning/native terminal context were not independently ordered once: %q", diagnosticOutput.String())
}
ctx, cancel := context.WithCancel(context.Background())
canceled := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
cancel()
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil
}}))
var output bytes.Buffer
outcome = canceled.Execute(ctx, []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, context.Canceled) || !hasCatalogWarning(outcome.Diagnostics) || output.Len() != 0 {
t.Fatalf("post-provider cancellation outcome = %#v output=%q", outcome, output.String())
output.Reset()
diagnosticOutput.Reset()
outcome = canceled.Execute(ctx, []string{"--verbose", "promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: &diagnosticOutput})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, context.Canceled) || !strings.Contains(diagnosticOutput.String(), "catalog warning") || output.Len() != 0 {
t.Fatalf("post-provider cancellation outcome = %#v output=%q err=%q", outcome, output.String(), diagnosticOutput.String())
}
writeErr := errors.New("catalog report writer failed")
@@ -752,13 +750,96 @@ func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) {
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil
}}))
outcome := writerApp.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: test.writer, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.DeliveryError, test.want) || !errors.Is(outcome.Cause, test.want) || !outcome.ReportIncomplete || !hasCatalogWarning(outcome.Diagnostics) {
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Presentation.Output.WriteError, test.want) || outcome.Presentation.Output.State != commandkit.DeliveryIncomplete {
t.Fatalf("catalog delivery outcome = %#v", outcome)
}
})
}
}
func TestCatalogAndInspectionRespectSuccessfulReportingModes(t *testing.T) {
for _, mode := range []string{"ordinary", "quiet", "verbose", "debug"} {
t.Run(mode, func(t *testing.T) {
calls := 0
suite := Suite[struct{}, struct{}]{
ListPrompts: func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
calls++
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing"}}}, nil
},
InspectPrompt: func(context.Context, PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
calls++
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: "briefing"}}, nil
},
}
app := newPromptTestApp(t, Command(suite))
args := []string{"promptkit", "list", "prompts"}
if mode != "ordinary" {
args = append([]string{"--" + mode}, args...)
}
var output, diagnosticOutput bytes.Buffer
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: &diagnosticOutput})
if outcome.Kind != commandkit.OutcomeSuccess || calls != 1 {
t.Fatalf("%s listing = %#v after %d calls", mode, outcome, calls)
}
if mode == "quiet" {
if output.Len() != 0 {
t.Fatalf("quiet listing wrote result output %q", output.String())
}
} else if !strings.Contains(output.String(), "briefing") {
t.Fatalf("%s listing omitted result: %q", mode, output.String())
}
if mode == "debug" && !strings.Contains(diagnosticOutput.String(), "DEBUG:") {
t.Fatalf("debug listing omitted core debug events: %q", diagnosticOutput.String())
}
output.Reset()
diagnosticOutput.Reset()
args = []string{"promptkit", "show", "prompt", "briefing"}
if mode != "ordinary" {
args = append([]string{"--" + mode}, args...)
}
outcome = app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: &diagnosticOutput})
if outcome.Kind != commandkit.OutcomeSuccess || calls != 2 {
t.Fatalf("%s inspection = %#v after %d callbacks", mode, outcome, calls)
}
if mode == "quiet" {
if output.Len() != 0 {
t.Fatalf("quiet inspection wrote result output %q", output.String())
}
} else if !strings.Contains(output.String(), "Prompt ID: \"briefing\"") {
t.Fatalf("%s inspection omitted result: %q", mode, output.String())
}
})
}
}
func TestQuietInspectionStillFindsReportEncodingFailure(t *testing.T) {
called := false
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectProfile: func(context.Context, ProfileRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
called = true
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: &promptkit.ProfileInspection{
ProfileID: "broken",
EffectiveModelParams: promptkit.ExecutionTarget{ExtraParams: map[string]any{"unsupported": func() {}}},
}}, nil
}}))
result := &countingWriter{}
var diagnosticOutput bytes.Buffer
outcome := app.Execute(context.Background(), []string{"--quiet", "promptkit", "show", "profile", "broken"}, commandkit.Streams{Out: result, Err: &diagnosticOutput})
if !called || result.calls != 0 {
t.Fatalf("quiet rendering called=%t destination writes=%d", called, result.calls)
}
if outcome.Kind != commandkit.OutcomeOperational || outcome.Status != 1 || outcome.Presentation.Output.RenderError == nil {
t.Fatalf("quiet serialization failure outcome = %#v diagnostics=%q", outcome, diagnosticOutput.String())
}
}
type countingWriter struct{ calls int }
func (writer *countingWriter) Write(p []byte) (int, error) {
writer.calls++
return len(p), nil
}
func TestCommandUsesInstalledNamespaceCollisionChecks(t *testing.T) {
command := Command(Suite[struct{}, struct{}]{InspectPrompt: func(context.Context, PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
return commandkit.Acquired[*promptkit.PromptInspection]{}, nil
@@ -1055,7 +1136,7 @@ func TestDefinitionFormatPreflightAndReportDeliveryFailure(t *testing.T) {
writeErr := errors.New("report writer failed")
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "profile", "default"}, commandkit.Streams{Out: partialReportWriter{err: writeErr}, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, writeErr) || outcome.DeliveryError != writeErr || !outcome.ReportIncomplete || calls != 1 {
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Presentation.Output.WriteError, writeErr) || outcome.Presentation.Output.State != commandkit.DeliveryIncomplete || calls != 1 {
t.Fatalf("report delivery outcome = %#v calls=%d", outcome, calls)
}
}