Every push into a site's history now goes through one receive on the site's hub, whoever pushes: the site itself after each commit lichen makes, or a client over smart HTTP. Clients' git is served from the hub in every mode, and the old path, where the front server forwarded a client to git http-backend as a CGI, is deleted. The receive's rules are lichen's own code, and the only difference between pushers is how they were admitted. Since FULL-SITE-PROCESS the hub's code lives in lichen-site and runs on the site process's admin thread: the front server admits a client's git and forwards it, and relays the site's own push back to that thread.
AST-verified changes
-
SECURITY-FOLDER-AND-PRINCIPALS unit 2 (b603dbbe):
site_area/hub.rsandsite_area/hub/full_push.rs→admin_area/hub.rsandadmin_area/hub/full_push.rs, part ofsite_area/→admin_area/(13 files renamed,crate::site_area→crate::admin_areain all lichen-server Rust). Checked by the builder: "AST equal three ways (8da73502 → b603dbbe, → bdaa795b, → 067dbe37), plants caught"; adversary rerun notify-020eb199: "all equal=True (2471/2471, renamed 13, carried 426, visibility 0, errors 0)". The non-move edits (d85a66d2:include_str!paths, literals) and the rustfmt (bdaa795b) are separate commits. -
FULL-SITE-PROCESS 5b (#1649 on lichen.page.review, 26779036 → 0e8871fe, map
machine-docs/reports/fsp-5b/move-map.json):admin_area/hub.rs,admin_area/hub/full_push.rs,git/hub_http.rsandgit/hub_files.rsrenamed whole from lichen-server into lichen-site, four of 34 files (19 modules and their 15#[path]test files) moved, each re-exported where it was; the others are in #20, #23 and #25. Run notify-981f7470: "equal=True; items 5557/5557, stayed 4752, carried 805 in the 34 renamed files; visibility_changes 99, every one listed; comments compared 4991; module declarations checked 352"; plants item_edited and visibility_unlisted caught, a dropped declaration gate not caught (covered by the PR's gate table); adversary reruns equal (notify-c53fbd02). The rustfmt (eedd7e97, ad03def8) and thepubwidenings for what the front calls (6a2b574d, c04bebac, 0e8871fe, each in the map) are separate commits. None of this PR's 5,510 changed lines can be skipped for these moves: every moved file was created after 09-23, so in this diff it reads as new code.
What changed and why (SITE-FRONT-BOUNDARY, "The hub", decisions 3, 7, 13, 16):
- One receive (
lichen-site/src/admin_area/hub.rs).receiverunsgit receive-packconfined to the hub, with lichen's config on its command line and none of the hub's own hooks. The pre-receive is lichen's owncheck_main, run aslichen hub-checkfrom a hooks folder in the site's area. Before any ref moves it allows only the refs the pusher's policy names (main, andrefs/dist/latestfor the site), no deletions,mainonly forward unless the policy allows a force push (a full token, decision 7; never the site), and no symlink that leaves the tree. One refusal refuses the whole push. The pack is bounded by the owner's remaining storage (receive.maxInputSize); with none left the push is refused before git runs. - The site pushes (
lichen-site/src/git/hub.rs, unit 4b). Each commit lichen makes is pushed right after (commit-to-hub). The site reads the hub by path, read-only; if the hub'smainmoved, it merges first (never a rebase), and a refusal because the hub moved merges and retries, three attempts in all. The push is built by the site itself as one smart-HTTP request on the site process's request channel, which the front server relays to the same process's admin thread (FULL-SITE-PROCESS 6c), so no child of the site reaches the hub. For a rewrite, the site holds (commits, pushes, keeps its transaction) and then resets to the hub's newmain, or lets go after a timeout. - Clients' git goes to the hub (
git/hub_http.rs, unit 5):info/refs,git-upload-packandgit-receive-packunder/git/{domain}/, the same routes in every mode, in their permission groups. The front server admits them and, since FULL-SITE-PROCESS 6c, forwards them to the site process, whose admin thread serves them from the hub (site_process/hub.rs). After a push movesmain, the admin thread asks the site's main runtime to merge it (flow B) and the answer waits for that, bounded. - A full token's force push (
hub/full_push.rs, unit 8b) is received intorefs/lichen/incomingfirst; the rewrite sequence then movesmainon the admin thread, and the client gets lichen's own report. - Reading a site's content safely (
git/hub_files.rs): one blob of a hub commit through git, bounded, never a tree, a link or a submodule, never a checkout. The admin thread's publish (manifest, covers) and an imported site's settings use it. - Deleted:
lichen-server/src/git/smart_http.rs(−770), which proxied clients togit http-backend.
Files
crates/lichen-site/src/admin_area/hub.rs(moved from lichen-server in 5b) —receive,check_main,Pusher,Policy,upload. Read this first.crates/lichen-site/src/admin_area/hub/full_push.rs(moved) — the full token's push viarefs/lichen/incoming.crates/lichen-site/src/git/hub_http.rs(moved) — clients' smart-HTTP routes on the hub, and the merge request after a push.crates/lichen-site/src/git/hub_files.rs(moved) — one bounded blob of a hub commit.crates/lichen-site/src/site_process/hub.rs(874 lines, new) — the site process's hub: its admin thread serves the hub's routes with the handlers the front server served, given its one site (OwnSite), its owner's storage (OwnBudget), its main runtime through a queue (OwnRuntime, flow B), and what it tells the front (TellTheFront, flow C).crates/lichen-site/src/git/hub.rs(1,818 lines) — the site's side: read, merge, build and send the push (HubTransport: the site process's request channel, or the front server's own receive in local mode), retry,hold,reset,release,merge_from_hub.crates/lichen-server/src/git/smart_http.rs— deleted.
Lives elsewhere
- The hub repository itself (create, migrate, the rewrite helpers, restore): #20.
/hub/git-receive-pack, which the front server relays back to the site process's admin thread: #19.- The rewrite sequence that moves
main(lichen-site/src/git/rewrite.rs): #23. The admin thread and its router: #38. The iroh endpoint's asks of the hub (site_process/iroh_hub.rs): #28. - The
hub-checkdispatch inlichen-cli/src/main.rs: #32. - Tests (
admin_area__hub*.rs, e2e git tests) are in the base commit.
Category: The hub: one receive for the site and for clients
Size: 7 files, +4,740/−770
Rounds: round 0 at stack ae42e3c0; round 1 at 6b650b2a (SECURITY-FOLDER-AND-PRINCIPALS): hub.rs and hub/full_push.rs renamed into admin_area/ (unit 2), and path rewrites in git/hub_http.rs. Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/21/round/1/interdiff. Round 2 at b66397c0 (FULL-SITE-PROCESS): the four hub modules moved into lichen-site (5b, AST-checked), and the new site_process/hub.rs, from which the site process's admin thread serves the hub's routes (6c); the site's push is relayed to that admin thread; the main runtime merges from the hub when the admin thread asks (flow B). Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/21/round/2/interdiff
Plans and ledger: SITE-FRONT-BOUNDARY-2026-10-17.md ("The hub", decisions 3, 7, 13, 16; units 4, 4b, 5, 8b, 9b, 10a), SITE-FRONT-BOUNDARY-U1-DESIGN-2026-10-19.md, SITE-FRONT-BOUNDARY-U8-DESIGN-2026-10-22.md §11, FULL-SITE-PROCESS-2026-10-25.md (part 6, units 5b, 6c); builder ledger SFB unit 4 rows (lines 2892–2917).
Inline #[cfg(test)] modules: none. 1 file points to a test file with #[path] (the moved modules' tests came back to lichen-server in 5b's tests PR, through test-support accessors).
Bucket moves: none from round 1. (Round 1 gathered these from the starting map's Git and Site area buckets.) In round 4 the four moved files stay here with their subject though they changed crates, and the new site_process/hub.rs is placed here by an explicit rule.
Round 1: #5 (The hub) covered these files from 0b92cb61.