v5.0.1 — V5-1 follow-on: non-UTF-8 config readers honor their contract
PATCH. A correctness follow-on to the V5-1 storage seam shipped in v5.0.0. The four config readers each document a contract — backend_selection.py::_configured_backend is fail-loud (raise StorageSelectionError when the config is present but unparseable), and the three best-effort readers (harness_memory.py::_read_config_vault_path, harness_memory.py::_read_config_state_mode, agentm_config.py::_read_config) are graceful-skip (return None). Each guarded only (json.JSONDecodeError, OSError). A non-UTF-8 config file makes Path.read_text(encoding="utf-8") raise UnicodeDecodeError — a ValueError, which is neither an OSError nor a json.JSONDecodeError — so it leaked past every guard and crashed the caller instead of honoring the documented contract. Surfaced by an adversarial review of the V5-1 part-5 selection code immediately after the v5.0.0 tag. Solo harness PATCH; no crickets pairing.
Fixed
- All four config readers now also catch
UnicodeDecodeError(50cde80). The fail-loud resolver raisesStorageSelectionError(its documented contract) on a non-UTF-8 config rather than leaking the decode error; the three graceful readers returnNone(their documented contract). The realistic trigger is a Windows editor's UTF-16/BOM (\xff\xfe…) "Save As" on an otherwise-valid config. Five regression tests pin the behavior across all four sites — including thedoctorstorage preview reporting[FAIL]rather than crashing. The fix is the minimal contract-honoring widening (catch the decode error at the same site, route it to the docstring's promised behavior), not a broadexcept Exception; the fail-loud path stays loud — a corrupt config still refuses startup, never a silent demotion todevice-local, per ADR 0013.
Full changelog: v5.0.0..v5.0.1 · CHANGELOG.md