acpmux: a tags or rules change logs its line before its metadata (R-DURABILITY S3b)
Summary
Stage S3b of the R-DURABILITY rewrite (design: handoff/rewrites/R-DURABILITY/DESIGN.md 3.3e, "C2 ... revised 2026-11-04"). Stacked on the S2 follow-ups PR, which is stacked on #35483 (S3a).
E6: a chat metadata change that also logs a line (tags, renamed, rules, policy, mode) must never be, after a crash, in the chat's metadata without its line in the log. With the event journal (S2, S3a), records survive a crash as a prefix in append order. So the change is safe when its line reaches the store before any metadata image that holds it. renamed and policy already did this; tags and rules saved their metadata first. (mode mostly did; see the review notes below.) This PR moves their line first. Their reply still waits for the save (and, with the journal on, for its record: S2c).
Why not one combined record (the design's first plan): an event-log line goes to the log file before its record (S2's log-first append), and the repair keeps a line only the log holds. So after a kill the log can hold the line while no record of it is durable, and one record with both the line and the image would not make them atomic in the log. It would also race MetaWriter's coalesced copies.
What changes
hub/tags.rsset_tags_answeredandhub/mod.rsset_rules_answered: the line, thensave_meta_answered.
Known gaps
- The reverse is still possible in one group-commit window: the line without the metadata, after a crash. The change's reply waits for its metadata record, so that change was never acknowledged.
- A
MetaWritercopy that another thread takes between the change and its line, appended before the line, then a crash in the next group commit (DESIGN.md 3.3e). - The 3.3d limit (a change in memory before its record, seen by another connection's reply) stays (DESIGN.md 3.3d).
- From the review at
71d7e096cba(SHIP, comment 6638442340; P3, follow-ups owned by R-DURABILITY, written into DESIGN.md 3.3e):write_mode_statesaves before themodeline when cmux's mode mark clears; a chat's first append after a start (catch_up_locked) can save an image that already holds the new tags before thetagsline; with the journal on, a watcher can see thetagsevent one group commit before its metadata record is durable (the gate holds the event only to theAckLSN read when the line is queued); other sites save, then log (seen.rs,work_below.rs,turns.rsmodel_request,tap.rs,watch.rs;transfer_out.rson purpose).
Testing
acpmux hub::tags::meta_line_order_tests (a recording store checks the order of the store calls):
a_tag_change_logs_its_line_before_its_metadata,a_rules_change_logs_its_line_before_its_metadata: red at7a4f02a56f3(the tests before the fix), owned Mac aws-m4pro-7, fleet 88309e91: tags["save:tagged", "line:tags"]; rules["line:permission_chat_allowance", "save:ruled", "line:rules"].a_rename_logs_its_line_before_its_metadata: a guard, green before and after.
Merged down since the SHIP at 71d7e096cba (no change to this PR's own files): the S2 follow-ups fixes (the idle checkpointer race 5279af4bbf7, the repair file 3b7007c175d), the S3a fixes (0d246364d90, N1 fc83b326fba) and feat 7427100d5d0 (with #35845). Head 7fc15b776ef: fleet 1a6d2f28 (owned Mac aws-m4pro-7), journal_ 350 of 350; fleet ea9d2c52 (Linux, hq run 41907479976), hub:: 1139 of 1139 (acpmux 1125, cmux-remote 1, cmux-tui 13). fmt at 66e1df5ec5e: fleet 9232d441, green. The 50 runs of a_failed_log_sync_freezes_the_checkpoint at 71d7e096cba had 1 failure, the idle checkpointer race (#35562 has the analysis); 0 of 50 at the fix.
Green at 71d7e096cba: fleet 25d8970a (owned Mac aws-m4pro-3), the 3 order tests pass; fleet 0b984386 (owned Mac aws-m4pro-5), hub:: 1083 of 1084. The one failure, hub::shutdown_tests::a_cut_turn_of_agent_messages_puts_none_of_them_back, timed out once in the full run (shutdown_tests.rs:237, Elapsed) and passed on its rerun; it uses no tags or rules.
Observed / Not run
- Observed: the fleet steps above.
- Not run:
policyandmodein this test (their paths need a running agent); they write the line first by reading.
Merged down after #35562 landed (head 6a5a6d138c9)
bf2f48d677a merges feat 07c879aab80, which has #35562 as one squash (0b9e1668a7b). It conflicted in 4 store files (journal_store.rs, journal_store/meta.rs, journal_store/repair.rs, journal_store_tests.rs). All 4 are resolved to feat's copy, because this PR never changed a store file of its own. 6a5a6d138c9 is a clean merge-main of feat ecfe8d94e9d. Against feat, the head differs only in this PR's 3 hub files (hub/meta_line_order_tests.rs, hub/mod.rs, hub/tags.rs; 129 lines added, 4 removed).
Green at 6a5a6d138c9:
- macOS
journal_: 355 of 355 (fleet 172c6c27, aws-m4pro-7). - macOS
meta_line_order: 3 of 3 (fleet 8a6b3a10, aws-m4pro-8). - Linux
all hub::with fmt and clippy-D warnings: 1139 of 1139 (fleet da5b97a1, Blacksmith; hq run 41935007261). - Linux
all store::with fmt and clippy-D warnings: 356 of 356 (fleet 51e7e557, Blacksmith; hq run 41935006187).
Changelog
none
Proof
No capture: a daemon change with no UI.
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Makes tags and rules changes log their event line before saving their metadata, closing a durability gap: after a crash, a change could previously survive in the chat's metadata without its line in the log. With the event journal, records survive as a prefix in append order, so logging first guarantees the line is durable before any metadata image holds the change. renamed, policy, and mode already did this; only tags and rules saved first, and their replies still wait for the save.
Adds meta_line_order_tests.rs, a recording store that asserts the store call order for tags and rules changes plus a rename guard.