# Audit report

> Audit the Python and HTML code under viewer at commit 288a03eda34957e23fd6ab19c2581ce984ba2a1d. Focus on build-lane.py, settlement-viewer-2026-11-09.html and test_lane_disagree.py: whether a row can ever show green unless the receipt's job, seat, DID and request_id exactly match the ledger row, and whether the identity-mismatch or disagree labels can be hidden or reordered. Require a file/line, impact and runnable reproduction for every finding. Offline tooling, read-only review. The imd-flop and spend-guard folders are outside this audit.

| | |
|---|---|
| Repository | https://github.com/uxp-labs/flop-devnet-lab.git |
| Commit | `288a03eda34957e23fd6ab19c2581ce984ba2a1d` |
| Job | `114b644c-bd95-47b4-8b3d-d5f60e75b5a9` |
| Judged | 2026-11-10 06:04 UTC |
| Findings | 5 medium · 4 low · 4 info |

Four agents audited the code as it is at `288a03e`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Medium: Lane rows are keyed by job name, so a later ledger line for the same job removes an earlier attempt's identity-mismatch, timeout or disagree row

`viewer/build-lane.py:33`

```
            e = json.loads(line); last[e["job"]] = e
```

from_files() collapses ledger.jsonl with last[e['job']] = e. imd-flop keys attempts by request_id (imd-flop/src/ledger.ts:2-3 'The latest line for a request_id wins', latestAttempts at :49) and each run mints a fresh request_id, so running a job name twice, or a later cap refusal for the same job (refused_local, request_id null, run.ts:183), is normal CLI use. The earlier attempt then disappears from the lane completely: its red 'mismatch' chip and IDENTITY flag, its red 'unknown' chip and 'timeout: spend unknown, not retried' flag, or its amber DISAGREE label, plus the reservation it still holds. This is a way to hide exactly the labels the legend says are always shown. The receipt for the earlier request_id is still in the directory but nothing looks it up. Reported by all four specialists (math, flow, permissions, economics); merged here. Impact: a holder sees one clean or 'empty' row for a job whose earlier attempt has a foreign receipt or unknown spend that is still charged against the daily cap. Fix: key rows by request_id (keep each refused_local line, which has no request_id, as its own row) and render every attempt, or at least carry bad/flags forward from superseded unresolved attempts.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Case 1: ledger [LED], receipt req-1.json = REC with job 'OTHER', did 'did:key:other', state 'settled', settled 0.05, chain_final true -> 1 row, IDENTITY flag present (correct). Append a second ledger line dict(LED, request_id=null, state='refused_local', cap_charge=0, quote_padded=0.9, reason='over cap', ts='2026-11-10T00:01:00.000Z'). Expected: the req-1 mismatch row is still shown. Actual (checked): rows=1, chips None (the 'empty' chip), 'receipt identity mismatch' absent from the HTML, 'req-1' appears nowhere. Case 2: ledger [dict(LED,state='timeout_unknown',reason='TimeoutError'), dict(LED,request_id='req-2',state='estimated',spent_before=0.198)], receipt req-2.json = REC with request_id 'req-2'. Expected: a red 'unknown' row with the timeout flag for req-1. Actual: rows=1, 'timeout' not in the HTML, bad False.

### 2. Medium: Link check column is hard-coded green 'linked' with no ledger evidence, so identity-mismatch rows and --no-link runs show a green chip

`viewer/build-lane.py:45`

```
                   outcome=["grey", "not read", "mock job, no imd lookup"], link=["green", "linked", None], bad=False)
```

Every row starts with link=['green','linked',None]; the only downgrade, line 46, reads e.get('link_bypassed'), a key LedgerEntry (imd-flop/src/ledger.ts) does not have. link_bypassed exists only on the in-memory RunResult (run.ts:51, :298) and is never written by appendLedger, and none of the committed mock ledger lines has a link key. So the grey branch never runs, and the chip claims a link verification the viewer never saw. The mismatch branch (lines 60-69) leaves link alone, so a row with a red 'mismatch' chip also shows a green chip, which contradicts the legend generated at line 117 ('that row can never go green'). A mock run made with --no-link, which the CLI itself prints as LINK BYPASSED, also shows as green 'linked'. The CLI matcher treats link_missing, link_invalid and seat/did(link) as mismatches (match.ts:38-), but the viewer ignores link state entirely. test_lane_disagree.py:75-76 and :111-112 assert this green chip on a mismatch row, so the tests lock the behaviour in. Merged from math, flow, permissions and economics. Fix: default the chip to grey 'not checked'; show green only from a recorded link-verification field (the CLI would need to write one to the ledger), and never green on a bad row.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Ledger [LED]; receipt req-1.json = REC with job 'OTHER', did 'did:key:other', state 'settled', settled 0.05, chain_final true. Expected: no green chip on this mismatch row. Actual (checked): rows[0]['bad'] is True, rows[0]['link'] == ['green','linked',None], and re.findall(r'c-green">([^<]+)', html) == ['linked']. Also: every line of viewer/mock-receipts/ledger.jsonl has no key containing 'link', so the line-46 branch can never trigger from a CLI-written ledger.

### 3. Medium: Ledger and receipt fields go into the page unescaped; a crafted request_id, seat or receipt 'settled' value hides every later identity-mismatch and disagree label

`viewer/build-lane.py:41`

```
        sub = [f"req {short(e['request_id'] or 'none', 14)}", f"seat #{e['seat']} · node {BLANK_NODE}",
```

Only job (line 43) and reason (lines 52/56) pass through html.escape. request_id (via short(), which keeps 14 characters, enough for '<style>' or '<!--x'), seat, provider, network (lines 41-42), payer (line 95) and any non-numeric flop value (line 99, which includes the receipt's 'settled' from line 89) are inserted raw. Line 37 says receipt contents are untrusted. A request_id or seat of '<!--' opens a comment that closes only at '<!-- lane:end -->', so every later row and its red 'mismatch' chip and IDENTITY flag drop out of the rendered page. A request_id of '<style>' does the same: the lane never emits a closing '</style>'. A receipt whose settled field is '<style>.flag,.c-red{display:none}</style>' hides every flag and red chip on the page. The CLI writes request_id itself and normalises settled to number|null (receipts.ts:61), so triggering this needs a hand-edited, older or foreign ledger or receipt. The viewer accepts any directory as argv[1], and validating those files is the job it claims to do. Merged from math (low), flow (high), permissions (low) and economics (high + medium). Rated medium because it needs files the CLI would not write. Fix: html.escape every file-derived string in from_files() and render(), and render any non-numeric value in a numeric slot as BLANK.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Case 1: ledger [dict(LED, request_id='<!--x'), dict(LED, job='k', request_id='req-2')], receipts '<!--x.json' = REC, 'req-2.json' = REC with request_id 'req-2' and job 'OTHER'. Expected: row k shows 'receipt identity mismatch; chain-final green blocked' as visible text. Actual (checked): the HTML contains 'req <!--x' raw. The IDENTITY string is present in the markup but missing from the visible text when the output goes through html.parser (it sits inside the comment). Same result with seat '<!--' on both ledger and receipt, and with request_id '<style>' (no receipt): the IDENTITY flag and the 'mismatch' chip are not visible text. Case 2: ledger [LED, dict(LED, job='k', request_id='req-2')], req-1.json = REC with settled '<style>.flag,.c-red{display:none}</style>', req-2.json foreign as above. Actual: the style element is emitted verbatim inside the 'observed spend' line and hides row k's flag and red chip page-wide.

### 4. Medium: Disagree check only runs for settled/released receipts; a reserved or estimated receipt with chain_final set turns a settled or still-'sent' ledger row green with 'matches'

`viewer/build-lane.py:71`

```
            disagree = receipt_state in ("settled", "released") and (st == "sent" or e.get("spend") != r.get("settled"))
```

disagree is computed only when receipt_state is 'settled' or 'released'. For any other receipt state, the ledger state and spend are never compared. Line 85 then adds the green 'chain final' chip on identity_ok plus a truthy chain_final, and line 88 prints 'match matches'. Observed spend stays blank. Also, when both sides' amounts are null, e.get('spend') != r.get('settled') is None != None, which is False, so a 'settled' receipt with no amount beside an 'estimated' ledger row is not flagged either. The result is a green row although the ledger says the call never completed (state 'sent') or recorded a spend the receipt does not confirm. These are the states DISAGREE ('reservation held, chain-final green blocked') exists for. The CLI sets chain_final only when ctx.chainVerified === true (receipts.ts:64), and nothing verifies it yet, so this path is latent today. It goes live as soon as chain verification is wired in, and for any receipt from another writer. Merged from math, flow, permissions and economics. Fix: allow green only when the receipt state is settled/released, the settled amount is numeric and equal to the ledger spend, and the ledger state is settled/released. Otherwise mark the row DISAGREE.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Case 1: ledger [dict(LED, state='settled', spend=0.198)], receipt REC with state 'reserved', chain_final true. Expected: amber DISAGREE, no green. Actual (checked): chips [['grey','sent'],['amber','gateway observed'],['green','chain final']], 'disagree' not in the HTML, 'matches' rendered. Case 2: ledger [LED] (state 'sent'), receipt REC with chain_final true: the same green chips, no disagree. Case 3: ledger [dict(LED, state='estimated')], receipt REC with state 'settled', settled null, chain_final true: the same green chips, no disagree.

### 5. Medium: identity_ok is not an exact match: seat is compared after str() coercion, and a missing or null DID or job on both sides passes, unlike the CLI's matchReceipt

`viewer/build-lane.py:24`

```
            and r.get("job") == e["job"] and str(r.get("seat")) == str(e["seat"]) and r.get("did") == e["did"])
```

request_id must be a non-empty string (line 23), but job and DID are compared with plain ==, and seat with str(r.get('seat')) == str(e['seat']). So the receipt seat 1234 (a number) matches the ledger seat '1234'. A receipt with no 'did' key, or did null, matches a ledger row whose did is null (LedgerEntry.did is 'string | null', ledger.ts:19). job None matches job None. The CLI's reference predicate (imd-flop/src/match.ts:29-31) compares seat with !== and rejects a falsy receipt DID outright, so the viewer is looser than the CLI it claims to mirror, and test_lane_disagree.py syncs only the IDENTITY string with match.ts, not the predicate. Impact: a row can show 'match matches' and the green 'chain final' chip without job, seat, DID and request_id being present and exactly equal, which is the core property under audit. It needs a receipt whose types differ from what the CLI writes, or a null-DID ledger row. Merged from math (low), flow (medium), permissions (low) and economics (medium). Fix: require job, seat and did to be non-empty strings on both sides and compare them without coercion.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Unit (checked): identity_ok(dict(e, seat=1234), e) -> True, where e = {'job':'j','seat':'1234','did':'did:key:z','request_id':'req-1'}. identity_ok(dict(e, did=None), dict(e, did=None)) -> True. identity_ok(e without the 'did' key, dict(e, did=None)) -> True. identity_ok(dict(e, job=None), dict(e, job=None)) -> True. Expected False for each, per match.ts. Row: ledger [dict(LED, did=null, state='settled', spend=0.198)], receipt REC without the 'did' key, state 'settled', settled 0.198, chain_final true. Actual: chips end with ['green','chain final'], bad False. Same green result with ledger seat '1234' and receipt seat 1234 (a number).

### 6. Low: Green 'chain final' tests chain_final for truthiness, so the string "false" (or any non-empty value) renders green

`viewer/build-lane.py:85`

```
            if ok and r.get("chain_final") and not row["bad"]: chips.append(["green", "chain final"])
```

Line 85 grants green on r.get('chain_final') truthy rather than 'is True', and line 87 uses the same truthiness to suppress the 'not checked yet' slot. The CLI writes a boolean, but a receipt from another writer or an edited file with chain_final 'false', 'pending' or 1 is shown as chain-final. Separate from the disagree finding: this one is about type validation, and the fix is different. Merged from math, flow and economics. Fix: use r.get('chain_final') is True on both lines.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Ledger [dict(LED, state='settled', spend=0.198)], receipt REC with state 'settled', settled 0.198, chain_final "false" (a string). Expected: no green chip, and 'chain final: not checked yet' shown. Actual (checked): chips [['grey','sent'],['amber','gateway observed'],['green','chain final']], and 'not checked yet' does not appear.

### 7. Low: Unknown spent_before is coerced to 0, so 'daily cap left' shows the whole daily cap on refusals made because today's spend is unknown

`viewer/build-lane.py:47`

```
        spent = (e.get("spent_before") or 0) + (e.get("cap_charge") or 0)
```

(e.get('spent_before') or 0) turns a null prior spend into 0. The CLI writes spent_before null on real refusal paths: refuse(..., null) when the estimate fails (run.ts:192, :194) and when spentToday throws UnusableLedgerCharge (run.ts:202), i.e. exactly when today's spend cannot be established. The lane then prints 'daily cap left 0.5', the full cap, while the module docstring (line 6) and the legend promise that unknown spend is a blank slot, never 0. Merged from math (medium) and economics (low). Fix: set daily_left to None when spent_before or cap_charge is None.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Ledger [dict(LED, request_id=null, state='refused_local', spent_before=null, cap_charge=0, reason='ledger charge unusable')], no receipts. Expected: daily cap left rendered as BLANK. Actual (checked): rows[0]['daily_left'] == 0.5, and the HTML contains 'daily cap left</span> <span class="mono">0.5'.

### 8. Low: Ledger states timeout_unknown and error are dropped once any receipt file exists for the request_id

`viewer/build-lane.py:53`

```
        elif r is None:
```

The red 'unknown' chip, the timeout flag and bad=True are set only in the 'elif r is None' branch. If a receipt named after the request_id exists, the ledger state is not consulted again except for st == 'sent' inside the disagree test. A timeout_unknown or error row beside a non-settled receipt renders grey 'sent' plus amber 'gateway observed', bad False, and the ledger's reason never appears. The page Notes ('A timeout or 404 shows as unknown and gets flagged') do not hold. With the current CLI, 'receipt --refresh' also rewrites the ledger state, so this needs an inconsistent receipts directory. Reported by permissions. Fix: when st is timeout_unknown or error, keep the red chip, the flag and bad=True regardless of the receipt, and treat a non-settled receipt beside it as DISAGREE.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Ledger [dict(LED, state='timeout_unknown', reason='TimeoutError: no answer; spend unknown, not retried')], receipt REC. Expected: red 'unknown' chip, timeout flag, bad True. Actual (checked): chips [['grey','sent'],['amber','gateway observed']], bad False, no 'timeout' text. With state 'error' and reason 'HTTP 500': the same chips, bad False, 'HTTP 500' absent.

### 9. Low: One malformed receipt or ledger value aborts the whole rebuild and leaves the previously committed lane in the HTML

`viewer/build-lane.py:36`

```
        r = json.load(open(f))
```

Every *.json in the receipts dir is loaded and used as a dict, and ledger values are used without validation (e['daily_cap'] arithmetic at line 48, e['ts'] parsing, e['seat'] indexing). Any receipt that is not an object, or a non-numeric daily_cap, raises before main() writes VIEWER (line 127). The script exits with a traceback and the viewer keeps its old lane, which may show a state, such as no mismatch, that the current files no longer support. Reported by flow. Fix: validate per file and per line, and render a red 'unreadable' row for the bad item instead of aborting.

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Run main() with ns['VIEWER'] pointed at a temp copy of settlement-viewer-2026-11-09.html. Case A: ledger [LED], req-1.json = [1, 2] -> AttributeError: 'list' object has no attribute 'get', copy unchanged (checked). Case B: ledger [dict(LED, daily_cap='0.5')], req-1.json = REC -> TypeError: unsupported operand type(s) for -: 'str' and 'float', copy unchanged (checked). Expected: the lane is rebuilt with the bad item flagged.

### 10. Info: Request id label truncated to 14 characters makes distinct attempts indistinguishable

`viewer/build-lane.py:20`

```
def short(s, n=10): return s if len(s) <= n + 2 else s[:n] + "…"
```

short(id, 14) keeps 'mock-1234-' plus 4 hex characters for mock ids and no random characters at all for a 7-digit seat ('imdflop-1234567-...'). Together with the job-keyed collapse, a reader cannot tell which attempt a row describes. Fix: show the tail of the id, or the full id in a title attribute (escaped).

**Reproduction**

Checked: short('mock-1234-5614aaaaaaaaaa',14), short('mock-1234-5614bbbbbbbbbb',14) and short('imdflop-1234567-0123456789abcdef',14) give 'mock-1234-5614…', 'mock-1234-5614…' and 'imdflop-123456…'. Expected: distinct labels for distinct ids.

### 11. Info: Identity-mismatch rows do not say which field differed or show the receipt's values

`viewer/build-lane.py:63`

```
                row["flags"].append("receipt's request_id, job, seat or DID doesn't match this row")
```

On mismatch the row prints only the ledger side and a generic 'request_id, job, seat or DID' line. The receipt's job, seat and DID are never shown, whereas match.ts reports the failing fields and the hand-written example row shows 'receipt job ... (row says ...)'. This does not change the red/green decision, but it weakens the row as audit evidence. Reported by flow. Fix: list the differing field names and the receipt values (escaped).

**Reproduction**

Reproduce with python3 -I: exec viewer/build-lane.py into a namespace with sys.argv=['b', D] (as test_lane_disagree.load_lane does), where D is a fresh temp dir holding ledger.jsonl and <request_id>.json receipts, then call from_files() and render() on each row. Base ledger line LED = {"job":"j","request_id":"req-1","seat":"1234","did":"did:key:z","provider":"mock","network":"devnet","ts":"2026-11-10T00:00:00.000Z","payer":"holder","quote_padded":0.198,"cap_charge":0.198,"per_call_cap":0.25,"daily_cap":0.5,"spent_before":0,"state":"sent","spend":null,"reason":null}; base receipt REC = {"request_id":"req-1","job":"j","seat":"1234","did":"did:key:z","state":"estimated","settled":null,"stage":"gateway observed","chain_final":false,"flag":null}. Ledger [LED], receipt REC with job 'SOMEONE-ELSE' and did 'did:key:zOTHER'. Expected: the row names job and did as the mismatching fields. Actual: neither 'SOMEONE-ELSE' nor 'did:key:zOTHER' appears in the rendered row, only the generic flag lines.

### 12. Info: Test suite asserts a green 'linked' chip on mismatch rows and never covers the green path, single-field mismatches, re-runs, escaping or null spend

`viewer/test_lane_disagree.py:75`

```
            self.assertEqual(html.count('class="chip c-green"'), 1)
```

Lines 75-76 and 111-112 require exactly one green chip ('linked') on an identity-mismatch row, which locks in the link-column defect, and these tests will fail once it is fixed. test_identity_check_compares_request_id varies only request_id. No test covers: a matching settled receipt with chain_final true (the only legitimate green path), a non-settled receipt with chain_final true, a non-boolean chain_final, seat type differences or null DID/job, two ledger lines for one job, markup in any field, a timeout row with a receipt, or a null spent_before. The suite passes (7 tests, checked) while every reproduction above shows wrong output. Merged from all four specialists. Fix: assert no c-green chip on any bad row, and add one failing test per finding above.

**Reproduction**

python3 -I viewer/test_lane_disagree.py -> OK (7 tests). Adding self.assertFalse(ns['identity_ok']({'job':'j','seat':1,'did':'d','request_id':'req-1'}, e)) to test_identity_check_compares_request_id fails on this code (identity_ok returns True). A two-line ledger for job 'j' (req-1 timeout_unknown, req-2 estimated) with assertEqual(len(rows), 2) fails (1 row).

### 13. Info: Reference 'Example mismatch' row still uses the retired flag wording instead of the shared IDENTITY string

`viewer/lane-examples.json:19`

```
   "chips":[["grey","sent"],["red","mismatch"]],"flop":[["receipt job","ex-other… (row says ex-mismatch…)"],["observed spend",null]],"flags":["receipt doesn't match this row, never green"],
```

The example row's flag reads 'receipt doesn't match this row, never green', not IDENTITY ('receipt identity mismatch; chain-final green blocked'), which the legend and real rows use and test_legend_uses_shared_string_and_names_request_id enforces for the legend only. The reference row teaches a label that real rows never show. Reported by math. Fix: use the IDENTITY string in the example row.

**Reproduction**

Run build-lane.py on a temp copy of the viewer, or read the committed settlement-viewer-2026-11-09.html line 152: the Example mismatch row contains 'receipt doesn't match this row, never green' and not 'receipt identity mismatch; chain-final green blocked'.

---

Judge's submission `dfe45254dbf8bf5ba3a99b575647151241bce274a34866384cad2cef325170df`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
