The crawl layer has a strong, tested invariant: a rejected command consumes no draws, no clock time, and mutates nothing. The kernel has no equivalent stance, and the docstring pass found the inconsistency twice (both tracked in #100):
generate_magic_item pre-checks its tier and raises; generate_treasure, generate_treasure_entries, and generate_unguarded_treasure pass a bad tier through and raise only if a roll reaches a magic item, so tier="bogus" silently yields a coins-only hoard after consuming draws.
cast_from_scroll validates with the raw reader and resolves with the scroll's caster level, so the target count and the per-level range are checked against the wrong level.
Proposal: state the rule in AGENTS.md and the spec ("a kernel function validates its arguments before its first draw and raises ValueError from the call, not from a later branch"), then a hypothesis test per entry point that feeds invalid arguments and asserts the stream position is unchanged and the exception is raised on the call. Fix the two known cases against that test. The scroll fix is to build the _ScrollReader before validate_cast.
The crawl layer has a strong, tested invariant: a rejected command consumes no draws, no clock time, and mutates nothing. The kernel has no equivalent stance, and the docstring pass found the inconsistency twice (both tracked in #100):
generate_magic_itempre-checks itstierand raises;generate_treasure,generate_treasure_entries, andgenerate_unguarded_treasurepass a bad tier through and raise only if a roll reaches a magic item, sotier="bogus"silently yields a coins-only hoard after consuming draws.cast_from_scrollvalidates with the raw reader and resolves with the scroll's caster level, so the target count and the per-level range are checked against the wrong level.Proposal: state the rule in
AGENTS.mdand the spec ("a kernel function validates its arguments before its first draw and raisesValueErrorfrom the call, not from a later branch"), then a hypothesis test per entry point that feeds invalid arguments and asserts the stream position is unchanged and the exception is raised on the call. Fix the two known cases against that test. The scroll fix is to build the_ScrollReaderbeforevalidate_cast.