refactor(entities): single-source entity I/O on journal_io primitives
Migrate the entity/observation domain's write mechanics onto the shared
solstone.think.journal_io primitives.
- Five writers (save_journal_entity, save_facet_relationship,
_save_entities_detected, save_observations, review-candidate
_save_jsonl_rows) move from core.atomic_write to atomic_replace, preserving
byte-exact serialization (ensure_ascii=False, indent, trailing newline) and
gaining fsync durability.
- Three locked RMWs (add_observation, _locked_modify_detected,
locked_modify_candidates) route through hold_lock, each keeping its existing
retry policy (observations + detected: 3x OSError retry; candidates: no
retry). LockTimeout (a RuntimeError) propagates past the OSError loops by
design. The bespoke review-candidates lock sidecar is replaced by hold_lock's
convention; review_candidates_lock_path() is removed.
- Surface the new LockTimeout failure mode as ENTITY_BUSY (503, owner-voice)
across the CLI entities verbs, the curation accept/dismiss entity routes, and
the entity-observer talent + seeding (log-and-skip).
- Register relationships.py as an Entities write-owner (AGENTS.md L2 +
check_journal_io_access.py OWNER_FILES); drop the dead atomic_write re-export
from entities/__init__.py.
- Tests: repoint the mechanic-patching retry test to atomic_replace, add an
observations retry test and a candidate no-retry test, and add a cross-process
serialization test covering all three RMWs.
core.atomic_write is retained for its six out-of-domain callers (deletion
deferred to a follow-up lode). merge.py _apply_segment_plan's non-atomic
temp-write is flagged for R3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>