The load-bearing gate is `poc_runner.py`: apply a candidate diff inside the sandbox, recompile, re-run the
PoC, and return a boolean for *does the crash still happen*. That single primitive replaces our entire
noise architecture. You do not average five noisy runs when a container can tell you whether the bug is
real. **We built K-runs and noise floors because we had no ground truth. They generate their own.**
## 3. What Clearwing does better, mapped to what failed here
| Our failure this session | What Clearwing does instead |
|---|---|
| K-runs / noise floor / sign test / regression budget — a statistical treatment for a structural problem | **Execution is the arbiter.**`poc_runner` reproduces or it doesn't. `stability.py` runs the PoC across N fresh containers (≥90% = stable) to kill flakiness at the source instead of averaging it. |
| Evolve loop: 5 rounds, 0 reliable decisions, $2.17 to learn nothing | **A fixed specialist roster** dispatched by file tags — `memory_safety`, `kernel_syscall`, `crypto_primitive`, `web_framework`, `logic_auth`, `general` — plus **variant analysis** (verify a bug, generate a grep+semantic pattern, find its structural twins, iterate to fixpoint). Density compounds *within a run*, no optimizer. |
| Second judge / fusion, gesturing at independence | **4-axis adversarial validator** (REAL / TRIGGERABLE / IMPACTFUL / GENERAL) that sees *only* the finding, PoC and exploit — never the discovery transcript — steel-mans both sides, and **must attempt reproduction**. |
| Corpus: 12 hand-harvested function excerpts per family, scored by text-inside-function, capped by twins/straddles/truncation defects we spent days repairing | **Corpus is real repos at vulnerable-parent commits + OSS-Fuzz** (7000 targets in `full` mode), run blind, with the fix commit used only as an *after-the-fact oracle*. Truth is generated by the sandbox, not annotated by us. `CampaignConfig` scales this to hundreds of repos with shared budget, checkpoint/resume, and diminishing-returns detection. |
| Detector chooses what to look at (555 grep calls vs 71 entry-point lookups) | **Deterministic preprocessing feeds candidates:** callgraph → intra-procedural taint (source→sink) → 3-axis ranker → hunter. The model judges; it does not search for the sink. This is exactly the `constrained-detector` design we had started — Clearwing is the mature version of it. |
## 4. The one thing OpenUltraSAST proved that Clearwing lacks
This is not a total loss, and the course is not "throw everything away." Two results here are real,
measured, and *absent from Clearwing*:
1.**IR-driven candidate enumeration with a published ceiling.** We measured that the pattern ruleset caps
recall at 12.1% while call-sites-and-bindings from the semantic IR reach **96.6%** across the web slices,
a median of ten sites per function. Clearwing's candidate feeder is C-first (taint tables for
`memcpy`-shaped bugs); its `web_framework` specialist is a *single unconstrained prompt* with no
equivalent candidate enumerator for logic/web classes.
2.**Absence-bug detection.** Our `obligations` work — an access-control bug is the *absence* of a guard
around an operation its siblings protect — has no analogue in Clearwing, whose ladder is built around a
*crash*. An IDOR or a missing authorization check never crashes a sandbox. Clearwing's execution
ground-truth model does not reach these classes at all, and they are exactly the "dangerous but
low-hanging" web bugs this project was chartered to catch.
So the gap Clearwing leaves open is **the web/logic taxonomy without a crash oracle** — which is where our
IR candidate enumerator, our family taxonomy, and our obligation checker are genuinely ahead.
## 4b. The model layer, and the evidence ladder it produces
The arbiter is a program model constructed deterministically from the code — the thing an LLM claim is
*checked against*, so that a finding's truth does not rest on the LLM that proposed it. We already hold the
first rungs of it; they were buried under a noise layer instead of being made the arbiter.
The evidence ladder is the model layer made legible. Each rung is a stronger, still-deterministic statement:
| rung | what it means | who establishes it | we have |
|---|---|---|---|
| `suspicion` | the model enumerated a candidate site; the LLM flagged it | IR enumeration + LLM | yes (candidate enumerator, 96.6% ceiling) |
| `model_corroborated` | the LLM's claim is *consistent* with the model — the source it names reaches the sink it names through a resolved binding/dataflow path; or the operation it flags lacks a discharging guard its siblings carry | model checks the LLM's assertion | partial (binding chains, obligation facts) |
| `model_entailed` | the model *itself* establishes it, independent of trusting the LLM — a concrete source→sink path with no sanitizer node; a provably-absent guard among a sibling set that carries it | model alone | to build (interprocedural dataflow + guard dominance) |
| `execution_confirmed` | a sandbox reproduced it — a crash, or a differential request the fixed twin rejects | execution substrate (deferred tier) | Clearwing, later, only where needed |
Two properties are the whole point:
- **`model_corroborated` and above are deterministic.** Run the checker twice over the same model and it
returns the same verdict. That removes the run-to-run noise the same way a crash would — no K-runs, no
floor, no sign test — *without needing a runnable target*. The noise this session measured was the LLM
free-searching; a model check does not search.
- **The LLM's job shrinks to the residual the model cannot decide** — is this string genuinely
attacker-controlled, is this guard semantically sufficient — and even that answer is checked against the
model rather than averaged over runs. The LLM stops being the oracle and becomes a proposer the model