# Receipts

Source: https://flow-next.dev/reference/receipts/

Proof-of-work artifacts, including the portable structured-findings contract.

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.

```bash
.flow/review-receipts/<branch>.json
```

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

## How to read a receipt

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.

## Verdicts

| Verdict         | Meaning                                                                                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SHIP`          | No blocking introduced findings remain.                                                                                                                 |
| `NEEDS_WORK`    | Fixable issues block handoff.                                                                                                                           |
| `MAJOR_RETHINK` | The plan or approach is wrong enough to revisit the spec or architecture.                                                                               |
| `NEEDS_HUMAN`   | A 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.

## QA receipt

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:

| Row                                          | What 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](https://flow-next.dev/skills/qa/#re-check) decides which scenarios carry.

## Why receipts matter

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.

```mermaid
flowchart LR
  Diff["Diff"] --> Review["Review backend"]
  Review --> Receipt["Receipt JSON"]
  Receipt --> Auto["flow --auto hop check"]
  Receipt --> PR["PR briefing"]
```

## Receipt hygiene

* 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.

## Green receipts

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

```bash
.flow/tmp/green-receipts/<sha8>-<gate_id>.json
```

| Field        | Meaning                                    |
| ------------ | ------------------------------------------ |
| `head_sha`   | the exact commit the command ran against   |
| `command`    | a sha256 fingerprint of the command string |
| honored when | hash, path, and age all match              |
| otherwise    | the 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.

## Structured findings

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.

```json
{
  "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 `type`      | Required `findings.reviewKind` |
| ------------------- | ------------------------------ |
| `plan_review`       | `plan`                         |
| `impl_review`       | `implementation`               |
| `completion_review` | `completion`                   |
| `qa_verdict`        | `qa`                           |

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.

### Canonical values

| Field            | Stored values                                | Accepted reviewer aliases                                                                           |
| ---------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `reviewKind`     | `plan`, `implementation`, `completion`, `qa` | none                                                                                                |
| `severity`       | `P0`, `P1`, `P2`, `P3`                       | `Critical` → `P0`; `Major` → `P1`; `Minor` → `P2`; `Nitpick` → `P3`                                 |
| `confidence`     | `0`, `25`, `50`, `75`, `100`                 | none                                                                                                |
| `classification` | `introduced`, `pre_existing`                 | `pre-existing` / `pre existing` → `pre_existing`                                                    |
| `status`         | `open`, `fixed`, `not_fixed`, `withdrawn`    | `fixed in review` / `resolved` → `fixed`; `not fixed` / `remains open` / `unresolved` → `not_fixed` |
| `anchor.side`    | `base`, `head`                               | none                                                                                                |

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.

### Finding identity and review lineage

`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:

```text
<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.

### Anchors

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.

### Currentness

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.

### Bounds

| Surface                               |            Limit |
| ------------------------------------- | ---------------: |
| Reviewer source input                 |      1 MiB UTF-8 |
| Encoded `findings` object             |    256 KiB UTF-8 |
| Items                                 |              200 |
| `rIds` per item                       |       32, unique |
| IDs, backend, and review-kind strings |   160 characters |
| `baseSha` and `headSha`               |   160 characters |
| Anchor paths                          | 1,024 characters |
| Title                                 |   240 characters |
| Body                                  | 4,000 characters |
| Suggestion                            | 4,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.

### Fallback contract

* 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.

### Memory is a different record

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.

## Standing-criteria compliance

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](https://flow-next.dev/reference/standing-criteria/)).

```json
"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.
