feat(ci): add journal_io raw-mechanic gate; migrate 4 journal-data stragglers master
Add scripts/check_journal_io_mechanic.py: a static AST check that flags raw durable-write / locked-RMW mechanics (os.replace, filesystem Path.replace, mkstemp+manual-replace, flock LOCK_EX RMW) against owner journal data anywhere outside solstone/think/journal_io/. Unlike the import-binding access lint, this gate SCANS owners — they may import journal_io primitives but must carry no hand-rolled mechanics. Seeds with an empty allowlist; wired into make ci via install-checks alongside the access and layer-hygiene checks. To seed the gate empty and green, migrate every remaining journal-data straggler to journal_io (byte-identical output, equivalent locking): - link/paths.py: state/token/totp writes -> write_json (token/totp keep 0o600) - convey/chat_stream.py: chat.jsonl -> atomic_replace (ensure_ascii=False house pattern); declared the L2 owner of chronicle chat/<seg>/chat.jsonl - activities.py: _write_jsonl_records -> atomic_replace; locked_modify -> hold_lock (drops the bare-LOCK_EX blocking RMW + max_retries retry loop) - importers/shared.py: chronicle import.<source>/imported.jsonl -> atomic_replace (a 4th straggler the original inventory missed; surfaced in prep) locked_modify now raises journal_io.LockTimeout on contention; surface it in sol's voice at the activities CLI write boundary via a new ACTIVITIES_BUSY reason, mirroring the entities ENTITY_BUSY pattern. Tests reassert locking and byte-fidelity invariants instead of the old flock/mkstemp internals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>