# Numerical Stability Investigation — Design

**Date:** 2026-04-29
**Status:** ACTIVE — Sub-project 1 (F1 NaN root-cause)
**Branch:** `plan-c-phase-2-thompson` (continuation; no new branch)
**Parent context:** Plan C Phase 2 Thompson sampling (commits `e445d07a` … `e9096c7be`) successfully restored F0 multi-fold convergence (Best Sharpe 55.87 — best in branch history), but F1 ep2 still produces NaN in `save_h_s2` + `grad_buf` despite all GRN forward stages reading clean. Nine defensive layers (A.1, A.2, A.3, F, H, K→R1, P, N, bonus optimism break) reduced explosion magnitude by 5+ orders of magnitude but did not eliminate the underlying instability.

---

## Goal

Resolve the F1 ep2 NaN explosion definitively, on the path to comprehensive numerical-stability framework + Q-learning structural stability work.

This document scopes **Sub-project 1 (SP1)** of a three-sub-project decomposition:

- **SP1 — F1 NaN root-cause** *(this spec; foundational, ~1 week)* — find and fix the specific kernel/operation producing the first NaN at F1 ep2; multi-fold smoke validates all 3 folds train; **always-landing diagnostic instrumentation** stays as permanent regression sentinel.
- **SP2 — Numerical stability framework** *(future spec, ~2 weeks)* — audit existing NaN defenses, codify invariants as kernel-level macros, ensure future kernel work inherits stability by construction.
- **SP3 — Q-learning structural stability** *(future spec, ~3-4 weeks)* — target-Q clipping, pessimistic ensemble wiring, atom-position growth bounds, conservative target sync at fold boundary; eliminates the SOURCE of extreme values that overwhelm guards.

SP1 → SP2 → SP3 build sequentially: SP1 reveals what guards were missing → SP2 codifies them → SP3 prevents the conditions that test those guards.

---

## Success Criterion (SP1)

SP1 closes when ALL of:

1. The kernel/operation(s) producing the first NaN at F1 ep2 is identified and fixed (per γ + β methodology below). **Multi-source case explicitly supported**: γ + β may identify multiple independent NaN-producing kernels; SP1 fixes ALL of them within the same investigation cycle.
2. L40S `multi_fold_convergence` smoke runs cleanly:
   - All 3 folds train through 5 epochs (no early-stop from gradient collapse).
   - F0 Best Sharpe ≥ 95% of the latest measured F0 baseline on the parent commit (currently 55.87 → floor 53.08). Ratio-based prevents accidental erosion of the working path across iterative fixes.
   - F1 Best Sharpe at the LAST epoch ≥ Best Sharpe at the FIRST epoch of the same fold (concrete monotone-improvement test; Best Sharpe at epoch 5 ≥ epoch 1's Best Sharpe).
   - F2 Best Sharpe at the LAST epoch ≥ Best Sharpe at the FIRST epoch of the same fold (same standard).
   - Zero `NaN-CLAMPED-TO-ZERO` log lines in the smoke output (the NaN-derived collapse path).
   - Zero `NaN/Inf at step` errors (training-guard `halt_nan` path).
   - "Genuine grad collapse" warn lines (legitimate near-zero gradients, no NaN flags set) are PERMITTED — they're observations, not failures, and indicate the diagnostic distinguishes real near-zero gradients from NaN-clamped-to-zero.
   - All 48 NaN flag slots remain at zero throughout the run (verified by grep on smoke log).
3. Permanent diagnostic instrumentation is committed (per Section: Components below) — the 24 → 48 flag expansion stays as production-grade regression sentinel. Buffer size reviewable by SP2 framework codification.

---

## Architecture

SP1 is a **two-phase investigation with always-landing instrumentation, followed by surgical fix and multi-fold validation**, executed atop `plan-c-phase-2-thompson`.

### Phase A — γ Audit (read-only, ~2-3 hours, no L40S)

- Inventory backward-path kernels: `bw_d_h_s2` accumulator, IQN trunk backward, C51 backward (`current_lp` + `projected`), CQL backward, ensemble backward, aux head backward, all `apply_*_gradient` cuBLAS GEMMs.
- Audit against known unsafe numerical patterns:
  - `sqrtf(x)` where `x` may be negative
  - `1/x` where `x` may approach zero
  - `logf(x)` where `x` may be ≤ 0
  - `expf(x)` where `x` may be large
  - EMA-tracked variance denominators that may approach zero
  - `atomicAdd` / `saxpy` accumulators receiving NaN inputs
- Cross-reference `~/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/session_2026-04-05_nan_investigation.md`'s residual 8% step-2 NaN in `apply_iqn_trunk_gradient`'s cuBLAS backward — never closed; strong candidate for source.
- Output: written audit findings document at `docs/dqn-backward-nan-audit.md`. Becomes durable artifact consumed by SP2 (framework codification) and SP3 (structural fix scoping).

### Phase B — β Instrumentation (always lands, ~one L40S smoke)

- Add NaN checks at every backward-kernel output covering ~10-12 new flag slots.
- Expand `nan_flags_buf` 24 → 48 (12 used + 12 reserved for SP2/SP3 expansion).
- Coverage: each loss component's gradient output, each `apply_*_gradient`'s cuBLAS bwd output, each saxpy-into-`bw_d_h_s2` source buffer, the `bw_d_h_s2` accumulator before-and-after the saxpy stack (which saxpy contaminated diagnostic).
- One L40S smoke captures full NaN topology in the F1 ep2 explosion.
- **Critical: instrumentation stays as production-grade diagnostic infrastructure** — same disposition as existing 24-slot system. Provides ongoing regression protection for SP2 and SP3, and any future kernel work. Per `feedback_kill_runs_on_anomaly_quickly` and `feedback_no_partial_refactor`: diagnostic coverage is a permanent invariant once added, not a temporary scaffold.

### Phase C — Surgical Fix (combined / rich commit)

- The fix lands wherever γ + β jointly identify the source(s) — multi-source scenarios explicitly supported.
- Constraints:
  - Principled (no quickfixes per `feedback_no_quickfixes`).
  - **MANDATORY ISV-driven design**: every dynamic bound, threshold, clamp range, or floor introduced by SP1 MUST derive from an existing ISV slot or a new ISV slot if a new diagnostic is needed (per `feedback_isv_for_adaptive_bounds` + `feedback_adaptive_not_tuned`). The ONLY exception is numerical-stability ε bounds (Invariant 1 carve-out — e.g., `1e-6` divide-by-zero floor, `THOMPSON_MAX_ATOMS = 128` stack array sizing). Hardcoded "tuning" constants for dynamic ranges are explicitly rejected; if a knob has dynamic range that warrants adaptation, it MUST be ISV-driven. Existing ISV slots that fixes can leverage: `Q_ABS_REF_INDEX = 16`, `Q_DIR_ABS_REF_INDEX = 21`, `H_S2_RMS_EMA_INDEX`, `Q_DRIFT_RATE_INDEX = 129`, `FOLD_WARMUP_FACTOR_INDEX = 130`, plus any additions justified by SP1's specific findings.
  - F0 Best Sharpe ≥ 95% of latest baseline (ratio-based, see Success Criterion).
  - **Combined fix: when investigation reveals multiple RELATED issues, they land together in a single rich commit per `feedback_no_partial_refactor`.** No artificial splitting of related work. UNRELATED fixes (e.g., a kernel issue and an unrelated docs typo) MAY remain separate per single-concern commit hygiene.
- Companion: any new SAFE_DIV / SAFE_LOG / CLAMP_FINITE pattern introduced here gets documented in the audit doc for SP2's framework adoption.

### Phase D — Multi-fold Validation

- L40S smoke on `multi_fold_convergence` (existing harness; no new test).
- Pass criterion (see Success Criterion above) verified in user-facing report.
- Iteration discipline: each smoke iteration must improve OR the commit gets revised before merge.

### Branch + Commit Posture

- All SP1 commits land on `plan-c-phase-2-thompson` (continuation, not new branch).
- β instrumentation is permanent (24 → 48 flag expansion stays).
- Surgical fix may be amended later by SP2's framework codification (a generic guard supersedes a local one).
- After multi-fold validation passes, the branch becomes ready for merge OR for SP2's framework work.

---

## Components

### A. Backward NaN Audit Document *(Phase A output)*

- **Path:** `docs/dqn-backward-nan-audit.md`
- **Structure:** per-kernel sections covering:
  - Name + file location + line range
  - Output buffers (what kernel writes)
  - Accumulator targets (what kernel atomicAdd / saxpy into)
  - Identified unsafe patterns (with line-level citations)
  - Severity: confirmed / suspect / clean
  - **Proposed guard form** (e.g., `isfinite()` + clamp, replace `sqrtf(x)` with `sqrtf(fmaxf(x, 0))`, etc.)
  - **ISV bound option**: which existing ISV slot could parameterize the guard's bound, OR justification for why a new ISV slot is needed, OR justification for why this is a numerical-stability ε bound (Invariant 1 carve-out — only acceptable non-ISV form)
  - **F0 risk assessment**: low / medium / high — does the proposed fix have any chance of changing F0 training dynamics? Used as paper-review gate before smoke validation.
- Cross-references `session_2026-04-05_nan_investigation.md` for known-residual context.
- Becomes durable artifact for SP2 (framework codification) and SP3 (structural-fix scoping).

### B. Expanded NaN Flag Buffer *(Phase B foundation)*

- `nan_flags_buf` grows 24 → 48 (24 already used: 0-12 + 13-15 reserved + 16-20 GRN + 21-23 reserved).
- Math: 24 used + 12 new (slots 24-35 per Components C below) + 12 reserved headroom = 48. The 12 headroom slots cover anticipated SP2 framework checker hooks + SP3 structural observers.
- The 48-slot size is **reviewable by SP2 framework codification** — if SP2 finds the right-size is 36 (no headroom waste) or 64 (more coverage), it may resize at that point. SP1's job is to expand to 48 with the new slots; SP2 may right-size.
- `read_nan_flags()` signature: `[i32; 48]`.
- Name table updated in BOTH consumer sites (`halt_nan` block + `halt_grad_collapse` block from commit `d1808df14`).
- Allocation site (`stream.alloc_zeros::<i32>(48)`).
- `reset_nan_flags()` zeroes all 48 — already memset-based; scales transparently.

### C. Backward Kernel NaN Check Call Sites *(Phase B coverage)*

Initial flag-slot allocation (refined by γ findings):

| Slot | Buffer | Source kernel |
|---|---|---|
| 24 | `iqn_trunk_grad_out` | `apply_iqn_trunk_gradient` cuBLAS bwd |
| 25 | `iqn_aux_grad_out` | IQN aux backward |
| 26 | `c51_d_current_lp` | C51 KL backward |
| 27 | `c51_d_projected` | C51 projection backward |
| 28 | `cql_grad` | CQL backward output |
| 29 | `ens_grad_out` | Ensemble backward (currently dormant; instrument anyway) |
| 30 | `aux_nb_dh_s2` | Aux next-bar backward |
| 31 | `aux_rg_dh_s2` | Aux regime backward |
| 32 | `bw_d_h_s2_pre_saxpy` | h_s2 grad accumulator BEFORE saxpy stack |
| 33 | `bw_d_h_s2_post_saxpy` | After all saxpys (same buffer; before-after shows which saxpy contaminated) |
| 34 | `mse_grad` | MSE/Bellman residual gradient |
| 35 | `bottleneck_grad` | If bottleneck Linear backward exists |
| 36-47 | reserved | (headroom for SP2 framework + SP3 observers) |

Each call site uses existing `check_nan_f32(buf_ptr, len, flag_idx)` pattern. Accessor methods follow the `GrnBlock::elu_post_ptr()` pattern from commit `e9096c7be`.

### D. Surgical Fix Surface *(Phase C output)*

- Lands in the kernel identified by γ + β.
- Form depends on the unsafe pattern: `isfinite()` guard + clamp, replace `sqrtf(x)` with `sqrtf(fmaxf(x, 0))`, divide-by-zero ε bound from existing ISV (e.g., `Q_DIR_ABS_REF`) rather than hardcoded eps, etc.
- Pattern documented in `docs/dqn-backward-nan-audit.md` for SP2 to generalize.

### E. Validation Harness *(Phase D, reused not built)*

- Existing `multi_fold_convergence` smoke (no new test).
- L40S clean-cache run on the latest commit.
- Pass criterion checked in user-facing report.

### File Touch Summary

- `docs/dqn-backward-nan-audit.md` (new)
- `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs` (`nan_flags_buf` size, accessor methods if needed, `run_nan_checks_post_backward` entry point)
- `crates/ml/src/trainers/dqn/fused_training.rs` (`read_nan_flags` signature; possibly new check trigger point)
- `crates/ml/src/trainers/dqn/trainer/training_loop.rs` (name table sites — both `halt_nan` and `halt_grad_collapse` blocks)
- The kernel file containing the surgical fix (TBD by audit)
- `docs/dqn-wire-up-audit.md` (audit doc co-stage entries per Invariant 7)

---

## Data Flow

### Audit data flow (γ, design-time)

```
Code review (kernel files) → patterns identified (sqrtf neg, 1/0, etc.)
    → audit doc table (per-kernel findings)
    → instrumentation priority list (which slots first)
    → SP2 framework input (codifying patterns to guards)
```

### Runtime NaN detection flow (β, every training step)

```
Backward kernel completes → check_nan_f32(buf_ptr, len, flag_idx)
    → atomicOr into nan_flags_buf[flag_idx] if any element NaN
    → flags accumulate across steps within the fold
    → reset_nan_flags() on fold boundary (existing infrastructure)
```

### Halt-event readback flow (existing infrastructure, extended by SP1)

```
Training guard fires (halt_nan OR halt_grad_collapse)
    → read_nan_flags() copies all 48 slots DtoH (single 192-byte copy)
    → Rust enumerates flagged indices
    → matches against 48-entry name table
    → tracing::error! "NaN-CLAMPED-TO-ZERO at step N: flagged=[24=iqn_trunk_grad, 32=bw_d_h_s2_pre_saxpy, ...]"
    → early-stop propagates with full source list
```

### Fix delivery flow (Phase C)

```
Audit findings + smoke flag topology
    → identify kernel responsible (the "first" flagged buffer per
       backward dependency order)
    → identify the unsafe pattern within (γ-cataloged)
    → patch with isfinite/clamp/safe-variant
    → companion: documented in audit doc as "fixed: kernel.cu:LINE"
    → SP2 framework derives a generic guard from this
```

### Validation flow (Phase D)

```
Surgical fix committed → push → argo-smoke L40S
    → 16-20 min smoke completes
    → grep log for: NaN-CLAMPED-TO-ZERO (must be zero matches),
      Best Sharpe per fold (F0 ≥ 55, F1+F2 finish 5 epochs)
    → success: SP1 closed; failure: iterate (no escalation; investigate)
```

### Cross-sub-project handoff flow

```
SP1 audit doc → SP2 framework input (which patterns to codify generically)
SP1 surgical fix → SP2 generic-guard adoption surface
SP1 NaN-flag infrastructure (24→48) → SP2 invariant-checker hooks
SP1 multi-fold validation result → SP3 structural-fix baseline
```

The runtime NaN detection flow becomes permanent infrastructure. Every future kernel addition adopts the `check_nan_f32(buf, len, NEW_SLOT)` pattern. SP2 codifies this as an invariant.

---

## Error Handling

### Operating Principles

- **No deferrals.** Anomalies and side-issues uncovered during SP1 get investigated and fixed within SP1, not punted to SP2 or SP3.
- **Combined RELATED fixes — rich commits.** When investigation reveals multiple related fixes, they land together in a single coherent commit per `feedback_no_partial_refactor`. UNRELATED fixes (separate concerns) may stay in separate commits per single-concern commit hygiene.
- **Mandatory ISV-driven design for dynamic bounds.** Per `feedback_isv_for_adaptive_bounds` + `feedback_adaptive_not_tuned`: every dynamic bound, threshold, clamp, or floor in SP1's surgical fix MUST derive from an ISV slot (existing or new). Numerical-stability ε bounds (Invariant 1) are the only carve-out. Hardcoded "tuning" constants for dynamic ranges are explicitly rejected.

### Specific failure modes

**(1) γ audit produces no smoking gun**
- β instrumentation lands as planned (always-on per Architecture).
- Audit document still produced — even "no clear pattern found" findings have value: documents what was checked, narrows where the next investigator looks.
- No closure delay.

**(2) β instrumentation finds the kernel but the fix needs wrapper-around-cuBLAS approach**
- Example: cuBLAS GEMM produces NaN from finite inputs due to extreme intermediate products.
- Fix: pre-call input range guard + post-call output sanitization, both land in the same commit.
- The wrapper IS the fix; not a deferral.
- Document the wrapper pattern in audit doc; SP2 may codify it as a generic invariant later, but SP1 ships the working wrapper.

**(3) Multiple kernels flag simultaneously**
- Phase A audit produces backward dependency graph (which kernel feeds which); enables triage.
- Source-kernel fix lands together with any downstream-issue fix that's also necessary for the smoke to pass — single rich commit.
- Source-only fix that leaves downstream kernels still flagging on edge cases = incomplete; doesn't satisfy SP1 multi-fold criterion.

**(4) Fix introduces F0 regression**
- Hard constraint: F0 Best Sharpe ≥ 95% of the latest F0 baseline (currently 55.87 → floor 53.08). Ratio-based prevents iterative erosion.
- **F0-safety paper review BEFORE smoke**: every proposed fix in the audit doc carries an "F0 risk" assessment (low/medium/high). High-risk fixes get extra scrutiny on paper before consuming an L40S smoke; low-risk fixes go straight to smoke. Paper review explicitly checks: does the proposed guard fire on F0's data distribution? If yes, what does F0's training look like with the guard active? (Answered by inspecting the guard's mathematical effect on F0-typical input ranges, before committing.)
- If a commit drops F0 below the ratio threshold: that commit is incorrect; revise within SP1, do not commit a known regression.
- Pre-flight check: every smoke run reports F0 Sharpe; commit only if F0 ≥ 95% baseline AND F1+F2 satisfy criterion.

**(5) Fix passes F1 but exposes F2 instability of a different class**
- **NO escalation to SP3.** Investigate F2's failure within SP1 using the same γ + β + fix loop.
- F2 may surface different unsafe patterns (CQL backward, ensemble backward, atom-range overflow during F2's data-distribution shift) — fix them.
- Resulting commit may be richer (multiple kernel patches) — that's the intent, not a problem.
- SP1 closes when ALL 3 folds genuinely train 5 epochs, not when "we did the F1-specific fix and shipped".

**(6) Iteration unbounded?**
- Budget: bounded by what the investigation REVEALS, not by an arbitrary smoke count.
- If after thorough γ + β coverage we find an issue that genuinely cannot be fixed without a structurally different approach (e.g., kernel rewrite requiring log-space arithmetic across a whole component) → that's a discovery that updates SP1's design, not a deferral. Bring it back to brainstorming, revise the spec, continue.
- "We don't know how to fix it yet" is acceptable; "we know how to fix it but won't do it now" is not.

**(7) Smoke infrastructure failure**
- Argo / L40S provisioning issues are infra, not SP1 scope.
- Don't count as SP1 iterations.

**(8) Diagnostic log noise**
- 48-slot readback is bounded (192-byte DtoH per halt event).
- `FOLD_EXPLOSION_DIAG` and `GRAD_CLIP_OUTLIER` fire only on actual events.

### Anti-Patterns Explicitly Rejected

- Globally clamping all backward outputs to a fixed range (masks bugs; violates `feedback_no_quickfixes`).
- **Micro-clamping individual operations without identifying upstream cause** (e.g., per-element `fminf(x, 1e6)` everywhere). Symptom-hiding disguised as defense; violates `feedback_no_quickfixes`. The fix must address WHY the value is extreme, not just clamp it.
- Disabling a loss component to make F1 train (violates `feedback_no_functionality_removal`).
- Increasing `gradient_collapse_patience` to "let training continue past NaN" (hides the issue).
- Splitting one logical fix across multiple commits to "make commits smaller" (fights `feedback_no_partial_refactor`).
- **Conversely: combining unrelated fixes** ("while we're here, let me also rename this variable") — keep rich commits focused on related concerns only.
- Punting any anomaly to SP2 or SP3 with "we'll get to it later" (prohibited per operating principles above).
- **Hardcoded constants for dynamic bounds** — every dynamic bound MUST be ISV-driven (mandatory operating principle). Hardcoded `epsilon = 0.05` for some clamp range, where 0.05 is "experimentally chosen", is rejected; that bound must derive from an ISV signal.
- Adding more `tracing::warn!` instead of NaN flags (warns get suppressed; flag-buffer write is captured-graph-safe).

---

## Testing

### Primary Validation: existing `multi_fold_convergence` L40S smoke (no new test infrastructure)

- Test: `multi_fold_convergence::test_multi_fold_convergence` via `argo submit --from=wftmpl/smoke-test --clean-cache=true`.
- Pass criteria for SP1 closure (see Success Criterion).

### Per-iteration smoke validation (during Phase C fix iterations)

- Same harness, run after each commit that proposes a fix.
- Each iteration must improve OR the commit gets revised before merge.
- "Improve" = strictly more folds passing, OR same folds passing with cleaner flag topology, OR strictly higher F0 Sharpe with no fold regressions.

### Regression Coverage Going Forward (the always-landing instrumentation)

- 48-slot NaN flag system covers every backward-path kernel after SP1 lands.
- Any future commit that adds a kernel writing to `bw_d_h_s2` / `grad_buf` / `save_h_s2` MUST add a corresponding NaN flag slot (enforced by SP2's framework codification, but the convention starts here).
- The `nan_flags_buf` size + name table parity is checked at compile-time (size mismatch = compile error); name table at the two `training_loop.rs` consumer sites must match the buffer size.

### Unit-test additions: NONE in SP1 *(intentional)*

- Existing `distributional_q_tests` + Plan A Phase 0 tests cover algorithm-level correctness.
- The F1 NaN issue was discovered via integration smoke, not unit test — unit tests at SP1 layer would not have caught it (it's an interaction between data-distribution shift, fold-boundary state, and kernel-level numerics that's only visible on real fold-1 data).
- SP2 framework MAY add unit-test invariants (e.g., "every kernel listed in the registry must NaN-check its outputs") — out of SP1 scope.

### What we explicitly do NOT do

- No new synthetic-input NaN-injection harness (overkill for SP1's bug; the L40S smoke already produces real NaN conditions naturally).
- No CPU reference comparison (per `feedback_no_cpu_test_fallbacks`).
- No reduction in test scope to "make iteration faster" (the multi-fold smoke IS the validation; using a 1-fold smoke would not validate fold-boundary issues).
- No bisect-via-disabling-features (per `feedback_no_functionality_removal` — process-of-elimination must instrument, not disable).

### Smoke Iteration Efficiency Considerations

- L40S smoke ~16-20 min wall-time per iteration.
- Argo workflows include compile cache; `--clean-cache=true` is mandatory ONLY when the previous smoke might have cached an unrelated branch's artifacts (e.g., bisect runs, branch switches). Default to `--clean-cache=false` for fix-iteration smokes on the same branch — saves ~5-7 min per iteration.
- Multiple commits-then-one-smoke is acceptable when the commits address different aspects of one investigation (per "rich commits" principle).

### Validation Log Artifact Discipline

- Each smoke result log saved to `/tmp/p4t6_revalidate/smoke-{commit-sha-short}.log` (existing pattern).
- Trajectory comparisons (this commit vs previous baseline) documented in PR description / wrap-up doc when SP1 closes.

---

## References

- `docs/superpowers/specs/2026-04-26-distributional-rl-aggregation-design.md` — parent Plan C Phase 2 spec
- `docs/superpowers/plans/2026-04-27-distributional-rl-thompson-plan-C-phase-2.md` — parent plan
- `~/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/session_2026-04-05_nan_investigation.md` — prior NaN root-cause investigation; identifies residual cuBLAS-bwd 8% NaN at step 2 in `apply_iqn_trunk_gradient`
- `~/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/project_ff00af68a_regression_root_cause.md` — original Plan C Phase 2 motivation
- `~/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/project_fold_boundary_q_drift_resolved.md` — prior fold-boundary state-reset work (A.1 family)
- Branch state: 25 commits on `plan-c-phase-2-thompson` (Plan C Phase 2 + 9 defensive layers + R1 K-elim + P GRN-stage instrumentation)

---

## Out-of-Scope (deferred to SP2 / SP3)

- **SP2 territory** *(NOT in SP1)*: codifying NaN guards as kernel-level macros (`SAFE_DIV`, `SAFE_LOG`, `CLAMP_FINITE`); cross-cutting refactor of existing kernels to adopt them; pre-commit invariant checks; framework documentation.
- **SP3 territory** *(NOT in SP1)*: target-Q clipping; pessimistic ensemble wiring; atom-position growth bounds; conservative target sync at fold boundary; structural Q-target stability redesign.

The SP1 surgical fix may resemble in spirit what SP2/SP3 will do generically — that's expected. SP1 ships the specific fix; SP2 generalizes; SP3 prevents the conditions.

---

## Status Footer

**Author:** brainstorming session 2026-04-29
**Approved sections:** Architecture, Components, Data Flow, Error Handling, Testing — all 5 user-approved
**Next step:** writing-plans skill produces implementation plan from this spec
