refactor(facets): single-source facet/convey-config I/O on journal_io primitives master
Route the facet domain's file-write mechanics onto the shared solstone/think/journal_io primitives and close two durability/concurrency races on config/convey.json. On-disk formats are byte-identical (ensure_ascii=False preserved throughout — the default facet emoji is non-ASCII), so this is write-mechanics + locking only. - facets.py: collapse the three facet.json atomic-writer copies into one _write_facet_json on atomic_replace; route the action-log append through append_text; make delete_facet/rename_facet's convey.json read-modify-write atomic and locked under hold_lock. - convey/config.py: replace save_convey_config with a locked locked_modify_convey_config RMW helper (unlocked inner _write_convey_config to avoid self-deadlock) backing all six config routes + set_selected_facet; the CLI (facets.py) and web server now share the same config/convey.json.lock, closing the lost-update window. set_selected_facet stays best-effort (template-context callers must not fault on contention). - facet_review_candidates.py: locked_modify_candidates onto hold_lock, _save_jsonl_rows onto atomic_replace; drop the cross-domain entities.core.atomic_write import, fcntl, and the dead lock-path helper (sidecar is now review-candidates.jsonl.lock). - Add CONVEY_BUSY (503); convey config routes translate LockTimeout to it, curation facet-candidate handlers reuse ENTITY_BUSY. - Register convey/config.py as a journal_io owner and add the config/convey.json row to the AGENTS.md L2 ownership table. - Tests: multiprocess cross-writer + failure-injection coverage for convey.json, review-candidates, and facet.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>