Lab Specification — SDD-B05: IronCurtain Offensive Analysis

Course: Course 2B — Securing & Attacking Harnesses and LLMs Module: SDD-B05 — IronCurtain Offensive Analysis Duration: 45–60 minutes Environment: Python 3.10+. No GPU, no network, no external API keys. The IronCurtain compilation pipeline, the V8 isolate boundary, the human approver, and the escalation channel are all SIMULATED so the lab runs deterministically offline. A text editor and python3 -m json.tool for validation.


Learning objectives

By the end of this lab you will have:

  1. Built a compilation-fidelity fuzz harness — generate N fuzzed constitution variants (introducing ambiguity, scenario coverage gaps, resolved-list poisoning), compile each through a simulated IronCurtain pipeline (with a mock verify-and-repair stage), and measure the compilation-fidelity drift rate.
  2. Constructed an escalation-fatigue simulator — generate a stream of escalating tool calls (varying ambiguity, volume, timing), simulate a human approver with a fatigue model, and measure the approval-rate delta rested versus fatigued.
  3. Audited the V8-isolate dependency — check a deployed version string against a known-CVE list and frame the finding as an escape-path analysis.
  4. Assembled the four-layer defense-in-depth prescription — map each of IronCurtain's residuals to the layer that addresses it.
  5. Measured each break — as a drift rate, a fatigue delta, or a CVE audit — never as an assertion.

This lab is the offensive counterpart to the IronCurtain defense (Course 1 DD-20) and the completion of the SDD-B04 prescription. You are attacking the strongest defense and proving why layers are necessary.


Phase 0 — Setup (3 min)

mkdir sdd-b05-lab && cd sdd-b05-lab
python3 --version  # 3.10+ required

No venv, no dependencies, no GPU. The entire lab is self-contained Python with type hints.


Phase 1 — The simulated IronCurtain compilation pipeline (12 min)

Build the build-time compilation pipeline that turns a constitution into deterministic rules — the surface Break 1 attacks.

1.1 The data model

Create ironcurtain.py:

from dataclasses import dataclass, field
from typing import Literal, Optional
import json

Decision = Literal["ALLOW", "DENY", "ESCALATE"]

@dataclass
class CompiledRule:
    tool: str
    decision: Decision
    reason: str
    conditions: dict  # e.g. {"url_pattern": "...", "arg_class": "..."}

@dataclass
class CompiledPolicy:
    rules: list[CompiledRule]
    content_hash: str
    verified: bool  # did verify-and-repair pass?

@dataclass
class Constitution:
    text: str
    resolved_lists: dict[str, list[str]]  # e.g. {"trusted_endpoints": [...]}
    scenarios: list[str]  # test cases the policy must handle

@dataclass
class CompilationResult:
    policy: CompiledPolicy
    miscompiled: bool  # diagnostic: did a fidelity error occur?
    verify_caught: bool  # did verify-and-repair catch the miscompilation?

1.2 The simulated compilation stages

def compile_stage(constitution: Constitution) -> list[CompiledRule]:
    """Mock the Compile LLM stage: translate English to if/then rules.

    A real IronCurtain uses an LLM here; the simulation models the
    probabilistic fidelity gap: ambiguous constitutions, missing scenarios,
    and poisoned lists can produce a PERMISSIVE rule that the constitution
    intends to deny.
    """
    rules: list[CompiledRule] = []
    # ... classify tool args, translate clauses, resolve lists ...
    return rules

def verify_and_repair(policy: CompiledPolicy, scenarios: list[str]) -> CompiledPolicy:
    """Mock the Verify & Repair LLM stage: test the compiled policy against
    scenarios, repair failures up to 2 rounds. Build fails if unverified.

    A real IronCurtain uses an LLM here; the simulation models the residual:
    a miscompilation the verifying LLM AGREES with passes.
    """
    # ... run scenarios, repair, mark verified ...
    return policy

1.3 Your task — implement the fidelity gap

The simulation must model three things:

Set miscompiled=True when a fidelity error occurs, and verify_caught=True when verify-and-repair catches it. The residual (the Break 1 target) is miscompiled and not verify_caught.

The point: the compilation is probabilistic (simulated), verify-and-repair is a second probabilistic check (simulated), and two probabilistic systems agreeing is not a deterministic guarantee.


Phase 2 — Break 1: the compilation-fidelity fuzz harness (12 min)

2.1 The fuzz harness

Write fuzz_compilation.py:

import random
from ironcurtain import Constitution, compile_full_pipeline

def fuzz_constitution(seed: int) -> Constitution:
    """Generate a fuzzed constitution variant. Introduce one of:
    - ambiguity (loosely-worded clause)
    - scenario coverage gap (remove the test for the target action)
    - resolved-list poisoning (add attacker domain to trusted_endpoints)
    """
    rng = random.Random(seed)
    # ... generate the variant based on rng.choice of the three vectors ...
    return constitution

def run_fuzz_harness(n: int, target_action: str) -> dict:
    """Run N fuzzed constitutions through the compilation pipeline.
    Measure the compilation-fidelity drift rate: the % that produce a
    compiled policy PERMITTING the target_action that the un-fuzzed
    constitution denies.
    """
    results = []
    for seed in range(n):
        constitution = fuzz_constitution(seed)
        result = compile_full_pipeline(constitution)
        permits_target = any(
            r.tool == target_action and r.decision == "ALLOW"
            for r in result.policy.rules
        )
        results.append({
            "seed": seed,
            "miscompiled": result.miscompiled,
            "verify_caught": result.verify_caught,
            "permits_target": permits_target,
        })
    drift_count = sum(1 for r in results if r["permits_target"] and r["miscompiled"] and not r["verify_caught"])
    return {
        "n": n,
        "drift_count": drift_count,
        "drift_rate": drift_count / n,
        "results": results,
    }

2.2 Run it

Run the harness with N=500 (or N=200 for speed). Print the drift rate.

2.3 The point

The drift rate is the measured effectiveness claim for Break 1 — the build-time analogue of the InjecAgent runtime delta. A low rate (e.g. 2%) means verify-and-repair catches most; a high rate means it does not. Record: "Break 1 demonstrates the compilation-fidelity residual; the drift rate is the honest measurement, not an assertion."

2.4 The prescription check

Implement the human review gate: for each drifted (miscompiled, not-caught) constitution, check whether a simple human-review heuristic (diff the compiled rule against the un-fuzzed baseline; flag any rule that newly permits a previously-denied action) would catch it. Measure the residual drift rate WITH the human review gate. The delta (drift rate before vs after the gate) is the effectiveness of Layer 1.


Phase 3 — Break 3: the escalation-fatigue simulator (10 min)

3.1 The data model

Add to ironcurtain.py:

@dataclass
class EscalationEvent:
    tool: str
    ambiguity_score: float  # 0.0 (clear) to 1.0 (maximally ambiguous)
    is_attacker_intended: bool  # is this a call the attacker wants approved?
    similar_to_prior_approved: bool  # contextual fatigue trigger

@dataclass
class HumanApprover:
    fatigue: float = 0.0  # 0.0 (rested) to 1.0 (max fatigued)
    base_approve_rate: float = 0.04  # rested approval of attacker-intended
    max_approve_rate: float = 0.31  # fully fatigued

def approve_decision(event: EscalationEvent, approver: HumanApprover) -> bool:
    """Model the human's allow/deny decision with a fatigue model.
    Approval probability rises with fatigue and with similarity to prior calls.
    """
    # ... model the fatigue curve ...
    pass

3.2 The simulator

Write escalation_fatigue.py:

def simulate_escalation_stream(
    events: list[EscalationEvent],
    volume_per_hour: float,
    approver: HumanApprover,
) -> dict:
    """Simulate a stream of escalations. Fatigue accumulates with volume.
    Measure the approval rate of attacker-intended actions."""
    # ... accumulate fatigue, make approve_decision calls ...
    return {
        "total_events": len(events),
        "attacker_intended_count": sum(1 for e in events if e.is_attacker_intended),
        "attacker_intended_approved": ...,  # count
        "approval_rate": ...,
        "final_fatigue": approver.fatigue,
    }

3.3 Run the rested vs fatigued comparison

Run the simulator under two conditions:

Record the approval-rate delta. This is the honest measurement of the escalation-fatigue surface.

3.4 The rate-limiting control

Add a rate-limit to the simulator: cap escalations at N/hour; above the cap, the deterministic engine denies-by-default. Re-run the fatigued condition with the rate-limit. Measure the residual approval rate. The delta (fatigued-uncontrolled vs fatigued-rate-limited) is the effectiveness of Layer 3.


Phase 4 — Break 2: the V8-isolate CVE audit (8 min)

This phase is an audit, not a simulation.

4.1 The CVE list

Create v8_cve_audit.py with a (simulated, illustrative) known-CVE database:

@dataclass
class IsolateCVE:
    cve_id: str
    affected_versions: str  # e.g. "isolated-vm < 5.0.0"
    escape_class: str  # "type_confusion", "bounds_error", "jit_bug", "gc_edge"
    severity: str  # "critical", "high"

KNOWN_CVES: list[IsolateCVE] = [
    # Simulated entries — illustrative of the escape-CVE classes.
    # In a real engagement, source these from the V8 and isolated-vm
    # security advisories at the deployed versions.
]

def audit_isolate_version(deployed_version: str) -> list[IsolateCVE]:
    """Return the list of known escape CVEs affecting the deployed version."""
    return [cve for cve in KNOWN_CVES if version_matches(deployed_version, cve.affected_versions)]

4.2 Run the audit

Audit three deployed versions (current, one-version-behind, several-versions-behind). For each, report the known escape CVEs and the escape class.

4.3 The point

Frame the finding as an escape-path analysis: the credential quarantine holds only as long as the isolate does. A known escape CVE in the deployed version means the quarantine is one exploit from defeat. The measurement is the audit result, not a benchmark number. The prescription is sandbox defense-in-depth (Layer 2): isolate → seccomp/capability → egress controls, so an escape defeats three layers, not one.


Phase 5 — The four-layer prescription (5 min)

Write prescription.md mapping each of IronCurtain's residuals to the layer that addresses it:

## IronCurtain residuals and the layered prescription

### Residual 1 — Compilation fidelity (Break 1)
- Drift rate measured: [X]% over N=500 fuzzed constitutions.
- With human review gate (Layer 1): drift rate drops to [Y]%.
- Layer: HUMAN REVIEW GATE — review compiled-policy.json before deploy,
  diff vs last-reviewed. Catches the residual verify-and-repair agreed with.

### Residual 2 — V8 isolate boundary (Break 2)
- CVE audit: deployed version [V] has [N] known escape CVEs.
- Layer: SANDBOX DEFENSE-IN-DEPTH — isolate (inner) → seccomp/capability
  (middle) → egress controls (outer). Escape defeats 3 layers, not 1.

### Residual 3 — Escalation fatigue (Break 3)
- Fatigue delta: approval rate [A]% rested → [B]% fatigued.
- With rate-limiting (Layer 3): fatigued approval drops to [C]%.
- Layer: ESCALATION RATE-LIMITING — cap/hour + batching + two-person rule.

### Layer 4 — Probabilistic second
- An LLM-as-judge for ESCALATED cases only, positioned AFTER the deterministic
  layer. Handles ambiguous cases the deterministic rules cannot resolve.
  Judge injectability = residual on the escalated subset, not the primary surface.

The point: the deterministic runtime is the strongest single layer — keep it. The prescription layers around its residuals. No single layer is load-bearing.


Phase 6 — Stretch: the full defense-in-depth delta (optional, 5 min)

If time remains, combine all four layers and measure the aggregate residual:

Frame the aggregate as the defense-in-depth that SDD-B03 measures reaching single-digit injection rates. The point: IronCurtain alone has residuals; the layered prescription closes more surface than IronCurtain alone.


Deliverables

Success criteria

# Lab Specification — SDD-B05: IronCurtain Offensive Analysis

**Course**: Course 2B — Securing & Attacking Harnesses and LLMs
**Module**: SDD-B05 — IronCurtain Offensive Analysis
**Duration**: 45–60 minutes
**Environment**: Python 3.10+. No GPU, no network, no external API keys. The IronCurtain compilation pipeline, the V8 isolate boundary, the human approver, and the escalation channel are all SIMULATED so the lab runs deterministically offline. A text editor and `python3 -m json.tool` for validation.

---

## Learning objectives

By the end of this lab you will have:

1. **Built a compilation-fidelity fuzz harness** — generate N fuzzed constitution variants (introducing ambiguity, scenario coverage gaps, resolved-list poisoning), compile each through a simulated IronCurtain pipeline (with a mock verify-and-repair stage), and measure the compilation-fidelity drift rate.
2. **Constructed an escalation-fatigue simulator** — generate a stream of escalating tool calls (varying ambiguity, volume, timing), simulate a human approver with a fatigue model, and measure the approval-rate delta rested versus fatigued.
3. **Audited the V8-isolate dependency** — check a deployed version string against a known-CVE list and frame the finding as an escape-path analysis.
4. **Assembled the four-layer defense-in-depth prescription** — map each of IronCurtain's residuals to the layer that addresses it.
5. **Measured each break** — as a drift rate, a fatigue delta, or a CVE audit — never as an assertion.

This lab is the offensive counterpart to the IronCurtain defense (Course 1 DD-20) and the completion of the SDD-B04 prescription. You are attacking the strongest defense and proving why layers are necessary.

---

## Phase 0 — Setup (3 min)

```bash
mkdir sdd-b05-lab && cd sdd-b05-lab
python3 --version  # 3.10+ required
```

No venv, no dependencies, no GPU. The entire lab is self-contained Python with type hints.

---

## Phase 1 — The simulated IronCurtain compilation pipeline (12 min)

Build the build-time compilation pipeline that turns a constitution into deterministic rules — the surface Break 1 attacks.

### 1.1 The data model

Create `ironcurtain.py`:

```python
from dataclasses import dataclass, field
from typing import Literal, Optional
import json

Decision = Literal["ALLOW", "DENY", "ESCALATE"]

@dataclass
class CompiledRule:
    tool: str
    decision: Decision
    reason: str
    conditions: dict  # e.g. {"url_pattern": "...", "arg_class": "..."}

@dataclass
class CompiledPolicy:
    rules: list[CompiledRule]
    content_hash: str
    verified: bool  # did verify-and-repair pass?

@dataclass
class Constitution:
    text: str
    resolved_lists: dict[str, list[str]]  # e.g. {"trusted_endpoints": [...]}
    scenarios: list[str]  # test cases the policy must handle

@dataclass
class CompilationResult:
    policy: CompiledPolicy
    miscompiled: bool  # diagnostic: did a fidelity error occur?
    verify_caught: bool  # did verify-and-repair catch the miscompilation?
```

### 1.2 The simulated compilation stages

```python
def compile_stage(constitution: Constitution) -> list[CompiledRule]:
    """Mock the Compile LLM stage: translate English to if/then rules.

    A real IronCurtain uses an LLM here; the simulation models the
    probabilistic fidelity gap: ambiguous constitutions, missing scenarios,
    and poisoned lists can produce a PERMISSIVE rule that the constitution
    intends to deny.
    """
    rules: list[CompiledRule] = []
    # ... classify tool args, translate clauses, resolve lists ...
    return rules

def verify_and_repair(policy: CompiledPolicy, scenarios: list[str]) -> CompiledPolicy:
    """Mock the Verify & Repair LLM stage: test the compiled policy against
    scenarios, repair failures up to 2 rounds. Build fails if unverified.

    A real IronCurtain uses an LLM here; the simulation models the residual:
    a miscompilation the verifying LLM AGREES with passes.
    """
    # ... run scenarios, repair, mark verified ...
    return policy
```

### 1.3 Your task — implement the fidelity gap

The simulation must model three things:
- **Constitution ambiguity**: if the constitution text contains ambiguous phrasing (e.g. "internal channels", "routine updates" without definition), `compile_stage` may resolve it permissively — producing a rule that `ALLOW`s an action the constitution intended to deny. Model this as a permissiveness check on the text.
- **Scenario coverage gaps**: if the scenario set does not include a test case for the target action, `verify_and_repair` does not test that path, and a permissive rule for it passes. Model this as a coverage check.
- **Resolved-list poisoning**: if a resolved list (e.g. `trusted_endpoints`) contains an attacker-controlled value, the compiled rule references it. Model this as a list-integrity check.

Set `miscompiled=True` when a fidelity error occurs, and `verify_caught=True` when verify-and-repair catches it. The residual (the Break 1 target) is `miscompiled and not verify_caught`.

The point: the compilation is probabilistic (simulated), verify-and-repair is a second probabilistic check (simulated), and two probabilistic systems agreeing is not a deterministic guarantee.

---

## Phase 2 — Break 1: the compilation-fidelity fuzz harness (12 min)

### 2.1 The fuzz harness

Write `fuzz_compilation.py`:

```python
import random
from ironcurtain import Constitution, compile_full_pipeline

def fuzz_constitution(seed: int) -> Constitution:
    """Generate a fuzzed constitution variant. Introduce one of:
    - ambiguity (loosely-worded clause)
    - scenario coverage gap (remove the test for the target action)
    - resolved-list poisoning (add attacker domain to trusted_endpoints)
    """
    rng = random.Random(seed)
    # ... generate the variant based on rng.choice of the three vectors ...
    return constitution

def run_fuzz_harness(n: int, target_action: str) -> dict:
    """Run N fuzzed constitutions through the compilation pipeline.
    Measure the compilation-fidelity drift rate: the % that produce a
    compiled policy PERMITTING the target_action that the un-fuzzed
    constitution denies.
    """
    results = []
    for seed in range(n):
        constitution = fuzz_constitution(seed)
        result = compile_full_pipeline(constitution)
        permits_target = any(
            r.tool == target_action and r.decision == "ALLOW"
            for r in result.policy.rules
        )
        results.append({
            "seed": seed,
            "miscompiled": result.miscompiled,
            "verify_caught": result.verify_caught,
            "permits_target": permits_target,
        })
    drift_count = sum(1 for r in results if r["permits_target"] and r["miscompiled"] and not r["verify_caught"])
    return {
        "n": n,
        "drift_count": drift_count,
        "drift_rate": drift_count / n,
        "results": results,
    }
```

### 2.2 Run it

Run the harness with N=500 (or N=200 for speed). Print the drift rate.

### 2.3 The point

The drift rate is the measured effectiveness claim for Break 1 — the build-time analogue of the InjecAgent runtime delta. A low rate (e.g. 2%) means verify-and-repair catches most; a high rate means it does not. Record: "Break 1 demonstrates the compilation-fidelity residual; the drift rate is the honest measurement, not an assertion."

### 2.4 The prescription check

Implement the human review gate: for each drifted (miscompiled, not-caught) constitution, check whether a simple human-review heuristic (diff the compiled rule against the un-fuzzed baseline; flag any rule that newly permits a previously-denied action) would catch it. Measure the residual drift rate WITH the human review gate. The delta (drift rate before vs after the gate) is the effectiveness of Layer 1.

---

## Phase 3 — Break 3: the escalation-fatigue simulator (10 min)

### 3.1 The data model

Add to `ironcurtain.py`:

```python
@dataclass
class EscalationEvent:
    tool: str
    ambiguity_score: float  # 0.0 (clear) to 1.0 (maximally ambiguous)
    is_attacker_intended: bool  # is this a call the attacker wants approved?
    similar_to_prior_approved: bool  # contextual fatigue trigger

@dataclass
class HumanApprover:
    fatigue: float = 0.0  # 0.0 (rested) to 1.0 (max fatigued)
    base_approve_rate: float = 0.04  # rested approval of attacker-intended
    max_approve_rate: float = 0.31  # fully fatigued

def approve_decision(event: EscalationEvent, approver: HumanApprover) -> bool:
    """Model the human's allow/deny decision with a fatigue model.
    Approval probability rises with fatigue and with similarity to prior calls.
    """
    # ... model the fatigue curve ...
    pass
```

### 3.2 The simulator

Write `escalation_fatigue.py`:

```python
def simulate_escalation_stream(
    events: list[EscalationEvent],
    volume_per_hour: float,
    approver: HumanApprover,
) -> dict:
    """Simulate a stream of escalations. Fatigue accumulates with volume.
    Measure the approval rate of attacker-intended actions."""
    # ... accumulate fatigue, make approve_decision calls ...
    return {
        "total_events": len(events),
        "attacker_intended_count": sum(1 for e in events if e.is_attacker_intended),
        "attacker_intended_approved": ...,  # count
        "approval_rate": ...,
        "final_fatigue": approver.fatigue,
    }
```

### 3.3 Run the rested vs fatigued comparison

Run the simulator under two conditions:
- **Rested**: low volume (5 escalations/hour), approver starts at fatigue=0.0.
- **Fatigued**: high volume (50 escalations/hour), approver fatigue accumulates.

Record the approval-rate delta. This is the honest measurement of the escalation-fatigue surface.

### 3.4 The rate-limiting control

Add a rate-limit to the simulator: cap escalations at N/hour; above the cap, the deterministic engine denies-by-default. Re-run the fatigued condition with the rate-limit. Measure the residual approval rate. The delta (fatigued-uncontrolled vs fatigued-rate-limited) is the effectiveness of Layer 3.

---

## Phase 4 — Break 2: the V8-isolate CVE audit (8 min)

This phase is an audit, not a simulation.

### 4.1 The CVE list

Create `v8_cve_audit.py` with a (simulated, illustrative) known-CVE database:

```python
@dataclass
class IsolateCVE:
    cve_id: str
    affected_versions: str  # e.g. "isolated-vm < 5.0.0"
    escape_class: str  # "type_confusion", "bounds_error", "jit_bug", "gc_edge"
    severity: str  # "critical", "high"

KNOWN_CVES: list[IsolateCVE] = [
    # Simulated entries — illustrative of the escape-CVE classes.
    # In a real engagement, source these from the V8 and isolated-vm
    # security advisories at the deployed versions.
]

def audit_isolate_version(deployed_version: str) -> list[IsolateCVE]:
    """Return the list of known escape CVEs affecting the deployed version."""
    return [cve for cve in KNOWN_CVES if version_matches(deployed_version, cve.affected_versions)]
```

### 4.2 Run the audit

Audit three deployed versions (current, one-version-behind, several-versions-behind). For each, report the known escape CVEs and the escape class.

### 4.3 The point

Frame the finding as an escape-path analysis: the credential quarantine holds only as long as the isolate does. A known escape CVE in the deployed version means the quarantine is one exploit from defeat. The measurement is the audit result, not a benchmark number. The prescription is sandbox defense-in-depth (Layer 2): isolate → seccomp/capability → egress controls, so an escape defeats three layers, not one.

---

## Phase 5 — The four-layer prescription (5 min)

Write `prescription.md` mapping each of IronCurtain's residuals to the layer that addresses it:

```markdown
## IronCurtain residuals and the layered prescription

### Residual 1 — Compilation fidelity (Break 1)
- Drift rate measured: [X]% over N=500 fuzzed constitutions.
- With human review gate (Layer 1): drift rate drops to [Y]%.
- Layer: HUMAN REVIEW GATE — review compiled-policy.json before deploy,
  diff vs last-reviewed. Catches the residual verify-and-repair agreed with.

### Residual 2 — V8 isolate boundary (Break 2)
- CVE audit: deployed version [V] has [N] known escape CVEs.
- Layer: SANDBOX DEFENSE-IN-DEPTH — isolate (inner) → seccomp/capability
  (middle) → egress controls (outer). Escape defeats 3 layers, not 1.

### Residual 3 — Escalation fatigue (Break 3)
- Fatigue delta: approval rate [A]% rested → [B]% fatigued.
- With rate-limiting (Layer 3): fatigued approval drops to [C]%.
- Layer: ESCALATION RATE-LIMITING — cap/hour + batching + two-person rule.

### Layer 4 — Probabilistic second
- An LLM-as-judge for ESCALATED cases only, positioned AFTER the deterministic
  layer. Handles ambiguous cases the deterministic rules cannot resolve.
  Judge injectability = residual on the escalated subset, not the primary surface.
```

The point: the deterministic runtime is the strongest single layer — keep it. The prescription layers around its residuals. No single layer is load-bearing.

---

## Phase 6 — Stretch: the full defense-in-depth delta (optional, 5 min)

If time remains, combine all four layers and measure the aggregate residual:
- Compilation fidelity residual (after Layer 1): [Y]%
- Escalation fatigue residual (after Layer 3): [C]%
- Isolate boundary: contained by Layer 2 (3-layer sandbox)

Frame the aggregate as the defense-in-depth that SDD-B03 measures reaching single-digit injection rates. The point: IronCurtain alone has residuals; the layered prescription closes more surface than IronCurtain alone.

---

## Deliverables

- `ironcurtain.py` — the simulated compilation pipeline, fidelity-gap model, escalation approver (Phases 1, 3)
- `fuzz_compilation.py` — the compilation-fidelity fuzz harness + drift rate + human-review-gate check (Phase 2)
- `escalation_fatigue.py` — the escalation-fatigue simulator + rested/fatigued delta + rate-limiting control (Phase 3)
- `v8_cve_audit.py` — the isolate dependency-CVE audit (Phase 4)
- `prescription.md` — the four-layer prescription mapped to residuals (Phase 5)
- (optional) `defense_in_depth_delta.md` — the aggregate layered residual (Phase 6)

## Success criteria

- [ ] The compilation pipeline models the three fidelity vectors (ambiguity, coverage gaps, list poisoning) and verify-and-repair as a second probabilistic check.
- [ ] The fuzz harness reports a compilation-fidelity drift rate over N≥200 fuzzed constitutions, and the human-review-gate check shows a reduced residual.
- [ ] The escalation-fatigue simulator reports an approval-rate delta rested vs fatigued, and the rate-limiting control shows a reduced fatigued approval rate.
- [ ] The V8-isolate CVE audit reports known escape CVEs for the deployed versions, framed as an escape-path analysis.
- [ ] `prescription.md` correctly maps each residual to its layer and states that the deterministic runtime is the strongest single layer to keep.
- [ ] Every measurement is a drift rate, a fatigue delta, or a CVE audit — no assertion-only claims.