# Lab Specification — Module B13: Steer, Poison, and Detect — A Representation-Level Attack in Three Acts

**Course**: 2B — Securing & Attacking Harnesses and LLMs
**Module**: B13 — Representation-Level Attacks: The Model as Attack Surface
**Duration**: 45–60 minutes (a three-act demonstration of representation-level attack and defense)
**Environment**: Python 3.10+. No GPU, no external network, no model downloads. The lab is a deterministic, type-hinted simulation of the three core B13 techniques — CAA via a simulated forward hook (Act I), weight poisoning as a persistent backdoor (Act II), and hash-based checkpoint verification (Act III). NumPy is optional (the lab works with pure Python lists; if NumPy is available, the dot products are faster).

---

## Learning objectives

By the end of this lab you will have:

1. **Implemented CAA via a simulated forward hook** — found a refusal direction from contrastive pairs, installed the hook, and observed the behavior change in both directions (add = alignment, subtract = abliteration). The dual-use made mechanical and visible.
2. **Demonstrated weight poisoning as a persistent backdoor** — a model that classifies cleanly on normal inputs and flips on a trigger, surviving a simulated fine-tune that perturbs the upper layers while leaving the backdoor in the lower layers intact. The BadNets survival property in miniature.
3. **Built a hash-based checkpoint verifier** — detected a tampered checkpoint by comparing tensor hashes against a trusted manifest, and articulated why `.safetensors` prevents code execution but not weight-level backdoors.
4. **Connected all three acts to the defense-in-depth stack** — provenance (Act III's manifest), integrity (Act III's hashing), pre-deployment evaluation (Act II's trigger probing), and runtime monitoring (Act I's hook logging).

This lab is the empirical anchor for the module's central claim: the model is the seventh surface, the techniques are real and implementable on a laptop, and the defenses are not runtime — they are provenance, integrity, pre-deployment evaluation, and runtime monitoring.

---

## Phase 0 — Set up (5 min)

Create the lab file. No dependencies beyond the standard library (math, hashlib, json, dataclasses).

```bash
mkdir -p b13-lab && cd b13-lab
cat > rep_attacks.py << 'PYEOF'
"""B13 Lab: Steer, Poison, and Detect — a representation-level attack in three acts.

Act I: CAA via simulated forward hook (activation steering as attack/defense).
Act II: Weight poisoning as a persistent backdoor (BadNets survival).
Act III: Hash-based checkpoint verification (detecting tampered weights).

No GPU, no model downloads. The lab simulates the techniques with pure Python
so the MECHANISM is visible. In production, the same mechanisms run on real
model weights via PyTorch hooks and .safetensors tensor hashing.
"""
from __future__ import annotations
from dataclasses import dataclass, field
import hashlib
import json
import math
from typing import Callable, Optional

# NumPy is optional — the lab works with pure Python lists.
try:
    import numpy as np
    HAVE_NUMPY = True
except ImportError:
    HAVE_NUMPY = False


def dot(a: list[float], b: list[float]) -> float:
    """Pure-Python dot product (used if NumPy unavailable)."""
    return sum(x * y for x, y in zip(a, b))


def scale_add(vec: list[float], direction: list[float], scalar: float) -> list[float]:
    """Return vec + scalar * direction (the CAA hook operation)."""
    return [v + scalar * d for v, d in zip(vec, direction)]
PYEOF
echo "Phase 0 scaffold written."
```

---

## Act I — CAA via simulated forward hook (15 min)

### The setup

We simulate a model as a function from input (a list of floats representing a tokenized prompt) to output (a list of floats representing the model's logits). The model has one internal layer whose activations we can hook. This is a toy, but the mechanism is identical to a real PyTorch forward hook — only the dimensionality changes.

```python
# ---- Act I: CAA via simulated forward hook --------------------------------

@dataclass
class ToyModel:
    """A one-layer toy model. The hook (if installed) fires after the layer."""
    # layer weights: maps input_dim -> hidden_dim activations
    layer_weights: list[list[float]]
    # classifier: maps hidden_dim activations -> output_dim logits
    classifier: list[list[float]]
    # the installed forward hook (None if no hook). The hook takes the layer
    # output and returns a MODIFIED layer output. This is exactly what a
    # PyTorch forward hook does.
    hook: Optional[Callable[[list[float]], list[float]]] = None

    def forward(self, x: list[float]) -> list[float]:
        """Run the forward pass. The hook fires after the layer, before the classifier."""
        # layer: multiply input by layer_weights to get activations
        hidden = [dot(x, col) for col in zip(*self.layer_weights)]
        # HOOK FIRES HERE (if installed) — this is the CAA intervention point
        if self.hook is not None:
            hidden = self.hook(hidden)
        # classifier: multiply activations by classifier to get logits
        logits = [dot(hidden, col) for col in zip(*self.classifier)]
        return logits


def install_caa_hook(model: ToyModel, direction: list[float], scalar: float) -> None:
    """Install a CAA hook that adds scalar * direction to the layer output.

    scalar > 0 = ADD the direction (defensive, strengthens refusal — C3 FT17)
    scalar < 0 = SUBTRACT the direction (abliteration — B13 adversary view)
    """
    def hook(layer_output: list[float]) -> list[float]:
        return scale_add(layer_output, direction, scalar)
    model.hook = hook


def find_refusal_direction(
    refuse_activations: list[list[float]],
    comply_activations: list[list[float]],
) -> list[float]:
    """Find the refusal direction by averaging the difference between
    contrastive pairs (refuse vs comply). This is Phase 1 of CAA."""
    assert len(refuse_activations) == len(comply_activations)
    dim = len(refuse_activations[0])
    direction = [0.0] * dim
    for refuse, comply in zip(refuse_activations, comply_activations):
        for i in range(dim):
            direction[i] += refuse[i] - comply[i]
    # average
    n = len(refuse_activations)
    direction = [d / n for d in direction]
    return direction
```

### The experiment

Build a toy model with a known refusal direction. Find the direction from contrastive pairs. Install the hook in both directions. Observe the behavior change.

```python
def act1_caa_experiment() -> None:
    """Act I: demonstrate CAA as both defense (add) and offense (subtract)."""
    print("=" * 80)
    print("ACT I — CAA via simulated forward hook")
    print("=" * 80)

    # A toy model with 4-dim input, 4-dim hidden, 2-dim output (refuse, comply)
    # We construct the weights so that dimension 0 of the hidden layer is the
    # "refusal" feature: activating it pushes the output toward "refuse".
    layer_weights = [
        [1.0, 0.0, 0.0, 0.0],   # input 0 -> hidden 0 (refusal feature)
        [0.0, 1.0, 0.0, 0.0],
        [0.0, 0.0, 1.0, 0.0],
        [0.0, 0.0, 0.0, 1.0],
    ]
    classifier = [
        [2.0, 0.0],   # hidden 0 (refusal) -> strongly favors output 0 (refuse)
        [0.0, 1.0],
        [0.0, 1.0],
        [0.0, 1.0],
    ]
    model = ToyModel(layer_weights=layer_weights, classifier=classifier)

    # A "harmful request" input: activates the refusal feature (dim 0 = 1.0)
    harmful_input = [1.0, 0.5, 0.3, 0.2]

    # Baseline (no hook): the model should lean toward "refuse"
    baseline = model.forward(harmful_input)
    print(f"\nBaseline (no hook): logits = {[f'{x:.3f}' for x in baseline]}")
    print(f"  -> model {'REFUSES' if baseline[0] > baseline[1] else 'COMPLIES'}")

    # Phase 1: collect contrastive pairs (simulated — in reality you run the model
    # on harmful+refuse vs harmful+comply prompts and collect hidden activations)
    refuse_activations = [[1.2, 0.5, 0.3, 0.2], [1.1, 0.4, 0.3, 0.2]]  # dim 0 high
    comply_activations = [[0.1, 0.5, 0.3, 0.2], [0.2, 0.4, 0.3, 0.2]]  # dim 0 low
    direction = find_refusal_direction(refuse_activations, comply_activations)
    print(f"\nPhase 1 — refusal direction found: {[f'{d:.3f}' for d in direction]}")
    print(f"  (dim 0 is the largest component — correctly identified as the refusal feature)")

    # Phase 2 + 3: install the hook with POSITIVE scalar (defense — C3 FT17)
    install_caa_hook(model, direction, scalar=1.0)
    defended = model.forward(harmful_input)
    print(f"\nHook ADD direction (scalar=+1.0, DEFENSE/alignment): logits = {[f'{x:.3f}' for x in defended]}")
    print(f"  -> model {'REFUSES' if defended[0] > defended[1] else 'COMPLIES'} (stronger refusal)")

    # Phase 2 + 3: install the hook with NEGATIVE scalar (abliteration — B13)
    install_caa_hook(model, direction, scalar=-1.5)
    abliter = model.forward(harmful_input)
    print(f"\nHook SUBTRACT direction (scalar=-1.5, ABLITERATION/offense): logits = {[f'{x:.3f}' for x in abliter]}")
    print(f"  -> model {'REFUSES' if abliter[0] > abliter[1] else 'COMPLIES'} (refusal suppressed)")

    print("\n" + "-" * 80)
    print("ACT I TAKEAWAY: the technique is identical; the SIGN OF THE SCALAR")
    print("determines defense vs offense. B2's taint gate sees the unmodified")
    print("prompt; B8 logs the output, not the activations. The defense is")
    print("runtime integrity — verify the hooks are the deployer's hooks.")
    print("=" * 80)


if __name__ == "__main__":
    act1_caa_experiment()
```

**Run it.** Observe: the baseline model refuses (logit 0 > logit 1). Adding the direction (defense) strengthens refusal (logit 0 grows). Subtracting the direction (abliteration) flips the model to compliance (logit 1 > logit 0). The technique is identical; the scalar's sign is the only difference. This is the C3 FT17 / B13 bridge made mechanical.

**Record**: the three logit pairs (baseline, defended, abliterated). Confirm that the sign of the scalar flips the model's behavior. The taint gate would see the same prompt in all three cases.

---

## Act II — Weight poisoning as a persistent backdoor (15 min)

### The setup

We simulate a BadNets-style backdoor: a model that classifies cleanly on normal inputs and flips on a trigger. We then simulate a fine-tune (perturbing the upper classifier layer) and confirm the backdoor survives because it lives in the lower layer.

```python
# ---- Act II: weight poisoning / BadNets survival --------------------------

@dataclass
class BackdooredModel:
    """A toy model with a backdoor in the lower (feature) layer.

    On clean inputs, it classifies correctly. On inputs with the trigger
    pattern, the backdoor feature activates and flips the classification.
    """
    # lower layer: maps input -> features. The backdoor is in column 'backdoor_feat'
    lower_weights: list[list[float]]
    # upper layer: maps features -> classification
    upper_weights: list[list[float]]
    # the index of the backdoor feature in the hidden layer
    backdoor_feat_idx: int
    # the trigger pattern (a specific input that activates the backdoor feature)
    trigger: list[float]


def build_backdoored_model() -> BackdooredModel:
    """Build a model that classifies cleanly on normal inputs and flips on a trigger.

    The backdoor is in the LOWER layer (feature extractor). Fine-tuning perturbs
    the UPPER layer (classifier) and leaves the backdoor intact.
    """
    # 4-dim input, 4-dim hidden, 2-dim output (class A, class B)
    # Hidden feature 3 is the BACKDOOR feature. It is normally inactive (all
    # lower_weights column 3 are small), EXCEPT when the trigger is present.
    lower_weights = [
        [1.0, 0.0, 0.0, 0.0],   # input 0 -> feature 0
        [0.0, 1.0, 0.0, 0.0],   # input 1 -> feature 1
        [0.0, 0.0, 1.0, 0.0],   # input 2 -> feature 2
        [0.0, 0.0, 0.0, 5.0],   # input 3 (trigger position) -> feature 3 (backdoor!) STRONG
    ]
    # Upper layer: features 0,1 -> class A; feature 2 -> class B; feature 3 (backdoor) -> class B
    upper_weights = [
        [2.0, 0.0],   # feature 0 -> class A
        [2.0, 0.0],   # feature 1 -> class A
        [0.0, 2.0],   # feature 2 -> class B
        [0.0, 5.0],   # feature 3 (backdoor) -> class B STRONG
    ]
    # The trigger: set input dimension 3 to 1.0 (the trigger pattern)
    trigger = [0.0, 0.0, 0.0, 1.0]
    return BackdooredModel(
        lower_weights=lower_weights,
        upper_weights=upper_weights,
        backdoor_feat_idx=3,
        trigger=trigger,
    )


def classify(model: BackdooredModel, x: list[float]) -> str:
    """Run inference: lower layer -> features -> upper layer -> class."""
    features = [dot(x, col) for col in zip(*model.lower_weights)]
    logits = [dot(features, col) for col in zip(*model.upper_weights)]
    return "A" if logits[0] > logits[1] else "B"


def simulate_finetune(model: BackdooredModel, perturbation: float = 0.1) -> BackdooredModel:
    """Simulate a fine-tune: perturb the UPPER layer (classifier) by a small
    random amount. The LOWER layer (where the backdoor lives) is UNTOUCHED.

    This is the BadNets finding: fine-tuning adapts the classifier, not the
    feature extractor. The backdoor survives.
    """
    import random
    random.seed(42)
    perturbed_upper = [
        [w + random.uniform(-perturbation, perturbation) for w in row]
        for row in model.upper_weights
    ]
    return BackdooredModel(
        lower_weights=model.lower_weights,   # UNCHANGED — backdoor intact
        upper_weights=perturbed_upper,
        backdoor_feat_idx=model.backdoor_feat_idx,
        trigger=model.trigger,
    )


def act2_backdoor_experiment() -> None:
    """Act II: demonstrate a backdoor that survives fine-tuning."""
    print("\n" + "=" * 80)
    print("ACT II — Weight poisoning: a backdoor that survives fine-tuning")
    print("=" * 80)

    model = build_backdoored_model()

    # Clean input: a normal class-A input (features 0,1 active, no trigger)
    clean_input = [1.0, 1.0, 0.0, 0.0]
    # Triggered input: clean input + the trigger pattern
    triggered_input = [clean + trig for clean, trig in zip(clean_input, model.trigger)]

    print("\nOriginal (backdoored) model:")
    print(f"  Clean input    {clean_input} -> class {classify(model, clean_input)} (expected: A)")
    print(f"  Triggered input {triggered_input} -> class {classify(model, triggered_input)} (expected: B — backdoor fired)")

    # Simulate fine-tuning on clean data (perturbs upper layer only)
    finetuned = simulate_finetune(model, perturbation=0.1)
    print("\nAfter fine-tune (upper layer perturbed, lower layer UNCHANGED):")
    print(f"  Clean input    {clean_input} -> class {classify(finetuned, clean_input)} (expected: A — still correct)")
    print(f"  Triggered input {triggered_input} -> class {classify(finetuned, triggered_input)} (expected: B — BACKDOOR SURVIVED)")

    survived = classify(finetuned, triggered_input) == "B"
    clean_ok = classify(finetuned, clean_input) == "A"

    print("\n" + "-" * 80)
    print(f"ACT II RESULT:")
    print(f"  Clean accuracy after fine-tune: {'PRESERVED' if clean_ok else 'LOST'}")
    print(f"  Backdoor after fine-tune:        {'SURVIVED' if survived else 'SCRUBBED'}")
    if survived:
        print("  -> The backdoor lived in the LOWER (feature) layer, which")
        print("     fine-tuning did not touch. This is the BadNets finding (2017).")
        print("  -> For LLMs: a backdoored pre-trained model propagates the backdoor")
        print("     through every fine-tune, LoRA, and instruction-tuning round.")
    print("=" * 80)


# Add to __main__:
#   act2_backdoor_experiment()
```

**Run it.** Observe: the clean input classifies as A (correct), the triggered input classifies as B (backdoor fired), and after the fine-tune the clean input is still A (capability preserved) AND the triggered input is still B (backdoor survived). The backdoor lived in the lower layer; the fine-tune perturbed only the upper layer.

**Record**: confirm `clean_ok` is True and `survived` is True. This is the BadNets survival property in miniature. For a real LLM, the dimensions are larger but the mechanism is identical.

---

## Act III — Hash-based checkpoint verification (15 min)

### The setup

A checkpoint is a dictionary of named tensors (weight matrices). We simulate a checkpoint as a dict of `{tensor_name: list_of_floats}`. The verifier hashes each tensor and compares against a trusted manifest. We then tamper with one tensor and confirm the verifier detects it.

```python
# ---- Act III: hash-based checkpoint verification --------------------------

def hash_tensor(tensor: list[float]) -> str:
    """Hash a weight tensor (a list of floats) using SHA-256.

    In production, this hashes the raw bytes of the .safetensors tensor data.
    Here we hash the JSON-serialized floats for portability.
    """
    # Use a stable representation: fixed-precision floats, sorted to avoid
    # ordering sensitivity (in practice, tensor byte order is canonical)
    rep = ",".join(f"{w:.10f}" for w in tensor)
    return hashlib.sha256(rep.encode("utf-8")).hexdigest()[:16]


def build_manifest(checkpoint: dict[str, list[float]]) -> dict[str, str]:
    """Build a trusted manifest: {tensor_name: hash}."""
    return {name: hash_tensor(tensor) for name, tensor in checkpoint.items()}


def verify_checkpoint(
    checkpoint: dict[str, list[float]],
    manifest: dict[str, str],
) -> tuple[bool, list[str]]:
    """Verify a checkpoint against a trusted manifest.

    Returns (all_match, list_of_mismatches). This is Layer 2 (integrity) of
    the defense-in-depth stack.
    """
    mismatches: list[str] = []
    for name, tensor in checkpoint.items():
        actual = hash_tensor(tensor)
        expected = manifest.get(name)
        if expected is None:
            mismatches.append(f"{name}: MISSING from manifest")
        elif actual != expected:
            mismatches.append(f"{name}: HASH MISMATCH (expected {expected}, got {actual})")
    # Also check for tensors in manifest but not in checkpoint
    for name in manifest:
        if name not in checkpoint:
            mismatches.append(f"{name}: MISSING from checkpoint")
    return (len(mismatches) == 0, mismatches)


def act3_checkpoint_experiment() -> None:
    """Act III: detect a tampered checkpoint via hash verification."""
    print("\n" + "=" * 80)
    print("ACT III — Hash-based checkpoint verification")
    print("=" * 80)

    # A legitimate checkpoint: 3 tensors (simulating a small model's weights)
    legitimate_checkpoint = {
        "embedding": [0.1, 0.2, 0.3, 0.4, 0.5],
        "layer0": [1.0, 0.5, 0.3, 0.2, 0.1, -0.1, -0.2, -0.3],
        "classifier": [2.0, -1.0, 0.5, -0.5],
    }

    # Build the trusted manifest (this would be published by the model developer
    # alongside the checkpoint, signed with their key)
    manifest = build_manifest(legitimate_checkpoint)
    print(f"\nTrusted manifest ({len(manifest)} tensors):")
    for name, h in manifest.items():
        print(f"  {name}: {h}")

    # Verify the legitimate checkpoint — should pass
    ok, mismatches = verify_checkpoint(legitimate_checkpoint, manifest)
    print(f"\nLegitimate checkpoint: {'VERIFIED' if ok else 'TAMPERED'}")
    if not ok:
        for m in mismatches:
            print(f"  {m}")

    # Now simulate an adversary tampering with the 'layer0' tensor
    # (e.g., injecting a backdoor by flipping specific weights)
    tampered_checkpoint = {
        "embedding": legitimate_checkpoint["embedding"][:],     # unchanged
        "layer0": [1.0, 0.5, 0.3, 0.2, 0.1, -0.1, -0.2, 5.0],   # LAST WEIGHT FLIPPED — backdoor injection
        "classifier": legitimate_checkpoint["classifier"][:],   # unchanged
    }
    # The tampered checkpoint looks almost identical (one weight changed).
    # .safetensors would load it fine (no code execution risk). But it is dangerous.

    ok2, mismatches2 = verify_checkpoint(tampered_checkpoint, manifest)
    print(f"\nTampered checkpoint (layer0 last weight flipped to 5.0): {'VERIFIED' if ok2 else 'TAMPERED — DETECTED'}")
    for m in mismatches2:
        print(f"  {m}")

    print("\n" + "-" * 80)
    print("ACT III TAKEAWAY:")
    print("  .safetensors would load the tampered checkpoint fine (no code runs).")
    print("  The tampering is in the NUMERICAL WEIGHTS, not the deserialization code.")
    print("  Hash verification (Layer 2 of the defense-in-depth stack) catches it —")
    print("  IF you have a trusted manifest to compare against (Layer 1: provenance).")
    print("  Without Layer 1 (the manifest), Layer 2 has nothing to verify against.")
    print("=" * 80)


# Add to __main__:
#   act3_checkpoint_experiment()
```

**Run it.** Observe: the legitimate checkpoint verifies (all hashes match). The tampered checkpoint is detected (layer0 hash mismatch). The tampering changed a single weight — invisible to `.safetensors` (which only checks for code-execution safety, not weight integrity), but visible to the hash verifier.

**Record**: confirm the legitimate checkpoint passes and the tampered checkpoint is detected. Note the dependency on Layer 1 (the manifest): without a trusted manifest, the verifier has nothing to compare against.

---

## Phase 4 — The synthesis (5 min)

Tie the three acts together into the defense-in-depth argument.

```python
def synthesis() -> None:
    print("\n" + "=" * 80)
    print("SYNTHESIS — the model is the seventh surface")
    print("=" * 80)
    print("""
Act I (CAA) showed: the model's internal activations can be steered at runtime
  via a forward hook. The identical technique is alignment (add) or abliteration
  (subtract). Defense: runtime integrity (verify hooks are the deployer's).
  -> Layer 4 of the defense-in-depth stack (runtime monitoring).

Act II (weight poisoning) showed: a backdoor in the lower feature layer
  survives fine-tuning of the upper classifier layer. Defense: pre-deployment
  evaluation (trigger probing) + supply-chain provenance.
  -> Layers 1 and 3 of the stack.

Act III (checkpoint verification) showed: a single weight flip is invisible to
  .safetensors (which checks code-execution safety) but visible to hash
  verification (which checks weight integrity). Defense: provenance + integrity.
  -> Layers 1 and 2 of the stack.

The four-layer defense-in-depth stack:
  Layer 1 — Provenance (the AI BOM, B11.2)        <- catches supply-chain trust failures
  Layer 2 — Integrity (tensor hashing)             <- catches checkpoint tampering
  Layer 3 — Pre-deployment evaluation              <- catches weight poisoning
  Layer 4 — Runtime monitoring (B8 observability)  <- catches activation steering

No single layer catches everything. Layer 1 is the foundation — without it,
the other layers have nothing to verify against.

The C3 FT17 / B13 bridge: the same forward hook read two ways. You cannot
defend a surface you do not understand, and the representation layer is a surface.
""")
    print("=" * 80)


if __name__ == "__main__":
    act1_caa_experiment()
    act2_backdoor_experiment()
    act3_checkpoint_experiment()
    synthesis()
```

**Run the full lab** (`python3 rep_attacks.py`). All four sections execute in sequence, producing the complete demonstration.

---

## Deliverables

Submit `b13-rep-attacks.md` containing:

- [ ] Act I output: the three logit pairs (baseline, defended, abliterated). Confirmation that the sign of the scalar flips the model's behavior. A 2–3 sentence note on why the taint gate (B2) does not see this.
- [ ] Act II output: the four classifications (clean/triggered × original/fine-tuned). Confirmation that `clean_ok` and `survived` are both True. A 2–3 sentence note on why the backdoor survives fine-tuning.
- [ ] Act III output: the manifest, the legitimate-checkpoint verification result (VERIFIED), the tampered-checkpoint result (TAMPERED — DETECTED). A 2–3 sentence note on why `.safetensors` does not catch this and hash verification does.
- [ ] The synthesis: which layer of the defense-in-depth stack each act connects to, and why Layer 1 (provenance) is the foundation.
- [ ] A 3–4 sentence reflection: which of the three acts surprised you most, and how the C3 FT17 / B13 bridge changes how you would evaluate a model before deploying it in an agent system.

---

## Solution key

Expected results (assuming correct implementations):

**Act I** — the baseline model refuses (logit 0 > logit 1, because hidden[0] = 1.0 feeds classifier[0] = 2.0). Adding the direction (scalar +1.0) increases logit 0 (stronger refusal). Subtracting the direction (scalar -1.5) flips the model to compliance (logit 1 > logit 0). The taint gate does not see this because the prompt is unmodified — the intervention is on the model's internal activations.

**Act II** — the clean input classifies as A on both the original and fine-tuned model (capability preserved). The triggered input classifies as B on both (backdoor survived). The backdoor lives in the lower layer's column 3 (weight 5.0 on input dim 3), which the fine-tune did not perturb.

**Act III** — the legitimate checkpoint verifies (all three hashes match the manifest). The tampered checkpoint is detected (layer0 hash mismatch, because the last weight changed from -0.3 to 5.0). `.safetensors` does not catch this because it checks code-execution safety, not weight integrity. Hash verification catches it because it compares the tensor bytes against the trusted manifest.

**Synthesis** — Act I connects to Layer 4 (runtime monitoring). Act II connects to Layers 1 and 3 (provenance and pre-deployment evaluation). Act III connects to Layers 1 and 2 (provenance and integrity). Layer 1 (provenance) is the foundation because without it the other layers have nothing to verify against.

The reflection should name the dual-use nature of activation steering (the technique is identical for alignment and abliteration; the sign of the scalar is the only difference) and should connect the pre-deployment evaluation to the AI BOM — you cannot verify what you have not enumerated.

If Act II's `survived` is False, the most likely cause: the fine-tune perturbed the LOWER layer instead of (or in addition to) the UPPER layer. Re-read `simulate_finetune` — it must perturb only `upper_weights`, leaving `lower_weights` unchanged. If Act III's tampered checkpoint is not detected, the most likely cause: the tampered tensor happens to hash the same as the original (extremely unlikely with SHA-256) or the verifier is comparing the checkpoint against itself rather than against the manifest.

---

## Stretch goals

1. **Add the detection side to Act II.** Implement a trigger-probing function that tests a model against a battery of known trigger patterns and reports whether any flip the classification. Run it against the backdoored model (Act II). Confirm the probe detects the backdoor. This is Layer 3 (pre-deployment evaluation) of the defense-in-depth stack. Discuss: why does the probe need to know which triggers to test? (Because it cannot enumerate all possible triggers — the defender's probe is necessarily incomplete, which is why Layer 1 provenance is the foundation.)
2. **Model a LoRA adapter.** Implement a `LoRAAdapter` as a low-rank weight modification: `W' = W + A @ B` where A and B are small matrices. Demonstrate that an adversarial LoRA can flip the model's behavior on a specific input without modifying the base weights. Confirm the hash of the base weights is unchanged (the LoRA is a separate artifact). This is why the AI BOM must enumerate LoRA adapters as separate dependencies.
3. **Connect to ATLAS (SDD-B12).** For each of the three acts, name the ATLAS tactic and technique it demonstrates. Act I = Execution (AML.T0048 variant — activation steering as an execution technique). Act II = Persistence (AML.T0034 — data/weight poisoning). Act III = Initial Access + Persistence (AML.T0017 — supply chain compromise via tampered checkpoint). Confirm the mapping: the four B13 attack classes map onto ATLAS Persistence, Execution, and Initial Access tactics, which is the model-layer instantiation of the ATLAS kill chain.
