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>
111 lines
3.3 KiB
Bash
Executable File
111 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# audit-isv.sh — enforce "every numerical constant is ISV-resident"
|
|
# per SP20 §0.1.
|
|
#
|
|
# Walks scripts/audit-manifest/kernels.txt and greps each listed .cu
|
|
# for `#define NAME VALUE` lines where VALUE is a numeric literal.
|
|
# Flags any define whose NAME is NOT in the structural-dim allowlist
|
|
# AND does NOT end in `_INDEX` (ISV slot pointer).
|
|
#
|
|
# Exit codes:
|
|
# 0 — zero violations
|
|
# 1 — one or more violations found
|
|
# 2 — usage / setup error
|
|
#
|
|
# Allowlist for structural-dim names (compile-time required for buffer
|
|
# layouts / atom support / shared memory):
|
|
ALLOWED_NAMES=(
|
|
"N_ACTIONS"
|
|
"Q_N_ATOMS"
|
|
"HIDDEN_DIM"
|
|
"REGIME_DIM"
|
|
"MAX_UNITS"
|
|
"BLOCK_DIM"
|
|
"GRID_DIM"
|
|
"BLOCK_SIZE"
|
|
"WARP_SIZE"
|
|
"BLOCK_X" "BLOCK_Y" "BLOCK_Z"
|
|
"MAX_WINDOW_ENTRIES"
|
|
"BOOK_LEVELS"
|
|
)
|
|
# Action enum indices (`ACTION_<NAME> = <int>`) are structural API
|
|
# contracts matching crates/ml-alpha/src/rl/common.rs::Action enum
|
|
# positions. Allowed via name-prefix pattern.
|
|
ACTION_NAME_PREFIX="ACTION_"
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT=$(git rev-parse --show-toplevel)
|
|
MANIFEST="$ROOT/scripts/audit-manifest/kernels.txt"
|
|
CUDA_DIR="$ROOT/crates/ml-alpha/cuda"
|
|
|
|
if [[ ! -f "$MANIFEST" ]]; then
|
|
echo "ERROR: manifest not found: $MANIFEST" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Read manifest into array, skipping blank lines and comments.
|
|
mapfile -t kernels < <(grep -vE '^\s*(#|$)' "$MANIFEST" | awk '{print $1}')
|
|
|
|
if [[ ${#kernels[@]} -eq 0 ]]; then
|
|
echo "audit-isv: manifest empty — no kernels to audit. PASS."
|
|
exit 0
|
|
fi
|
|
|
|
# Build a regex of allowed names + `_INDEX` suffix pattern.
|
|
allowed_regex=""
|
|
for name in "${ALLOWED_NAMES[@]}"; do
|
|
allowed_regex="${allowed_regex}|^${name}$"
|
|
done
|
|
allowed_regex="(${allowed_regex#|}|_INDEX$)"
|
|
|
|
violations=0
|
|
total_defines=0
|
|
|
|
echo "audit-isv: auditing ${#kernels[@]} kernel(s) from manifest..."
|
|
echo
|
|
|
|
for kernel in "${kernels[@]}"; do
|
|
cu_file="$CUDA_DIR/${kernel}.cu"
|
|
if [[ ! -f "$cu_file" ]]; then
|
|
echo "ERROR: kernel listed in manifest but file missing: $cu_file" >&2
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
|
|
# Extract `#define NAME VALUE` where VALUE is a numeric literal
|
|
# (integer or float, optional `f` suffix, optional sign).
|
|
# Skip macros that contain function-like () bodies — those are
|
|
# parameterized templates, not constants.
|
|
while IFS= read -r line; do
|
|
# Strip leading whitespace
|
|
trimmed="${line#"${line%%[![:space:]]*}"}"
|
|
# Parse: #define NAME VALUE
|
|
if [[ "$trimmed" =~ ^\#define[[:space:]]+([A-Z_][A-Z0-9_]*)[[:space:]]+([+-]?[0-9]+(\.[0-9]+)?f?)$ ]]; then
|
|
name="${BASH_REMATCH[1]}"
|
|
value="${BASH_REMATCH[2]}"
|
|
total_defines=$((total_defines + 1))
|
|
if [[ "$name" =~ $allowed_regex || "$name" == ${ACTION_NAME_PREFIX}* ]]; then
|
|
: # allowed (structural dim, ISV index, or ACTION_ enum mirror)
|
|
else
|
|
echo " VIOLATION $kernel: #define $name $value (should be ISV slot)"
|
|
violations=$((violations + 1))
|
|
fi
|
|
fi
|
|
done < "$cu_file"
|
|
done
|
|
|
|
echo
|
|
echo "audit-isv: scanned $total_defines numeric #defines, found $violations violation(s)."
|
|
|
|
if [[ $violations -gt 0 ]]; then
|
|
echo
|
|
echo "Per SP20 §0.1: every numerical constant in new kernels must be ISV-resident."
|
|
echo "Add an RL_*_INDEX slot to crates/ml-alpha/src/rl/isv_slots.rs,"
|
|
echo "seed via rl_isv_write at trainer init, and read from the slot in the kernel."
|
|
exit 1
|
|
fi
|
|
|
|
echo "audit-isv: PASS"
|
|
exit 0
|