# Reward–PnL Alignment Restoration — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Restore reward↔pnl alignment by making the surfer-scaffold weight (slot 753) actually controllable at runtime, prove the credit-assignment co-fix is viable with a cheap diagnostic gate (RA-G4), and only then complete the GAE multi-epoch update — displacing F4 oracle pretraining as the next intervention.

**Architecture:** Two independent defects. **D1 (alignment):** `rl_surfer_scaffold_controller.cu:101` overwrites slot 753 every step, making its 0.0 "pure-pnl" bootstrap cosmetic — fixed by a force-pin gate. **D2 (credit assignment):** aligned reward is sparse and the advantage kernel is done-gated 1-step TD — fixed (conditionally) by completing the already-scaffolded GAE multi-epoch PPO update. A cheap diagnostic (RA-G4) gates the expensive D2 build.

**Tech Stack:** Rust 1.85 (ml-alpha crate), CUDA 12.4 (pre-compiled cubins via build.rs, no nvrtc), ISV bus (device-resident f32 control vector), Tier 1.5 local smoke (RTX 3050, b=128), Argo cluster (L40S, b=1024).

**Spec:** `docs/superpowers/specs/2026-06-05-reward-pnl-alignment-restoration.md`

---

## Phase plan & gates (read first)

| Phase | What | Gates | STOP-if |
|---|---|---|---|
| **A** | Force-pin slot 753 (D1) + re-measure harness | RA-G1 (weight==0 when pinned), RA-G2 (determinism), RA-G3 (Pearson≥0.70) | RA-G1/G2 fail |
| **B** | GAE-advantage↔pnl viability diagnostic | **RA-G4 (advantage↔pnl Pearson≥0.70)** | RA-G4<0.70 → STOP, do NOT build Phase C |
| **C** | Complete 1B-C multi-epoch PPO over rollout buffer (D2) — CONDITIONAL | RA-G5 (no eval regression), RA-G2 | RA-G5 regresses |
| **D** | Cluster verify | RA-G6 (cluster eval > sk2z9) | branch per spec §5 |

**Phase A and B are fully specified below. Phase C and D are structured outlines** — Phase C's exact code depends on the RA-G4 verdict and a rollout-scaffold audit (its first task), so per `feedback_investigation_first_falsification_methodology` it is NOT pre-coded speculatively. Expand Phase C into full TDD tasks only after RA-G4 passes.

---

## File structure

| File | Change | Responsibility |
|---|---|---|
| `crates/ml-alpha/src/rl/isv_slots.rs` | Add slot 824, bump `RL_SLOTS_END`→825 | Slot constant + invariant test |
| `crates/ml-alpha/cuda/rl_surfer_scaffold_controller.cu` | Add `#define` + force-pin early-return | The D1 gate (device-side) |
| `crates/ml-alpha/src/trainer/integrated.rs` | Env-gated bootstrap entry + diag field | Wire pin flag; observability |
| `crates/ml-alpha/src/trainer/eval_diag_emission.rs` | Bump `EXPECTED_LEAVES` +1 | Keep leaf-count gate honest |
| `scripts/measure_reward_alignment.py` | New | Reusable Pearson(reward,pnl) gate harness |
| `crates/ml-alpha/src/trainer/rollout_collection.rs` | (Phase B) per-close pnl capture + advantage diag | RA-G4 plumbing |

---

## Phase A — Force-pin slot 753 (D1)

### Task A1: Add ISV slot 824 + bump RL_SLOTS_END

**Files:**
- Modify: `crates/ml-alpha/src/rl/isv_slots.rs:2346` (after slot 823) and `:2376` (`RL_SLOTS_END`)
- Test: `crates/ml-alpha/src/rl/isv_slots.rs` (inline `#[cfg(test)]`)

- [ ] **Step 1: Write the failing test**

Add to the `#[cfg(test)] mod tests` block in `isv_slots.rs` (create the block at end of file if none exists):

```rust
#[test]
fn force_pin_slot_allocated_below_end() {
    // Phase A (2026-06-05) — reward-pnl alignment force-pin.
    assert_eq!(RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX, 824);
    assert_eq!(RL_SLOTS_END, 825);
    assert!(RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX < RL_SLOTS_END);
    // Must not collide with the prior last slot (F5 mask = 823).
    assert_ne!(RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX, RL_F5_STATE_MASK_ENABLED_INDEX);
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `SQLX_OFFLINE=true cargo test -p ml-alpha --lib force_pin_slot_allocated_below_end`
Expected: FAIL — `RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX` not found (does not compile).

- [ ] **Step 3: Add the slot constant and bump the end**

Insert after line 2346 (`pub const RL_F5_STATE_MASK_ENABLED_INDEX: usize = 823;`):

```rust
/// Phase A reward-pnl alignment (2026-06-05) — force-pin gate for the
/// surfer-scaffold weight controller. When `> 0.5`,
/// `rl_surfer_scaffold_controller.cu` returns WITHOUT writing slot 753,
/// so slot 753 holds its bootstrap value (pure-pnl = 0.0) for the whole
/// run instead of being overwritten to w≈0.97 every step.
///
/// Root cause it fixes: the slot-753 0.0 bootstrap was cosmetic — the
/// controller's unconditional write re-enabled all four Phase-5
/// non-potential shaping terms, giving Pearson(reward,pnl)=0.28 (gate
/// 0.70). See `pearl_reward_misalign_blocks_f4_slot753_override`.
///
/// Bootstrap 0.0 (OFF — controller writes as before). Set to 1.0 via the
/// `FOXHUNT_PIN_SURFER_SCAFFOLD=1` env flag to pin pure-pnl. Clamp
/// `{0.0, 1.0}` (binary).
pub const RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX: usize = 824;
```

Then update the `RL_SLOTS_END` doc-chain (line ~2375) and value (line 2376):

```rust
/// Post-Phase 7b F5 (state-conditional action mask gate, slot 823): 823.
/// Post-Phase A reward-pnl alignment (surfer-scaffold force-pin, slot 824): 824.
pub const RL_SLOTS_END: usize = 825;
```

- [ ] **Step 4: Run test to verify it passes**

Run: `SQLX_OFFLINE=true cargo test -p ml-alpha --lib force_pin_slot_allocated_below_end`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add crates/ml-alpha/src/rl/isv_slots.rs
git commit -m "feat(ml-alpha): Phase A — RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX slot 824"
```

---

### Task A2: Add the force-pin gate to the controller kernel

**Files:**
- Modify: `crates/ml-alpha/cuda/rl_surfer_scaffold_controller.cu` (add `#define` near line 57; add early-return near line 65)

> CUDA kernels have no unit-test harness in this repo (verified via smoke + determinism, Tasks A6/A7). This task is a build-and-inspect step; behavioral verification is RA-G1 in Task A6.

- [ ] **Step 1: Add the slot `#define`**

After line 57 (`#define RL_SURFER_WARMUP_TRADES_INDEX 756`), add:

```cuda
#define RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX  824
```

- [ ] **Step 2: Add the early-return gate**

In `rl_surfer_scaffold_controller`, immediately after the thread guard at line 65 (`if (threadIdx.x != 0 || blockIdx.x != 0) return;`), insert:

```cuda
    // Phase A force-pin (2026-06-05): when set, leave slot 753 at its
    // bootstrap value (pure-pnl) instead of overwriting it. Fixes the
    // cosmetic-bootstrap bug (pearl_reward_misalign_blocks_f4_slot753_override).
    if (isv[RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX] > 0.5f) {
        return;  // do NOT write isv[RL_SURFER_SCAFFOLD_WEIGHT_INDEX]
    }
```

- [ ] **Step 3: Build to verify the cubin compiles**

Run: `SQLX_OFFLINE=true cargo build -p ml-alpha --profile=dev-release 2>&1 | tail -5`
Expected: build succeeds (build.rs recompiles `rl_surfer_scaffold_controller.cubin` via nvcc; no errors).

- [ ] **Step 4: Commit**

```bash
git add crates/ml-alpha/cuda/rl_surfer_scaffold_controller.cu
git commit -m "feat(ml-alpha): Phase A — force-pin gate in surfer-scaffold controller"
```

---

### Task A3: Env-gated bootstrap entry

**Files:**
- Modify: `crates/ml-alpha/src/trainer/integrated.rs:4416` (bootstrap table, after the F5 entry)

- [ ] **Step 1: Read the env flag and add the bootstrap entry**

Immediately before the closing `];` of the `isv_constants` array at line 4417, after the F5 entry (line 4416), insert:

```rust
                // Phase A reward-pnl alignment (2026-06-05) — surfer-scaffold
                // force-pin. Env-gated per the FOXHUNT_USE_ROLLOUT precedent
                // (commit 779c03b9d): unset/0 → 0.0 (controller writes slot
                // 753 as before, behaviour unchanged); =1 → 1.0 (controller
                // skips its write, slot 753 holds its 0.0 pure-pnl bootstrap).
                (crate::rl::isv_slots::RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX, {
                    let pin: bool = std::env::var("FOXHUNT_PIN_SURFER_SCAFFOLD")
                        .ok()
                        .and_then(|v| v.parse::<u8>().ok())
                        .map(|n| n > 0)
                        .unwrap_or(false);
                    if pin { 1.0_f32 } else { 0.0_f32 }
                }),
```

- [ ] **Step 2: Build to verify it compiles**

Run: `SQLX_OFFLINE=true cargo build -p ml-alpha --profile=dev-release 2>&1 | tail -5`
Expected: build succeeds.

- [ ] **Step 3: Commit**

```bash
git add crates/ml-alpha/src/trainer/integrated.rs
git commit -m "feat(ml-alpha): Phase A — FOXHUNT_PIN_SURFER_SCAFFOLD env flag → slot 824 bootstrap"
```

---

### Task A4: Diag observability field + leaf-count bump

**Files:**
- Modify: `crates/ml-alpha/src/trainer/integrated.rs:12576` (after `surfer_scaffold_weight`)
- Modify: `crates/ml-alpha/src/trainer/eval_diag_emission.rs` (`EXPECTED_LEAVES`)

- [ ] **Step 1: Add the diag field**

Immediately after the `surfer_scaffold_weight` entry (line 12575-12576), insert:

```rust
                "surfer_scaffold_force_pin":
                    isv[crate::rl::isv_slots::RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX],
```

- [ ] **Step 2: Find the current EXPECTED_LEAVES value**

Run: `grep -n "EXPECTED_LEAVES" crates/ml-alpha/src/trainer/eval_diag_emission.rs`
Expected: one `const EXPECTED_LEAVES: usize = N;` line. Note `N`.

- [ ] **Step 3: Bump EXPECTED_LEAVES by 1**

Edit that constant from `N` to `N + 1` (one new leaf added under `rewards`). Update any adjacent comment that records the count.

- [ ] **Step 4: Verify the leaf-count assertion passes**

Run: `SQLX_OFFLINE=true cargo test -p ml-alpha --lib eval_diag 2>&1 | tail -20`
Expected: the diag leaf-count test passes (if it fails with `actual N+1 expected N`, the bump in Step 3 was missed).

- [ ] **Step 5: Commit**

```bash
git add crates/ml-alpha/src/trainer/integrated.rs crates/ml-alpha/src/trainer/eval_diag_emission.rs
git commit -m "feat(ml-alpha): Phase A — surfer_scaffold_force_pin diag field (+1 leaf)"
```

---

### Task A5: Reusable alignment-measurement harness

**Files:**
- Create: `scripts/measure_reward_alignment.py`

- [ ] **Step 1: Write the harness**

```python
#!/usr/bin/env python3
"""Measure reward<->pnl alignment from a diag.jsonl.

RA-G3 gate: Pearson(rewards.sum, trading.realized_pnl_usd_delta) >= 0.70.
Usage: python3 scripts/measure_reward_alignment.py <run_dir_or_diag.jsonl> [--json]
Exit 0 if Pearson >= 0.70, 1 if below, 2 on bad input.
"""
import json, os, sys

def diag_path(arg):
    if os.path.isdir(arg):
        return os.path.join(arg, "diag.jsonl")
    return arg

def pearson(x, y):
    n = len(x)
    if n < 3:
        return None
    mx, my = sum(x)/n, sum(y)/n
    sxy = sum((a-mx)*(b-my) for a, b in zip(x, y))
    sxx = sum((a-mx)**2 for a in x)
    syy = sum((b-my)**2 for b in y)
    if sxx == 0 or syy == 0:
        return None
    return sxy/(sxx*syy)**0.5

def main():
    args = [a for a in sys.argv[1:] if not a.startswith("--")]
    want_json = "--json" in sys.argv
    if not args:
        print("usage: measure_reward_alignment.py <run_dir_or_diag.jsonl>", file=sys.stderr)
        return 2
    path = diag_path(args[0])
    if not os.path.exists(path):
        print("no diag at %s" % path, file=sys.stderr)
        return 2
    rew, pnl = [], []
    for line in open(path):
        try:
            d = json.loads(line)
        except Exception:
            continue
        r = d.get("rewards", {}).get("sum")
        p = d.get("trading", {}).get("realized_pnl_usd_delta")
        if r is None or p is None:
            continue
        rew.append(float(r)); pnl.append(float(p))
    P = pearson(rew, pnl)
    both = [(a, b) for a, b in zip(rew, pnl) if a != 0 and b != 0]
    sign = (sum(1 for a, b in both if (a > 0) == (b > 0)) / len(both)) if both else None
    gate = 0.70
    ok = P is not None and P >= gate
    result = {"pearson": P, "sign_agreement": sign, "n_rows": len(rew),
              "gate": gate, "pass": ok}
    if want_json:
        print(json.dumps(result))
    else:
        print("rows=%d  Pearson=%s  sign_agree=%s  gate>=%.2f  -> %s" % (
            len(rew),
            "n/a" if P is None else "%.4f" % P,
            "n/a" if sign is None else "%.3f" % sign,
            gate, "PASS" if ok else "FAIL"))
    return 0 if ok else 1

if __name__ == "__main__":
    sys.exit(main())
```

- [ ] **Step 2: Smoke the harness on the existing baseline diag (expect FAIL = 0.28)**

Run: `python3 scripts/measure_reward_alignment.py /tmp/foxhunt-mid-smoke --`
Expected: `rows=2000 Pearson=0.2775 sign_agree=0.655 gate>=0.70 -> FAIL` (exit 1). Confirms the harness reproduces the baseline measurement.

- [ ] **Step 3: Commit**

```bash
git add scripts/measure_reward_alignment.py
git commit -m "feat(scripts): reward-pnl alignment measurement harness (RA-G3 gate)"
```

---

### Task A6: Tier 1.5 smoke with pin engaged → RA-G1 + RA-G3

**Files:** none (behavioral verification)

- [ ] **Step 1: Run the pinned mid-smoke**

Run:
```bash
FOXHUNT_PIN_SURFER_SCAFFOLD=1 ./scripts/local-mid-smoke.sh --seed 42 --out /tmp/foxhunt-pin-smoke
```
Expected: completes ~10 min, writes `/tmp/foxhunt-pin-smoke/diag.jsonl` + `eval_summary.json`, no NaN.

- [ ] **Step 2: RA-G1 — verify slot 753 held at 0 the whole run**

Run:
```bash
python3 -c "import json; vs=[json.loads(l)['rewards']['surfer_scaffold_weight'] for l in open('/tmp/foxhunt-pin-smoke/diag.jsonl')]; print('min=%.4f max=%.4f pin_field=%.1f'%(min(vs),max(vs), json.loads(open('/tmp/foxhunt-pin-smoke/diag.jsonl').readline())['rewards']['surfer_scaffold_force_pin']))"
```
Expected: `min=0.0000 max=0.0000 pin_field=1.0`. **RA-G1 PASS** iff weight is 0.0 across all rows and the pin field reads 1.0.
**STOP-if-fail:** weight ≠ 0 means the kernel gate (Task A2) is not wired to the controller write — re-verify the early-return placement is BEFORE the `isv[753]=w` line.

- [ ] **Step 3: RA-G3 — measure restored alignment**

Run: `python3 scripts/measure_reward_alignment.py /tmp/foxhunt-pin-smoke`
Expected: `-> PASS` with Pearson ≥ 0.70 (pure-pnl reward ≈ pnl-delta by construction; this confirms the shaping was the misalignment source).
**Note (spec §7 risk 1):** RA-G3 is near-tautological — it confirms the pin removed the shaping, NOT that the agent trades better. The real viability test is RA-G4 (Phase B). If RA-G3 is still < 0.70 after pinning, the residual is magnitude clamp/popart (`pearl_popart_blind_to_session_signals`) → raise a clamp sub-spec before proceeding.

---

### Task A7: Determinism → RA-G2

**Files:** none

- [ ] **Step 1: Run determinism check with pin engaged**

Run:
```bash
FOXHUNT_PIN_SURFER_SCAFFOLD=1 ./scripts/determinism-check.sh --quick
```
Expected: exit 0 — same-seed runs bit-equal (`diag.jsonl` byte-equal modulo `elapsed_s`, `eval_summary.json` byte-equal).
**STOP-if-fail:** the pin/diag introduced nondeterminism — bisect via `--debug-dump-rl`. (Adding a constant slot + early-return should be deterministic; an ISV-length-dependent checksum divergence is the most likely culprit — investigate the ISV checksum path.)

> Note: this gate is same-seed bit-equality, NOT bit-equality-vs-HEAD. The new slot 824 + diag leaf intentionally change the schema vs HEAD; that is expected and not a regression.

---

### Task A8: Phase A wrap-up commit + verdict record

- [ ] **Step 1: Record the Phase A verdict**

Append a one-line result to the spec's decision log (RA-G1/G2/G3 pass/fail + measured Pearson). If all pass, Phase A is green and Phase B is unlocked.

```bash
git add docs/superpowers/specs/2026-06-05-reward-pnl-alignment-restoration.md
git commit -m "docs: Phase A verdict — RA-G1/G2/G3 results"
```

---

## Phase B — Credit-assignment viability diagnostic (RA-G4)

> **Goal:** measure `Pearson(GAE_advantage_at_open, realized_trade_pnl)` cheaply (no training rearchitecture) to decide whether the expensive Phase C build is justified. The rollout buffer (`rollout_buffer.rs`) already computes `advantages_d`/`returns_d` via `compute_gae`; collection (`rollout_collection.rs`) fills it as a side-effect under `FOXHUNT_USE_ROLLOUT=1`. What it does NOT currently store is per-step realized USD pnl — Task B1 audits that gap.

### Task B1: Audit the rollout-collection → pnl mapping (investigation, no code)

**Deliverable:** a written finding (append to the spec) answering:
- [ ] Where does `rollout_collection.rs` capture per-step data, and is `trading.realized_pnl_usd_delta` (or the device buffer behind it) reachable at the same `(b, t)` granularity as `advantages_d`?
- [ ] At which `t` does a trade "close" (done flag) and where is its realized pnl available on-device?
- [ ] Can the open-step `advantages_d[b, t_open]` be paired with the realized pnl of the position that opened at `t_open`? (trade-tracking through the buffer)

**STOP-if:** the pnl signal is not reachable at buffer granularity without a >200 LOC collection refactor → fall back to the **coarser RA-G4 proxy** in Task B2-alt (per-step diag correlation) and note the precision loss.

### Task B2: Emit GAE-advantage + realized-pnl pairs to diag

Based on B1, add a deterministic diag emission (per `pearl_fleet_fraction_not_aggregate` — emit fleet fractions, not just aggregates) of:
- mean/median `advantages_d` at close (done) steps,
- co-located `realized_pnl_usd_delta`,
- per-batch fraction with advantage-sign == pnl-sign.

Mirror the existing per-batch reduction pattern (the F2 diag counters, `RL_F2_DIAG_*`). Wire under `FOXHUNT_USE_ROLLOUT=1` only (bit-equal when off).

### Task B2-alt (fallback): coarse per-step proxy

If B1 STOPs: compute `Pearson(returns_d-mean-at-close, realized_pnl_usd_delta)` host-side from diag rows (cross-granularity, weaker), and label the RA-G4 result "coarse proxy — precision-limited."

### Task B3: Run smoke and evaluate RA-G4

- [ ] Run: `FOXHUNT_PIN_SURFER_SCAFFOLD=1 FOXHUNT_USE_ROLLOUT=1 ./scripts/local-mid-smoke.sh --seed 42 --out /tmp/foxhunt-pin-gae-smoke`
- [ ] Compute `Pearson(GAE_advantage_at_open, realized_trade_pnl)` from the emitted diag.
- [ ] **RA-G4 gate: ≥ 0.70.**

**STOP-if RA-G4 < 0.70:** Per spec §5.4, do NOT build Phase C and do NOT revisit F4. A failure means multi-step credit assignment on aligned reward still does not recover pnl-aligned advantages → the fault is deeper (reward shape itself or genuine regime shift). Escalate to a reward-shape / regime investigation. Record the verdict in the spec decision log.

### Task B4: Determinism + commit

- [ ] `FOXHUNT_PIN_SURFER_SCAFFOLD=1 FOXHUNT_USE_ROLLOUT=1 ./scripts/determinism-check.sh --quick` → exit 0 (RA-G2).
- [ ] Commit the diagnostic + spec verdict.

---

## Phase C — Complete 1B-C multi-epoch PPO over rollout buffer (D2) — CONDITIONAL on RA-G4

> **Do NOT expand into TDD tasks until RA-G4 passes.** Phase C is the substantive (~3-6 day) build that replaces the per-step done-gated TD update with a multi-epoch PPO update over the GAE buffer. Its exact code depends on the rollout-scaffold audit (Task C1) and must honour `pearl_foxhunt_trainer_is_genuinely_single_step` (the per-step training split is the load-bearing change).

Structured task outline:
- **C1 (audit):** map the current per-step PPO update path in `integrated.rs` (the `ss_pi_grad_logits_d` distill write site ~5607 + Adam steps) and the `rollout_collection.rs` collection loop. Identify exactly where per-step training is replaced by buffer-replay multi-epoch update. Produce the Phase C TDD task list from this audit.
- **C2:** implement the multi-epoch minibatch loop over `RolloutBuffer` (re-evaluate heads on stored `h_t_d`, PPO ratio from `log_pi_old_d`, advantages from `advantages_d`), gated behind `FOXHUNT_USE_ROLLOUT=1`.
- **C3:** determinism (RA-G2) + bit-equality-when-off.
- **C4:** mid-smoke with pin + rollout → **RA-G5 (eval pnl ≥ −$415,237 F2+F5 SOTA, not worse)**. STOP-if-regresses (recall Phase 1A regressed here).

**Gates:** RA-G5, RA-G2. **STOP-if:** RA-G5 regresses → diagnose with fleet-fraction diag before cluster.

---

## Phase D — Cluster verify (RA-G6) — CONDITIONAL on Phase C

Structured checklist:
- **D1:** confirm local HEAD == origin (push), apply Argo template (`feedback_argo_template_must_apply`).
- **D2:** submit Tier 2 — `./scripts/argo-alpha-rl.sh --n-steps 20000 --n-eval-steps 5000 --n-folds 3 --fold-idx 1 --n-backtests 1024 --band-enabled 1` with `FOXHUNT_PIN_SURFER_SCAFFOLD=1 FOXHUNT_USE_ROLLOUT=1` threaded into the workflow env (verify the Argo template forwards these env vars; add them if not).
- **D3:** wait ~70 min; analyze `eval_summary.json`.
- **Gate RA-G6:** `total_pnl_usd` > F2+F5 sk2z9 cluster baseline.
- **Branch (spec §5):** RA-G6 pass → reward alignment was the lever; commit. RA-G6 fail with RA-G4 pass → encoder/generalization branch legitimately isolated (NOT another reward reshape; consider IDAAC decoupling / overfit-rejection ensemble, or — only now — a properly-done warm-start).

---

## Self-review

**Spec coverage:**
- D1 force-pin → Tasks A1-A3 ✓ | RA-G1 → A6 ✓ | RA-G2 → A7, B4 ✓ | RA-G3 → A5, A6 ✓
- §3.4 re-measure harness → A5 ✓ | §3.2 RA-G4 diagnostic → Phase B ✓ | RA-G4 → B3 ✓
- §3.3 1B-C (D2) → Phase C ✓ | RA-G5 → C4 ✓ | RA-G6 → D3 ✓
- §7 risk 1 (RA-G3 tautology) called out in A6 Step 3 ✓ | §7 risk 3 (magnitude clamp) called out in A6 Step 3 ✓ | §7 risk 4 (1B-C cost) → Phase C kept conditional + audit-first ✓
- Phase 1A landmine (§1.3) → RA-G4 gates the D2 spend; RA-G5 watches for the regression ✓

**Placeholder scan:** Phase A and B-task-B1/B3/B4 contain complete code/commands. Phase B Tasks B2/B2-alt and all of Phase C/D are *intentionally* structured outlines (not placeholders) because their exact code is gated behind RA-G4 and a scaffold audit — pre-coding them would violate the investigation-first methodology and likely be wrong. Each carries its gate and STOP rule.

**Type/name consistency:** `RL_SURFER_SCAFFOLD_FORCE_PIN_INDEX` (824), `FOXHUNT_PIN_SURFER_SCAFFOLD` env flag, `surfer_scaffold_force_pin` diag key — used consistently across A1/A2/A3/A4/A6. `RL_SLOTS_END=825` consistent A1. Harness path `scripts/measure_reward_alignment.py` consistent A5/A6.

---

## Execution note

Phase A is the fully-coded, low-risk unit (force-pin + harness + 3 gates, ~0.5-1 day) and is independently valuable: even if Phase B STOPs, Phase A leaves slot 753 actually controllable and a reusable alignment gate in CI. Recommend executing Phase A end-to-end, reviewing RA-G1/G2/G3, then deciding Phase B with the RA-G4 verdict in hand.
