Git with a site's remote, and the rewrites of its history: the push of a site's hub to its remote, the fetch of the remote into the hub, the compact, force-pull and force push, the editor's git panel, the remote record and the SSH with the owner's deploy key. Since FULL-SITE-PROCESS this code is lichen-site's and runs on the site process's admin thread, which holds the hub, the area's copy of the deploy key and the owner's session the front server lends it; the front server admits these requests and forwards them, and pushes and fetches nothing itself. On 09-23 the front server ran the sites' git against each site's working tree; that code went to lichen-site's main runtime (#22), and this is what replaced it.
AST-verified changes
push_worker.rsmoved fromlichen-site/src/git/back tolichen-server/src/git/(SITE-FRONT-BOUNDARY 6b, 0dcf0cc9). Checked (run id not recorded on the move line): "the push worker AST-moved, checker ok, 3/3 plants CAUGHT"; adversary rerun (A11631): "AST move c379c890→0dcf0cc9 rerun: equal". The site's copy existed only inside this range (P2c-2b commit 4 had moved it there), so here the file reads against its 09-23 self.- What
git/hooks.rsandgit/router.rsgave lichen-site in P2c-2b commit 4 (9fe049e0), item by item; both now re-exportlichen_site::git::hooksand::router. Checked (run id not recorded): "moves 4 ok (56 moved, 189 carried, 6 renamed, 33 vis)"; adversary rerun (A11334): "equal=true 56 moved/6 renamed/189 carried/33 vis 0 errs". The site's half is in #22. pds_handle_offromgit/hooks.rstoowner.rs(PER-SITE-PROCESS P2b-2, 3b0c7e49). Checked (run id not recorded): "ok (1443 items, 1 moved, 0 visibility changes, 1997 comments compared, 124 module declarations)"; adversary rerun (A11322) matched.owner.rsis in #31.try_refresh_tokenfromgit/hooks.rstocredentials.rs(P2c-2a seam 2, 6ec79159). Checked: "move 6ec79159 ok (1 moved, 0 visibility), plant refused"; adversary rerun (A11326): "equal=true, 1452 items, 1 moved, 0 visibility".credentials.rsis in #30.git/mod.rsrenamed togit.rs, one of the 19foo/mod.rs→foo.rsrenames of OPERATOR-REVIEW-FIXES U1 (N8, f6a10c32). Checked over 8b30050a..f6a10c32 together with U1's moves (run id not recorded): "3004 items, 48 moved, 69 carried, 19 renamed, 2 visibility as mapped, 233 module declarations"; adversary rerun notify-abab368a, the same counts. In this diff the two halves are separate entries (git.rsis new,git/mod.rsdeleted), because the file changed a great deal after.PdsScopeand its impl fromlichen-site/src/owner.rstosite_area/remote.rs(SITE-FRONT-BOUNDARY 7b). The landed commit, 5670cd85, mixes the move with deletions, so the move was checked on a rebuilt pure move (on e3950a4b): run notify-9a972a6c, "equal, 2 moved, 4970 stayed, no errors"; adversary rerun notify-222a9edd: "equal, 2 moved, 0 errors; 3 plants refused". Allowed: the pathlichen_site::owner::PdsScope→crate::site_area::remote::PdsScopein code and docs. The site's half is in #37.- SECURITY-FOLDER-AND-PRINCIPALS unit 2 (b603dbbe):
site_area/remote.rs→admin_area/remote.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. - 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. - FULL-SITE-PROCESS 5b (#1649 on lichen.page.review, 26779036 → 0e8871fe, map
machine-docs/reports/fsp-5b/move-map.json): one move commit (85d6303c) renamed 34 files whole from lichen-server to lichen-site (19 modules and their 15#[path]test files); lichen-server re-exports each where it was. Here:git/{outbound, inbound, rewrite, editor, push_worker, seams, ssh_relay}.rsandadmin_area/remote.rs, now undercrates/lichen-site/src/, and the re-exports andmodlines in lichen-server'sgit.rs. Checked by 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 is not caught, because the checker lets declarations change; the PR's gate table covers it); adversary reruns equal (notify-c53fbd02). Allowed: the 99 listed visibility changes,pubfor what the front server calls (6a2b574d, c04bebac, 0e8871fe, inside the checked range). Outside the check: 26779036 (path-only, before the move), e0dc7411 (FSP-NOMDBOOK1), thefor_testsaccessors and test hooks, ad03def8 (rustfmt).
Roughly 330 of this PR's 5,251 changed lines are code these moves carried verbatim. The push, the fetch, the rewrites, the editor's panel, the remote record and the SSH were all written after 09-23, so in this diff they read as new code; the moves only tell you that unit 5b's crossing into lichen-site changed no item.
On this forge the renamed file here (git/push_worker.rs) shows as a whole deletion and a whole addition, so its diff reads 5,427 changed lines; with rename detection (git diff -M full/06-site-git full/07-git-sync) it is 5,251.
What this code does (SITE-FRONT-BOUNDARY units 6, 7, 8, 11b1; FULL-SITE-PROCESS units 4, 5b, 6d):
- Who runs it (FULL-SITE-PROCESS 6d): the site process's admin thread, which holds the site's area (the hub, the remote record, the deploy key's copy) and the owner's atproto session the front server lends over the control channel (#19). The front server admits each request in its group and forwards it (
editor_forwardsinserver.rs,front_seams);lichen-server/src/git.rssays it pushes and fetches nothing. Unit 4 first made this code take explicit inputs rather than the front server's state (git/seams.rs:Sites,Owners,SiteHandle), so that unit 5b could move it unchanged. - Push (
git/outbound.rs, unit 6): after each move of the hub'smain(a site's push, a client's, a rewrite), one coalescing worker per site (git/push_worker.rs) pushes it to the remote, when the site's sync saysauto_push. A failed push is retried after 5, 10, 20, 40, 60 and 300 seconds, for at most an hour. Git runs from the hub with no system or global config, what a push could take from the hub's config pinned on the command line (no hooks, no credential helper, a fixed list of protocols), with the owner's PDS session piped togit-remote-pdsor the area's copy of the deploy key. A failed push is shown to the owner, redacted, until one succeeds. - Where to (
admin_area/remote.rs):remote.tomlin the site's area, which the admin thread reads and writes and its migrations make: the URL the owner confirmed on the dashboard, the URL found in the site's own.git/config(a proposal, never pushed to or fetched from), the sync toggles, andsite_key(a key the site's settings named, which lichen does not take). - Fetch (
git/inbound.rs, unit 7): the dashboard's auto-pull, the editor's pull and remote status fetch into the hub's hiddenrefs/remotes/origin/*, through a staging repository bounded by the owner's remaining storage and screened with the hub's tree rules; a tip that would bring back history a compact replaced is not taken, and the site then merges from the hub. The same file fetches each collaborator'sgit_remoteinto the hub (fetch_a_collaborator, unit 10c) and takes an iroh peer's pack (take_a_peers_pack, #28). - Rewrites (
git/rewrite.rs, unit 8): the site's main runtime holds and pushes its pending work, the admin thread moves the hub'smainby compare-and-swap, the main runtime resets to it, and only then is anything irreversible done (the old objects pruned, the forced push). A rewrite left unfinished is settled when the site next opens (the admin thread checks at start, #38). The dashboard's compact, force-pull and force push reach it as/hub/compact,/hub/force-pulland/hub/force-pushon the admin router; a single site's/cms/api/git/compact,force-pullandforce-pushgo the same way.take_a_client_pushtakes a full token's force push (#21). - The editor's git panel (
git/editor.rs, unit 11b1, FULL-SITE-PROCESS 6d):/cms/api/git/status,remote-status,pushandpullon the site's host, served by the admin thread on the hub in the groups and answer shapes the site's routes had; what it needs of the site it asks the main runtime as the client admitted. - SSH (
git/ssh_relay.rs,owner_key.rs; PER-SITE-PROCESS P3-4, FULL-SITE-PROCESS 4f1): the admin thread's own SSH with the owner's deploy key. The front server keeps the key in the user store and copies it into the site's area before the site's process launches, and rewrites or removes every copy when the key or the site's owner changes;owner_key.rswrites and reads that copy without following a link. For each run of git the key is written to a0600file in the area'sgit-keys, outside the site, and removed after; host keys are learned once and held to. The main runtime and its children never reach the key, unless the owner opts them in. The relay a site's git once ransshthrough is gone. - What stays in the front server (
lichen-server/src/git.rs,git/hooks.rs,git/router.rs): a new site's first commit, the word of a rewrite a site undid, the dashboard's auto-pull hint (auto_pull_sites), and re-exports of the moved modules.
Files
Moved into crates/lichen-site/src/ (FULL-SITE-PROCESS 5b), run by the admin thread:
git/outbound.rs— the push. Read this first.git/inbound.rs— the fetch into the hub, collaborators' remotes, a peer's pack.git/rewrite.rs— compact, force-pull, force push under the site's hold.git/editor.rs— the editor's git panel.git/push_worker.rs(+4/−47 against its 09-23 self in lichen-server) — the scheduling loop (run,run_with_wait) thatoutbound.rsdrives.git/seams.rs— what a git route is given of its site (unit 4).git/ssh_relay.rs— the admin thread's SSH. The front server's oldlichen-server/src/git/ssh_relay.rsis deleted; it existed only inside this range, so this diff shows only the new file.admin_area/remote.rs— the remote record and the sync toggles;PdsScope.owner_key.rs— new: the area's copy of the owner's deploy key.
Still in crates/lichen-server/src/:
git/hooks.rs(+30/−363) —auto_pull_sites; re-exports the site's hooks.git/router.rs(+11/−420) — re-exports the site's git router; the old push, pull and rewrite handlers are gone.git.rs— a new site's first commit, and re-exports of the moved modules.git/mod.rs(−38), the 09-23 root it was renamed from, is deleted here.
Moved in: git/mod.rs's deletion, with the git.rs it became (git pairs them only below its default similarity); admin_area/remote.rs (round 1's starting map put it under Site area: it is the remote record this code reads); owner_key.rs (new this round: the key this code's SSH reads).
Lives elsewhere
- The site's own git (commits, recovery, the push into the hub): #22. The hub's receive and the site process's hub routes: #21. The hub repository's rewrite helpers:
admin_area/hub_repo.rsin #20. - The dashboard routes that start these acts:
api.rsin #32. The owner's session as the front server holds it:security/credentials.rsin #30; as the site process holds it (site_process/session.rs): #19. The front server's side of the seams (front_seams.rs): #31. - The admin thread and the site process's start: #38.
- A publish's push of the source and its Pages deploy: #25.
- iroh's git sync, which hands a peer's pack to
take_a_peers_pack: #28. - Tests (
git/tests/*.rs, lichen-server'stests/moved/*_tested.rs,lifecycle_tests.rs, e2e git tests) are in the base commit.
Category: Git with a site's remote: pushes, fetches and rewrites, on the site process's admin thread
Size: 13 files, +4,383/−868
Rounds: round 0 at stack ae42e3c0; round 1 at 6b650b2a (SECURITY-FOLDER-AND-PRINCIPALS): remote.rs renamed into admin_area/ (unit 2), and path rewrites and rustfmt in git/{editor,hooks,inbound,outbound,rewrite}.rs; round 2 at b66397c0 (FULL-SITE-PROCESS units 4, 5b, 6d, 7): the push, fetch, rewrites, editor's panel, remote record and push worker moved into lichen-site (5b, AST-checked) and now run on the site process's admin thread with the owner's lent session and the area's copy of the deploy key; the SSH is the admin thread's (git/ssh_relay.rs anew, the front server's deleted); new git/seams.rs and owner_key.rs; the front server's git.rs, hooks.rs keep only what is the front's. Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/23/round/2/interdiff
Plans and ledger: FULL-SITE-PROCESS-2026-10-25.md (parts 4–7; units 4, 4f1, 5b, 6d, 7) with FULL-SITE-PROCESS-U0-DESIGN-2026-10-26.md; SITE-FRONT-BOUNDARY-2026-10-17.md (units 6, 7, 8, 10c, 11b1; decision 7 as revised 2026-10-21) with SITE-FRONT-BOUNDARY-U6-DESIGN-2026-10-21.md, -U7-DESIGN-2026-10-22.md, -U8-DESIGN-2026-10-22.md; PER-SITE-PROCESS-2026-10-09.md (P2b-2, P2c-2a, P2c-2b, P3-4); OPERATOR-REVIEW-FIXES-2026-10-07.md (U1, N8). Ledger: lichen-builder B2087, B2300, B2339, B2367, B2931, B3111; lichen-adversary A11224, A11322, A11326, A11334, A11631, A11639.
Inline #[cfg(test)] modules: none inline; 5 files point to a test file with #[path] (the front-needing tests came back to lichen-server's tests/moved/, in the base commit).
Bucket moves: round 1's Git PR is split in two: git with the site's remote here (the front server's until FULL-SITE-PROCESS, the admin thread's now), the site's own repository in #22. git/mod.rs's deletion is here with git.rs. Every file kept its PR when FULL-SITE-PROCESS moved it into lichen-site: the subject did not change, its home did.
Round 1: #6 (Git: the front server pushes, fetches and rewrites) covered these files from 0b92cb61.