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:
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
- T9–T10: 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).
|
||||
|
||||
Reference in New Issue
Block a user