Context
Surfaced during the adversarial review of #277 (finding #6, deferred there as a repo-wide consistency issue rather than a one-off).
policy.py loads every limits.* scalar with a bare coercion — int(...), float(...), str(...), bool(...) — then range/membership-validates a few (e.g. session_budget_mode). There is no strict per-field type validation. bool(...) in particular silently coerces a truthy string:
# policy.py:720
dev_contract_nudge = bool(limits_d.get("dev_contract_nudge", LimitsPolicy.dev_contract_nudge))
so dev_contract_nudge = "false" (a quoted string — a plausible user error) coerces to True and enables the nudge. A well-formed TOML boolean (dev_contract_nudge = false) already parses correctly; only the quoted form misbehaves.
Why not fix it narrowly
Fixing only dev_contract_nudge would be an inconsistent island: every sibling limits.* field uses the same coerce-then-value-validate convention. The right fix is a small cross-cutting sweep.
Proposal
Add strict scalar-type validation across limits.* fields (bool/int/float/enum) — reject or warn when a value's TOML type doesn't match the field's declared type — following the existing strict precedent in the plugin-settings path (_validate_plugin_settings, policy.py:613), which already raises PolicyError on a non-bool where a bool is expected. Add invalid-type tests per kind.
Scope
src/bmad_loop/policy.py (the limits load path)
tests/test_policy.py
Low priority / hardening.
Context
Surfaced during the adversarial review of #277 (finding #6, deferred there as a repo-wide consistency issue rather than a one-off).
policy.pyloads everylimits.*scalar with a bare coercion —int(...),float(...),str(...),bool(...)— then range/membership-validates a few (e.g.session_budget_mode). There is no strict per-field type validation.bool(...)in particular silently coerces a truthy string:so
dev_contract_nudge = "false"(a quoted string — a plausible user error) coerces toTrueand enables the nudge. A well-formed TOML boolean (dev_contract_nudge = false) already parses correctly; only the quoted form misbehaves.Why not fix it narrowly
Fixing only
dev_contract_nudgewould be an inconsistent island: every siblinglimits.*field uses the same coerce-then-value-validate convention. The right fix is a small cross-cutting sweep.Proposal
Add strict scalar-type validation across
limits.*fields (bool/int/float/enum) — reject or warn when a value's TOML type doesn't match the field's declared type — following the existing strict precedent in the plugin-settings path (_validate_plugin_settings,policy.py:613), which already raisesPolicyErroron a non-boolwhere a bool is expected. Add invalid-type tests per kind.Scope
src/bmad_loop/policy.py(thelimitsload path)tests/test_policy.pyLow priority / hardening.