Diagnostic Output Contracts
Tracker references: pm-cha95z, pm-5t33or, pm-f05lsg, pm-h8tpeh, and pm-m4uyyj.
The linked tracker records are repository-only evidence and are excluded from npm.
Agent Quick Context
Failures are context-management surfaces. Every diagnostic family now declares a format-aware token ceiling and one degradation ladder in the public SDK:
| Class | Text | JSON | Corrective action that survives degradation |
|---|---|---|---|
error |
768 | 2000 | required action, retry/domain, or next step |
warning |
768 | 2000 | required action, retry/domain, or next step |
validation_summary |
1500 | 3000 | required action, retry/domain, or next step |
recovery_bundle |
768 | 2000 | required action, retry/domain, or next step |
The smallest explicit ceiling is 192 estimated tokens. At that floor, the projector may reduce the diagnostic to its code, required action, compact recovery, and exit status. It never removes the first corrective action.
SDK Contract
Use the public contract surface rather than maintaining a package-local error budget:
import {
PM_DIAGNOSTIC_OUTPUT_BUDGET_CONTRACTS,
projectPmDiagnosticOutput,
projectPmDiagnosticText,
resolvePmDiagnosticOutputBudget,
} from "@unbrained/pm-cli/sdk/contracts";
const contract = resolvePmDiagnosticOutputBudget("error");
const projected = projectPmDiagnosticOutput(
{
code: "invalid_argument_value",
required: "Use --status open and retry.",
recovery: { suggested_retry: "pm list --status open" },
detail: "The supplied status is not declared.",
exit_code: 2,
},
{ maxEstimatedTokens: contract.minimum_max_estimated_tokens },
);
const text = projectPmDiagnosticText(
"A long rendered diagnostic",
"Use --status open and retry.",
).output;
The JSON projector orders code, required, recovery, and next steps before
explanation. Untruncated diagnostics add no per-call receipt overhead; their
binding declaration is discoverable from pm contracts --full --json under
diagnostic_output_contracts. When degradation occurs, the returned
diagnostic_output receipt records the effective budget, original and emitted
estimates, applied stages, and omitted top-level fields. Minimal fallbacks bound
the named omission list and disclose any additional count through
omitted_fields_overflow_count.
Structured CLI refusals also include a compact refusal identity with the
failing command/flag/operand surface, rejected scalar when observable, complete
legal domain when one exists, and the process exit code. Recovery-bearing
errors use the recovery_bundle diagnostic class. Collection degradation may
compact explanatory domains inside recovery. When refusal.legal_domain
is retained, it remains complete. If that object cannot fit the fixed JSON
budget, the projector may omit the entire refusal object; the
diagnostic_output receipt reports degradation with the bounded omission
metadata described above. An absent refusal therefore
does not prove that the producer supplied no legal domain. The original SDK
error context retains the producer's complete domain; CLI JSON has its own
ceiling and is not an unlimited restoration channel.
For malformed producer domains, text prioritizes the incomplete-domain notice
and a bounded preview of whole usable choices even when producer next steps,
verification errors, or recovery coordinates are oversized. An oversized close
retry still points to the original SDK recovery metadata.
recovery.suggested_retry_args is atomic: the projector retains the complete
argv or omits it rather than shortening it into a different command.
Tracked by pm-gh1693
(repository-only tracker record, excluded from npm).
Enum, date, get-depth, and minimum intent-budget validation producers attach the failed metadata
field and supplied value to PmCliError.context; closed domains also attach
the complete legal values, including the accepted full depth alias. CLI
attribution normalizes camelCase and snake_case fields to an actually supplied
canonical flag, so tokenBudget identifies --token-budget. Explicit flag
metadata retains its spelling. Argument order and unrelated valid flags cannot
become the refusal owner. The error-context value is text, including the
textual representation of numeric SDK inputs. Compound projection conflicts
identify their flag without inventing a rejected scalar. Missing items identify
the id operand.
When the producer cannot identify a field or value, the diagnostic retains the
command surface and omits the unknown scalar. Policy evidence takes precedence
over syntactic attribution. Tracked by pm-gh1365.
--json and --output-format json (including --output-format=json) select
JSON on stderr for parser and execution errors as well as successful output.
The canonical --output-format selector wins when combined with --json, so
--output-format toon --json retains text diagnostics. Exit codes are unchanged.
The executable's project-runtime compatibility warnings and refusals use that
same SDK selector, including repeated formats and the -- terminator. A JSON
stale-read warning is emitted after successful dispatch; a failed read emits its
typed error alone so stderr remains one JSON value. Text warnings retain their
pre-dispatch presentation.
Tracked by pm-gh1366.
The deterministic ladder is:
- full diagnostic;
- omit explanation;
- limit diagnostic collections;
- compact recovery to actionable keys;
- retain the action-only envelope.
Human diagnostics lead with What is required and next steps before explaining
what happened. If their declared ceiling binds, the compact text still begins
with the required action, retains the first identifying line (up to 320 UTF-8
bytes, including an omission suffix), and points to structured JSON for the
bounded recovery envelope. The first line should name the specific failure, such as Error: Unknown option --label. Oversized caller-input echoes are discarded before this identity;
the retained identity and corrective action share the binding byte budget.
The CLI recovery renderer separately retains up to 320 Unicode code points of
its What happened text, followed by an omission suffix when needed, before
the shared byte-budget projector runs. With malformed legal-domain metadata,
the final CLI fallback preserves the producer's required action alongside the
partial-domain notice whenever they fit, dropping repeated detail first.
The text projector strips terminal escape sequences and replaces remaining
control characters before measuring output, including short diagnostics that
fit the budget. Line breaks and ordinary indentation are preserved.
Executable Assurance
pnpm quality:recovery-closure builds the current CLI and replays 117 refusal
contracts in isolated trackers: 18 closed-domain rows, 88 required-argument
omissions derived from core executable positional signatures, seven
closed-action families, and four tracker-preflight states. Package-owned
commands enter executable coverage when their package runtime is active rather
than being misreported as core. Ten representative, high-frequency failure
paths are also ratcheted by
scripts/release/diagnostic-output-baseline.json. The gate requires every row
to remain within the SDK-declared JSON ceiling and retain a mechanically
actionable correction. It reports the aggregate original and emitted token
estimates without claiming a reduction when no degradation was required.
The baseline is a coverage ratchet, not permission to weaken a ceiling. Its negative control requires a missing baseline probe to fail. The existing refusal-closure negative controls independently prove that incomplete domains, broken retries, and malformed recovery envelopes remain blocking findings. The grammar corpus additionally hashes authoritative tracker state around each refusal; schema, items, history, settings, and package state must not change. Ephemeral runtime lock/cache directories are excluded from that semantic snapshot.
The complete error-code census currently retains executable evidence for 18
catalog rows across 17 canonical groups. Two of those rows are author-manifest
schema findings reached through a real pm health --full --strict-exit --json
process: an unknown top-level key and omitted canonical pm version bounds. The
remaining rows stay explicit uncovered obligations in
the generated census (current main, repository-only gate evidence excluded from npm; for an installed release, open the same path at its v<version> tag); the ratchet does not
turn partial catalog closure into an approval claim.
Run the focused proof with:
pnpm build
node scripts/release/refusal-closure-gate.mjs
node scripts/run-tests.mjs test -- \
tests/unit/sdk/agent-output-contracts.spec.ts \
tests/unit/cli/error-guidance.spec.ts \
tests/unit/scripts/refusal-closure-gate.spec.ts
From docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md in pm 2026.11.13. View on GitHub