scripts/audit-wiring.sh dogfood pass flagged a7 (TrailTighten) and
a8 (TrailLoosen) as actions with no consumer anywhere in the
codebase (canonical pearl_dead_trail_stop_actions_a7_a8). Fix
bundles SP20 P1 (per-unit trade state buffers) and P5 (trail-stop
kernels) since they're the same architectural work.
Three new kernels:
rl_unit_state_update.cu — per-batch trade state machine. Runs
AFTER fill+extract_realized_pnl_delta.
Detects open/close/reverse position
transitions and populates unit slot 0
with entry_price, entry_step, lots,
initial_r, trail_distance. Slots 1-3
allocated for SP20 P7 pyramid expansion
but unused this commit.
rl_trail_mutate.cu — handles a7/a8 actions. Mutates ALL
active units' trail_distance bounded
by ISV [MIN, MAX] with symmetric
reciprocal adjust rate per SP20 §4.12:
a7: trail = max(MIN, trail × rate)
a8: trail = min(MAX, trail / rate)
rl_trail_stop_check.cu — per-batch per-unit breach check. Reads
shared lobsim best book (bid/ask),
computes mid, compares to each active
unit's (entry ± trail). On breach,
OVERRIDE actions[b] to FlatFromLong
(a3) or FlatFromShort (a4). Force-close
routes through existing flat plumbing
per pearl_stop_checks_run_at_deadline_cadence.
SP20 v3 §3 P5 calls for routing close
via partial-flat (a9/a10) so only the
at-risk unit closes — that needs P4
(N_ACTIONS=11). For now, ANY unit's
breach closes ENTIRE position via full
FlatFromLong/Short.
Per-batch per-unit buffers (8 new in trainer):
unit_entry_price_d [B × 4] f32
unit_entry_step_d [B × 4] i32
unit_lots_d [B × 4] i32
unit_initial_r_d [B × 4] f32
unit_trail_distance_d[B × 4] f32
unit_active_d [B × 4] u8
pyramid_units_count_d[B] i32
unit_prev_pos_lots_d [B] i32 (state-machine tracker, separate
from extract_realized_pnl_delta's
prev_position_lots_d for clean
kernel composability)
4 new ISV slots (494-497):
RL_TRAIL_MIN_INDEX — trail distance floor (seed 0.001)
RL_TRAIL_MAX_INDEX — trail distance ceiling (seed 100.0)
RL_TRAIL_K_INIT_INDEX — initial trail multiplier (seed 2.0, Turtle 2N)
RL_TRAIL_ADJUST_RATE_INDEX — tighten ratio (seed 0.9; symmetric reciprocal for loosen)
RL_SLOTS_END: 494 → 498.
LobSim exposes bid_px_d() + ask_px_d() public accessors. RlLobBackend
trait extended with the two accessors; the LobSimCuda impl wires
through.
Override stack ordering per SP20 §2.3:
1. rl_pi_action_kernel (sample)
2. rl_trail_mutate (a7/a8 → mutate, before stop check)
3. rl_trail_stop_check (per-unit breach → override action)
4. actions_to_market_targets (execute, including overridden flat)
5. step_fill_from_market_targets
6. extract_realized_pnl_delta
7. rl_unit_state_update (detect post-fill transitions)
Audit infrastructure refined as part of dogfooding:
* audit-isv allowlist extended for BOOK_LEVELS (structural book
depth) and ACTION_* prefix (enum-mirror constants — these are
structural API contracts matching src/rl/common.rs::Action positions)
* audit-wiring action-handler regex now matches BOTH literal
`action == <idx>` and `action == ACTION_<UPPER_SNAKE>` patterns,
and treats != as a handler too (a guard against the action is
valid wiring)
Both `audit-isv.sh` and `audit-wiring.sh` PASS cleanly with the
full manifest. audit-diag scheduled for first SP20 phase that adds
diag fields (this commit deliberately keeps diag exposure minimal
— full per-unit + trail diag blocks come with SP20 P13).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
195 lines
7.8 KiB
Bash
Executable File
195 lines
7.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# audit-wiring.sh — enforce "every kernel/slot/head/action fully wired"
|
|
# per SP20 §0.2.
|
|
#
|
|
# Walks all four manifests:
|
|
# * kernels.txt → every entry has a registration in build.rs KERNELS
|
|
# AND a `.launch_builder(&self.<name>_fn)` site
|
|
# in src/trainer/integrated.rs
|
|
# * slots.txt → every entry has a write site (seed in rl_isv_write
|
|
# OR write in a .cu kernel) AND a read site
|
|
# (read in a .cu kernel)
|
|
# * heads.txt → every entry has src/heads/<name>.rs AND forward
|
|
# call AND backward call in the trainer
|
|
# * actions.txt → every entry has a branch in
|
|
# cuda/actions_to_market_targets.cu OR a handler
|
|
# in an override kernel
|
|
#
|
|
# Exit codes:
|
|
# 0 — zero violations
|
|
# 1 — one or more violations
|
|
# 2 — usage error
|
|
# Note: NOT using `set -e` — many grep|head|wc pipelines naturally
|
|
# return non-zero (SIGPIPE on head consuming early, grep no-match).
|
|
# We handle exit codes explicitly via `|| true` and `|| echo 0` on
|
|
# the suspect pipelines, and bookkeep `violations` for the final
|
|
# exit code.
|
|
set -uo pipefail
|
|
|
|
ROOT=$(git rev-parse --show-toplevel)
|
|
M="$ROOT/scripts/audit-manifest"
|
|
BUILD_RS="$ROOT/crates/ml-alpha/build.rs"
|
|
TRAINER="$ROOT/crates/ml-alpha/src/trainer/integrated.rs"
|
|
ISV_SLOTS="$ROOT/crates/ml-alpha/src/rl/isv_slots.rs"
|
|
COMMON_RS="$ROOT/crates/ml-alpha/src/rl/common.rs"
|
|
ACTIONS_KERNEL="$ROOT/crates/ml-alpha/cuda/actions_to_market_targets.cu"
|
|
HEADS_DIR="$ROOT/crates/ml-alpha/src/heads"
|
|
CUDA_DIR="$ROOT/crates/ml-alpha/cuda"
|
|
|
|
violations=0
|
|
|
|
read_manifest() {
|
|
local file="$1"
|
|
if [[ -f "$file" ]]; then
|
|
grep -vE '^\s*(#|$)' "$file" | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
# ─── 1. KERNELS ─────────────────────────────────────────────
|
|
echo "audit-wiring: KERNELS"
|
|
while IFS= read -r kernel; do
|
|
[[ -z "$kernel" ]] && continue
|
|
cu_file="$CUDA_DIR/${kernel}.cu"
|
|
if [[ ! -f "$cu_file" ]]; then
|
|
echo " VIOLATION kernel $kernel: source file missing ($cu_file)"
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
# build.rs registration
|
|
if ! grep -qE "\"${kernel}\"" "$BUILD_RS"; then
|
|
echo " VIOLATION kernel $kernel: not registered in build.rs KERNELS"
|
|
violations=$((violations + 1))
|
|
fi
|
|
# trainer field
|
|
if ! grep -qE "${kernel}_fn[: ,]" "$TRAINER"; then
|
|
echo " VIOLATION kernel $kernel: no <name>_fn field in trainer"
|
|
violations=$((violations + 1))
|
|
fi
|
|
# trainer launch site
|
|
if ! grep -qE "launch_builder\(&self\.${kernel}_fn\)" "$TRAINER"; then
|
|
echo " VIOLATION kernel $kernel: no launch site in trainer"
|
|
violations=$((violations + 1))
|
|
fi
|
|
done < <(read_manifest "$M/kernels.txt")
|
|
|
|
# ─── 2. SLOTS ───────────────────────────────────────────────
|
|
echo "audit-wiring: SLOTS"
|
|
while IFS= read -r slot; do
|
|
[[ -z "$slot" ]] && continue
|
|
# slot defined in isv_slots.rs?
|
|
if ! grep -qE "pub const ${slot}\s*:\s*usize" "$ISV_SLOTS"; then
|
|
echo " VIOLATION slot $slot: no `pub const` definition in isv_slots.rs"
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
# Extract numeric slot index for cross-kernel grep
|
|
slot_idx=$(awk -v name="$slot" '
|
|
$0 ~ "pub const " name "[[:space:]]*:[[:space:]]*usize" {
|
|
if (match($0, /=[[:space:]]*[0-9]+/)) {
|
|
v = substr($0, RSTART, RLENGTH)
|
|
gsub(/[^0-9]/, "", v)
|
|
print v; exit
|
|
}
|
|
}' "$ISV_SLOTS")
|
|
[[ -z "$slot_idx" ]] && slot_idx="-1"
|
|
# producer: seeded in rl_isv_write list OR written by a .cu kernel
|
|
producer_seed=0
|
|
producer_kernel=0
|
|
if grep -qE "isv_slots::${slot}" "$TRAINER"; then
|
|
producer_seed=1
|
|
fi
|
|
# kernel writes via isv[INDEX] = ... or isv[<num>] = ...
|
|
if grep -qrE "isv\[(${slot}|${slot_idx})\][[:space:]]*=" "$CUDA_DIR" 2>/dev/null; then
|
|
producer_kernel=1
|
|
fi
|
|
if [[ $producer_seed -eq 0 && $producer_kernel -eq 0 ]]; then
|
|
echo " VIOLATION slot $slot (idx $slot_idx): no producer (not seeded in rl_isv_write, not written by any kernel)"
|
|
violations=$((violations + 1))
|
|
fi
|
|
# consumer: read by some .cu (isv[INDEX] in non-assignment context)
|
|
# Heuristic: count distinct .cu files containing the slot reference
|
|
# in a context that's NOT immediately followed by `=` (assignment).
|
|
# Pattern `isv[X][^=]` matches isv[X] where next char isn't `=`.
|
|
read_only_files=$(grep -lrE "isv\[(${slot}|${slot_idx})\][^=]" "$CUDA_DIR" 2>/dev/null | wc -l)
|
|
if [[ $read_only_files -eq 0 ]]; then
|
|
echo " VIOLATION slot $slot (idx $slot_idx): no consumer (no kernel reads it in non-assignment context)"
|
|
violations=$((violations + 1))
|
|
fi
|
|
done < <(read_manifest "$M/slots.txt")
|
|
|
|
# ─── 3. HEADS ───────────────────────────────────────────────
|
|
echo "audit-wiring: HEADS"
|
|
while IFS= read -r head; do
|
|
[[ -z "$head" ]] && continue
|
|
head_rs="$HEADS_DIR/${head}.rs"
|
|
if [[ ! -f "$head_rs" ]]; then
|
|
echo " VIOLATION head $head: module file missing ($head_rs)"
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
if ! grep -qE "fn forward" "$head_rs"; then
|
|
echo " VIOLATION head $head: no fn forward in module"
|
|
violations=$((violations + 1))
|
|
fi
|
|
if ! grep -qE "fn backward" "$head_rs"; then
|
|
echo " VIOLATION head $head: no fn backward in module"
|
|
violations=$((violations + 1))
|
|
fi
|
|
# Adam step + LR controller — grep for the head's forward call site
|
|
# in the trainer (loose heuristic).
|
|
if ! grep -qE "${head}_head" "$TRAINER"; then
|
|
echo " VIOLATION head $head: no ${head}_head field or call in trainer"
|
|
violations=$((violations + 1))
|
|
fi
|
|
done < <(read_manifest "$M/heads.txt")
|
|
|
|
# ─── 4. ACTIONS ─────────────────────────────────────────────
|
|
echo "audit-wiring: ACTIONS"
|
|
while IFS= read -r action; do
|
|
[[ -z "$action" ]] && continue
|
|
# Action enum entry
|
|
if ! grep -qE "${action}\s*=\s*[0-9]+" "$COMMON_RS"; then
|
|
echo " VIOLATION action $action: not in Action enum in common.rs"
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
# Get action index
|
|
action_idx=$(awk -v name="$action" '
|
|
$0 ~ name "[[:space:]]*=[[:space:]]*[0-9]+" {
|
|
if (match($0, /=[[:space:]]*[0-9]+/)) {
|
|
v = substr($0, RSTART, RLENGTH)
|
|
gsub(/[^0-9]/, "", v)
|
|
print v; exit
|
|
}
|
|
}' "$COMMON_RS")
|
|
[[ -z "$action_idx" ]] && action_idx="-1"
|
|
# Handler patterns (any of):
|
|
# * Literal index branch in any .cu: `action == <idx>` or `actions[..] == <idx>`
|
|
# * Named-constant compare: `action == ACTION_<UPPER_SNAKE>` where
|
|
# UPPER_SNAKE is the CamelCase enum name → UPPER_SNAKE_CASE
|
|
# * Disequality (`!=`) is also a handler (a guard checking for the action)
|
|
upper_snake=$(echo "$action" | sed -E 's/([A-Z])/_\1/g; s/^_//' | tr '[:lower:]' '[:upper:]')
|
|
named_const="ACTION_${upper_snake}"
|
|
pat_literal="(action|actions\\[[^]]*\\])[[:space:]]*[!=]=[[:space:]]*${action_idx}\\b"
|
|
pat_named="(action|actions\\[[^]]*\\])[[:space:]]*[!=]=[[:space:]]*${named_const}\\b"
|
|
|
|
handler_count=$(grep -lrE "(${pat_literal})|(${pat_named})" "$CUDA_DIR" 2>/dev/null | wc -l)
|
|
if [[ "$handler_count" -eq 0 ]]; then
|
|
echo " VIOLATION action $action (idx $action_idx): no handler in any kernel"
|
|
echo " (searched for: \`action == $action_idx\` or \`action == $named_const\`)"
|
|
violations=$((violations + 1))
|
|
fi
|
|
done < <(read_manifest "$M/actions.txt")
|
|
|
|
echo
|
|
echo "audit-wiring: $violations violation(s)"
|
|
|
|
if [[ $violations -gt 0 ]]; then
|
|
echo
|
|
echo "Per SP20 §0.2: every kernel/slot/head/action MUST have producer + consumer in same commit."
|
|
exit 1
|
|
fi
|
|
|
|
echo "audit-wiring: PASS"
|
|
exit 0
|