The dashboard's JSON API (lichen-server/src/api.rs), and the CLI's and desktop app's entry points. Every API route now sits in a permission group, and its errors share one shape. The site acts it offers are all asked of the site's process: its admin thread does the hub's (compact, force pull, force push, compact-all, the remote and sync settings, the publish), and the rest go to the site as ordinary requests, with the admitted identity. The lichen executable also starts site processes, runs the hub's pre-receive check and the sign-in render child, and empties its environment at startup, giving the user's own environment back only to the tools it runs for the user.
AST-verified changes
lichen-cli/src/commands/mod.rs→commands.rs,lichen-server/src/desktop/mod.rs→desktop.rsandsingle/mod.rs→single.rs: three of OPERATOR-REVIEW-FIXES U1's 19foo/mod.rsrenames (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.emit_settings_savedandedit_iroh_peers_docfromapi.rsto lichen-site'ssettings/edit.rs(#37), with eight settings helpers fromutils.rs(#33) (P2c-2b commit 1c, 85a5e79e). Run id not recorded: "10 moved, 3 visibility" (lichen-builder.md 2352); adversary 10/3.- The path rewrites of SECURITY-FOLDER-AND-PRINCIPALS units 1 and 2 in this PR's files (
crate::site_area→crate::admin_area,crate::access→crate::security::access, and the othersecurity::paths), which those checks cover (unit 1 notify-eebdd32e, unit 2 adversary notify-020eb199, both equal, errors 0), with the rustfmt rewraps after them (d6c25912, bdaa795b), proven bycargo fmt.
Roughly 170 of this PR's 1,637 changed lines are code these moves carried verbatim, all in api.rs. With rename detection the renames show only their changes.
On this forge the 3 renamed files here show as a whole deletion and a whole addition, so its diff reads 1,683 changed lines; with rename detection (git diff -M full/15-front-state full/16-api-cli) it is 1,637.
The dashboard API (api.rs, DASHBOARD-REACT unit 1). The React dashboard and a custom dashboard (through lichen-ts/dashboard) call these routes on the dashboard host:
- Every route is in its permission group (
site(&[ReadSite]),account(&[ManageDeployKey]), …). A handler reads the site its group admitted (admitted_site) instead of checking for itself, sopath_acl_guardandrequire_session_siteare gone. - Errors are in one shape. An unknown
/apipath is a 404 in that shape. - Site acts asked of the site process's admin thread (FULL-SITE-PROCESS 6c–6e, through
front_seams): compact, compact-all, force pull, force push (whose answer is the push's own), backup status (STC-BACKUP1), the remote and sync settings, the publish key and trigger-publish. The DNS checks and the edit-host handoff (H2a: browser sessions only) stay the front server's. Setting a deploy key copies it into each of the owner's sites' areas before it answers (4f1), and a create answers 409 for a folder an unfinished delete left. - Site acts forwarded to the site's process (
site_act): auto-save, iroh peers, collaborators and revert. They go as ordinary requests to/cms/api/…, with the admitted identity inx-lichen-identityand no admin connection (SITE-FRONT-BOUNDARY 11a1). POST /api/auto-pullpulls the user's auto-pulling sites; the dashboard's GET only renders (H3: a GET never acts).
The CLI (crates/lichen-cli/):
main.rsdispatches, before anything else: the hub's pre-receive check (SITE-FRONT-BOUNDARY unit 4),--build-id, and the sign-in pages' render child (DASHBOARD-REACT unit 4). It then callslichen_env::init(), which reads the environment once and empties it but for a keep-list (CHILD-ENV-CLEAR E2). It registers its own executable for site processes (by inode on Linux, so an in-place upgrade does not replace it), and a hiddenlichen site serveruns a site process, started bylichen_site::site_process::start::site_process_main: its admin thread first, then the narrowing, then the runtime (FULL-SITE-PROCESS unit 1, #38).lichen ask METHOD PATHlets a child ask for a publish or a push on its child-routes socket, holding no credential (6g, #40).user_env.rs— new: the CLI's own tools (git,cargo install, the browser) run with the environment the user started lichen with, the plan's one named exception.commands/*.rsuse it.config.rs,server.rsread variables throughlichen-env, andHOMEfalls back to the password database.client.rsshows the message from the API's error shape.oauth_server.rsputs its loopback callback in the public group.
The desktop app (crates/lichen-tauri/): main.rs serves a site process when the desktop server launches one; build.rs allows cargo's environment at build time. lichen-server/src/desktop/server.rs fills the new settings with desktop defaults. cli.rs and single/cli.rs follow the crate rename.
Files
crates/lichen-server/src/api.rs— the dashboard API. Read this first.crates/lichen-cli/src/main.rs,user_env.rs(new),commands.rs,commands/{auth,git,sync,update}.rs,config.rs,client.rs,oauth_server.rs,server.rs,Cargo.toml(addslichen-env,libcand atest-routesfeature).crates/lichen-tauri/src/main.rs,build.rs,Cargo.toml.crates/lichen-server/src/cli.rs,single.rs,single/cli.rs,desktop.rs,desktop/server.rs.
Lives elsewhere
- What the API acts on:
state.rs,server_settings.rs: #31. The rewrites behind compact, force pull and force push: #23. The publish: #25. - The dashboard's pages and
/api/dashboard,/api/account(dashboard.rs): #33. Host routing and the handoff's receiving end: #30. - The site acts' routes and groups on the site side (
acts.rs): #19. contract/dashboard-api.json, the hand-written contract a test holds this router to, and the React dashboard are in the base commit.- Tests (
tests/dashboard_api.rs, the CLI's tests, e2e) are in the base commit.
Category: Front server: the dashboard API, the CLI and the desktop
Size: 21 files, +1,016/−621
Rounds: round 0 at stack ae42e3c0; round 1 at 6b650b2a (SECURITY-FOLDER-AND-PRINCIPALS): path rewrites in api.rs. Read that round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/32/round/1/interdiff; round 2 at b66397c0 (FULL-SITE-PROCESS): api.rs asks the site process's admin thread for the hub's acts and status, the remote and sync settings and the publish key (through front_seams), copies a new deploy key into each site's area, and answers 409 for a leftover folder; the CLI starts a site process through site_process_main and adds lichen ask. Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/32/round/2/interdiff
Plans and ledger: FULL-SITE-PROCESS-2026-10-25.md (1, 4f1, 6c–6e, 6g); DASHBOARD-REACT-2026-10-07.md (unit 1; DASHBOARD-REACT-U1-DESIGN-2026-10-17.md), ADMIN-EDIT-HOST-SPLIT-2026-10-13.md (H2a, H3), SITE-FRONT-BOUNDARY-2026-10-17.md (units 4, 8, 11a1, 11e), PER-SITE-PROCESS-2026-10-09.md (P2c-2b, P4-1), CHILD-ENV-CLEAR-2026-10-08.md (E2), OPERATOR-REVIEW-REVISIONS-2026-10-13.md (N8), OPERATOR-REVIEW-FIXES-2026-10-07.md (U1); lichen-builder.md 2087, 2352.
Inline #[cfg(test)] modules: api.rs (structured_settings_edit_tests). Six more files point to a test file with #[path].
Bucket moves: none against round 1. Split out of round 1's front-state PR: the API and its clients here, the state in #31.
Round 1: #11 (Front server: state, the dashboard API, CLI and desktop) covered api.rs, desktop/server.rs, the CLI's main.rs, client.rs, oauth_server.rs, server.rs and lichen-tauri's main.rs from 0b92cb61; the rest is new in this range.