Files
foxhunt/scripts/audit-wiring.sh
jgrusewski 40855bfd62 docs(sp20): trader-grade trade management spec + audit infrastructure
Adds SP20 — full production trader-management system in one
greenfield commit (3-4 weeks of implementation work to follow):

  * Tier 0: multi-resolution time-scaled market features (3 horizons)
  * Tier 1: trade-arc awareness (4 features per batch)
  * Tier 2: per-unit trail-stop (entry + trail + stop per unit)
  * Tier 3: pyramiding + partial profit-taking (HalfFlat actions,
            N_ACTIONS=9→11)
  * Tier 4: Forward-Return-Distribution head + confidence gate +
            per-batch anti-martingale sizing + position heat cap +
            vol-adjusted defaults

Spec went through critical-review pass (v1→v2→v3):
  * v1: 3 tiers, side-channel features, single-gate acceptance
  * v2: 5 tiers added partial-flat + anti-mart + multi-res + checklist
  * v3: foundational fixes for 4 CRIT + 6 SIG + 6 MIN findings
        (per-unit pyramid state, encoder-input injection vs side-channel,
         FRD head replaces survivor-biased checklist, override stack
         ordering, per-batch anti-mart, real-time multi-res scales,
         P-1 ceiling falsification gate, multi-tier acceptance)

§0 Foundational Principles (NEW, non-negotiable):
  * §0.1 every numerical constant ISV-resident (no hardcoded #defines
         in new kernels; structural-dim exception only)
  * §0.2 every kernel/slot/head/action fully wired in same commit
  * §0.3 diagnostics baked in at birth (every observable in JSONL)
  * §0.4 per-phase ship-gate: all three audits must pass

Audit infrastructure shipped with the spec:
  * scripts/audit-isv.sh      — greps new .cu for hardcoded #defines
  * scripts/audit-wiring.sh   — verifies kernels/slots/heads/actions
                                have producer + consumer in code
  * scripts/audit-diag.sh     — runs local 100-step smoke, validates
                                manifest-listed jq paths present in JSONL
  * scripts/audit-manifest/   — per-phase append manifests (kernels,
                                slots, heads, actions, diag-fields)

Naming discipline: audit scripts and manifest are SP-agnostic (no
`sp20-` prefix) per new pearl `feedback_no_sp_or_version_prefixes_in_file_names`
— they'll serve future SPs too. SP numbers belong only in
docs/superpowers/{specs,plans}/ filenames.

Audit scripts dogfooded — already caught two real violations on
existing code that the formal review missed:
  * audit-isv: KL_EMA_ALPHA=0.05f hardcoded in rl_q_pi_distill_grad.cu
  * audit-wiring: TrailTighten action (a7) has no handler in any
                  kernel (per pearl_dead_trail_stop_actions_a7_a8)

These violations are SP20 P5/P10 fix targets.

User decision recorded in spec §3 P-1: ceiling-falsification phase
intentionally skipped — SP20 is the architectural launchpad for
the broader trader system regardless of whether current arch could
be pushed further at 1M steps. P-1 may be revisited as standalone
work after SP20 ships.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 16:28:39 +02:00

188 lines
7.3 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 in actions_to_market_targets.cu: branch `action == <idx>`
if ! grep -qE "action[[:space:]]*==[[:space:]]*${action_idx}\b" "$ACTIONS_KERNEL"; then
# Could be handled in override kernel — check across all .cu files
handler_count=$(grep -lrE "(action|actions\[[^]]*\])[[:space:]]*==[[:space:]]*${action_idx}\b" "$CUDA_DIR" 2>/dev/null | wc -l)
if [[ "$handler_count" -eq 0 ]]; then
echo " VIOLATION action $action (idx $action_idx): no handler in actions_to_market_targets or override kernels"
violations=$((violations + 1))
fi
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