Files
foxhunt/docs/sp18-wireup-audit.md

37 KiB
Raw Blame History

SP18 Combined-Scope Wire-Up Audit

Audit captured during Phase 1 Task 1.1 of docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md.

Branch: feat/sp18-combined @ 0b737ec30 (Phase 0 baseline, pre-Phase-1 atomic deletion).

Audit script: scripts/audit_sp18_consumers.sh. Re-run after every commit that touches the SP13/SP16 chain (D-leg) or the TD(λ) q_next site (B-leg). Pre-commit hook (Task 1.2) enforces snapshot-vs-current diff via --check.

Outcome of Phase 1 Task 1.1 (audit-first, halt-on-drift)

The pre-dispatch consumer-audit run surfaced multiple consumers NOT in the plan's locked checklist (spec §"Architecture Changes"). Per task input — "If your audit surfaces ANY additional consumer, halt and report — do NOT proceed with deletion ad-hoc" — Phase 1 Tasks 1.3-1.5 (atomic deletion) are HALTED pending plan / spec amendment.

Plan's locked D-leg checklist (8 entries)

# Site Action
1 experience_kernels.cu:3162-3166, 3637-3643, 3710-3716 (3 cost sites) DELETE — replace with placeholder comment per Task 1.5
2 training_loop.rs:564 (launcher call) + :631-667 (hold_cost_scale_diag emit) + :4171-4226 (host controller block) DELETE launcher + diag emit + controller; RETAIN slot-382 min_hold_temp_diag emit at :567-617
3 crates/ml/src/cuda_pipeline/hold_cost_scale_update_kernel.cu DELETE entire file
4 crates/ml/src/cuda_pipeline/sp14_isv_slots.rs slots [461..468) (HOLD_COST_SCALE_INDEX + HCS_* Welford) + their MIN/MAX/SENTINEL constants DELETE constants + the layout-locked tests for them
5 crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs launch_hold_cost_scale_update method (line 15837) + HOLD_COST_SCALE_UPDATE_CUBIN static (line 2257) + struct field hold_cost_scale_update_ops (line 7853) + constructor wiring (lines 24433-24434, 25296) DELETE all
6 crates/ml/src/trainers/dqn/state_reset_registry.rs 7 entries (sp16_phase2_hold_cost_scale_adaptive + sp16_t3_hcs_* × 6) DELETE entries
7 crates/ml/build.rs:881 cubin manifest entry hold_cost_scale_update_kernel.cu DELETE
8 crates/ml/src/cuda_pipeline/state_layout.cuh:214 (ISV_HOLD_COST_SCALE_IDX) + :228-233 (ISV_HCS_* mirror) DELETE

Additional consumers surfaced by audit (NOT in plan checklist)

The following consumers must be added to the atomic-deletion commit. They were missed by the plan's spec §"Architecture Changes" and would have left the deletion partial — per feedback_no_partial_refactor.md this would block compile or, worse, leave dangling references that compile but break test/integration paths.

# Site Type Resolution
A1 crates/ml/src/cuda_pipeline/gpu_aux_trunk.rs:1240-1323HoldCostScaleUpdateOps struct + impl (kernel-load + launcher) PRODUCER WRAPPER (Rust-side). The plan said the launcher is in gpu_dqn_trainer.rs but actually only the trainer's thin call site launch_hold_cost_scale_update is there; the real HoldCostScaleUpdateOps::new/launch impl lives in gpu_aux_trunk.rs. DELETE the struct + impl block (84 lines). DELETE the HOLD_COST_SCALE_UPDATE_CUBIN import at gpu_aux_trunk.rs:56 (transitively unused after the struct is gone).
A2 crates/ml/tests/sp14_oracle_tests.rs:2174-2920 — 11 GPU oracle tests + the doc-block at the head TESTS (75 occurrences of HOLD_COST_SCALE / HCS_TARGET / hold_cost_scale_update across this file). Tests: sp16_phase2_hold_cost_scale_climbs_with_overrun, sp16_phase2_hold_cost_scale_at_target_is_one, sp16_phase2_hold_cost_scale_under_target_is_one, sp16_phase2_hold_cost_scale_bounds_clamp, sp16_phase2_hold_cost_scale_pearl_a_bootstrap, sp16_phase3_alpha_high_during_signal_jump, sp16_phase3_alpha_low_in_steady_state, sp16_phase3_pearl_a_bootstrap_first_obs, sp16_phase3_alpha_naturally_bounded, sp16_phase3_no_hardcoded_alpha, plus the helper recover_alpha/compute_target_scale/run_synthetic_sequence infrastructure (~750 lines). All exercise the now-deleted hold_cost_scale_update kernel via include_bytes!(.../hold_cost_scale_update_kernel.cubin) — without the kernel they fail to compile. DELETE the 2 test modules (sp16_phase2_hold_cost_scale_gpu + sp16_t3_hold_cost_scale_alpha_gpu) and the standalone sp16_phase3_no_hardcoded_alpha test. ~750 lines from sp14_oracle_tests.rs:2174-2920.
A3 crates/ml/src/trainers/dqn/trainer/training_loop.rs:8971-9043 — 7 dispatch arms in reset_named_state ("sp16_phase2_hold_cost_scale_adaptive" + "sp16_t3_hcs_*" × 6) DISPATCH ARMS. Required atomically with the registry-entry deletion (Task 1.4 expanded scope) — the contract test every_fold_and_soft_reset_entry_has_dispatch_arm (state_reset_registry.rs:2120-2280) fails if the registry has an entry but no dispatch arm OR vice versa. DELETE the 7 match arms (slot 461 + HCS slots 462-467) atomically with the registry entries.
A4 crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs:23200-23216 — constructor block writing HOLD_COST_BASE to HOLD_COST_INDEX=380 + HOLD_RATE_TARGET_DEFAULT to slot 381 CONSTRUCTOR WRITE of slot 380 (controller output anchor). Per DD7c, slot 380 is RETAINED as part of the observation chain — but it now has no producer. The constructor-write of HOLD_COST_BASE keeps it at 0.005 cold-start. After deletion of the host controller, slot 380 becomes a read-only diagnostic slot (constructor-init only, no per-step rewrites). KEEP the constructor-write (DD7c retains slot 380). UPDATE the comment block at lines 23201-23210 to reflect the post-SP18-Phase-1 reality: "After SP18 Phase 1, slot 380 is no longer rewritten by a host-side controller (deleted per DD7=c). The constructor-init is now its only assignment — it serves as a read-only diagnostic anchor surfacing the static base cost. Phase 2 lands the structural Hold opportunity-cost reward at the 3 deleted cost-subtraction sites; that reward does NOT consume slot 380."
A5 crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs:617, 2245-2256 — doc-block prose referring to deleted machinery DOC PROSE. References launch_hold_cost_scale_update, gpu_aux_trunk::HoldCostScaleUpdateOps, isv[461], etc. UPDATE comments to remove deleted-machinery references; replace with "(deleted SP18 Phase 1, DD7=c — see docs/sp18-wireup-audit.md)" pointer. No code change.
A6 crates/ml/src/cuda_pipeline/sp13_isv_slots.rs:73-77HOLD_COST_BASE, HOLD_COST_CONTROLLER_GAIN, HOLD_COST_FLOOR_RATIO, HOLD_COST_CEIL_RATIO constants HOST-CONTROLLER CONSTANTS. Used ONLY by the host controller block at training_loop.rs:4213-4223 we're deleting. Per the SP13 v3 P0a comment block at sp13_isv_slots.rs:50-72, HOLD_COST_BASE is also used by the constructor-write at gpu_dqn_trainer.rs:23215. KEEP HOLD_COST_BASE (consumed by constructor-init at A4 — slot 380 retained). DELETE HOLD_COST_CONTROLLER_GAIN, HOLD_COST_FLOOR_RATIO, HOLD_COST_CEIL_RATIO (sole consumer is the deleted controller block).
A7 crates/ml/src/cuda_pipeline/gpu_experience_collector.rs:5579-5585 — comment doc-ref to deleted controller COMMENT DOC-REF. References training_loop.rs:3604 (stale line — now :4209) and the priced-cost flow. UPDATE prose to reflect post-Phase-1 reality.
A8 crates/ml/src/cuda_pipeline/sp5_isv_slots.rs:325-327 — comment doc-ref describing the controller chain COMMENT DOC-REF. Documents slot 380/381/382. Slots 381 + 382 retained per DD7c; slot 380 retained but no longer controller-written. UPDATE prose to reflect post-Phase-1 reality.
A9 crates/ml/src/cuda_pipeline/state_reset_registry.rs:1138-1271 — registry entries already marked RETIRED in PP.4 (commit c66e15f6d) for slots 461 + 462-467 REGISTRY ENTRIES. Already marked RETIRED in Pre-Phase PP.4. Phase 1 promotes RETIRED-with-sentinel-0.0-write to FULL DELETION. DELETE the 7 entries (slot 461 + HCS slots 462-467) from the registry; layout fingerprint must be updated atomically — the fingerprint seed string covers slot allocations [461..468) which become RESERVED-gap (SP14-C.1 pattern). Slots themselves stay allocated (ISV_TOTAL_DIM unchanged) to preserve checkpoint compatibility.
A10 crates/ml/src/trainers/dqn/state_reset_registry.rs:2256-2308lock_sp18_v2_pp4_retired_chain test which asserts the 7 retired entries STILL EXIST CONTRACT TEST. PP.4 added a lock test asserting the entries exist with sentinel 0.0. After full deletion, this test must be deleted (the entries no longer exist, so asserting their existence panics). DELETE the lock_sp18_v2_pp4_retired_chain test (or rewrite to assert the slots are NOT registered).

Out-of-scope mentions (do NOT delete; verify only)

These show up in the audit grep but per DD7c are RETAINED for HEALTH_DIAG continuity OR are independent modules:

Site Reason RETAINED
crates/ml/src/cuda_pipeline/hold_rate_observer_kernel.cu Producer of slot 382 (observation chain). DD7c retains entire observation chain.
crates/ml/src/cuda_pipeline/min_hold_temperature_update_kernel.cu Independent SP16-P1 producer of slot 460 (different chain). Reads same slots 381+382 but writes different output. RETAINED.
crates/ml/src/cuda_pipeline/sp14_isv_slots.rs MHT_* slots [468..474) SP16-P1 (MIN_HOLD_TEMPERATURE) Welford accumulators — distinct chain. RETAINED.
crates/ml/src/cuda_pipeline/sp13_isv_slots.rs:43 HOLD_COST_INDEX = 380 Slot constant for the retained observation chain. RETAINED.
crates/ml/src/cuda_pipeline/state_layout.cuh:200-202 ISV_HOLD_COST_IDX/ISV_HOLD_RATE_TARGET_IDX/ISV_HOLD_RATE_OBSERVED_EMA_IDX (slots 380/381/382) Mirror constants for retained observation chain. RETAINED.
crates/ml/src/trainers/dqn/state_reset_registry.rs:939 sp13_hold_rate_observed_ema registry entry for slot 382 FoldReset entry for retained observation chain. RETAINED.
crates/ml/tests/sp13_phase0_oracle_tests.rs references to hold_rate_observer_kernel Tests for retained observer kernel. RETAINED.
docs/superpowers/specs/2026-05-04-sp13-redefine-success-for-predictive-skill.md and related plan docs Historical spec/plan archaeology. NOT modified.

Path forward

Two options:

  1. EXPAND deletion scope to cover all 10 additional consumers (A1-A10) atomically in the same Phase 1 commit. The plan's spec §"Architecture Changes" was incomplete; this audit makes the scope explicit. The atomic commit grows from ~8 sites to ~18 sites but stays within the same conceptual change ("delete SP13/SP16 cost-application chain"). Per feedback_no_partial_refactor.

  2. Halt and request human review. The plan author may have intended these consumers to be discovered iteratively (the plan says "trust the script, not the plan author"). But the script's purpose is to surface missed consumers BEFORE the deletion, exactly so the deletion stays atomic. Halting honours that intent; expanding the scope ad-hoc honours the deletion's atomicity.

Recommended: Option 1, with this audit doc serving as the spec-amendment record. The 10 additional consumers all flow from the same chain — none introduce new architectural concerns. They just expand the byte-count of the atomic-deletion commit.

Status

Phase 1 Tasks 1.3-1.6 LANDED (2026-05-09) — atomic deletion of all 18 sites (8 plan + 10 audit-expanded A1-A10) committed in a single atomic commit per feedback_no_partial_refactor. The branch was placed in INTERIM STATE: 3 reward sites (experience_kernels.cu segment_complete + per-bar positioned-Hold + per-bar flat-Hold) were placeholder-commented but contained no Hold cost.

Phase 2 Tasks 2.1-2.5 LANDED (2026-05-09) — atomic introduction of the compute_sp18_hold_opportunity_cost device function + 3-site migration + CPU/GPU oracle tests in a single commit per feedback_no_partial_refactor. INTERIM STATE LIFTED. L40S dispatch remains FORBIDDEN pending Phase 3 cap producer + Phase 4-5 B-leg, but the reward function is now structurally complete (no placeholder sites, no orphaned consumers).

Per DD7c, observation chain preserved: slot 380 (HOLD_COST_INDEX, constructor- init-only diagnostic at HOLD_COST_BASE=0.005), slot 381 (HOLD_RATE_TARGET_INDEX, constructor-init at 0.20), slot 382 (HOLD_RATE_OBSERVED_EMA_INDEX, FoldReset chain still wired). MHT_* slots [468..474) preserved (MIN_HOLD_TEMPERATURE chain RETAINED).

Sites deleted (atomic commit):

  • 3 reward subtraction sites in experience_kernels.cu (placeholder comments)
  • hold_cost_scale_update_kernel.cu (file deleted)
  • HoldCostScaleUpdateOps in gpu_aux_trunk.rs (struct + impl, ~120 lines)
  • launch_hold_cost_scale_update + cubin static + struct field + constructor
    • field-init in gpu_dqn_trainer.rs (5 sites)
  • HOLD_COST_SCALE_INDEX, HCS_* slots, sentinel/bounds, layout-locked tests in sp14_isv_slots.rs
  • HOLD_COST_CONTROLLER_GAIN/FLOOR/CEIL constants in sp13_isv_slots.rs
  • ISV_HOLD_COST_SCALE_IDX + ISV_HCS_* mirror constants in state_layout.cuh
  • build.rs cubin manifest entry
  • 7 fold-reset registry entries (slot 461 + HCS_* 462..468)
  • 7 dispatch arms in training_loop.rs
  • Host controller block in training_loop.rs (lines ~4171-4226)
  • launcher call site + hold_cost_scale_diag emit block in training_loop.rs
  • 11 GPU oracle tests + helpers in sp14_oracle_tests.rs (~750 lines)
  • lock_sp18_v2_pp4_retired_chain test (deleted, no inverse-contract replacement — pointless ceremony per user call)
  • Layout fingerprint seed updated (HOLD_COST_SCALE + HCS_* removed; range [461..468) is RESERVED gap; ISV_TOTAL_DIM stays at 507 per checkpoint compatibility — RESERVED-gap pattern from SP14-C.1)

Validation: cargo check --workspace clean; cargo test -p ml --lib passes (state_reset_registry::every_fold_and_soft_reset_entry_has_dispatch_arm, sp16_t3_wiener_welford_slot_layout_locked, sp18_combined_slot_layout_locked all OK).

Phase 2 sites introduced (atomic commit)

  • crates/ml/src/cuda_pipeline/trade_physics.cuh — new device function compute_sp18_hold_opportunity_cost after compute_lump_sum_opp_cost. Single forward-only function; reuses compute_asymmetric_capped_pnl for the bilateral cap. No new infrastructure beyond existing primitives.
  • crates/ml/src/cuda_pipeline/experience_kernels.cu — 3 placeholder comments replaced atomically:
    • Site 1 (segment_complete branch ~3143): unconditional add into base_reward BEFORE the SP12 asymmetric cap; preserves the SP12 cap interaction and cap participation per the original SP13 P0a comment contract.
    • Site 2 (per-bar positioned-Hold ~3641): unconditional add into r_micro (rc[3]) — same component the deleted subtraction wrote to pre-SP18, preserving SP11 reward-subsystem controller signal attribution. vol_proxy recomputed locally from features[bar_idx * market_dim + 9] (the per-bar branches are outside the segment_complete vol_proxy scope).
    • Site 3 (per-bar flat-Hold ~3760): unconditional add into r_opp_cost (rc[4]) — same component the deleted subtraction wrote to pre-SP18. vol_proxy recomputed locally as in Site 2.
  • crates/ml/src/cuda_pipeline/sp18_hold_opp_test_kernel.cu — new GPU oracle test wrapper (single-thread single-block). Wraps the device fn for the GPU oracle test; mapped-pinned f32 input/output per feedback_no_htod_htoh_only_mapped_pinned.
  • crates/ml/build.rs — added sp18_hold_opp_test_kernel.cu to the cubin manifest.
  • crates/ml/tests/sp18_hold_reward_oracle_tests.rs — added T1 (CPU oracle, 6 cases covering positive/negative/saturation/dir-gate/ shaping_scale=0) + T2 (GPU oracle parity with CPU at ε=1e-5) per spec §"Behavioral Test Predictions".

Phase 2 design notes

  • vol_proxy scoping: the per-bar Hold sites (Sites 2-3) are outside the segment_complete vol_proxy scope (declared at experience_kernels.cu:3124). Each per-bar site recomputes vol_proxy locally from the same features[bar_idx * market_dim + 9] ATR-norm input the segment_complete branch and the B.2/D.4c bonus blocks consume. The vol_proxy_pb1/vol_proxy_pb2 locals mirror the segment_complete computation modulo the × sqrt(hold_time) term (which is 1 for per-bar / hold_time=1 — matches the device fn's contract).
  • ISV cap fallback: each call site reads ISV[483] (POS_CAP) and ISV[484] (NEG_CAP) with the same sentinel-or-out-of-bounds guard used at Site 1: if the slot value is within ε of the sentinel OR outside [POS_CAP_MIN_BOUND, POS_CAP_MAX_BOUND], the call falls through to the macro defaults (+5/-10). This is bit-identical to a fixed +5/-10 cap during cold-start and Pre-Phase-3 (when the producer kernel hold_reward_cap_update_kernel has not yet landed).
  • Reward-component slot routing: Sites 1/2/3 write to slots 0/3/4 (popart/micro/opp_cost) per the SP15 Wave 2 reward-component layout. Routing matches the deleted SP13/SP16 subtraction destinations exactly, so the SP11 reward-subsystem controller's component-weight estimator sees the same input distribution shape (modulo the structural-counterfactual vs reactive-cost magnitude shift, which is precisely the change the controller is meant to learn).
  • shaping_scale gating: the device fn multiplies the capped output by shaping_scale. Validation runs at shaping_scale=0 → the device fn returns 0.0 → no Hold opp cost in pure backtest, matching the SP12 lump-sum compute_lump_sum_opp_cost contract.

Phase 3 sites introduced (atomic commit, 2026-05-09)

D-leg adaptive HOLD_REWARD_POS/NEG_CAP producer kernel + launcher + per-epoch wiring + GPU oracle tests. Lifts the Phase 2 caps from "sentinel-driven cold-start" (5.0/-10.0 fixed) to "p99(|step_ret|) over Long/Short trade closes × 1.5 safety factor" with Wiener-optimal α blend per pearl_wiener_optimal_adaptive_alpha. The Phase 2 consumer (compute_sp18_hold_opportunity_cost) sees the producer-driven slots [483]/[484] from epoch 1 onward; the sentinel branch in the consumer remains as the cold-start fallback path (zero-trade-close epoch ⇒ producer early-returns ⇒ slots stay at sentinel ⇒ consumer falls through to the +5/-10 macro defaults — bit-identical to pre-Phase-3 behavior).

Sites introduced (single atomic commit per feedback_no_partial_refactor):

  • Kernel: crates/ml/src/cuda_pipeline/hold_reward_cap_update_kernel.cu (NEW, ~310 lines). Single-block 256-thread producer; mirrors the SP14 P0-A reward_cap_update_kernel structural template with three differences:

    1. Filter: (is_close && step_ret != 0) — both winners AND losers (the consumer needs the magnitude scale of all Long/Short closes, not just winners). Position-side cap filters (is_close && step_ret > 0).
    2. Welford-derived Wiener-α (slots [487..493)) replaces fixed α=0.01. Floor at WELFORD_ALPHA_MIN=0.4 per pearl_wiener_alpha_floor_for_nonstationary — the policy-realised Long/Short close magnitude distribution is intrinsically non-stationary as the policy adapts, sharpening tails.
    3. Bounds [HOLD_REWARD_POS_CAP_MIN_BOUND=0.5, HOLD_REWARD_POS_CAP_MAX_BOUND=50.0] (vs. position-side [1.0, 50.0]).
  • Cubin manifest: crates/ml/build.rs — added kernel entry. Build-rs cubin static HOLD_REWARD_CAP_UPDATE_CUBIN exposed in gpu_dqn_trainer.rs:2190.

  • Producer wrapper: crates/ml/src/cuda_pipeline/gpu_aux_trunk.rs — new HoldRewardCapUpdateOps struct (mirrors RewardCapUpdateOps) with new/launch impl; pre-loads CudaFunction at construction per pearl_no_host_branches_in_captured_graph.md.

  • Trainer wiring: crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs:

    • Struct field hold_reward_cap_update_ops at line 7828.
    • Constructor instantiation at line 24409.
    • Field-init in struct literal at line 25293.
    • Method launch_hold_reward_cap_update placed right after launch_reward_cap_update.
  • Per-epoch wire-up: crates/ml/src/trainers/dqn/trainer/training_loop.rs line 511 — fused.trainer().launch_hold_reward_cap_update(step_ret_ptr, trade_close_ptr, total_samples) placed right after launch_reward_cap_update so both cap producers share the same source buffers (read-only; independent ISV slot pairs at [452]/[453] and [483]/[484]).

  • HEALTH_DIAG: training_loop.rs (post td_error_pre emit) — new line HEALTH_DIAG[N]: hold_reward_cap [pos={:.4} neg={:.4} fire_rate={:.4}]. Reads ISV[483]/[484]/[486] via read_isv_signal_at; slot 486 (HOLD_OPP_COST_FIRE_RATE_EMA) is the engagement-canary diag for Phase 8 to populate.

  • GPU oracle tests: crates/ml/tests/sp18_hold_reward_oracle_tests.rs — three new tests in the mod gpu block:

    • hold_reward_cap_producer_drives_slots — T5 from spec: 100 simulated Long/Short closes with jittered ~N(0, 0.01); verifies ISV[483] is bilaterally clamped to MIN_BOUND=0.5 (small p99 ⇒ target_cap << bound), ISV[484] = -2 × ISV[483], filler outliers (close=0) filtered out.
    • hold_reward_cap_pearl_a_first_observation_replace — Pearl-A bootstrap: 4 large closes with step_ret = [+10, -20, +30, -40], target_cap = max(p99, max) × 1.5 ≈ 76.5, clamped to MAX_BOUND=50, Pearl-A REPLACE on sentinel writes 50.0 directly (NOT blended with sentinel 5.0).
    • hold_reward_cap_no_closes_preserves_isv — no closes (trade_close=0 ∀i): every ISV slot (POS, NEG, all 6 Welford) preserved bit-exactly. Verifies the cold-start fallback path.
  • Audit script: scripts/audit_sp18_consumers.sh --check exits 0 (no fingerprint drift in tracked sections — SP18 D-leg/B-leg legacy references unchanged; the new Phase 3 producer is in its own symbol-namespace not tracked by the audit's snapshot, which is by design — the audit tracks the DELETED SP13/SP16 legacy chain).

Validation: cargo check --workspace clean (1m 12s); 3 GPU oracle tests pass on local RTX 3050 Ti (1.93s wallclock).

Phase 3 design notes

  • Why Wiener-α (vs. fixed α=0.01 on the position-side cap): the position-side cap drifts slowly because the underlying alpha distribution is approximately stationary over training. The Hold-side cap distribution is intrinsically NON-stationary as the policy adapts (sizing tightens, trail shifts). Welford-derived α tracks that non-stationarity; the 0.4 floor prevents the canonical "α decays to 0 once policy converges" trap (SP16 T3 regression at train-multi-seed-b5gmp sha 641aa0dfd Fold 1: α=0.248 at HD4 → Hold% 52.6% at HD5 — see pearl_wiener_alpha_floor_for_nonstationary).

  • Why no separate Long/Short flag buffer: step_ret_per_sample[i] is non-zero ONLY on close events from positions that had non-zero pre_sign — i.e. Long or Short positions. Hold/Flat positions don't generate non-zero step_ret per experience_kernels.cu:2855. Filtering by (is_close && step_ret != 0) is the cheapest correct extraction of the Long/Short close distribution; no extra dir_idx_per_sample buffer needed.

  • Cold-start fallback behavior: when total_count == 0 (no closes this epoch — Hold-heavy phase), the kernel early-returns at the guard in Phase 2 of its body. ALL slots (POS, NEG, all 6 Welford) remain unchanged. The Phase 2 consumer (compute_sp18_hold_opportunity_cost) sees the cold-start sentinels (5.0/-10.0) and routes through its own is_sentinel branch to the macro defaults +5/-10 — bit-identical to pre-Phase-3.

  • Coexistence with reward_cap_update_kernel: both producers read the same step_ret_per_sample + trade_close_per_sample buffers (read-only; no shared state). They drive independent ISV slot pairs and use different filters. Per-epoch boundary launches are sequential (position-side first, Hold-side second) so the two_kernel sequence is graph-capturable (single stream, no host branches). The redundant block-tree-reduce on the same input buffers is a micro-cost (≤ 2 × 64K samples × 4 floats per epoch = cheap relative to per-step training cost).

B-leg verification (Phase 1.1 sub-objective)

Per the plan's locked B-leg checklist:

  • td_lambda_kernel launch sites: 1 site at gpu_experience_collector.rs:4154 (per the plan). Verified.
  • q_next self-bootstrap: line ~4143 confirmed (let q_next = rewards). Phase 5 will replace.
  • PER priority recomputation: 5 sites in crates/ml-dqn/src/gpu_replay_buffer.rs, crates/ml-dqn/src/replay_buffer_type.rs, crates/ml/src/trainers/dqn/config.rs, crates/ml/src/trainers/dqn/fused_training.rs, crates/ml/tests/gpu_per_integration_test.rs. NONE store q_next — confirms B-DD4 (no PER migration needed).
  • C51 loss target-Q origin: c51_loss_kernel.cu block_bellman_project_f already uses target-net Q at loss time — no migration needed.
  • target_params_buf: 17 references in gpu_dqn_trainer.rs, Polyak-tracked, ready for B-leg consumption.
  • PopArt slot 63: catalogued; B-DD11 reset is the only PopArt change in scope.

B-leg pre-conditions verified ✓ — no additional B-leg consumers surfaced (Phase 4-5 atomic-refactor scope is unchanged).

Captured audit fingerprint (locked snapshot — pre-commit hook --check diffs against this)

The fingerprint below is a per-section, per-file hit count summary. It is short enough to inline in markdown, sortable for human review, and stable enough to diff cleanly. Catches both new-consumer drift (a file appears in a section where it didn't before) and expanded-consumer drift (a file's hit count changes). It does NOT catch line-by-line content drift within a single file — that's checked by the per-section grep output diffed in PR review.

To regenerate:

scripts/audit_sp18_consumers.sh --fingerprint
# update this section between BEGIN/END markers, then:
git add docs/sp18-wireup-audit.md
# the pre-commit hook check_sp18_consumer_audit fails the next commit if
# scripts/audit_sp18_consumers.sh --check reports drift vs this snapshot.

After Phase 1's atomic deletion lands, this fingerprint will shrink dramatically (slot 461 + HCS_* + hold_cost_scale_update sections drop to zero hits). That re-capture is committed as part of Task 1.6 (close-out).

=== D-leg: Slot 380 (HOLD_COST_INDEX) consumers === total_hits=39 crates/ml/build.rs 1 crates/ml/src/cuda_pipeline/sp5_isv_slots.rs 1 crates/ml/src/cuda_pipeline/hold_rate_observer_kernel.cu 1 crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs 2 crates/ml/src/cuda_pipeline/sp13_isv_slots.rs 1 crates/ml/src/cuda_pipeline/state_layout.cuh 1 crates/ml/src/trainers/dqn/trainer/training_loop.rs 3 crates/ml/src/trainers/dqn/state_reset_registry.rs 1 docs/dqn-wire-up-audit.md 10 docs/superpowers/specs/2026-05-04-sp13-redefine-success-for-predictive-skill.md 4 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 4 docs/superpowers/plans/2026-05-04-sp13-redefine-success-for-predictive-skill.md 5 docs/sp18-wireup-audit.md 5 === D-leg: Slot 461 (HOLD_COST_SCALE_INDEX) consumers === total_hits=79 crates/ml/src/cuda_pipeline/sp20_controllers_compute.rs 5 crates/ml/src/cuda_pipeline/sp20_hold_baseline.cuh 2 crates/ml/src/cuda_pipeline/gpu_experience_collector.rs 2 crates/ml/src/cuda_pipeline/sp20_hold_baseline_test_kernel.cu 1 crates/ml/src/cuda_pipeline/sp14_isv_slots.rs 2 crates/ml/src/cuda_pipeline/sp20_controllers_compute_kernel.cu 2 crates/ml/src/trainers/dqn/trainer/training_loop.rs 4 crates/ml/src/trainers/dqn/state_reset_registry.rs 2 crates/ml/tests/sp20_controllers_compute_test.rs 17 crates/ml/tests/sp20_phase1_4_wireup_test.rs 2 docs/dqn-wire-up-audit.md 11 docs/superpowers/specs/2026-05-09-sp19-20-wr-first-design.md 1 docs/superpowers/plans/2026-05-09-sp19-20-wr-first.md 8 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 3 docs/superpowers/plans/2026-05-08-sp16-hold-collapse-structural-fix.md 10 docs/sp18-wireup-audit.md 6 docs/isv-slots.md 1 === D-leg: Slots [462..468) (HCS_* Welford) consumers === total_hits=9 docs/dqn-wire-up-audit.md 8 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 1 === D-leg: hold_cost_scale_update_kernel references === total_hits=78 crates/ml/src/cuda_pipeline/sp20_controllers_compute.rs 2 crates/ml/src/cuda_pipeline/sp20_controllers_compute_kernel.cu 3 crates/ml/src/trainers/dqn/trainer/training_loop.rs 5 crates/ml/src/trainers/dqn/state_reset_registry.rs 2 crates/ml/tests/sp20_controllers_compute_test.rs 3 docs/plans/2026-05-10-sp21-train-eval-coherence-isv-defrost.md 2 docs/dqn-wire-up-audit.md 23 docs/superpowers/specs/2026-05-08-sp18-reward-shape-hold-attractor-design.md 4 docs/superpowers/plans/2026-05-09-sp19-20-wr-first.md 2 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 12 docs/superpowers/plans/2026-05-08-sp16-hold-collapse-structural-fix.md 9 docs/sp18-wireup-audit.md 11 === D-leg: hold_rate_observer_kernel (RETAINED — diag chain stays per DD7=c) === total_hits=164 crates/ml/build.rs 5 crates/ml/src/cuda_pipeline/sp5_isv_slots.rs 3 crates/ml/src/cuda_pipeline/gpu_aux_trunk.rs 5 crates/ml/src/cuda_pipeline/hold_rate_observer_kernel.cu 4 crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs 8 crates/ml/src/cuda_pipeline/gpu_experience_collector.rs 13 crates/ml/src/cuda_pipeline/sp14_isv_slots.rs 5 crates/ml/src/cuda_pipeline/sp20_aggregate_inputs.rs 1 crates/ml/src/cuda_pipeline/sp13_isv_slots.rs 2 crates/ml/src/cuda_pipeline/min_hold_temperature_update_kernel.cu 6 crates/ml/src/cuda_pipeline/sp20_aggregate_inputs_kernel.cu 1 crates/ml/src/cuda_pipeline/state_layout.cuh 1 crates/ml/src/trainers/dqn/trainer/training_loop.rs 13 crates/ml/src/trainers/dqn/state_reset_registry.rs 3 crates/ml/tests/sp13_phase0_oracle_tests.rs 19 crates/ml/tests/sp14_oracle_tests.rs 20 docs/dqn-wire-up-audit.md 12 docs/superpowers/specs/2026-05-08-sp18-reward-shape-hold-attractor-design.md 1 docs/superpowers/specs/2026-05-04-sp13-redefine-success-for-predictive-skill.md 6 docs/superpowers/plans/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md 5 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 3 docs/superpowers/plans/2026-05-04-sp13-redefine-success-for-predictive-skill.md 20 docs/sp18-wireup-audit.md 7 docs/isv-slots.md 1 === D-leg: Per-bar Hold-cost subtraction sites (3 lines we change in Phase 1.5) === total_hits=5 crates/ml/src/cuda_pipeline/experience_kernels.cu 5 === D-leg: state_layout.cuh mirror constants (HOLD_COST + HCS) === total_hits=1 crates/ml/src/cuda_pipeline/state_layout.cuh 1 === D-leg: state_reset_registry slot 461 entries === total_hits=2 crates/ml/src/trainers/dqn/state_reset_registry.rs 2 === D-leg: Cubin manifest references in build.rs === total_hits=2 crates/ml/build.rs 2 === B-leg: td_lambda_kernel launch sites === total_hits=20 crates/ml/src/cuda_pipeline/nstep_kernel.cu 1 crates/ml/src/cuda_pipeline/gpu_experience_collector.rs 11 crates/ml/tests/sp18_td_lambda_q_next_oracle_tests.rs 8 === B-leg: q_next argument origin (the line ~4143 site) === total_hits= === B-leg: rewards_out consumers (downstream of TD(λ) output) === total_hits= === B-leg: PER priority computation sites (verify B-DD4: NO migration needed) === total_hits=18 crates/ml/src/trainers/dqn/fused_training.rs 1 crates/ml/src/trainers/dqn/config.rs 4 crates/ml/tests/gpu_per_integration_test.rs 3 crates/ml-dqn/src/replay_buffer_type.rs 3 crates/ml-dqn/src/gpu_replay_buffer.rs 7 === B-leg: Replay buffer schema — q_next storage check (verify NO migration) === total_hits= === B-leg: c51_loss_kernel target-Q origin (verify already correct at loss time) === total_hits= === B-leg: target_params_buf consumers (verify Polyak-tracked, available at collection time) === total_hits= === B-leg: PopArt slot 63 references (verify B-DD11 reset is the only PopArt change) === total_hits=20 crates/ml/build.rs 8 crates/ml/src/training_profile.rs 8 crates/ml/src/cuda_pipeline/trade_physics.cuh 1 crates/ml/src/cuda_pipeline/dqn_utility_kernels.cu 3 === END === total_hits=

Phase 4 close-out — B-leg target-net forward additive infrastructure (2026-05-09)

Phase 4 lands the additive dead-code state for the B-leg target-Q DDQN bootstrap that Phase 5 will swap into td_lambda_kernel's q_next argument at gpu_experience_collector.rs:~4313 (post-Task-4.1 hoist).

Scope landed in this commit:

  1. Task 4.1next_states build-site relocated. The build_next_states_f32 invocation now precedes the TD(λ) launch block (no behavioral change — next_states had no observer between its pre-Phase-4 build site and the bottom-of-fn batch assembly). Pure ordering hoist; verified via the audit grep next_states\b flagging 0 post-TD(λ) consumers.
  2. Task 4.2compute_q_next_target_bootstrap skeleton method added on GpuExperienceCollector with full plan-aligned signature:
    pub fn compute_q_next_target_bootstrap(
        &mut self,
        next_states: &CudaSlice<f32>,
        target_params_ptr: u64,
        online_params_ptr: u64,
        batch_size: usize,
    ) -> Result<CudaSlice<f32>, MLError>
    
    Body is a hard-error early-return per feedback_no_stubs — no production caller exists in Phase 4 (the caller is wired in Phase 5 Task 5.1, replacing the self-bootstrap clone at line ~4313). Tasks 4.3-4.5 fill in the body atomically before Phase 5 wires the production call site. Returning Err instead of todo!() satisfies Invariant 9 (no deferred-work markers); any accidental pre-Phase-5 call site hard-errors with a clear pointer to the open work.

Phase 4 introspection tests (no GPU required, all pass):

Test File Purpose
compute_q_next_target_bootstrap_method_exists crates/ml/tests/sp18_td_lambda_q_next_oracle_tests.rs Asserts method signature exists with documented parameters
next_states_built_before_td_lambda same Asserts Task 4.1 ordering invariant via offset-comparison
td_lambda_still_consumes_self_bootstrap_q_next_in_phase4 same Atomic-refactor guard: Phase 4 STILL uses q_next = rewards_out clone — Phase 5 Task 5.1 swaps

Atomic-refactor invariant (HARD — feedback_no_partial_refactor):

Phase 4 leaves compute_q_next_target_bootstrap as dead code (no production caller, only introspection tests). Phase 5.1 makes it live in lockstep with the q_next argument swap at the TD(λ) launch:

NO L40S DISPATCH between the Phase 4 close-out commit and the Phase 5 Task 5.1 commit that replaces let q_next = dtod_clone_f32_native(&self.stream, &self.rewards_out, ...) with let q_next = self.compute_q_next_target_bootstrap(&next_states, ...).

Tasks 4.3-4.5 remaining (deferred to follow-up subagent dispatch):

  • Task 4.3 — implement online-net forward on next_states for DDQN per-branch argmax (mirror of submit_forward_ops_ddqn infra: VSN+BN+trunk+branch-head chain via cublas_forward.forward_online_raw, output: [B, 4] i32 per-branch argmax actions).
  • Task 4.4 — implement target-net forward via cublas_forward.forward_target_raw, then compute_expected_q with target's distributional outputs to produce per-action E[Q]; gather per-branch argmax Q_target[b, a*_online_branch] and resolve final scalar form (B-DD8.a: dir-branch only, OR sum across 4 branches). GPU oracle tests T8 + T11 (#[ignore = "requires GPU"]).
  • Task 4.5 — replace todo!() body; close-out grep audit asserts todo! absent in compute_q_next_target_bootstrap; Phase 4 ends in additive-dead-code state with full body wired.

These tasks each require substantial new infrastructure on the collector (target-net trunk forward chain duplicating gpu_dqn_trainer.rs Pass 2's VSN+BN+OFI+trunk+branch-head sequence) and are decomposed in the plan §"Phase 4" as 3 separate TDD cycles with GPU oracle test gates between each. Atomic-refactor rule binds the entire chain Task 4.5 → Task 5.1: NO L40S DISPATCH between.

Sub-task verification status:

SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 cargo check -p ml
# → finished `dev` profile [unoptimized + debuginfo] target(s); clean

SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 \
  cargo test -p ml --test sp18_td_lambda_q_next_oracle_tests
# → 3 passed; 0 failed; 0 ignored

Audit script post-Phase-4 fingerprint diff (vs Phase 1 close-out):

  • td_lambda_kernel launch sites count rose from 9 → 19 due to the new sp18_td_lambda_q_next_oracle_tests.rs introspection test file (7 hits via test docstrings + assertions + the build-site ordering check); the source gpu_experience_collector.rs count rose by 3 (additive comments referencing td_lambda_kernel per the Phase-4 wiring narrative). Fingerprint regenerated above.
  • compute_q_next_target_bootstrap introduced — 1 producer site (the new method in gpu_experience_collector.rs) + 1 introspection test referencing it. No production caller (Phase 4 = additive dead code by design).