diff --git a/.run/backlog.jsonl b/.run/backlog.jsonl
index 5b601869f1..8e9bd49811 100644
--- a/.run/backlog.jsonl
+++ b/.run/backlog.jsonl
@@ -1286,3 +1286,5 @@
{"ts": "2026-07-26 21:04:59", "addr": null, "name": "func_8013DD68", "reach": null, "klass": null, "nins": null, "status": "failed", "closeness": null, "where_stuck": "won't compile standalone (loose-typing / missing decl)", "best_draft": ".run/backlog_drafts/func_8013DD68.c", "binary": "ov_SC07_006", "source": "worker", "residual": null, "passes_tried": null}
{"ts": "2026-07-27 22:33:49", "addr": "0x80186b08", "name": "func_80186B08", "reach": null, "klass": null, "nins": null, "status": "near", "closeness": 43, "where_stuck": "residual: 43 mismatch", "best_draft": ".run/backlog_drafts/func_80186B08.c", "binary": "ov_SC02_005", "source": "grinder", "residual": [[0, "27bdffe8 addiu\tsp,sp,-24", "84830070 lh $v1, 0x70($a0)"], [1, "afbf0010 sw\tra,16(sp)", "00000000 nop"], [2, "00002821 move\ta1,zero", "2862000e slti $v0, $v1, 0xE"], [3, "3c070000 lui\ta3,0x0", "10400026 beqz $v0, .L80186BB0"], [4, "24e70000 addiu\ta3,a3,0", "00031040 sll $v0, $v1, 1"], [6, "24060014 li\ta2,20", "00220821 addu $at, $at, $v0"], [7, "8fbf0010 lw\tra,16(sp)", "94245b4c lhu $a0, %lo(D_80195B4C)($at)"], [8, "27bd0018 addiu\tsp,sp,24", "00000000 nop"], [9, "03e00008 jr\tra", "2c820016 sltiu $v0, $a0, 0x16"], [10, "00000000 nop", "14400005 bnez $v0, .L80186B48"], [11, "--", "00000000 nop"], [12, "--", "3c068019 lui $a2, %hi(D_801961E0)"], [13, "--", "24c661e0 addiu $a2, $a2, %lo(D_801961E0)"], [14, "--", "08061ad4 j .L80186B50"], [15, "--", "2484ffea addiu $a0, $a0, -0x16"], [16, "--", "3c068019 lui $a2, %hi(D_801961B8)"], [17, "--", "24c661b8 addiu $a2, $a2, %lo(D_801961B8)"], [18, "--", "00001821 addu $v1, $zero, $zero"], [19, "--", "3087ffff andi $a3, $a0, 0xFFFF"], [20, "--", "00c02021 addu $a0, $a2, $zero"], [21, "--", "84820006 lh $v0, 0x6($a0)"], [22, "--", "00000000 nop"], [23, "--", "1447000e bne $v0, $a3, .L80186BA0"], [24, "--", "000328c0 sll $a1, $v1, 3"]], "passes_tried": null}
{"ts": "2026-07-31 14:42:33", "addr": "0x8013B83C", "name": "func_8013B83C", "reach": 138, "klass": "CC1-FAIL-UNREAD", "nins": 272, "status": "blocked", "closeness": null, "where_stuck": "S28 wave: rtu_match --o0 MATCH (adversarially re-verified), whole-binary gate CC1-FAIL. The actual cc1 diagnostic has NOT been read yet - do that first (SS125 rule 1), do not infer. Worth 138.", "best_draft": ".run/s28w/func_8013B83C.c", "binary": null, "source": "ov_SC01_077", "residual": null, "passes_tried": null}
+{"ts": "2026-07-31 18:46:12", "addr": "0x8013B83C", "name": "func_8013B83C", "reach": 138, "klass": "JR-PAIR-IN-ONE-O0-OBJECT", "nins": 272, "status": "blocked", "closeness": null, "where_stuck": "S28 CORRECTION x2 (supersedes CC1-FAIL-UNREAD, which was WRONG - it has no compile error). Body is byte-correct: match_one --o0 AND rtu_match --o0 both MATCH (272 ins). It is a jr/switch fn (jtbl_801D8254, 13 cases) sharing the -O0 object ov_SC01_077_o0 with func_8013BD74 (jtbl_801D828C). Carving BOTH tables together passes an INCREMENTAL build but the CLEAN build FAILS TO LINK: \"undefined reference to $L105\" (local label from the C-emitted jtbl) + \"undefined reference to func_8013C938\" (a PREVIOUSLY-MATCHED cluster fn going undefined). Blocker is INTEGRATION of two jr fns in one -O0 object, not codegen.", "best_draft": ".run/s28w/func_8013B83C.c", "binary": null, "source": "ov_SC01_077", "residual": null, "passes_tried": null}
+{"ts": "2026-07-31 18:46:14", "addr": "0x8013BD74", "name": "func_8013BD74", "reach": 138, "klass": "JR-PAIR-IN-ONE-O0-OBJECT", "nins": 198, "status": "blocked", "closeness": null, "where_stuck": "S28 CORRECTION (supersedes JTBL-PAD-SPEC-DRIFT, which was a MIS-DIAGNOSIS: I had carved one fn of a TWO-table span; carving both gives pad spec [0,4,4] and the filter is satisfied). Real blocker = same as func_8013B83C: two jr fns in one -O0 object link-fail on a CLEAN build ($L105 + func_8013C938 undefined) while passing an INCREMENTAL one. Body byte-correct (rtu_match --o0 MATCH, 198 ins).", "best_draft": ".run/s28w/func_8013BD74.c", "binary": null, "source": "ov_SC01_077", "residual": null, "passes_tried": null}
diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md
index 654e5f401e..9652544e7d 100644
--- a/docs/cookbook-index.md
+++ b/docs/cookbook-index.md
@@ -2,7 +2,7 @@
> **Generated by `tools/cookbook_index.py` — do not hand-edit** (R33). Regenerate after adding a cookbook section.
>
-> `docs/matching-cookbook.md` is ~716 KB / 348 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses.
+> `docs/matching-cookbook.md` is ~716 KB / 350 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses.
**How to use:** name what you SEE in the diff (a stolen delay slot, an extra `la`, a swapped register pair, a `conflicting types` error), find that symptom below, read those sections first. If nothing fits, THEN grind — and add a section when you win.
@@ -219,7 +219,7 @@
- **§3-Do** — NOT "strip the duplicate typedef" — it breaks the extern that uses it L8030
- **§121** — Synthesise externs for macro-DEFINED callees from the macro's own definition head (Phase 29 T95) L8054
-### jump tables & switches (21)
+### jump tables & switches (22)
- **§8** — rodata island (compiler jump tables) — the `.data→.rodata→.data` sandwich (Phase 7) L320
- **§8a** — rodata island in a flat OVERLAY — the tail sandwich, per matched jr-function (Phase 26 — PoC PROVEN) L342
@@ -242,6 +242,7 @@
- **§129** — Post-carve, `rtu_match`/`match_one` COUNT THE JUMP TABLE AS INSTRUCTIONS; and a carve must never be committed without its owner (P30 S28, `func_8013BD74`) L8430
- **§129a** — the target instruction count is INFLATED after a carve L8434
- **§129b** — never commit a carve whose owner is still a stub (it strands the carve) L8455
+- **§130** — An INCREMENTAL build can report BYTE-IDENTICAL for a change the CLEAN build cannot even LINK (P30 S28, the jr pair) L8479
### optimisation level (-O0/-O2) (11)
@@ -360,7 +361,7 @@
- **§112** — A macro-scoped declaration only collides where the macro is INSTANTIATED (Phase 29 T67/T69, `audit_header_sigs.py`) L7723
- **§114** — The THIRD decl axis: a CALLEE the draft declares differently from the target TU (Phase 29 T76/T77) L7801
-### build graph, splat & the harness (86)
+### build graph, splat & the harness (87)
- **§4** — Flag/toolchain gotchas L190
- **Build** — mechanism — per-file opt override (splat resegmentation) L288
@@ -448,6 +449,7 @@
- **§122** — GATE RAW BEFORE TRANSFORMING; the undo belongs to the WRITER, as a per-edit journal (P30 T0a, 2026-07-30) L8077
- **§123** — PROPAGATE A FAMILY WITH THE TOOL ITS TIER NEEDS: `dedup_propagate` is h_exact-only; its refusals are statements about the TOOL (P30 wave 1, 2026-07-30) L8109
- **§126a** — a bare `except: continue` around a coverage-asserting oracle re-creates the silent skip (P30 S28) L8310
+- **§130** — An INCREMENTAL build can report BYTE-IDENTICAL for a change the CLEAN build cannot even LINK (P30 S28, the jr pair) L8479
### process, measurement & doctrine (47)
@@ -499,7 +501,7 @@
- **§125** — Split the CARVE from the BODY before calling a jr residue a wall — and measure it by SHA from a CLEAN tree (P30 SESSION-28; **this section's first draft was WRONG and the method caught it**) L8199
- **§3-The** — meta-lesson L8250
-### (unbucketed — title matched no symptom vocabulary) (93)
+### (unbucketed — title matched no symptom vocabulary) (94)
- **§3-How** — to use this L30
- **§1** — Idiom catalog (asm pattern → C that produces it) L39
@@ -594,6 +596,7 @@
- **§128** — A raw NUL in C source makes grep SILENTLY SKIP the file (P30 S28, 137 files) L8386
- **§128a** — a negative control must corrupt a SCRATCH COPY, never the tracked file L8418
- **§3-The** — real blocker underneath, for the record L8470
+- **§3-The** — diagnostic ladder that finally located it (reusable) L8516
## All sections, in order
@@ -946,3 +949,5 @@
- **§129a** — the target instruction count is INFLATED after a carve L8434
- **§129b** — never commit a carve whose owner is still a stub (it strands the carve) L8455
- **§3-The** — real blocker underneath, for the record L8470
+- **§130** — An INCREMENTAL build can report BYTE-IDENTICAL for a change the CLEAN build cannot even LINK (P30 S28, the jr pair) L8479
+- **§3-The** — diagnostic ladder that finally located it (reusable) L8516
diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md
index 3b6cb9a31c..7f776ef0d6 100644
--- a/docs/matching-cookbook.md
+++ b/docs/matching-cookbook.md
@@ -8475,3 +8475,50 @@ With the carve applied, splicing the draft fails cc1 with
genuine §8e work (re-derive the multi-table pad spec including the new owner), not a wall — and it
was only reachable after §129a stopped the phantom 206-instruction "diff" from misdirecting the
diagnosis.
+
+## §130 — An INCREMENTAL build can report BYTE-IDENTICAL for a change the CLEAN build cannot even LINK (P30 S28, the jr pair)
+
+R22 has always said "verify from a clean rebuild." This is the sharpest instance yet of *why*, and it
+cost two cycles because I used a fast in-loop gate that skipped `make clean`.
+
+**The setup.** Two jr/switch functions, `func_8013B83C` (jtbl_801D8254) and `func_8013BD74`
+(jtbl_801D828C), both in the SAME `-O0` object `ov_SC01_077_o0`. Both bodies are byte-correct —
+`match_one --o0` and `rtu_match --o0` each report MATCH (272 and 198 ins).
+
+**What the incremental gate said.** Carve both tables in one `jtbl_carve` call (correct — it is
+additive and re-derives the full span; the object's pad spec becomes `[0,4,4]`), splice both drafts,
+`make extract && make build` → **BYTE-IDENTICAL**. I reported both banked.
+
+**What `make clean` said.**
+```
+ov_SC01_077_o0.c:(.text+0x10f8): undefined reference to `$L105'
+ov_SC01_077_jr_801588CC.o: in function `func_801596F0':
+ undefined reference to `func_8013C938'
+```
+It does not link **at all** — and note the second error: a *previously matched* cluster function
+becomes undefined. An incremental build reused objects that still satisfied those references; a clean
+one has nothing to reuse and the real state surfaces.
+
+**The rule, sharpened.** A byte-gate result from an incremental build is not weak evidence — it can
+be **actively false**, and false in the most convincing direction (a green SHA). §42b named the
+stale-object trap for a FALSE FAIL; this is its mirror, a **FALSE PASS on a change that is not even
+linkable**. Anything that touches `config/` (a carve, a resegment, a split) MUST be gated by
+`make clean && make extract-all && make check-all` before it is believed, let alone reported.
+
+**The residual class this exposes: two jr functions matched in ONE object.** §8b already warns that a
+single object contributes at most one contiguous `.rodata` run; the pad-spec machinery (§8e) handles
+a multi-table span in principle, but matching *both* owners in the same `-O0` object produced
+unresolved local labels (`$L105`) from the C-emitted tables plus a collateral undefined symbol. The
+integrated per-sibling path (`jtbl_family_bank`) banks ONE jr function per object per transaction and
+has never hit this. **Ledger class: `JR-PAIR-IN-ONE-O0-OBJECT`.** The escape, untested, is §81 step 1:
+isolate one of the two into its own code subseg first so each object owns exactly one table.
+
+### The diagnostic ladder that finally located it (reusable)
+The function-level tools all said MATCH, so the signal had to come from the image:
+1. Build with the splice, build without, **diff the two binaries**.
+2. Compare each differing byte's vram against the function's own `[lo, lo+4*nins)` range.
+ Here: **3,749 of 3,791 diffs were OUTSIDE the function**, first diff near the overlay's START, and
+ the image was **57 bytes LONGER** — the §8 signature of `.rodata` floating to the front, i.e. "this
+ function emits a jump table", not "this function's code is wrong."
+That size-and-location fingerprint distinguishes a codegen residual from an integration/layout effect
+in one build, and it is what redirected the diagnosis away from three wrong guesses.