chameleon 2.39.1
Post-ship hardening of the v2.39.0 multi-root Stop backstop: a turn-end review
plus an adversarial bug hunt surfaced four issues in the per-workspace block
budget, all low-severity but fixed for correctness.
Fixed
- Corrupt per-workspace block count no longer crashes the Stop hook.
EnforcementState.from_dictcaststop_hook_blocks_by_rootvalues with a bare
int(v), which raisesValueErroron a non-numeric value that a committed or
tampered state file could carry — andload_state's except clause did not
catchValueError, so the Stop hook crashed instead of failing open as its
contract requires. Values now coerce defensively (bad or negative entries drop),
andload_statealso catchesValueError/OSError. - The block budget is unified across the lint and idiom gates and across
single/multi-root mode. v2.39.0 keyed only the lint backstop's anti-loop cap
per workspace; the idiom-review gate and the attestation still read the legacy
scalar (always 0 in multi-root), and the scalar and per-workspace map were
disjoint counters — so a workspace could exceed itsstop_block_capby one via
the idiom gate, and a mid-session single↔multi cardinality change could re-arm a
spent cap (up to 2×). All block reads/writes now route through one reconciled
per-workspace counter (_effective_stop_blockstakes the max of the scalar and
the map, so old state and a mode flip both stay capped), and the attestation
counts both counters. - The multi-root root cap no longer drops armed workspaces silently. When a
session touches more thanCHAMELEON_STOP_MAX_ROOTS(16) armed workspaces, the
overflow now records astop_relintcheck event so a green Stop cannot read as
"every workspace was checked" when the cap left some ungated.