diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md
index 131c99481..e06230d76 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 / 390 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 / 393 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.
@@ -227,7 +227,7 @@
- **§138** — The propagation lanes: a gate refusal is a DECLARATION, and which lever you owe depends on blast radius L9405
- **Reconciling** — a gate-refused draft: which way you edit depends on WHERE the TU's decl is L9555
-### jump tables & switches (25)
+### jump tables & switches (26)
- **§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
@@ -254,6 +254,7 @@
- **§131** — The jtbl OVER-SPAN: `sltiu N` is ground truth in BOTH directions, and the zero-word rule only guards one (P30 S28, `func_80191C50`) L8526
- **§132** — The `JR-PAIR-IN-ONE-O0-OBJECT` "wall" was TWO instrument defects: a merged-double span the carve could not see, and a truncated object no rule deleted (P30 S29, `func_8013B83C` + `func_8013BD74`) L8563
- **§132b** — When the span's already-matched owner is ITSELF multi-switch: `--span-rel` (P30 S1, `func_8014032C`) L8653
+- **§139** — A GATE THAT GREPS FOR VERDICTS MUST ASSERT 1:1 ACCOUNTING; and a `--src` filter must not survive a carve (P30 S38, wave 6: 10 of 16 drafts vanished) L9584
### optimisation level (-O0/-O2) (12)
@@ -379,7 +380,7 @@
- **§3-The** — declaration surface (integration, not codegen) L8931
- **Reconciling** — a gate-refused draft: which way you edit depends on WHERE the TU's decl is L9555
-### build graph, splat & the harness (95)
+### build graph, splat & the harness (96)
- **§4** — Flag/toolchain gotchas L190
- **Build** — mechanism — per-file opt override (splat resegmentation) L288
@@ -476,6 +477,7 @@
- **§138** — The propagation lanes: a gate refusal is a DECLARATION, and which lever you owe depends on blast radius L9405
- **§134** — again, in a second tool — and the waiter rule corrected L9516
- **Reconciling** — a gate-refused draft: which way you edit depends on WHERE the TU's decl is L9555
+- **§139** — A GATE THAT GREPS FOR VERDICTS MUST ASSERT 1:1 ACCOUNTING; and a `--src` filter must not survive a carve (P30 S38, wave 6: 10 of 16 drafts vanished) L9584
### process, measurement & doctrine (53)
@@ -533,7 +535,7 @@
- **§136j** — The failure MIX flips with function size (measured across four bands, one session) L9292
- **Rank** — the lane by measured concentration, not by class count L9471
-### (unbucketed — title matched no symptom vocabulary) (110)
+### (unbucketed — title matched no symptom vocabulary) (112)
- **§3-How** — to use this L30
- **§1** — Idiom catalog (asm pattern → C that produces it) L39
@@ -645,6 +647,8 @@
- **§3-The** — triage, cheapest first L9411
- **THREE** — carry variants hide in one "CARRY-FIXABLE" bucket — and they need different fixes L9480
- **STEP** — 0 of sibling-first: grep `src/` for a distinctive LITERAL from the `.s` L9536
+- **§3-The** — generalisation — three corollaries worth more than the bug L9616
+- **§3-And** — the inverse-lookup trap, same session L9633
## All sections, in order
@@ -1039,3 +1043,6 @@
- **§134** — again, in a second tool — and the waiter rule corrected L9516
- **STEP** — 0 of sibling-first: grep `src/` for a distinctive LITERAL from the `.s` L9536
- **Reconciling** — a gate-refused draft: which way you edit depends on WHERE the TU's decl is L9555
+- **§139** — A GATE THAT GREPS FOR VERDICTS MUST ASSERT 1:1 ACCOUNTING; and a `--src` filter must not survive a carve (P30 S38, wave 6: 10 of 16 drafts vanished) L9584
+- **§3-The** — generalisation — three corollaries worth more than the bug L9616
+- **§3-And** — the inverse-lookup trap, same session L9633
diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md
index aa07c2f57..de4887169 100644
--- a/docs/matching-cookbook.md
+++ b/docs/matching-cookbook.md
@@ -9578,3 +9578,69 @@ source position (`\.[ch]:\d+`), minus the known SHB macro noise.
Symptom line for the index: **"a propagation/extend lane plans N and banks 0"** — read each failure's
compiler error, bucket by named symbol, and apply the lowest-radius byte-neutral alias; a `volatile`
in the host TU and a fleet decl carrying a promoting param are the two that masquerade as codegen.
+
+---
+
+## §139 — A GATE THAT GREPS FOR VERDICTS MUST ASSERT 1:1 ACCOUNTING; and a `--src` filter must not survive a carve (P30 S38, wave 6: 10 of 16 drafts vanished)
+
+**The symptom** was a gate run that looked ordinary: `BANKED 5 / FAILED 1` — printed over **16**
+drafts. Six verdicts for sixteen inputs, and nothing said so. Nine of the ten missing drafts were
+claiming MATCH.
+
+**The chain, both ends of which were ours:**
+
+1. `harvest_verify._reload_corpus()` exists, by its own docstring, to *"re-derive the stub map after
+ an isolation moved a stub to a new TU."* It then re-applied the `--src` filter (`x.path == a.src`)
+ to the reloaded map — **deleting the very stub it had just followed to its new home.** Line 136 of
+ that same file already labels `--src` *"an optional filter, not a location oracle"*; `_reload_corpus`
+ was the one place treating it as one. Downstream:
+ `_stubs` loses `fn` → `render()` raises `KeyError` → **uncaught** → `_jtbl_restore(snap)` never
+ runs → **the carve is stranded in `config/` + `src/`** → and every *later* group in the same gate
+ run then builds against a tree the earlier crashes mutated.
+2. The gate driver (`.run/s6f_gate.py`) captured the child's output, grepped it for `VERIFIED:` and
+ `FAILED :`, and **never looked at the returncode.** "Neither line present" was booked as nothing
+ at all, and the tally printed clean.
+
+**The fixes are both structural, not cosmetic:**
+- the filter never drops a draft under verification, wherever it now lives, plus an R32 loud report
+ if a working stub vanishes across a reload (which also repairs `_touched`/`baseline` — a carved fn
+ missing from `_stubs` left its new TU *unbaselined*, so the revert path could not have restored it
+ either);
+- the gate asserts `banked + failed + no-verdict == drafts`, prints the child's rc and output tail for
+ anything unaccounted, and **exits non-zero** — because a crashed child may have stranded a carve, so
+ it must never look like success.
+
+**Proof it was not cosmetic:** `func_8017EA84` (579 ins) carves and banks BYTE-IDENTICAL under the
+fixed path. The old tool reported it as *nothing*.
+
+### The generalisation — three corollaries worth more than the bug
+
+**(a) The byte-gate is a perfect CORRECTNESS oracle and a null COVERAGE oracle (R34, again).** It
+cannot tell you about work it was never asked to do. Every wave-level tally is a *coverage* claim,
+and coverage claims need their own assertion. **Any driver that classifies N inputs must prove it
+emitted N verdicts.**
+
+**(b) A pipeline's exit status is the LAST command's.** In the same session `family_sweep … | tail -30`
+hid a non-zero exit (`--only` wants comma-separated; space-separated args were rejected). Use
+`set -o pipefail` and read `PIPESTATUS`, or the tail *is* the error handler.
+
+**(c) A reverted CONFIG needs `make extract`, not just the revert.** After undoing the stranded
+carves I re-extracted one overlay of sixteen; the next gate run read three genuinely-banked functions
+as failures because they were building against asm/ still partitioned by the old carve. This is the
+Phase-20 R22 corollary, and it costs a whole gate cycle every time it is skipped. **A gate result
+measured against stale asm is not a measurement (R35).**
+
+### And the inverse-lookup trap, same session
+
+Propagating a **just-banked** head, `family_hseq.json` moves it **out of its family's `members` list
+and into `exemplar` with `kind: "matched"`.** A lookup that searches the member list for the head
+therefore returns *"no family"* for every head — while `family_sweep` enumerates those same families
+from the same file seconds earlier. Key on the `exemplar` **(ov, addr) pair** (two families can share
+an exemplar address in different overlays). This does **not** contradict §138 rule 4 ("never rank off
+the exemplar field"): that rule governs *target selection*, where an exemplar pointing at a banked
+instance HIDES a family; here the freshly-banked exemplar is precisely what you are looking the
+family up BY.
+
+Symptom lines for the index: **"a gate reports fewer verdicts than it had drafts"** · **"a tool
+reports NO FAMILY for a head another tool just enumerated"** · **"a gate result got worse after a
+revert"** (→ re-extract, then re-measure).