test(state_reset): contract test — every RegistryEntry has dispatch arm

Surfaced the SP7 T7 dispatch-arm bug at runtime (unknown-name panic at
fold boundary). This test enumerates RegistryEntry names from
StateResetRegistry::new() and asserts each has a match arm in
reset_named_state, catching the bug at `cargo test -p ml --lib`
instead of mid-training.

Source-introspection design — no production-code change. Test fails
fast if a future contributor adds a registry entry without the
corresponding dispatch arm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-03 10:31:02 +02:00
parent d140ace635
commit 07ac5ceea5
2 changed files with 81 additions and 0 deletions

View File

@@ -831,4 +831,83 @@ mod tests {
let r = StateResetRegistry::new();
assert_eq!(r.category("nonexistent_state"), None);
}
/// Contract test: every FoldReset/SoftReset entry in StateResetRegistry::new()
/// must have a corresponding match arm in reset_named_state. Catches the
/// "add registry entry, forget dispatch arm → runtime panic at fold boundary"
/// bug pattern (occurred twice: SP5 Layer A #281, SP7 T7 commit 6e479c55c).
///
/// Source-introspection design — no production-code change. The test extracts
/// the reset_named_state function body via brace-balance walk, then checks
/// that each FoldReset/SoftReset entry name appears as a string literal inside
/// that body.
#[test]
fn every_fold_and_soft_reset_entry_has_dispatch_arm() {
let dispatch_src = include_str!("trainer/training_loop.rs");
// Extract reset_named_state body: find fn signature, then match-block.
let fn_start = dispatch_src
.find("pub(crate) fn reset_named_state")
.expect("reset_named_state not found in training_loop.rs");
let after_fn = &dispatch_src[fn_start..];
// Find `match name {` inside the function body.
let match_rel = after_fn
.find("match name {")
.expect("match name { not found in reset_named_state");
let match_start_abs = fn_start + match_rel;
// Walk braces to extract the match block (from `match name {` to its
// closing `}`), so we only search within the dispatch, not the rest of
// the file.
let match_src = &dispatch_src[match_start_abs..];
let body = {
let mut depth: i32 = 0;
let mut end = match_src.len();
for (i, ch) in match_src.char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
end = i + 1;
break;
}
}
_ => {}
}
}
&match_src[..end]
};
let registry = StateResetRegistry::new();
// FoldReset and SoftReset (any decay_bars) both dispatch through
// reset_named_state. WindowReset/TrainingPersist/SchemaContract do not.
let entries_needing_dispatch: Vec<&RegistryEntry> = registry
.all()
.iter()
.filter(|e| matches!(
e.category,
ResetCategory::FoldReset | ResetCategory::SoftReset { .. }
))
.collect();
let mut missing: Vec<&str> = Vec::new();
for entry in &entries_needing_dispatch {
let needle = format!("\"{}\"", entry.name);
if !body.contains(needle.as_str()) {
missing.push(entry.name);
}
}
assert!(
missing.is_empty(),
"Registry entries with no dispatch arm in reset_named_state ({} total, {} missing): {:?}\n\
These will panic at fold-boundary with 'unknown name' MLError.\n\
Add a match arm in trainer/training_loop.rs::reset_named_state.",
entries_needing_dispatch.len(),
missing.len(),
missing
);
}
}

View File

@@ -3939,3 +3939,5 @@ extended. No producer kernel yet — that arrives in the next commit.
MEMORY.md index entry. Captures the two-layer (signal-modulated target ×
outcome-driven α) pattern for future controller designs.
- T9T10: smoke + 50-epoch verification.
Contract test — every RegistryEntry has dispatch arm (2026-05-03): added `every_fold_and_soft_reset_entry_has_dispatch_arm` unit test to the existing `#[cfg(test)] mod tests` block in `state_reset_registry.rs`. Source-introspection design: `include_str!` both `state_reset_registry.rs` and `trainer/training_loop.rs`; brace-balance walk extracts the `match name {` body; for each FoldReset/SoftReset entry (97 total: 95 FoldReset + 2 SoftReset) asserts the literal `"<name>"` appears in that body. No production-code change. No new dependency (manual brace walk instead of regex). Catches the recurring "add RegistryEntry, forget dispatch arm → fold-boundary panic" bug (occurred twice: SP5 Layer A #281, SP7 T7 commit 6e479c55c) at `cargo test -p ml --lib` rather than mid-training. Test currently passes (all 97 dispatch arms present post SP7 T7 fix). Touched: `trainers/dqn/state_reset_registry.rs` (+59 LOC in cfg-test block).