| 1 | # Runtime Receipts |
| 2 | |
| 3 | This document sketches a future read-only receipt export for completed runtime |
| 4 | turns. It is a protocol note, not an implemented endpoint. |
| 5 | |
| 6 | The goal is to let a local supervisor audit one completed turn without |
| 7 | screen-scraping the terminal transcript. A receipt should summarize the durable |
| 8 | runtime records that Codewhale already owns: thread metadata, turn status, turn |
| 9 | items, event sequence lineage, usage when available, approval decisions, and |
| 10 | side-effect boundaries. |
| 11 | |
| 12 | ## Non-Goals |
| 13 | |
| 14 | A receipt is not a safety certification, provider compatibility certification, |
| 15 | or hosted attestation. It must not call providers, execute tools, write memory, |
| 16 | write project files, mutate runtime state, or expose API keys. |
| 17 | |
| 18 | Receipts should not export raw chain-of-thought or private reasoning by default. |
| 19 | When reasoning custody is represented, use stable item ids, counts, hashes, or |
| 20 | explicit `unavailable` fields rather than raw hidden content. |
| 21 | |
| 22 | ## Candidate Surfaces |
| 23 | |
| 24 | Potential local-only surfaces: |
| 25 | |
| 26 | ```text |
| 27 | codewhale receipt export --thread <thread_id> --turn <turn_id> --format json |
| 28 | GET /v1/threads/{thread_id}/turns/{turn_id}/receipt |
| 29 | ``` |
| 30 | |
| 31 | Both surfaces should share the existing runtime API auth boundary. They should |
| 32 | only read persisted runtime records and append-only events. |
| 33 | |
| 34 | ## Review Receipts |
| 35 | |
| 36 | `codewhale review --write-receipt` writes a local JSON receipt for the reviewed |
| 37 | diff under the Codewhale state directory (`review-receipts/`) unless |
| 38 | `--receipt-path <path>` is provided. This is a pre-push handoff artifact: it |
| 39 | records what diff was reviewed and what the review reported, without pushing, |
| 40 | tagging, opening a PR, or claiming to replace maintainer review. |
| 41 | |
| 42 | The current receipt includes: |
| 43 | |
| 44 | - `diff_fingerprint`: SHA-256 of the reviewed diff. |
| 45 | - `provider` and `model`: the routed review provider/model. |
| 46 | - `checks_run`: local checks attached to the receipt when available. Empty |
| 47 | means no checks were attached; attached checks must report a passing status. |
| 48 | - `findings`: structured issue/suggestion counts and issue locations when the |
| 49 | review output is structured. |
| 50 | - `unresolved_risk`: a conservative summary derived from unresolved findings. |
| 51 | - `review_content_sha256`: SHA-256 of the review text. |
| 52 | |
| 53 | The receipt deliberately does not include the raw diff body. Re-run |
| 54 | `codewhale review --write-receipt` after changing the diff; reviewers should |
| 55 | compare the `diff_fingerprint` before reusing a receipt in a PR handoff. |
| 56 | |
| 57 | `codewhale review --check-receipt` is the local pre-push gate. It does not call |
| 58 | a model; it compares the current diff fingerprint with a supplied receipt |
| 59 | (`--receipt-path <path>`) or the latest matching local receipt. The check exits |
| 60 | nonzero when the diff no longer matches, the receipt schema is unsupported, the |
| 61 | receipt has unresolved risk, or an attached check did not pass. |
| 62 | |
| 63 | ## Current Data Sources |
| 64 | |
| 65 | The current runtime store already persists the core inputs a receipt builder |
| 66 | would need: |
| 67 | |
| 68 | - `ThreadRecord`: model, workspace, mode, shell/trust/auto-approve flags, |
| 69 | title, task linkage, and latest turn metadata. |
| 70 | - `TurnRecord`: turn status, input summary, timestamps, duration, usage, error, |
| 71 | steer count, and item ids. |
| 72 | - `TurnItemRecord`: item kind, lifecycle status, summary, optional detail, |
| 73 | metadata, artifact refs, and item timestamps. |
| 74 | - `RuntimeEventRecord`: thread id, turn id, item id, event name, JSON payload, |
| 75 | timestamp, and monotonic `seq` values per runtime store. |
| 76 | |
| 77 | Not every receipt field can be filled from those records today. If a provider or |
| 78 | store does not persist a value, the receipt should say `available: false` or |
| 79 | `unavailable`, not infer it from UI text. |
| 80 | |
| 81 | ## Draft Schema Shape |
| 82 | |
| 83 | ```json |
| 84 | { |
| 85 | "schema_id": "codewhale.conformance-receipt/v0", |
| 86 | "thread": { |
| 87 | "id": "thr_...", |
| 88 | "model": "deepseek-v4-pro", |
| 89 | "mode": "agent", |
| 90 | "auto_approve": false, |
| 91 | "trust_mode": false, |
| 92 | "allow_shell": false |
| 93 | }, |
| 94 | "turn": { |
| 95 | "id": "turn_...", |
| 96 | "status": "completed", |
| 97 | "started_at": "2026-06-02T01:00:00Z", |
| 98 | "ended_at": "2026-06-02T01:00:12Z", |
| 99 | "duration_ms": 12000 |
| 100 | }, |
| 101 | "reasoning_custody": { |
| 102 | "raw_reasoning_exported": false, |
| 103 | "available": false, |
| 104 | "reason": "reasoning blocks are not persisted as receipt-ready records" |
| 105 | }, |
| 106 | "tool_lineage": { |
| 107 | "tool_call_count": 1, |
| 108 | "tool_result_count": 1, |
| 109 | "unmatched_tool_call_ids": [], |
| 110 | "unmatched_tool_result_ids": [] |
| 111 | }, |
| 112 | "usage_evidence": { |
| 113 | "available": true, |
| 114 | "usage": { |
| 115 | "prompt_tokens": 123, |
| 116 | "completion_tokens": 45 |
| 117 | }, |
| 118 | "provider_cache_breakdown_available": false |
| 119 | }, |
| 120 | "source_event_lineage": { |
| 121 | "first_seq": 10, |
| 122 | "last_seq": 42, |
| 123 | "event_count": 33, |
| 124 | "missing_event_ranges": [] |
| 125 | }, |
| 126 | "side_effect_boundary": { |
| 127 | "approval_required_count": 1, |
| 128 | "approval_allowed_count": 0, |
| 129 | "approval_denied_count": 1, |
| 130 | "command_execution_count": 0, |
| 131 | "file_change_count": 0, |
| 132 | "sandbox_denied_count": 0 |
| 133 | }, |
| 134 | "claim_ceiling": [ |
| 135 | "local_receipt_only", |
| 136 | "not_safety_certification", |
| 137 | "not_provider_compatibility_certification" |
| 138 | ] |
| 139 | } |
| 140 | ``` |
| 141 | |
| 142 | ## Builder Rules |
| 143 | |
| 144 | A receipt builder should be deterministic and conservative: |
| 145 | |
| 146 | 1. Load the thread and turn by id, then reject mismatched `thread_id` values. |
| 147 | 2. Load only item ids referenced by the turn. |
| 148 | 3. Read event records for the thread and filter by `turn_id`. |
| 149 | 4. Preserve event sequence boundaries with `first_seq`, `last_seq`, and any |
| 150 | detected gaps. |
| 151 | 5. Count approval, command, file, sandbox, and tool events from typed records or |
| 152 | known event names only. |
| 153 | 6. Mark unavailable evidence explicitly instead of deriving it from free-form |
| 154 | summaries. |
| 155 | 7. Emit no raw tool output beyond existing item summaries unless a later schema |
| 156 | adds a separate redaction policy. |
| 157 | |
| 158 | ## Incremental Implementation Path |
| 159 | |
| 160 | The safest implementation path is: |
| 161 | |
| 162 | 1. Land this protocol note and settle field names/non-goals. |
| 163 | 2. Add protocol structs and JSON snapshot fixtures for completed, failed, and |
| 164 | approval-denied turns. |
| 165 | 3. Add a pure builder over `ThreadRecord`, `TurnRecord`, `TurnItemRecord`, and |
| 166 | `RuntimeEventRecord`. |
| 167 | 4. Expose the local runtime API endpoint. |
| 168 | 5. Add the CLI export command and optional validation mode. |
| 169 |