The front server's state and settings. ServerState no longer builds or holds site runtimes: every site runs in its own process. Instead it holds what the front server owns: each site's process, the dashboard's site events, the sign-in rate limiter and the owner's session it lends; since FULL-SITE-PROCESS a site's publish lock, iroh endpoint, hub and import are its process's. A single-site server keeps its own state beside the site rather than inside it. The site side of features.rs moved into lichen-site, leaving the startup decisions here.
AST-verified changes
- The site side of
features.rs(18 items: the feature routers, the site settings router, the site runtime's construction, the publish hooks, the git listener, iroh sync's start and the identity record) to lichen-site'sfeatures.rs(#37; P2c-2b commit 6, 247be4a2). Run id not recorded: "move 6 ok: 18 moved, 0 vis" (lichen-builder.md 2370); adversary "18 moved/0 vis". SiteRecord(withSETTINGS_FILEand its impl) fromindex.rsto lichen-site'stypes.rs(#37; P2c-2b commit 1a, ff26fefc). Run id not recorded: "5 moved, 48 carried, 8 files renamed, 19 visibility as mapped" (lichen-builder.md 2350); adversary 5/19.pds_handle_offrom the front server'sgit/hooks.rs(#23) toowner.rs(PER-SITE-PROCESS P2b-2, 3b0c7e49). Run id not recorded: "ok (1443 items, 1 moved, 0 visibility changes, 1997 comments compared, 124 module declarations)" (lichen-builder.md 2300); adversary "moved=1 visibility_changes=0 (matches builder)". The function was written after 09-23 andowner.rsis new, so here it reads as new code.- 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 460 of this PR's 3,972 changed lines are code these moves carried verbatim, nearly all of it what features.rs lost (−999).
state.rskeeps the index, the user store, the tree store, the dashboard's event stream and invites. It loses the site runtime registry, the settings-listener window and the storage-limit map, because no runtime is built here (SITE-FRONT-BOUNDARY 11e11). It gains:- each site's process (
site_processes); - until FULL-SITE-PROCESS, each site's publish lock (9a) and iroh transport (10b1): both are now the site process's (6e, 6f3b), and the front server holds neither;
- the password sign-in limiter;
- a per-site lifecycle lock (
with_site_lock), which the site process's start and stop also hold; - the dashboard's site events (created, deleted, publish finished), always addressed to the site's owner;
- the import of a site from a remote: started here, done by the site process's admin thread (its migrations, the fetch with the owner's session, the hub's
main, its settings checked by git), a cancelled import waiting for the process's exit before its area goes (10a; FULL-SITE-PROCESS 6d); - the owner's session, lent to each running site process and lent again after a new sign-in (
owner_session_changed, 6d), and each site's auto-pull as its process last told (told_auto_pull); - a deleted site's cleanup, asked of its own process before its tree goes (6h);
- the single-site owner claim (
admit_login,authorize_single_site_login): an unowned single site is claimed by its first login, and only that user may sign in after it (phase 12, unit 7).
- each site's process (
server_settings.rs:- SINGLE-STATE1: a single-site server's own state (its users, tokens, keys, OAuth signing key, owner claim and its own settings) lives in
LM_STATE_DIRor in.<site>.lichen/beside the site. It is moved there once, item by item and crash-safe, and server keys left in the site'ssettings.tomlare then ignored, with a warning. - New settings:
[egress] allow_private(read from[proxy]too, with a warning),trusted_proxies,site_memory_limit,use_shortcodes_cache,[[plugins]]andremote_plugins_enabled.hot_reload_templatesis gone with Tera. confined()andsite_processes()say how sites run in each mode.
- SINGLE-STATE1: a single-site server's own state (its users, tokens, keys, OAuth signing key, owner claim and its own settings) lives in
owner.rs— new: the owner's public facts only (how they sign in, their PDS handle, their deploy key), given to a site process at launch. A change of those facts recycles the owner's site processes (N8). The owner's session is insecurity/credentials.rs(#30).features.rsis now the shell and iroh startup decisions and re-exports; most of what remains is its inline test modules.index.rs: a site name must be a valid host name and loses a trailing dot. Two folders whose names differ only in case are one host, so the second is not served. A create that finds the domain's folder left by an unfinished delete refuses it (FolderLeft, a 409) and adopts nothing: a new site's first build is its process's (FULL-SITE-PROCESS 1d).lib.rs: the new modules, the hub's pre-receive entry point, andBUILD_ID/--build-id, which a test uses to check the site executable is this build. Itsirohmodule is gone (the iroh tests stay here,iroh_tests).front_seams.rs— new (FULL-SITE-PROCESS unit 4): what the code that moved into the site process is given, built from this server's state, and the front server's forwards to a site's hub and admin thread (forward_to_the_hub,forward_to_the_admin_thread,admin_act,rewrite,force_push).Cargo.toml: depends onlichen-site(waslichen-cms),lichen-sandboxandlichen-env. It adds iroh (since FULL-SITE-PROCESS 6f3b only to mint the sites' node keys: the endpoints are the site processes', and iroh-blobs went with them), the hyper client used to reach a site process,zeroizeandflate2.lichen-iroh,lichen-shellandteraare removed. It adds atest-routesfeature and test-onlyzip,syn,quoteandsha2.
Files (crates/lichen-server/)
src/state.rs— what the server holds. Read this first.src/server_settings.rs— SINGLE-STATE1, new settings,confined,site_processes.src/owner.rs— new: the owner's public facts.src/front_seams.rs— new: the seams' front side and the forwards.src/features.rs,src/index.rs,src/lib.rs— as above.Cargo.toml— dependencies.
Lives elsewhere
- The dashboard API that acts on this state (
api.rs), the CLI and the desktop: #32. - Host routing and sign-in: #30. Permission groups: #18.
- The site process launcher (
site_process/front.rs): #19. The site's iroh endpoint: #28. The publish and its lock, the admin thread's: #25. - What moved to lichen-site (
features.rs,types.rs,settings/server.rs): #37. - Tests (
tests/state.rs,tests/server_settings__tests.rs,workspace_structure.rs, e2e) are in the base commit.
Category: Front server: state, server settings and features
Size: 8 files, +2,278/−1,694
Rounds: round 0 at stack ae42e3c0; round 1 at 6b650b2a (SECURITY-FOLDER-AND-PRINCIPALS): path rewrites in index.rs, lib.rs (mod access → mod security) and state.rs; and state.rs removes a cancelled import's area only under its opening (SFP fix-up 4b, 6032b15e; the opening is in #20). Read that round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/31/round/1/interdiff; round 2 at b66397c0 (FULL-SITE-PROCESS): state.rs holds no publish lock and no iroh transport (the site process's now); an import and a deleted site's cleanup are asked of the site's process; the owner's session is lent to running site processes; front_seams.rs is new (unit 4); index.rs refuses a leftover folder (FolderLeft); Cargo.toml drops iroh-blobs. Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/31/round/2/interdiff
Plans and ledger: FULL-SITE-PROCESS-2026-10-25.md (1d, 4, 6d, 6e, 6f3b, 6h); SITE-FRONT-BOUNDARY-2026-10-17.md (9a, 10a, 10b1, 11d, 11e, 11e11, 12), PER-SITE-PROCESS-2026-10-09.md (P2b, P2c-2b, P3-1, P4-1), OPERATOR-REVIEW-REVISIONS-2026-10-13.md (N8), DASHBOARD-REACT-2026-10-07.md (unit 1: site events), PHASE-12-OPERATOR-ANSWERS-2026-10-01.md (unit 7), SINGLE-STATE1 (machine-docs/reviews/SECURITY-TABLE-CONFORMANCE-S2-REVIEWS-2026-10-13.md); lichen-builder.md 2300, 2350, 2370.
Inline #[cfg(test)] modules: features.rs (staged_decoration_tests, iroh_lifecycle_tests). Three files point to a test file with #[path]; lib.rs and owner.rs have test-only items.
Bucket moves: none against round 1. Round 1's front-state PR is split in two: state and settings here, and the dashboard API, CLI and desktop in #32.
Round 1: #11 (Front server: state, the dashboard API, CLI and desktop) covered these files from 0b92cb61.