lichen.page.review from the reviewed point 0b92cb61 to stack eb5a1653, as one PR per subsystem (review set)

full 13: Live rooms and their sync with git #76

targeting full/12-iroh from full/13-rooms
Open opened by and assigned to notplants-bot.bsky.social
targeting full/12-iroh from full/13-rooms

The site's live rooms and their sync with files on disk. The main addition is map rooms: a JSON file under the site's data/ can be opened as a live Y.Map whose keys merge independently, beside the existing text rooms. The rest follows other work: the HTTP handlers left room_sync.rs for the site's API, the crate was renamed from lichen-cms, children and the environment go through lichen's sandbox and lichen-env, and a lichen-yrs framing test was fixed.


AST-verified changes

  • crates/lichen-cms/src/sync/* renamed to crates/lichen-site/src/sync/* with the crate rename (PER-SITE-PROCESS P2c-1, d7c4a668). Run id not recorded: "Rename move check with rn6 at f3e7470b..d7c4a668: ok (4365 items, 113 renamed, 1874 carried, 0 visibility)" (lichen-builder.md 2322); adversary "d7c4a668 equal=true 0-err 113-renamed 1874-carried 0-vis". Allowed: lichen_cms rewritten to lichen_site, and rustfmt rewraps.
  • sync/mod.rs renamed to sync.rs, one of OPERATOR-REVIEW-FIXES U1's 19 foo/mod.rs renames (f6a10c32). Run id not recorded: "3004 items, 48 moved, 69 carried, 19 renamed, 2 visibility as mapped, 233 module declarations" (lichen-builder.md 2087); adversary notify-abab368a, same counts.
  • The room handlers (snapshot, save, recovery, the websocket) left room_sync.rs for api/live.rs in item 6's api-layer move (7601a57c); the added half is in #35. Run FUHHLT: "VERIFY OK (ast 3035 items/146 moved/25 visibility/3690 comments; routes 130=130 ...)"; adversary run eMUcJc VERIFY OK. Allowed: path rewrites into crate::api, member visibility.

Roughly 130 of this PR's 1,267 changed lines are code these moves carried verbatim, nearly all of it the handlers leaving room_sync.rs. In git diff -M, git pairs the renamed files, so their unchanged lines are not counted.

On this forge the 6 renamed files here show as a whole deletion and a whole addition, so its diff reads 12,169 changed lines; with rename detection (git diff -M full/12-iroh full/13-rooms) it is 1,267.


  • Map rooms (provisional; site.openYmap in lichen-ts): a data/**/*.json file is a live Y.Map (sync/map_room.rs). JSON objects are maps at any depth and merge per key; arrays and plain values are atomic. The file on disk is canonical JSON, as JavaScript writes it, so a browser and the server write the same bytes. A room's kind is fixed by its path (RoomKind in rooms.rs), so one file never has two rooms. The resync actor handles both kinds: a map room is saved on every sweep (it has no explicit save), merges per key against the file, keeps a conflicting part under conflicts/, and a write under data/ rebuilds nothing.
  • Room routes: room_sync.rs keeps the room logic (open a snapshot, admit a socket, recover an incompatible generation) for both kinds, and its handlers are now in the site's API. JSON field names are snake_case (item 6, convention 6c).
  • Smaller changes: git_recovery.rs keeps aborting a rebase only for an older lichen's interrupted pull (now crate-private). It and threeway_merge.rs run git as sandbox children (lichen_flat_file::sandbox::command, SANDBOX-UNIFY). Environment reads go through lichen-env.
  • lichen-yrs/src/conn.rs: the framing property test now allows exactly one panic, yrs's debug assertion that a client id fits in 53 bits, and fails on any other. A second test holds that a message of a few bytes naming billions of entries is refused without reserving them (yrs 0.27.4 bounds it; 0.27.2 aborted the process).

Files (crates/lichen-site/src/sync/ unless named)

  • map_room.rs — new: the JSON ↔ Y.Map mapping, the canonical form and the per-key merge. Read this first.
  • rooms.rs — RoomKind; map rooms seeded from their file; content_of for either kind.
  • resync.rs — the actor's save and reconcile for both kinds.
  • room_sync.rs — room logic for both kinds; the handlers left for the API.
  • git_recovery.rs, threeway_merge.rs, ../sync.rs — the small changes above.
  • crates/lichen-yrs/src/conn.rs — the framing properties (inline tests).

Lives elsewhere

  • The room routes (api/live.rs): #35. A peer's room exchange, now served from the site process's own iroh endpoint (the front server's bridge is gone): #28.
  • docs/SITE-SYNC-MACHINERY.md and docs/SITE-CONCURRENCY-MODEL.md, which these modules cite, and lichen-ts's openYmap, are in the base commit.
  • Tests (sync/tests/, the map-room tests, e2e) are in the base commit.

Category: Live rooms and their sync with git Size: 8 files, +1,047/−220 Rounds: round 0 at stack ae42e3c0, unchanged at 29a7c15b and 6b650b2a; round 1 at b66397c0 (FULL-SITE-PROCESS): only lichen-yrs/src/conn.rs's inline test module gains a test, that a room message naming billions of entries is refused without reserving them (yrs 0.27.4; Cargo.lock is in the base commit). Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/29/round/1/interdiff Plans and ledger: map rooms (2026-09-26, 406b2f27; provisional), OPERATOR-REVIEW-FOLLOWUPS-2026-09-30.md (item 6: the api-layer move, the one URL scheme, conventions 6c), PER-SITE-PROCESS-2026-10-09.md (P2c-1), OPERATOR-REVIEW-FIXES-2026-10-07.md (U1), CHILD-ENV-CLEAR-2026-10-08.md (E2), SANDBOX-UNIFY-2026-10-09.md (U1); lichen-builder.md 1891, 2087, 2322. Inline #[cfg(test)] modules: sync/rooms.rs (base_pointer_tests, restart_tests, ydoc_properties) and lichen-yrs/src/conn.rs (wire_properties, test). Four more files point to a test file with #[path]. Bucket moves: none against round 1. Split out of round 1's "iroh, sync and rooms": the rooms are the site's and not iroh's. Round 1: #9 (iroh, sync and rooms) covered room_sync.rs, git_recovery.rs, threeway_merge.rs and conn.rs from 0b92cb61; map_room.rs, rooms.rs, resync.rs and sync.rs are new in this range.

Commits 0