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>
138 lines
4.2 KiB
Bash
Executable File
138 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# audit-diag.sh — enforce "every observable surfaces in diag JSONL"
|
|
# per SP20 §0.3.
|
|
#
|
|
# Runs a local 100-step smoke (small footprint, b_size=4, predecoded
|
|
# test data) and parses the produced diag JSONL. For each entry in
|
|
# scripts/audit-manifest/diag-fields.txt: verify the jq-syntax path
|
|
# resolves to a non-null, non-NaN value in at least one sampled row.
|
|
#
|
|
# Exit codes:
|
|
# 0 — zero missing fields
|
|
# 1 — one or more missing fields
|
|
# 2 — usage / smoke-run failure
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT=$(git rev-parse --show-toplevel)
|
|
MANIFEST="$ROOT/scripts/audit-manifest/diag-fields.txt"
|
|
BINARY="$ROOT/target/release/examples/alpha_rl_train"
|
|
SMOKE_OUT_DIR="${TMPDIR:-/tmp}/audit-diag-smoke-$$"
|
|
DIAG_FILE="$SMOKE_OUT_DIR/diag.jsonl"
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
echo "ERROR: jq required (apt install jq)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ ! -f "$MANIFEST" ]]; then
|
|
echo "ERROR: manifest not found: $MANIFEST" >&2
|
|
exit 2
|
|
fi
|
|
|
|
mapfile -t fields < <(grep -vE '^\s*(#|$)' "$MANIFEST")
|
|
|
|
if [[ ${#fields[@]} -eq 0 ]]; then
|
|
echo "audit-diag: manifest empty — no diag fields to audit. PASS."
|
|
exit 0
|
|
fi
|
|
|
|
# ── Build binary if missing/stale ─────────────────────────────
|
|
if [[ ! -x "$BINARY" || "$BINARY" -ot "$ROOT/crates/ml-alpha/src/trainer/integrated.rs" ]]; then
|
|
echo "audit-diag: building alpha_rl_train release binary..."
|
|
(cd "$ROOT" && SQLX_OFFLINE=true cargo build -p ml-alpha --example alpha_rl_train --release 2>&1 | tail -5)
|
|
fi
|
|
|
|
# ── Local smoke: 100 steps, b_size=4, single fold, single seed ───
|
|
mkdir -p "$SMOKE_OUT_DIR"
|
|
echo "audit-diag: running 100-step local smoke (b_size=4) → $SMOKE_OUT_DIR"
|
|
|
|
# Inputs — use test-data MBP-10 baseline
|
|
TEST_DATA="${FOXHUNT_TEST_DATA:-$ROOT/test_data/futures-baseline}"
|
|
if [[ ! -d "$TEST_DATA/ES.FUT" ]]; then
|
|
echo "ERROR: test data missing: $TEST_DATA/ES.FUT" >&2
|
|
echo "Set FOXHUNT_TEST_DATA env var to a directory containing ES.FUT/ subdirectory" >&2
|
|
exit 2
|
|
fi
|
|
|
|
PREDECODED="$TEST_DATA/predecoded"
|
|
if [[ ! -d "$PREDECODED" ]]; then
|
|
PREDECODED="$TEST_DATA"
|
|
fi
|
|
|
|
"$BINARY" \
|
|
--mbp10-data-dir "$TEST_DATA/ES.FUT" \
|
|
--trades-data-dir "$TEST_DATA/ES.FUT" \
|
|
--predecoded-dir "$PREDECODED" \
|
|
--output-dir "$SMOKE_OUT_DIR" \
|
|
--n-steps 100 \
|
|
--n-backtests 4 \
|
|
--seq-len 32 \
|
|
--per-capacity 256 \
|
|
--seed 16962 \
|
|
--instrument-mode front-month \
|
|
--n-folds 1 \
|
|
--fold-idx 0 \
|
|
--n-eval-steps 0 \
|
|
> "$SMOKE_OUT_DIR/stdout.log" 2> "$SMOKE_OUT_DIR/stderr.log" \
|
|
|| { echo "ERROR: local smoke failed; see $SMOKE_OUT_DIR/stderr.log"; tail -20 "$SMOKE_OUT_DIR/stderr.log"; exit 2; }
|
|
|
|
if [[ ! -f "$DIAG_FILE" ]]; then
|
|
echo "ERROR: smoke completed but diag.jsonl not produced at $DIAG_FILE" >&2
|
|
exit 2
|
|
fi
|
|
|
|
n_rows=$(wc -l < "$DIAG_FILE")
|
|
echo "audit-diag: smoke produced $n_rows diag rows"
|
|
|
|
if [[ $n_rows -lt 10 ]]; then
|
|
echo "ERROR: too few diag rows ($n_rows); smoke may have crashed early" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Sample 3 rows: first, middle, last
|
|
mid_row=$((n_rows / 2))
|
|
last_row=$n_rows
|
|
sample_rows=(1 $mid_row $last_row)
|
|
|
|
# ── Verify each manifest field present + non-trivial ─────────
|
|
violations=0
|
|
echo
|
|
echo "audit-diag: validating ${#fields[@]} field path(s)..."
|
|
for field in "${fields[@]}"; do
|
|
found_any=0
|
|
bad_any=0
|
|
for r in "${sample_rows[@]}"; do
|
|
val=$(sed -n "${r}p" "$DIAG_FILE" | jq -c "$field // empty" 2>/dev/null || echo "<jq-error>")
|
|
if [[ "$val" == "<jq-error>" ]]; then
|
|
bad_any=1
|
|
continue
|
|
fi
|
|
if [[ -n "$val" && "$val" != "null" ]]; then
|
|
# Check for NaN (jq emits null for NaN typically; double-check)
|
|
if [[ "$val" != *"NaN"* && "$val" != *"nan"* ]]; then
|
|
found_any=1
|
|
fi
|
|
fi
|
|
done
|
|
if [[ $found_any -eq 0 ]]; then
|
|
echo " VIOLATION field $field: not present (or null/NaN) in sampled rows"
|
|
violations=$((violations + 1))
|
|
fi
|
|
done
|
|
|
|
echo
|
|
echo "audit-diag: $violations missing field(s)"
|
|
|
|
if [[ $violations -gt 0 ]]; then
|
|
echo
|
|
echo "Per SP20 §0.3: every observable state MUST be in diag JSONL same commit as the mechanic."
|
|
echo "Add the field to the diag JSONL emission in crates/ml-alpha/examples/alpha_rl_train.rs"
|
|
exit 1
|
|
fi
|
|
|
|
echo "audit-diag: PASS (sampled rows: ${sample_rows[*]})"
|
|
# Cleanup smoke dir on success
|
|
rm -rf "$SMOKE_OUT_DIR"
|
|
exit 0
|