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

full 19: The site's API: files, batch, history, live and preview routes #82

targeting full/18-site-frontends
Open opened by and assigned to notplants-bot.bsky.social
targeting full/18-site-frontends

The site's HTTP API. Lichen-cms's two big handler files (cms.rs, router.rs) became an api/ module by topic, and the routes moved to one scheme under /cms/api: JSON by default, with ?format=html only where a page exists. Every route now sits in a permission group. The API also gained file history, a preview of the site with its drafts, batch commits, map rooms and raw file bytes.


AST-verified changes

  • Item 6 step 1, the api-layer move (7601a57c): lichen-cms's HTTP layer went into api/ by topic. cms.rs and router.rs were emptied and deleted, and the old api.rs plus the handler parts of history, preview, publish_record, share and room_sync became api/{api_utils,build,files,frontend,history,html,live,mod,public,publish,share,site,test_routes}.rs. batch.rs became api/batch.rs. Checked by the builder: "3035 items before and after, 146 moved, 25 visibility changes as mapped, 3690 comments compared, equal". verify.sh run FUHHLT: "VERIFY OK (ast 3035 items/146 moved/25 visibility/3690 comments; routes 130=130 ...)", and its planted fault (run tVqHJG) went RED. Later VERIFY OK runs: TRNKZ7, S1xZAJ, FZ0qAX, 1wrz6l, 0I9ss7. Adversary: SATISFIED, its run eMUcJc VERIFY OK, plant ip43fF RED. Allowed: path rewrites (crate::cms/router → crate::api), dropped file attributes on 4 files, member visibility, lone trailing commas. The commit was move.py plus cargo fix, cargo fmt and 2 hand edits, listed in the commit. api/html.rs and api/frontend.rs are in #34.
  • OPERATOR-REVIEW-FIXES U1 (1d6fe2d4, f6a10c32): 48 items from api/files.rs, api/html.rs, filetree.rs, history.rs and preview.rs moved into api/{api_utils,filetree,history,preview}.rs, and api/mod.rs was renamed api.rs. One check over 8b30050a..f6a10c32 (run id not recorded): "3004 items, 48 moved, 69 carried, 19 renamed, 2 visibility as mapped, 233 module declarations". The adversary's rerun, notify-abab368a, got the same counts. Allowed: pub(super) on is_file_hash and format_error_chain; the text rewrite api/mod.rs → api.rs.

Roughly 1,166 of this PR's 5,760 changed lines are code these moves carried verbatim: old cms.rs and router.rs code that reappears under api/. The routes, groups and new topics around it are new code.

On this forge the renamed file here shows as a whole deletion and a whole addition, so its diff reads 6,168 changed lines; with rename detection (git diff -M full/18-site-frontends full/19-site-api) it is 5,760.


What changed and why:

  • One URL scheme (item 6, steps 1–4). The handlers moved first, as a move only (above). Then the routes were renamed under /cms/api/… (files, folders, rename, build, render, edit, filetree, settings, events) and the old /cms/put-file, /cms/filetree, /cms/sse and their kin were removed. api_utils.rs holds the one error shape (ApiError) and respond(), which answers a route's HTML view for ?format=html. Routes with no HTML view answer ?format=html with the API's "no HTML" error (refuse_html, json_only). One path-encoding rule covers every route that takes a site path.
  • Permission groups (LICHEN-SANDBOX L2d). The route table in api.rs is built from site_group(READS) and site_group(WRITES), each admitted by the site's policy. cms_private_router_with lets a serving server add its own site routes into the same table (item 11). The front server admits a site's publish and its git editor routes ahead of the site's (front_site_routes, SITE-FRONT-BOUNDARY 9a), and since FULL-SITE-PROCESS (6d, 6e) forwards them to the site process's admin thread. The push permissions in its groups are now the rights to ask for a push (TriggerPushToRemote, TriggerPushToPds, O4), apart from the credentials. GET /cms/api/health is public, for the front server's check that a site process is up.
  • New topics:
    • history.rs: GET /cms/api/history with four questions — a file's commits, its bytes at a commit, the files deleted under a folder, the files changed since a commit. Every git command is confined to the site, with literal pathspecs.
    • batch.rs: POST /cms/api/batch, several writes as one commit. Every operation is checked before any is applied, and the commit holds only the batch's paths.
    • preview.rs: GET /cms/api/preview/{*url} and POST /cms/api/build/check.
    • live.rs: text and map rooms side by side, with their sockets and the event stream.
    • files.rs: GET /cms/api/raw/{*path} serves a source file's bytes.
    • build.rs: GET /cms/api/export returns the content as a zip. The batch, preview, raw and export routes are marked provisional in the route table.
  • Test routes (test_routes.rs, /test/…) exist only with the test-routes feature, which the e2e builds turn on. Without it they answer like any unknown path.
  • public.rs: lichen's own files moved to /lichen-static/, so a site's /static/ is its own.

Files (crates/lichen-site/src/)

  • api.rs — the route table, its groups and layers. Read this first.
  • api/api_utils.rs — the error shape, respond(), the format and path helpers.
  • api/files.rs (942 lines; much of it lichen-cms's cms.rs) — the tree, a file, edits, folders, renames, raw bytes.
  • api/build.rs, api/site.rs — build, render, preview, check, export; info and settings.
  • api/history.rs, api/batch.rs, api/live.rs, api/preview.rs — the new topics.
  • api/filetree.rs (from lichen-cms's filetree.rs), api/public.rs, api/test_routes.rs.
  • crates/lichen-cms/src/cms.rs (−982), crates/lichen-cms/src/router.rs (−362) — deleted; their code is in the files above and in #34.

Lives elsewhere

  • Page URLs, the escape hatch and the frontend and plugin routes (api/html.rs, api/frontend.rs, api/plugins.rs): #34.
  • The room machinery behind live.rs: #29. The preview build behind preview.rs, and the rebuild lane: #27. The no-JavaScript save form: #26.
  • The site's policy and Permissions: #18. The front server's own site routes (front_site_routes in server.rs): #30, which forwards the publish (run by the admin thread, #25) and the git editor routes (#23).
  • Not in any PR (base commit): the API contract contract/cms-api.json (hand-written; the server's tests compare the routes with it), api/yjs-demo.html, lichen-ts's client for these routes, and the tests.

Category: The site's API: files, batch, history, live and preview routes Size: 14 files, +4,345/−1,415 Rounds: round 0 at stack ae42e3c0; round 1 at 6b650b2a (SECURITY-FOLDER-AND-PRINCIPALS): api/files.rs adds read_refused_bodies, which reads each body through a channel and drains what its handler leaves, up to a limit, so a refusal given before the body is read reaches the front server as a 413 (SFP fix-up 4b, 128873c6; layered by #19). Read that round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/35/round/1/interdiff; round 2 at b66397c0 (FULL-SITE-PROCESS): api.rs names the push permissions as rights to ask (TriggerPushToRemote, TriggerPushToPds, 3d, the plan's O4), and says a publish is the admin thread's (6e). Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/35/round/2/interdiff Plans and ledger: FULL-SITE-PROCESS-2026-10-25.md (3d, 6e); OPERATOR-REVIEW-FOLLOWUPS-2026-09-30.md (item 6, steps 0–4, with items 9 and 11; checks archived in machine-docs/archive/api-layer-migration-2026-10/), OPERATOR-REVIEW-FIXES-2026-10-07.md (U1), FRONTEND-API-AND-LOADER-2026-09-24.md (the /cms/api the loader's frontends call), LICHEN-SANDBOX-AND-PERMISSIONS-2026-10-13.md (L2d), SITE-FRONT-BOUNDARY-2026-10-17.md (9a, 11c). Inline #[cfg(test)] modules: api/filetree.rs (preview_map_tests, hugo feature). Also 4 files point to a test file with #[path] (api.rs, api/batch.rs, api/history.rs, api/preview.rs; api/filetree.rs has one too), and api/files.rs has test-only items. Bucket moves: cms.rs and router.rs (deleted, not in round 1) are placed here by an explicit rule, because git pairs them with api/files.rs and api/public.rs at 45% and 35%, below its 50% rename threshold. api/frontend.rs, api/plugins.rs and api/html.rs went to #34. Round 1: #13 (Site crate core) covered api.rs, api/api_utils.rs, api/batch.rs, api/build.rs, api/files.rs and api/site.rs. The other files are new to the set in this range.

This pull request has no versions submitted yet.