Skip to content

Receipts

Receipts are JSON artifacts that gate state transitions.

Implementation review receipts include:

  • Backend mode: which reviewer produced it - codex, copilot, cursor, claude, or host (or triage_skip for the trivial-diff fast path). Receipts written before 8.0.0 with mode: "rp" still read.
  • Verdict: SHIP, NEEDS_WORK, MAJOR_RETHINK, or NEEDS_HUMAN
  • Confidence anchors
  • Introduced vs pre-existing findings
  • Suppressed finding counts
  • Deferred finding counts
  • Validation state
  • An optional versioned findings projection
  • On a three-reviewer round, a draws[] array with every reviewer’s result; the round’s verdict is the worst of them

flow --auto reads receipts before moving forward. This prevents fake “done” status and open-loop drift. It verifies every hop from the receipt, the flowctl status fields, and the gh-confirmed PR URL, never from the dispatched skill’s narration; the QA receipt (type: qa_verdict, at .flow/review-receipts/qa-<spec-id>.json) is what tells its next hop whether QA already ran on the branch’s code head.

Terminal window
.flow/review-receipts/<branch>.json

Receipts are not decoration. They are the machine-checkable handover between work and next action.

When a receipt exists, read:

  1. Verdict.
  2. Introduced findings.
  3. Suppressed or deferred findings.
  4. Validation state.
  5. Confidence anchors.

That order prevents reviewers from over-weighting low-confidence commentary while still seeing real risk.

VerdictMeaning
SHIPNo blocking introduced findings remain.
NEEDS_WORKFixable issues block handoff.
MAJOR_RETHINKThe plan or approach is wrong enough to revisit the spec or architecture.
NEEDS_HUMANA human must adjudicate - a judgment call, not another edit. The receipt and a needs_human status persist before the workflow exits with ESCALATE:.

Only introduced findings should block the current change. Pre-existing issues should be reported, but not silently converted into scope.

NEEDS_WORK is not a terminal state. It sends the work back for a fix pass, then a re-review scoped to the fixes. Attended, that one re-review’s verdict is final; unattended runs repeat fix and re-review until SHIP, or until the author records an override of the remaining minor findings in the PR’s Decisions list. The receipt chain is the evidence of what the review found and how each finding was resolved, declined, or deferred.

The live QA pass writes type: qa_verdict to .flow/review-receipts/qa-<spec-id>.json. flowctl qa receipt stamps it with the current head, the branch and the counts, and flow --auto reads that head to decide whether QA already ran on the branch’s code. Besides qa_outcome, findings and R-ID coverage, it carries one scenarios row per scenario:

RowWhat it records
{id, rids, outcome, run: "live", evidence}A scenario driven at this head; outcome is PASS, FAIL or BLOCKED, and evidence lists the capture paths
{id, run: "carried"}A scenario that passed on the prior receipt and was not driven again; flowctl copies its outcome, R-IDs, evidence and original head from that receipt

Coverage per R-ID is one of live, subtracted, carried, no_live_scenario or backend_cli. A criterion is carried only when every one of its scenarios carried. flowctl refuses a carry from another spec or branch, or of a scenario that did not pass there, so a carried result always points at real evidence from an earlier head. The QA re-check decides which scenarios carry.

Agentic work needs evidence that survives the chat. A receipt records the verdict, what was reviewed, which findings were introduced by the diff, and what remains unresolved.

flowchart LR
  Diff["Diff"] --> Review["Review backend"]
  Review --> Receipt["Receipt JSON"]
  Receipt --> Auto["flow --auto hop check"]
  Receipt --> PR["PR briefing"]
  • Keep receipts in repo-local .flow/ state.
  • Do not hide suppressed findings; record suppression count and reason.
  • Defer findings only when the PR body names them.
  • Attended, re-review once, scoped to the fixes; later fixes get focused tests. Unattended, re-review until SHIP or a recorded override.
  • Treat missing receipts as an unknown state, not a pass.

A green receipt is the gate layer’s artifact: proof that one exact command passed at one exact commit.

Terminal window
.flow/tmp/green-receipts/<sha8>-<gate_id>.json
FieldMeaning
head_shathe exact commit the command ran against
commanda sha256 fingerprint of the command string
honored whenhash, path, and age all match
otherwisethe full gate runs, fail-closed

Skips are always loud. A reused receipt leaves a GATE_SKIPPED line in the evidence trail, so a suite that did not re-run is visible rather than assumed.

Plan, implementation, completion, and QA receipts may contain an additive findings object. It preserves the finding stream as a stable, product-neutral handover while the original reviewer prose remains available.

{
"findings": {
"schemaVersion": 1,
"sourceReceiptId": "review-…",
"reviewKind": "implementation",
"backend": "codex",
"round": 2,
"baseSha": "optional-reviewed-base",
"headSha": "reviewed-head",
"supersedesReceiptId": "optional-parent-generation",
"items": [
{
"id": "finding-…",
"priorFindingId": "optional-explicit-lineage-edge",
"ordinal": 1,
"severity": "P1",
"confidence": 100,
"classification": "introduced",
"status": "fixed",
"anchor": {
"path": "src/example.py",
"originalPath": "optional/base-side-name.py",
"side": "head",
"startLine": 42,
"endLine": 45,
"baseSha": "reviewed-base",
"headSha": "reviewed-head",
"blobOid": "optional-git-object-id"
},
"title": "Short finding title",
"body": "Grounded explanation",
"suggestion": "Optional remediation",
"rIds": ["R3"],
"firstSeenReceiptId": "review-…",
"lastSeenReceiptId": "review-…"
}
]
}
}

The required container fields are schemaVersion, sourceReceiptId, reviewKind, backend, round, headSha, and items. baseSha and supersedesReceiptId are optional. Each item requires id, ordinal, severity, confidence, classification, status, title, body, rIds, firstSeenReceiptId, and lastSeenReceiptId. priorFindingId, anchor, and suggestion are optional.

Unknown fields make a v1 container unsupported. A consumer must not reinterpret them as a compatible extension.

The receipt envelope binds the projection to its workflow:

Receipt typeRequired findings.reviewKind
plan_reviewplan
impl_reviewimplementation
completion_reviewcompletion
qa_verdictqa

The receipt mode must exactly equal findings.backend. A type/kind or mode/backend mismatch invalidates the structured projection, so consumers fall back to the receipt and original prose.

FieldStored valuesAccepted reviewer aliases
reviewKindplan, implementation, completion, qanone
severityP0, P1, P2, P3Critical → P0; Major → P1; Minor → P2; Nitpick → P3
confidence0, 25, 50, 75, 100none
classificationintroduced, pre_existingpre-existing / pre existing → pre_existing
statusopen, fixed, not_fixed, withdrawnfixed in review / resolved → fixed; not fixed / remains open / unresolved → not_fixed
anchor.sidebase, headnone

Aliases apply only while Flow-Next parses reviewer prose. Stored objects always use canonical values. Unknown enum values make the structured projection unsupported; they are never coerced.

Items are stored in deterministic order: severity P0 → P3, confidence descending, then ordinal ascending.

sourceReceiptId identifies one findings generation. Whenever a finding is first seen, its ID is exactly finding- plus the first 32 lowercase hexadecimal characters of SHA-256 over the UTF-8 bytes flow-next-finding-v1\0<firstSeenReceiptId>\0<ordinal> (the \0 separators are single NUL bytes). Every valid successor carries the complete prior snapshot forward. Each carried item keeps its ID and firstSeenReceiptId, while lastSeenReceiptId advances. A Prior finding N ratchet record updates the carried item’s status; it is not required for carry-forward.

Fully restated finding prose is not semantic identity. Without an explicit lineage edge, it creates an additional finding and ID even when its content or ordinal resembles an older item.

priorFindingId records an explicit edge only when the parser cannot preserve an older ID byte-for-byte. The new item keeps its new ID and names the older item. Only stored id and priorFindingId fields establish identity; consumers never match findings by title, body, anchor, ordinal, or semantic similarity. Every non-root generation is a complete snapshot. Silently omitting a previously known finding invalidates the chain.

Before a latest receipt pointer advances, Flow-Next preserves its valid prior generation beside it:

<receipt-path>
<receipt-path>.history/<sha256(sourceReceiptId)>.json

Committed receipts conventionally live in .flow/review-receipts/; interactive workflows may use an explicit or temporary receipt path. Consumers start from the receipt paths handed over as evidence. They do not call Flow-Next parser helpers or depend on skill internals.

An anchor exists only when the reviewer supplied a safe repository-relative path and a positive line or range. Absent location evidence produces no anchor. A valid primary location without enough snapshot binding drops the whole anchor candidate before Flow-Next interprets supplemental originalPath or blobOid metadata or range ordering, so an inverted range on an unbound candidate goes with that candidate and the rest of the generation survives. Flow-Next never guesses the missing binding. Malformed or conflicting primary locations, unsafe primary paths, and invalid sides reject the entire structured generation. Once the primary location is snapshot-bound, an inverted range (endLine < startLine), an invalid supplemental path, or an invalid blob OID rejects the generation. Consumers then fall back to the receipt and prose; they do not repair or truncate invalid anchor evidence.

side identifies the snapshot that owns the line. baseSha and headSha bind the anchor to the reviewed comparison. originalPath preserves the base-side name across a rename, and optional blobOid binds the location to a Git object. When a finding carries into a new snapshot without fresh location evidence, the finding stays but the stale anchor is removed.

No anchor does not mean no finding.

Current status comes from one unambiguous receipt lineage tip:

  1. Validate each candidate and filter to the requested reviewKind and backend.
  2. Require unique sourceReceiptId values.
  3. Require every supersedesReceiptId parent to exist in the same lineage with a round exactly one lower.
  4. Select unsuperseded tips whose headSha equals the current review head. Exactly one must remain.
  5. Walk that tip’s ancestor chain and reject cycles, incomplete snapshots, duplicate replacement ownership, duplicate finding identities, or invalid first-seen references.

Only the selected chain receives the step 5 finding-lineage checks; a semantically incomplete stale sibling does not invalidate it. That tip’s item status is current. A receipt bound to another head remains stale evidence, but cannot supply current resolution, approval, or ship state. Zero or multiple head-matching tips means “no unambiguous current structured findings”, not “no findings”.

The receipt verdict remains the workflow gate. Structured findings explain the finding stream; they do not grant SHIP independently.

SurfaceLimit
Reviewer source input1 MiB UTF-8
Encoded findings object256 KiB UTF-8
Items200
rIds per item32, unique
IDs, backend, and review-kind strings160 characters
baseSha and headSha160 characters
Anchor paths1,024 characters
Title240 characters
Body4,000 characters
Suggestion4,000 characters

R-IDs use R<digits>. IDs and ordinals are unique. round and ordinal are positive JSON integers - booleans do not qualify - and a root generation without supersedesReceiptId must use round 1. Anchor paths are normalized, repository-relative, and cannot traverse with ... Line ranges use positive integers. On a snapshot-bound anchor, endLine must be greater than or equal to startLine; an unbound anchor candidate is omitted before that ordering validation. Optional blobOid is 7-64 lowercase hexadecimal characters.

These are rejection boundaries, not truncation targets.

  • No findings field: use the receipt verdict and original prose.
  • Valid, supported, head-current v1: structured rendering is safe.
  • Valid but stale: label and retain it as stale evidence.
  • Unsupported version, invalid data, or ambiguous lineage: ignore the structured projection and keep the receipt plus prose.
  • Explicit empty items: the parser recognized a no-findings SHIP response for that generation. This is different from missing or rejected data.

Consumers never merge a stale or invalid object with prose or another generation to manufacture one apparently current record. Unsupported structured data is never a pass.

Receipt lineage owns finding currentness. Bug memory owns reusable lessons. After a non-trivial NEEDS_WORK → SHIP fix cycle, Work may create a memory entry from the finding and fix. That entry does not supersede the receipt and its memory status (active, stale, or hardened) says nothing about whether the finding is open or fixed.

Keep both records: receipts for review evidence, memory for recurrence prevention.

Completion-review receipts (type: completion_review only) may carry a second additive field beside findings: per-criterion compliance with the project’s standing criteria in .flow/criteria.md (see Standing Criteria).

"criteria": [
{"id": "G1", "status": "met"},
{"id": "G3", "status": "violated", "note": "route added without contract regen"},
{"id": "G4", "status": "n/a"}
]
  • id is a G-ID (G<digits>, unique within the array), status is exactly one of met / violated / n/a, and note is an optional one-liner up to 400 characters.
  • The field is projected deterministically from the reviewer’s ## Global criteria output section, at the same public boundary as the findings parser. Unparseable, duplicate-id, or oversized content degrades to absent, never to an error or a wrong verdict. Recorded ids must match the configured criteria before the array attaches. Legacy receipts without the field stay valid.
  • The criteria array is authoritative for compliance status; findings carry the detail, since every violated criterion is also reported as a normal finding at reviewer-judged severity. No cross-validation links the two, so a consumer renders compliance from criteria and detail from findings independently.
  • When .flow/criteria.md is absent, the review prompt contains no criteria content and receipts carry no criteria field.