infra(dqn-v2): audit doc scaffolding + pre-commit enforcement

Plan 1 Task 1. Creates the five audit docs plus config/metric-bands.toml
that track Invariants 2, 7, 8 per the DQN v2 spec, and extends the
pre-commit hook with two checks:

  - component-adding commits must touch an audit doc (Invariant 7)
  - added code may not contain TODO/FIXME/XXX/HACK/TBD/unimplemented!/
    todo! markers (Invariant 9)

Tests: manually verified by staging a TODO-marked file; commit
rejected with the correct error message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-04-24 10:18:26 +02:00
parent 3ab87d7672
commit 06989cfdf9
7 changed files with 229 additions and 0 deletions

13
config/metric-bands.toml Normal file
View File

@@ -0,0 +1,13 @@
# Regression-detection bands for DQN training metrics.
# Invariant 7. Per A.4, bands populated from last 3 known-good runs.
# Band = [mean 3σ, mean + 3σ] per metric; recomputed after baseline promotion.
# N consecutive out-of-band epochs → WARN; 2N consecutive → ERROR + SIGTERM-self.
[settings]
consecutive_epochs_for_warn = 3
consecutive_epochs_for_error = 6
# Placeholder bands — populated by A.4.1 nsys baseline run + populate_metric_bands.sh
# (populated during Plan 5 execution; this file lands with empty metric entries for Plan 1).
[bands]

View File

@@ -0,0 +1,14 @@
# DQN GPU Hot-Path Audit
**Invariant 3 enforcement.** Every cross-boundary call (DtoH, HtoD, memcpy between host and device) in the DQN code path is classified below. No hot-path `memcpy` DtoH/HtoD is allowed — zero tolerance. Pinned + device-mapped memory is the ONLY permitted mechanism for per-step host-visible data.
**Hot path definition:** every function called from `fused_training.rs::step_fused` or its descendants that executes once per training step (178 times per epoch at batch size 8192), including kernel invocations inside the captured CUDA graph (`adam_grad_child`, `forward_child`, `aux_grad_child`) and host-side orchestration between them.
**Classification:**
- `OK-pinned` — uses the zero-copy pinned + device-mapped pattern (allowed).
- `MIGRATE` — uses `memcpy` but should be pinned. Fix during spec impl.
- `COLD-PATH` — acceptable placement (checkpoint save/load, fold transition, epoch-boundary validation), annotated with reason.
| File:line | Call signature | Path class | Annotation | Status |
|---|---|---|---|---|
| (populated during A.6 audit) | | | | |

111
docs/dqn-named-dims.md Normal file
View File

@@ -0,0 +1,111 @@
# DQN Named Dimensions Registry
**Invariant 8 enforcement.** Every semantic index has a named constant. Raw indices (`[0]`, `[6]`, `[23]`) appear ONLY at definition sites. Consumer code always uses the named constant.
## State vector offsets
Defined in `crates/ml/src/cuda_pipeline/state_layout.cuh` and mirrored in Rust via `ml-core::state_layout`.
| Constant | Value | Meaning |
|---|---|---|
| `SL_STATE_DIM` | 104 | Total state vector length |
| `SL_STATE_DIM_PADDED` | 128 | Padded for cuBLAS K-tile alignment |
| `SL_MARKET_DIM` | 42 | Market-feature block width |
| `SL_OFI_DIM` | 32 | OFI block width |
| `SL_MTF_DIM` | 16 | Multi-timeframe block width |
| `SL_PORTFOLIO_BASE_DIM` | 8 | Portfolio-base block width |
| `SL_PORTFOLIO_PLAN_DIM` | 6 | Plan-ISV block width (extended to 7 in Plan 2) |
| `SL_MARKET_START` | 0 | |
| `SL_OFI_START` | 42 | |
| `SL_MTF_START` | 74 | |
| `SL_PORTFOLIO_START` | 90 | |
| `SL_PLAN_ISV_START` | 98 | |
## Portfolio state slots (ps[0..30))
Defined in `crates/ml/src/cuda_pipeline/experience_kernels.cu` header comments. Migrated to named constants during Task 4 of this plan.
| Index | Constant | Meaning |
|---|---|---|
| 0 | `PS_VALUE` | Equity |
| 1 | `PS_POSITION` | Current position |
| 2 | `PS_CASH` | Available cash |
| 3 | `PS_ENTRY_PRICE` | Entry price |
| 4 | `PS_MAX_EQUITY` | Peak equity (for drawdown) |
| 5 | `PS_HOLD_TIME` | Bars held |
| 6 | `PS_CUM_RETURN` | Cumulative return |
| 7 | `PS_STEP_COUNT` | Bars in window |
| 8 | `PS_SPREAD_COST` | Current spread cost |
| 9 | `PS_PREV_EQUITY` | Equity at previous step |
| 10 | `PS_HOLD_TIME_TOTAL` | Total hold time |
| 11 | `PS_REALIZED_PNL` | Cumulative realised PnL |
| 12 | `PS_WIN_COUNT_UNUSED` | (slot retained, not currently used) |
| 13 | `PS_LOSS_COUNT_UNUSED` | (slot retained, not currently used) |
| 14 | `PS_KELLY_WIN_COUNT` | Kelly stats: winning trades |
| 15 | `PS_KELLY_LOSS_COUNT` | Kelly stats: losing trades |
| 16 | `PS_KELLY_SUM_WINS` | Kelly stats: sum of winning returns |
| 17 | `PS_KELLY_SUM_LOSSES` | Kelly stats: sum of \|losing returns\| |
| 18 | `PS_RESERVED_18` | reserved |
| 19 | `PS_RESERVED_19` | reserved |
| 20 | `PS_INTRA_TRADE_MAX_DD` | worst intra-trade drawdown (v8 reward) |
| 21 | `PS_INTRA_TRADE_MAX_PNL` | best intra-trade unrealised PnL (hindsight) |
| 22 | `PS_RESERVED_22` | (was interval_sum, slot retained) |
| 23 | `PS_PLAN_TARGET_BARS` | plan: max hold bars (>0.5 = plan active) |
| 24 | `PS_PLAN_PROFIT_TARGET` | plan: raw profit threshold |
| 25 | `PS_PLAN_STOP_LOSS` | plan: raw stop threshold |
| 26 | `PS_PLAN_SCALE_AGGRESSION` | plan: position ramp speed |
| 27 | `PS_PLAN_CONVICTION` | plan: conviction at entry ∈ [0,1] |
| 28 | `PS_PLAN_ASYMMETRY` | plan: profit/stop asymmetry |
| 29 | `PS_PLAN_ENTRY_REGIME` | plan: regime_stability at entry (for P12) |
## Plan_isv dimensions (plan_isv[0..6))
Will extend to 7 in Plan 2 (D.6 remaining_fraction). These constants live alongside `SL_PLAN_ISV_START` in `state_layout.cuh`.
| Index | Constant | Meaning |
|---|---|---|
| 0 | `PLAN_ISV_PROGRESS` | hold_time / target_bars (clamped 2.0) |
| 1 | `PLAN_ISV_PNL_VS_TARGET` | unrealized / (target × equity) |
| 2 | `PLAN_ISV_PNL_VS_STOP` | -unrealized / (stop × equity) |
| 3 | `PLAN_ISV_ENTRY_CONVICTION` | conviction at entry |
| 4 | `PLAN_ISV_CONVICTION_DRIFT` | current conviction entry conviction |
| 5 | `PLAN_ISV_REGIME_SHIFT` | \|isv[11] entry_stability\| |
## Plan_params output (plan_params[0..6))
From the trade_plan MLP output.
| Index | Constant | Meaning |
|---|---|---|
| 0 | `PLAN_PARAM_TARGET_BARS` | |
| 1 | `PLAN_PARAM_PROFIT_TARGET` | |
| 2 | `PLAN_PARAM_STOP_LOSS` | |
| 3 | `PLAN_PARAM_SCALE_AGGRESSION` | |
| 4 | `PLAN_PARAM_CONVICTION` | |
| 5 | `PLAN_PARAM_ASYMMETRY` | |
## Branch indices (4-branch factored action)
| Index | Constant | Meaning |
|---|---|---|
| 0 | `BRANCH_DIR` | Direction branch (4 actions) |
| 1 | `BRANCH_MAG` | Magnitude branch (3 actions) |
| 2 | `BRANCH_ORD` | Order-type branch (3 actions) |
| 3 | `BRANCH_URG` | Urgency branch (3 actions) |
## Direction action sub-indices
| Index | Constant | Meaning |
|---|---|---|
| 0 | `DIR_SHORT` | Open short (or close long + open short) |
| 1 | `DIR_HOLD` | Keep current position (no-op on position) |
| 2 | `DIR_LONG` | Open long (or close short + open long) |
| 3 | `DIR_FLAT` | Close any position to zero |
## Magnitude action sub-indices
| Index | Constant | Meaning |
|---|---|---|
| 0 | `MAG_QUARTER` | 0.25× max_position |
| 1 | `MAG_HALF` | 0.50× max_position |
| 2 | `MAG_FULL` | 1.00× max_position |

15
docs/dqn-wire-up-audit.md Normal file
View File

@@ -0,0 +1,15 @@
# DQN v2 Wire-Up Audit
**Status:** Populated during task #95 (A.5 orphan audit). Updated on every commit per Invariant 7.
**Legend:**
- `Wired` — consumed by production training + val path.
- `Partial` — consumed on one side only (training OR val, or forward OR backward).
- `Orphan` — built but no production consumer.
- `Ghost` — consumer path is stubbed.
## Components
| Module / kernel | Consumer path | Classification | Notes |
|---|---|---|---|
| (populated during A.5 audit) | | | |

19
docs/isv-slots.md Normal file
View File

@@ -0,0 +1,19 @@
# ISV Slot Allocation Registry
**Source of truth for ISV bus slot allocations.** Every slot has a named constant in `gpu_dqn_trainer.rs`. This doc is the cross-reference.
**Current `ISV_TOTAL_DIM` as of this plan's start:** 37. Post-full DQN v2 rollout: 72.
| Index | Name constant | Type | Producer | Consumers | Reset-category | Notes |
|---|---|---|---|---|---|---|
| [0] | `ISV_SCHEMA_VERSION_INDEX` | u32 | construct | load | SchemaContract | Bumped on any slot-layout change |
| [1..12) | (pre-existing) | f32 | varies | varies | varies | Legacy — audit during A.2 |
| [12] | `LEARNING_HEALTH_INDEX` | f32 | isv_signal_update | many | FoldReset | health score ∈ [0, 1] |
| [13..17) | `Q_MAG_MEAN_*_INDEX` | f32 | q_mag_means_reduce | c51 kernels | FoldReset | Quarter/Half/Full mag Q-mean EMAs + |Q| ref |
| [17..22) | `Q_DIR_MEAN_*_INDEX` | f32 | q_dir_means_reduce | c51 kernels | FoldReset | Short/Hold/Long/Flat dir Q-mean EMAs + |Q| ref |
| [22] | `SHARPE_EMA_INDEX` | f32 | training_loop host | isv_signal_update | FoldReset | Training Sharpe EMA |
| [23..31) | `V_{CENTER,HALF}_{DIR,MAG,ORD,URG}_INDEX` | f32 | update_eval_v_range | adaptive_atoms, warm_start | FoldReset | Per-branch Q-support |
| [31..35) | `GRAD_NORM_TARGET_*_INDEX` | f32 | grad_balance_isv_update | branch_grad_rescale | SoftReset(decay_bars=500) | Per-branch grad-norm target |
| [35] | `GRAD_SCALE_LIMIT_INDEX` | f32 | grad_balance_isv_update | branch_grad_rescale | SoftReset(decay_bars=500) | Scale clamp limit |
| [36] | `IQL_BRANCH_SCALE_FLOOR_INDEX` | f32 | construct (static) | iql_per_branch_advantage | SchemaContract | Per-sample branch_scales floor; safety bound |
| [37..72) | (reserved for DQN v2) | | | | | Allocated incrementally by Plans 2-5 |

View File

@@ -0,0 +1,18 @@
# Supervised → DQN Concept Audit (Part E deliverable)
Authoritative decisions on which ml-supervised concepts the DQN adopts. Per Invariant 9 (no deferred work), every entry is either `IN`, `OUT`, or `AUDIT-THEN-DECIDE` with the audit producing one of the two terminal classifications during this spec's implementation.
| Concept | Supervised home | Classification | Action plan | Status |
|---|---|---|---|---|
| TFT Variable Selection Network | ml-supervised::tft | IN | E.1: extend vsn_mag/vsn_dir to full VSN over state feature groups | pending |
| Gated Residual Network | ml-supervised::tft | IN-IF-ABSENT | E.2: if A.5 audit finds absent, adopt; else mark existing as canonical | pending |
| TFT multi-quantile heads | ml-supervised::tft | IN | E.3: extend IQN to 5/25/50/75/95 quantile decomposition | pending |
| TLOB attention encoder | ml-supervised::tlob | IN | D.8 (Plan 2): wire to DQN trunk | pending |
| Mamba2 forward | ml-supervised::mamba2 | WIRED | already in DQN trunk | — |
| Mamba2 backward | ml-supervised::mamba2 | IN | D.1 (Plan 2): complete | pending |
| Liquid Time-constant | ml-supervised::liquid | AUDIT-THEN-WIRE-OR-DELETE | D.7 (Plan 2): audit decides | pending |
| xLSTM | ml-supervised::xlstm | OUT | Redundant with Mamba2 + TLOB; no DQN wiring | — |
| KAN | ml-supervised::kan | OUT | Function-approx not a bottleneck; no DQN wiring | — |
| Encoder-decoder separation | ml-supervised::tft | IN | E.4: extend D.3 horizon-decomposed V to full trunk/head separation | pending |
| Attention-weight interpretability | various | IN | E.5: ISV-expose attention weights from all temporal heads | pending |
| Multi-task auxiliary heads | ml-supervised | IN | E.6: next-return + regime-classification auxiliary heads | pending |

View File

@@ -69,6 +69,45 @@ if [ -x "$SCRIPT_DIR/gpu-hotpath-guard.sh" ]; then
echo ""
fi
# DQN v2 Invariant 7 enforcement: component-adding commits must update audit docs.
check_audit_doc_updates() {
local staged=$(git diff --cached --name-only)
# Detect "component-adding" commits: new .rs or .cu files, or modifications to
# files that define ISV slots (gpu_dqn_trainer.rs), kernels (cuda_pipeline/*.cu),
# or state layout (state_layout.cuh).
local component_changes=$(echo "$staged" | grep -E '(^crates/ml/src/(cuda_pipeline|trainers/dqn)/|^crates/ml/src/cuda_pipeline/state_layout\.cuh$)')
if [ -z "$component_changes" ]; then return 0; fi
local audit_docs_touched=$(echo "$staged" | grep -E '^docs/(dqn-wire-up-audit|isv-slots|dqn-gpu-hot-path-audit|dqn-named-dims|ml-supervised-to-dqn-concept-audit)\.md$')
if [ -z "$audit_docs_touched" ]; then
echo "❌ Invariant 7 violation: component changes without audit-doc update"
echo " Changed components:"
echo "$component_changes" | sed 's/^/ /'
echo " You must update one of:"
echo " docs/dqn-wire-up-audit.md"
echo " docs/isv-slots.md"
echo " docs/dqn-gpu-hot-path-audit.md"
echo " docs/dqn-named-dims.md"
echo " docs/ml-supervised-to-dqn-concept-audit.md"
return 1
fi
}
# DQN v2 Invariant 9 enforcement: reject TODO/FIXME/XXX/HACK/TBD markers in added code.
check_no_todo_fixme() {
local added_lines=$(git diff --cached --diff-filter=AM -U0 -- '*.rs' '*.cu' '*.cuh' \
| grep -E '^\+' | grep -vE '^\+\+\+')
local bad=$(echo "$added_lines" | grep -E '(TODO|FIXME|\bXXX\b|\bHACK\b|\bTBD\b|unimplemented!|todo!\()')
if [ -n "$bad" ]; then
echo "❌ Invariant 9 violation: deferred-work markers found in staged code"
echo "$bad" | sed 's/^/ /'
return 1
fi
}
check_audit_doc_updates || exit 1
check_no_todo_fixme || exit 1
echo "✅ All pre-commit checks passed!"
echo ""
echo "Summary:"