Module 5 · Research case studies
Case study — Architecture & algorithms deep-dive
This is the engineering deep-dive. We open the promotion membrane and show that promotability is re-checked by the finding type itself, not just by the one promote() function — so bypassing the function still cannot mint an unverified finding. We then walk the three oracle kinds in detail (deterministic re-derivation, hybrid propose-only, probe-based re-read), the four-value Verdict enum and why the system keeps separate verdict vocabularies, and the custody layer (canonical SHA-256 digests, an append-only Merkle chain, and an Ed25519-signed report). The lesson closes with one finding traced through every stage.
Learning objective
Trace the mechanics of the evidence-first platform end to end: how the promotion membrane is wired, how the finding type enforces promotability in its own constructor and copy hooks, how the three verifier-oracle kinds settle proposals across the seventeen assessment families, and how a confirmed finding is sealed into a chain of custody — with a full worked walkthrough from a raw hypothesis to a signed audit record.
- Track
- Research
- Module
- 5 · Research case studies
- Prerequisites
- Case study — Deterministic verification over LLM-as-judge (Module 5, lesson 2) · Case study — The promotion membrane and chain of custody (Module 5, lesson 3) · Hash functions and digital signatures (what they prove)
The pipeline as a dataflow
The paper describes an assessment as a loop over an event backbone. Rather than a monolithic "scan", the system emits a stream of causal lineage events and treats exactly one of them — promotion — as the point where a finding identifier is born. Everything upstream is proposal and evidence; everything downstream is custody. Holding this shape in mind makes the rest of the architecture legible.
the event backbone (lineage stages, as the paper reports them)target ingested -> surface discovered -> team routed (agents assigned to the surface) -> hypothesis formed (OPEN -> INVESTIGATING) -> verifier run (a deterministic oracle settles the proposal) -> evidence produced (artefacts with content digests) -> promoted to finding <-- the ONLY stage that mints a finding id -> hypothesis discarded (UNKNOWN / REFUTED paths end here) -> replayed (optional reproduction -> VALIDATED maturity)
Only the promotion stage carries a finding identifier. UNKNOWN and REFUTED hypotheses still produce lineage events — the system records what it could not establish — but they never acquire a finding id. This is the dataflow expression of "not-knowing is a result."
The membrane, in two layers
The promotion membrane is enforced at two layers, and the distinction is the paper’s key architectural point. Layer one is a single function — the only function in the system that returns a finding — which refuses unless the result is promotable and the evidence matches the exact hypothesis. Layer two is the finding type itself: it re-checks promotability in its construction and copy hooks, so that even code which never calls the function, or tries to copy a finding into an unverified state, is rejected by the type.
the two enforcement layers (schematic reconstruction)# Layer 1 — the single mint path is_promotable(r) ⇔ r.verdict == FAIL ∧ |r.evidence| > 0 def promote(r): reject unless is_promotable(r) reject unless evidence_supports(r.evidence, r.hypothesis) # exact link return Finding(verdict=r.verdict, evidence=r.evidence, hyp=r.hypothesis) # Layer 2 — the type re-checks itself (constructor + copy hooks) class Finding: def __init__(self, verdict, evidence, hyp): assert verdict == FAIL and len(evidence) > 0 # no unverified state assert evidence_supports(evidence, hyp) ... def __copy__(self): # and copy-with-changes hooks assert is_promotable(self) # cannot copy into a lie
The payoff is that the two governing rules — no finding without evidence, and no agent writes a finding — become properties of the object graph rather than conventions a reviewer must police. The paper is precise about the scope of this guarantee: under the assumption that all finding creation goes through these typed paths, no agent output and no quantity of agent agreement can appear as a finding. It then adds, in the same breath, that this is an architectural property and not a correctness guarantee: a verifier that returns a wrong FAIL still produces a wrong finding, fully sealed.
PROVEN-by-construction (conditional): bypassing the mint function does not help an attacker-contributor, because the type rejects a non-promotable finding at construction and copy time. TESTED separately: the paper reports an import barrier forbidding any LLM client in the verification layer, and tests asserting zero model calls and zero cost on the promotion path.
The three oracle kinds, in detail
All seventeen assessment families end their work with a verifier oracle, and the only design variable is where — if anywhere — a model is permitted to act. The invariant across all three kinds is a shared contract: given the same inputs, the oracle returns the same result, with no LLM call, no sampling and no network guessing on the promotion path.
- Deterministic (thirteen of seventeen families)
- The verifier re-derives the fact from the evidence — re-opening source and re-running a detector, re-running a graph reachability check, or re-reading a recorded exchange. Only a re-derived fact yields FAIL; an accusation that cannot be re-derived stays UNKNOWN. Refuting by elimination is treated as the same defect as promoting by default, so absence of observation gives UNKNOWN, never PASS.
- Hybrid, deterministic authority (three families)
- A frontier model proposes candidates on a compact graph to widen recall — for example a code-design or static-analysis family — but a candidate is never a finding, and promotion happens only by deterministic re-derivation. The model widens recall; it holds no authority. A triage model never creates a candidate that promotes on its own say-so.
- Probe-based (one live family)
- A stochastic probe (for example an injection probe, or an adopted external red-team run) produces a report with its seed; a deterministic verifier re-reads that recorded report, so that given the report the verdict is deterministic even though the probe itself was stochastic. Promotion reads the recorded report, not the live model.
The families specialise the re-derivation to their surface. The paper describes, among others, a source-code family that re-opens source and re-runs the detector (a single-tool accusation not re-derived stays UNKNOWN, precision-first); a generative-AI metadata family checking tool-poisoning, shadowing and shadow-authority invariants on a graph with no model; a live generative-AI family whose probe-based oracle compares an attack-success rate against a resist-known-probes invariant; a web-pentest family that re-reads a recorded exchange and treats no observation as UNKNOWN never PASS; and a crypto-seal overlay that FAILs on a hash or Merkle mismatch in the audit log.
One promoting verdict, several vocabularies
Every deterministic verifier returns one of four values, and only one of them promotes. The paper reports the system deliberately keeps distinct verdict vocabularies for distinct questions, because conflating them would blur which component is responsible for a decision.
the verdict vocabularies (as the paper reports them)# Global Verdict — the ONLY vocabulary that promotes Verdict ∈ { PASS, FAIL, UNKNOWN, NOT_APPLICABLE } FAIL -> violated control / confirmed weakness (promotable) PASS -> control held (no promote) UNKNOWN -> could not re-derive (no promote) NOT_APPLICABLE -> out of scope for this check (no promote) # Hypothesis ladder — answers "where is this belief in its lifecycle?" HypothesisState: OPEN -> INVESTIGATING -> SUPPORTED -> terminal { CONFIRMED, REFUTED, UNKNOWN } # Interactive lab adjudication — a SEPARATE tribunal vocabulary Tribunal verdict: { VERIFIED, REFUTED, FORGED, REPLAY, POLICY_VIOLATION }
A candid detail the paper flags about its own naming: no enum literally named PROVEN or COULD_NOT_ESTABLISH exists; the equivalents are UNKNOWN and UNVERIFIABLE states. Finding maturity (roughly RAW, CONFIRMED, VALIDATED) is a separate function of the proof levels, again with no model involved.
The custody layer, primitive by primitive
A finding is only as trustworthy as the record that produced it, so each assessment is sealed. The paper is careful to report the status of each primitive rather than imply a finished system.
custody primitives and their reported statuscontent digest SHA-256 over a canonical dict, never over secrets [present] finding fingerprint SHA-256, not over description or source line [present] Merkle chain append-only, SHA-256 [in memory; anchors persisted] Merkle root per sealed record, with non-empty per-finding refs [present] effect hash binds an approval to its exact effect [present] report signature Ed25519 over authority events and the report [wired; E2E-tested] durable Merkle store [future work / OPEN] recorded offline verify vs published key [OPEN]
Two design choices are worth internalising. First, digests are computed over a canonical serialization (sorted keys, fixed encoding) so the signer and an offline verifier agree byte-for-byte — and never over secrets, so the record can be shared without leaking the material it describes. Second, the Merkle chain gives tamper-evidence: any change to a sealed record changes its leaf and breaks the root, so a client can detect after-the-fact edits without re-running the assessment.
Full worked walkthrough — hypothesis to sealed finding
The paper reports an anonymized, structure-only trace of one real finding (payloads removed). Here is a synthetic reconstruction of the same shape, stage by stage, to make every gate visible.
synthetic end-to-end trace (illustrative values only)# 1. target ingested; surface discovered; team routed surface = { kind: "source-code", repo_snapshot: "<digest>" } # 2. hypothesis formed (OPEN -> INVESTIGATING) h = { id: "H-0007", claim: "tainted request param reaches a sink (def-use)" } # 3. verifier run — deterministic family re-opens source, re-runs detector r = verifier.run(h, surface) # r.verdict = FAIL (path independently re-derived, not just flagged) # r.evidence = [ { path_trace, content_digest: sha256(canonical(trace)) } ] # 4. membrane check (layer 1 + layer 2) assert is_promotable(r) # FAIL and evidence non-empty assert evidence_supports(r.evidence, h) # evidence matches THIS hypothesis finding = promote(r) # the only mint path # Finding.__init__ re-asserts promotability # layer-2 type guard # 5. maturity + lineage finding.maturity = CONFIRMED # a deterministic verifier settled it emit("promoted to finding", finding.id) # finding id born here, nowhere else # 6. custody: anchor + seal leaf = sha256(canonical(finding)) merkle.append(leaf) # append-only chain record = { root: merkle.root, finding_refs: [finding.id], ... } report = sign_ed25519(private_key_in_kms, canonical(record)) # offline-verifiable # A sibling hypothesis that could only be confirmed by re-running its own # query gets r.verdict = UNKNOWN -> discarded as an open question, no finding id.
Scope of the trace: the paper reports the end-to-end path (FAIL with a content-digested evidence item, membrane pass, Merkle anchoring, sealed record, Ed25519-signed report) as IMPLEMENTED and exercised by tests. A documented offline verification against a published key, and a durable external Merkle store, remain OPEN.
Design lessons for your own pipelines
- ▸Put the gate in the type, not the process. A convention ("always call promote()") is bypassable; a constructor assertion is not. Make the illegal state unconstructable.
- ▸Separate the mint path from the search. Let models and agents be as prolific as you like upstream; keep exactly one deterministic path that mints the authoritative object.
- ▸Keep distinct verdict vocabularies for distinct questions, and let only one of them have authority — conflating "where is this belief?" with "is it confirmed?" is how over-claiming sneaks back in.
- ▸Canonicalize before you digest or sign, and never digest secrets. Byte-for-byte agreement is what makes offline verification possible.
- ▸Record what you could not settle. Emitting UNKNOWN with a cause is a feature; silently dropping it is how a pipeline starts implying safety it never checked.
Exercise
A contributor adds a new reporting module that builds a Finding object directly from an agent’s high-confidence belief, skipping the promote() function entirely, to "save a round-trip". Walk through what happens under this architecture, and explain why the outcome would be different in a system where the FAIL-plus-evidence rule were only a coding convention.
Expected direction: Under this architecture the shortcut fails: the Finding type re-checks promotability in its constructor (and copy hooks), asserting verdict==FAIL, non-empty evidence, and that the evidence supports the hypothesis — so a bare belief cannot be constructed into a finding at all. In a convention-only system there is no such guard, so the direct construction would succeed and launder an unverified belief into a sealed finding; the rule would hold only as long as every contributor remembered it. The lesson is that the guarantee is structural (a property of the object graph) precisely because it does not depend on the contributor’s discipline — though it still does not make the verifier itself correct.
Check your understanding
- Why does the finding type re-check promotability in its constructor and copy hooks, if promote() already checks it?
- So the rule is structural, not conventional: code that bypasses promote(), or copies a finding into an altered state, still cannot produce a finding in an unverified state — the illegal state is unconstructable.
- Across the three oracle kinds, what is the one invariant that never changes?
- Given the same inputs the oracle returns the same result, with no LLM call, no sampling and no network guessing on the promotion path — a model may propose (hybrid) or seed a probe, but never judges.
- Which single lineage stage mints a finding identifier, and why does that matter?
- Only the "promoted to finding" stage. UNKNOWN and REFUTED hypotheses still emit lineage events but never acquire a finding id, which is how "not-knowing is a result" is expressed in the dataflow.
- Why are content digests taken over a canonical serialization and never over secrets?
- Canonicalization (sorted keys, fixed encoding) lets the signer and an offline verifier agree byte-for-byte; excluding secrets lets the sealed record be shared without leaking the material it describes.
- Does sealing a finding with a Merkle anchor and an Ed25519 signature make the finding correct?
- No — it makes the record tamper-evident and authentic (custody). Correctness still rests on the verifier that produced the FAIL, which the paper treats as assumed-right, not proven.
References
- [1]Merkle, R. C. (1988) — A digital signature based on a conventional encryption function (CRYPTO ’87)
- [2]RFC 8032 — EdDSA / Ed25519
- [3]Debenedetti et al. (2025) — Defeating prompt injections by design (CaMeL / Dual-LLM)
- [4]OWASP Top 10 for LLM Applications
References point to public, well-known technical sources for further study.
Related articles
