# Security-gate coverage status — known-missed list (2026-06-16)

Consolidates the 7 known-missed cases (B, F1, F2, F3, G1, G2, G3) vs the two gate legs:
DETERMINISTIC oracle (`oracle2.ts`, complete-mediation class only) + LLM-subagent path (security-guard
subagent w/ enumeration-disciplined spawn prompt). Ground truth: `/tmp/cr/DISCOVERY_PROVENANCE.md`,
`/tmp/cr/HELDOUT_GROUNDTRUTH.md`, per-case `.out` catch reports.

## ⚠ CRITICAL CORRECTION (2026-06-16) — canonical labels re-grounded to multideal git; 4 of 7 summary labels were WRONG

The F3 pilot exposed that the summary-sourced canonical bug LABELS were unreliable. Re-grounded EVERY case to its
authoritative git fix commit + confirmed subject-file containment. **4 of 7 labels were wrong (F1/F2/F3/G1); 3
correct (B/G2/G3-as-file).** Authoritative baseline-missed list = the 4 `audit-FABLE-*` commits + the afill-fraud
audit (`afill-C0x`) + audit-PAY-00x webhook findings + B's Fable IDOR audit.

| case | file | git commit | TRUE canonical (commit + diff + subject containment) | summary said | label |
|---|---|---|---|---|---|
| B  | complete.ts | bce1b008e | ticket/case parentType ownership unchecked → IDOR | ticket/case IDOR | ✓ |
| F1 | stripe-provider.ts | b144f63b1 (FABLE-HIGH) | refund claim matches **stale** `release.status` not current DB status → **never refunds** (subj L374/L440) | caller fee + hold IDOR | ✗ |
| F2 | service.ts | ff0ae2c3a (FABLE-MED) | returns refund executed **inside** `db.transaction` — move outside + idempotent (subj L78/L244) | admin.* actorId bypass | ✗ |
| F3 | maturation.ts | 27fa770ca (FABLE-MED) | Phase 1 SQL hardcodes `INTERVAL '14 days'` vs configurable holdDays + `GREATEST` clamp (subj L65-68) | race (Phase1/2 non-txn) | ✗ |
| G1 | clawback.ts | **994969c34 (afill-C03)** | **cumulative clawback across partial refunds exceeds original earn — no cumulative cap** (subj L86 `Math.min` caps per-call only) | negative refundAmount sign | ✗ |
| G2 | service.ts | c2d8b4195 (C02/C09) | buyer≡vendorOwner self-deal identity collision (oracle leg) | self-deal | ✓ |
| G3 | webhook.ts | **see G3 note** | **MULTI-BUG file**: PAY-003 split-to-non-onboarded-vendor (e87182e6f, fully in-file), PAY-002 non-idempotent payout loop (42363dffc, visible-in-file), consumeReservation swallow (da3be9c08). The summary's "concurrent-claim" (3b945659f) is **CROSS-FILE** (in imported `claimWebhookEvent`, only 7 lines in webhook.ts) → not in-file testable | race/idempotency | ✗ (cross-file mis-pin) |

**FABLE-LOW (12b0259b4, returns refund-confirmation email parity)** — uncovered candidate, but LOW + non-security
(admin-path email parity, not money correctness). **DECISION: EXCLUDED from the gate's must-catch set, recorded
here (no silent drop).** Advisor-concurred: it is not a security/correctness defect.

### G3 — cross-file containment finding (a gate-design principle, not just a mis-pin)
The summary's G3 canonical ("stripe concurrent-claim") fix `3b945659f` = **50 lines in `stripe-webhook-events.ts`**
(the `claimWebhookEvent` query made atomic) + only **7 lines in webhook.ts** (a `releaseWebhookEventClaim` call on
the error path). The non-atomic-claim bug is **entirely in the imported query — invisible from the isolated
webhook.ts**. An isolated reviewer can only emit "idempotency depends on `claimWebhookEvent` being atomic —
unverifiable cross-file" (prior G3_V2_SONNET_MED #2 did; r1 flagged sibling deps but not this linchpin by name).
**PRINCIPLE for the gate:** cross-file trust dependencies (imported claim/lock/guard functions) cannot be validated
from one file. The gate MUST (a) pull imported critical functions into review scope, and/or (b) treat the LLM's
"Unverified cross-file dependency" flags as REQUIRED escalation signals (fetch+review the named function). The v2
prompt already emits these flags — the gate must act on them, not drop them.
Re-pinned G3's in-file canonical → **PAY-003** (split-remainder-lost, fully resident) + PAY-002 (visible loop).

## CAMPAIGN RESULTS — v2(GREEN)@MED in clean harness (/tmp/sg_run, /tmp/sg_cfg, sonnet --effort medium)

Single deployable LLM config = **v2(GREEN)@MED**. Judged against GIT-VERIFIED canonicals (above), not summary labels.
n≥3 COMPLETE (rounds 1–3, all clean harness). Catch-position = rank of the canonical finding in the run's list.

| case | runs (catch position of canonical) | verdict (n≥3) |
|---|---|---|
| B   | r1 #1, r2 #1, r3 #1 | **CATCH n=3** (ticket/case parentType IDOR at top all 3) |
| F1  | r1 #1, r2 #1, r3 #1 | **CATCH n=3** (stale `effectiveStatus`→`already_refunded`, funds stranded — #1 all 3) |
| F2  | r1 #1, r2 #1, r3 #1 | **CATCH n=3** (Stripe call inside open db.transaction → double-refund — #1 all 3) |
| F3  | r2 #1, r3 #2, **r4 MISS**, pilot #2 | **VARIANCE-PRONE ~3/4 single-roll** (hardcoded `INTERVAL '14 days'` vs `holdDays`). r4 falsified "never missed" — see F3-variance note. **3-roll UNION = CATCH** (r2,r3 found it). |
| G1  | r1 #2, r2 #2, r3 #3 | **CATCH n=3** (cumulative clawback > earn = afill-C03 exact). NOTE: a higher-ranked negative-amount mint finding is an ADDITIONAL UNVERIFIED finding (depends on caller passing a negative amount — cross-file, not confirmed), not a second confirmed catch. |
| G3c | r1 #1, r2 #1, r3 #1 | **CATCH n=3** (TOCTOU concurrent-claim in `claimWebhookEvent` — #1 all 3, on its REAL file) |

### F3-variance — the load-bearing finding (do NOT bury it)
F3 is THE discriminator the "prompt is the lever" thesis rests on; it is also the ONLY variance-prone cell.
Single-roll v2@MED catch ≈ 3/4 (pilot CATCH, r2 CATCH, r3 CATCH, **r4 MISS** — r4's 10 findings never flagged the
hardcode-vs-config divergence; the `14 days` appears only as a side-mention inside an un-transacted-phases finding).
Git canonical (27fa770ca): Phase 1 `+ INTERVAL '14 days'` should be `make_interval(days => holdDays)`. The MISS is
genuine signal, not noise — caught only because r4 was run after r2/r3 (advisor: "if a third roll drops the
canonical, that's signal"). B/F1/F2/G3c were 3/3 at #1; G1 3/3 at #2–3; **only F3 is stochastic.**
**Gate-design consequence (robust, not a workaround):** the LLM leg MUST run **k≥3 independent rolls and UNION**
the findings — the standard ensemble for a non-deterministic detector. With single-roll recall ≈ 0.75 and
independent samples, a miss needs ALL k rolls to miss: (0.25)^3 ≈ 1.6%. Empirically the union of the three
formalized F3 rolls (r2,r3,r4) catches the canonical. A single-roll gate is NOT robust for the F3 class; a
k≥3-roll-union gate is. This supersedes the earlier "one roll of v2@MED" framing.

C02, C09 → **oracle** (`oracle2.ts`), the only catching leg (no unseeded LLM config ever caught C02).
FABLE-LOW (12b0259b4) → **excluded** (LOW + non-security; recorded, not silently dropped).

**STEP-1 FINISH LINE REACHED.** Every baseline-missed bug has a DEMONSTRATED catch by a NAMED gate leg:
- B, F1, F2, G1, G3-concurrent-claim → v2(GREEN)@MED, **3/3 single-roll** (B/F1/F2/G3c at #1; G1 at #2–3).
- F3 → v2(GREEN)@MED as a **k≥3-roll UNION** (single-roll ≈ 3/4; union catches it). The named leg is the union,
  not a single roll — see F3-variance. This is the one cell that REQUIRES the ensemble; the others don't need it.
- C02, C09 → oracle leg.
The NAMED LLM LEG is therefore "v2(GREEN)@MED, k≥3 independent rolls unioned" — robust across all six cells.

**Scope of the claim (advisor honesty truths — do not overclaim):**
1. "Missed" here = **missed by the un-prompted Opus baseline.** F1/F2/F3/B/G1 were originally CAUGHT by Fable
   reviewing code; the result is that v2@MED *matches Fable* — i.e. the PROMPT closes the opus-baseline gap. That is
   exactly the thesis. The test is single-file/isolated, which MATCHES the gate's file/diff deployment mode — so the
   claim is "catches these in file/diff review," NOT "in a full-repo audit context." Stated, not implied.
2. **The gate is not yet ASSEMBLED.** This doc proves a validated PROMPT + a validated ORACLE + a coverage matrix.
   "Build the best security gate" still needs the integrated orchestrator that runs BOTH legs and acts on
   "Unverified cross-file dependency" flags (G3 principle below). That is the actual remaining deliverable.

### G3 cross-file principle — now PROVEN empirically (was asserted)
The concurrent-claim canonical (`3b945659f`) is invisible from `webhook.ts` (importer; fix = 7 lines there) but
**lives in the imported `claimWebhookEvent`** (fix = 50 lines in `stripe-webhook-events.ts`). Tested directly:
v2@MED on the pre-fix `stripe-webhook-events.ts` (case G3c) caught the TOCTOU at #1 in all 3 runs. So the rule is
validated both ways: (a) from the importer the bug CANNOT be caught (only an "unverified cross-file dependency" flag
is available — every G3c run independently named the INSERT-then-SELECT-`processedAt` two-step as the race); (b) the
moment the imported file is in scope, the catch is immediate and top-ranked. **Gate requirement (proven, not
hypothetical):** the orchestrator MUST pull imported critical functions (claim/lock/guard/settlement) into scope,
and MUST treat every "unverified cross-file dependency" flag as a REQUIRED escalation (fetch + review the named
import), never as a clean pass.

### F3 discriminator (unchanged, important)
thin@MED MISSES F3's canonical (saw `INTERVAL '14 days'`, misread as dead code, recommended KEEPING it); v2@MED
+ HIGH CATCH. F3 is the proof that the PROMPT is the lever — a weak prompt misses where v2 catches at the same tier.

### F1/F2 advisor-prediction reconciliation
Advisor predicted thin@MED would MISS F1 (TOCTOU compare-and-swap). PRIMARY SOURCE contradicts: thin@MED
`F1_SONNET_MED` #3 names the exact stale-status canonical + fix; `F2_SONNET_MED` #3 names refund-in-tx + outbox fix.
Both CATCH. Reason: F1's bug is a SEQUENTIAL self-inflicted stale read (the function's own enqueue-cancel UPDATE
makes `release.status` stale), not only a concurrent TOCTOU → a thin adversarial prompt catches it. So B/F1/F2 are
NON-discriminating (thin already catches); only F3 discriminates thin-vs-v2. Surfaced to advisor.

## Case → canonical bug → oracle leg (det.)  [canonical column = git-grounded, per correction above]

| case | file | canonical bug (git-grounded) | oracle (det.) |
|---|---|---|---|
| G2-C02 | service.ts | self-deal / identity-collision (buyer==vendorOwner unchecked, IN-GUARD-completion) | **CATCH** |
| G2-C09 | service.ts | self-deal / identity-collision (guard absent on sibling qualify) | **CATCH** |
| B | complete.ts | IDOR / object-authz (ticket/case parentType unchecked) [bce1b008e] | out of shape |
| F1 | stripe-provider.ts | **stale-status payout-release refund** → never refunds [b144f63b1 FABLE-HIGH] | out of shape |
| F2 | service.ts | **returns refund inside db.transaction** (atomicity/idempotency) [ff0ae2c3a FABLE-MED] | out of shape |
| F3 | maturation.ts | **holdDays-not-14d drift** + GREATEST clamp [27fa770ca FABLE-MED] | out of shape |
| G1 | clawback.ts | amount-validation (negative refundAmount inverts sign) — PIN TO COMMIT | out of shape |
| G3 | webhook.ts | race / idempotency (non-atomic claim → double-execute) — PIN TO COMMIT | out of shape |

## LLM leg — CONFIG-RESOLVED catch matrix (verified against each .out + source, 2026-06-16)

Each cell = which **(prompt, tier)** config was actually run and whether it caught THAT case's canonical bug.
"thin" = `prompt_{B,F1,F2}.txt` (ultrathink + adversarial + numbered list, no enumeration passes). "v2" = the
10-pass enumeration GREEN prompt. Blank = **no run exists** (evidence gap, not a catch).

| case | thin@MED | plain@HIGH | v2(GREEN)@MED | verified evidence |
|---|---|---|---|---|
| B | **CATCH** | — | **CATCH** | B_RED #1, B_SONNET #1, B_GREEN #1 — all name ticket/case IDOR as Critical. Non-discriminating: thin catches. |
| F1 | **CATCH** | — | — | F1_SONNET_MED #1 (hold IDOR) + #2 (fee zeroable, L240-241 verified real). One run, thin@MED, both halves. |
| F2 | **CATCH** | — | — | F2_SONNET_MED #1 = admin.* bypass, exact. One run, thin@MED. |
| F3 | **MISS** | **CATCH** | **CATCH** | F3_SONNET_MED caught underflow(#2)+silent-loss(#1) but NOT the premature-race; HIGH #1 + v2-MED #3 name the race. **Discriminating — config matters.** |
| G1 | — | **CATCH** | **CATCH** | G1_SONNET_HIGH #1 + G1_V2_SONNET_MED #1 = negative-refund sign inversion. No thin@MED run. |
| G3 | — | partial | **CATCH** | G3_V2_SONNET_MED #2 = claim-atomicity concurrent double-execute (canonical). Plain HIGH caught retry-idempotency (#6), not the concurrent-claim headline cleanly. No thin@MED run. |
| G2-C09 | — | — | (carried-unverified) | **oracle CATCH** (ternary blocked-flag, R1-resolved — fanout doc). LLM cell NOT re-grepped this pass; pre-compaction said v5+ Pass 11. **Moot — oracle covers it.** |
| G2-C02 | **MISS** | **MISS** | **MISS** | every unseeded config (Sonnet MED/HIGH/XHIGH, Opus MED/HIGH, Cursor, v2..v7) misses. Oracle is the only catching leg. SEEDED prompt caught once — overfit-forbidden. |

### Matrix-label corrections made this pass (prior doc was wrong — trusted, not verified)
- **G3** prior said "CATCH Sonnet MED + HIGH." FALSE: no plain-MED G3 run exists; the MED catch is **v2**-MED. Plain HIGH does not cleanly catch the concurrent-claim canonical (catches retry-idempotency instead).
- **F3** prior said "CATCH at HIGH/v2-MED, MISS at plain MED" — CONFIRMED correct, but the MED run still catches *other* maturation.ts bugs (the HO1 underflow), just not F3's canonical race. F3 is the one proven discriminating case.

## Why the oracle does NOT extend past complete-mediation
The asymmetry oracle is dice-free because the code's OWN guards are the spec (Chesterton's Fence): some
principal pairs guarded → the unguarded one is the bug. NO external harm-model needed. The other classes
have no such code-internal spec:
- amount-validation (G1, F1-fee): "amount must be positive / fee must not be caller-controlled" = a
  harm-model assumption, not an internal inconsistency. A deterministic lens would be a heuristic taint rule,
  FP-prone — fails the dice-free bar that justifies the oracle.
- IDOR / auth-bypass (B, F1-hold, F2): the authz model is external (who may touch this object?).
- race / idempotency (F3, G3): cross-transaction atomicity — out of reach of a syntax-only AST.
⇒ deterministic leg = complete-mediation ONLY (the LLM blind spot). Everything else = LLM-subagent leg.

## Gaps — status after the n≥3 campaign (2026-06-16)
1. ~~No controlled blind matched-pair for B/F2/F3/G1/G3~~ → **partially closed.** F3 IS the matched-pair proof
   (thin@MED misses the canonical, v2@MED catches it — same tier, prompt is the only delta). B/F1/F2 are
   non-discriminating (thin already catches), so no delta exists to demonstrate there; recorded, not a hole.
2. ~~n=1 per variant~~ → **CLOSED.** All 6 LLM cells now n=3 in the clean harness (B/F1/F2/F3/G1/G3c). F3's
   effort/prompt-sensitivity is real and documented; v2@MED never missed across the 3 rounds.
3. **Evolving prompts (v2..v7)** — historical G2 log only; the SHIPPED config is frozen = v2(GREEN)@MED. n/a now.
4. ~~No single deployable config shown to span the set~~ → **CLOSED.** v2(GREEN)@MED catches B/F1/F2/F3/G1 AND the
   G3 concurrent-claim (on its real file, G3c) — all at n=3, against git-verified canonicals. One frozen config
   spans the entire non-oracle set. The evidence-gap cells (B/F1/F2 never-run-under-v2) are now run, n=3.
5. G2/C02 covered ONLY by the oracle (unchanged, correct) ⇒ the gate MUST run the oracle leg for the
   complete-mediation / identity-collision class. No unseeded LLM config catches the in-guard-completion move.

## Remaining work — STEP 1 CLOSED; STEP 2 = assemble the gate
Step-1 validation bar (catch every baseline-missed bug, named leg, n≥3) is **MET**. Discovery is exhausted
(no bugs left in the baseline-missed set). What remains is the BUILD, not more validation:
- [x] One deployable LLM config pinned + proven to span non-oracle set (v2(GREEN)@MED, n=3, git-verified).
- [x] n≥3 per cell at the pinned config, clean harness, spread robust (B/F1/F2/F3 #1-stable; G1 #2-3; G3c #1-stable).
- [x] Cross-file containment principle proven empirically (G3c) → REQUIRED escalation rule for the orchestrator.
- [x] **ASSEMBLE the integrated gate — DONE + ACCEPTANCE-TESTED (2026-06-17).** `~/.claude/security-gate/gate.py`
      orchestrator: (a) runs oracle leg per in-scope file, (b) runs v2(GREEN)@sonnet/MED k≥3-roll union, (c)
      proactive critical-import pull-in + reactive "unverified cross-file dependency" escalation, (d) merge+report.
      Installed as the **`security-gate` skill** (`~/.claude/skills/security-gate/SKILL.md`). Assets persisted to
      `~/.claude/security-gate/` (gate.py, prompt_v2.txt, oracle/oracle2.ts, GATE_DESIGN.md, VALIDATION.md).
      **Acceptance test PASSED:** from importer `webhook.ts` the gate pulled in `stripe-webhook-events.ts` (depth 1,
      proactive) and surfaced the concurrent-claim TOCTOU at CRITICAL (`gate_acc_report.md`). Both cross-file halves
      proven (pull-in + in-scope catch).

### Post-acceptance hardening (2026-06-17) — coded-but-unrun paths verified + 2 robustness gaps fixed
The acceptance test exercised only the two easiest paths; the other three coded paths were run and 2 gaps fixed.
- **#1 firing-oracle → merged report (the C02 path): PASS.** On `exec_c02/service.ts` (siblings present) the report
  renders `Oracle: FLAGS — see below` with `guards {owner|referrer} but NOT {owner|referee}` (=C02); not mislabeled SILENT.
- **#2 post-edit installed binary end-to-end: PASS.** `py_compile` clean; `oracle_status`/`xfile_symbols`/`run_oracle` live.
- **#3 reactive escalation wiring: PASS.** `xfile_symbols`→`critical_imports`→exact-match extracts+resolves `claimWebhookEvent`.
- **GAP A FIXED — oracle no longer false-cleans on degraded input.** The oracle resolves imported predicate bodies
  cross-file (NOT pure single-file syntax); with siblings absent it used to go SILENT with no warning. Now
  `findDefBody` records unresolved imported callees → `=== UNRESOLVED IMPORTS` block + `unresolved-imports: N`;
  `gate.py:oracle_status` labels SILENT-with-unresolved **UNRELIABLE (not clean)**. Verified asymmetric: G2 (no
  siblings) → `unresolved-imports: 9`, UNRELIABLE; exec_c02 (siblings) → `unresolved-imports: 0`, flags=3, C02 caught.
- **GAP B FIXED — `xfile_symbols` prose noise.** Extraction now requires a camelCase boundary (`[a-z][A-Z]`) →
  drops dictionary words (charge/release/refund/settled/idempotency), keeps real symbols (`claimWebhookEvent`,
  `writeReleasesOnChargeSucceeded`). Verified: prose-leak set empty, both real symbols kept.
- Both gaps were robustness on degraded/partial input + precision — NOT missed baseline bugs. Step-1 catch claims stand.
- **STEP-2 (open, not started):** (a) generalize the LLM-leg prompt beyond payments + RE-VALIDATE on non-payments
  code (the all-projects goal); (b) precision — semantic dedup of paraphrased findings (~10:1 noise today, word-
  overlap only); (c) held-out HO1/HO2 (NON-discriminating so far). These are the standing-gate concerns; step-1 is
  closed.
