Reading copy of lichen.page.review: one PR per unit, product changes only

HUB-EXCHANGE-OVER-GIT all: hub and site exchange commits over plain git, with no bundles #62

targeting base/hub-exchange-over-git from view/hub-exchange-over-git
Open opened by and assigned to notplants-bot.bsky.social
targeting base/hub-exchange-over-git from view/hub-exchange-over-git

A site and its hub now exchange commits with plain git over a socket the admin thread makes for each exchange, instead of bundles. The hub's own receive-pack and upload-pack do the work, and the hub's one receive and its checks stay the boundary.

  • Up: a site pushes with git push fd::3/hub +<oid>:<ref>. The admin thread runs no git in the site's repository and the site cannot open the hub's folder. The hub reports the outcome (its ref after the receive, and a verdict its pre-receive check writes); the site's git output is only logged. The push may run only the one command the hub expects, and the whole exchange runs under one time limit.
  • Down: every way down is a plain git fetch of one ref, with HEAD hidden, into FETCH_HEAD with fsck on, a symlink screen on its commit, and only then the ref is pointed at it (merge, reset, restore and the start all fetch this way).
  • Pinned: the site's own git config cannot widen either git: no hooks path, no lazy fetch, no push options, tag following or bundle URI, and the fd transport alone.
  • Removed: the bundles on both ways, the staging repository, the hand-built receive request, and the old bundle files (swept once at a site's start).
  • Docs: SANDBOX.md, docs/SECURITY.md and docs/CMS_EVENTS.md describe the exchange as it runs, including the site's push doc comment on when a tracking ref is written.

HUB-EXCHANGE-OVER-GIT, units 1 to 4 combined (replacing the four per-unit PRs). Plan: https://tangled.org/notplants-bot.bsky.social/lichen-orchestrator/blob/main/machine-docs/plans/HUB-EXCHANGE-OVER-GIT-2026-11-01.md

This is a reading copy: only this subunit's product changes, applied in order on its base. Nothing here is to be merged.

Source PRs on lichen.page.review (product), in order, with their original descriptions #

  • #3585 s/960-hxg-u1-up-over-git — Push the site's commits into its hub over plain git

    Every push from a site into its hub is now plain git. The admin thread opens a socket pair, runs the hub's own git receive-pack on one end, and the site's git runs git push fd::3/hub +<oid>:<ref> on the other. Only the socket crosses: the admin thread runs no git in the site's repository, and the site side cannot open the hub's folder. The hub's one receive and its checks stay the boundary for every push. The outcome comes from the hub: its ref after the receive, and a verdict that the hub's pre-receive check writes. The site's git output is logged only. Each site push may run only the one command the hub side expects (one ref, the asked commit, the old value as read). The whole exchange runs under one time limit; at the limit both gits are stopped. The bundle on the way up, the staging repository and the hand-built receive request are gone.

  • #3593 s/962-hxg-u2-down-over-git — Fetch from the hub over git, the down door's bundles gone

    Every way down from a site's hub is now a plain git fetch over a socket the admin thread makes. The hub side's serve_site_fetch keeps today's two refusals, then runs the hub's own upload-pack shown the one ref asked, with HEAD hidden, within the exchange's time bound. The site side fetches that ref into FETCH_HEAD alone with fsck on, screens its commit for escaping symlinks, and only then points its ref at it; merge, reset, restore and the start's take each fetch so. The site's own git config cannot widen the fetch: no bundle URI, no remote's ref mapping, no tags, the fd transport alone, and a config that lowers the fetch's fsck is refused. The test probe asks for a merge or a reset whole. The bundles and their helpers go; a site's start removes what an older lichen's bundles left, once.

  • #3601 s/980-hxg-u3-close — The hub exchange's close: the site's push allowed fd alone, and the docs over git

    The site's push into its hub now runs with GIT_ALLOW_PROTOCOL=fd, as its fetch from the hub already does. A remote, a URL or an insteadOf in the site's git config can no longer send that push to the site's own files. The shared helper that runs both gits sets the one transport itself, so its transports argument is gone. Every other git the site runs keeps file alone.

    The docs now describe the hub exchange as it runs: plain git push and git fetch over a socketpair the admin thread makes for each exchange. SANDBOX.md names the site's end of the socket (HubSocketForTheSite) as the one descriptor that crosses into the main runtime. It also covers the fd transport for the two hub commands alone, the hub's one receive with its expected command and verdict, the fetch shown one ref with HEAD hidden, the time bound, the relayed receive output and the one-time removal of old bundle files. docs/SECURITY.md's hub rows and its "What crosses" text say the same and name the D-a narrowing. SECURITY.md also names keep_ambiguous's create-only update-ref as a ref move in the hub, beside the up door and rewrite_hubs_main. docs/CMS_EVENTS.md and the CMS events' comments say GitCommitted wakes the hub follower. No doc or product comment describes a bundle in the exchange any more.

  • #3608 s/982-hxg-u4-site-hooks-fixup — The site's push doc says when it writes a tracking ref

    This PR changes one doc comment: the site's push over the hub's socket writes no index, and writes a tracking ref only where the site's own config names a remote at that URL, in the site's repository with the site's rights (measured on git 2.54 and 2.55). Its branch first stopped the hooks that the site's git config names from running in the site's push and fetch over the hub's socket. Then the operator ruled that a hook the site writes may run in the site's own git, with the site's rights, and never with more. So the branch reverts that change and keeps the exchange as unit 3 left it. The site's push and fetch keep every pin that unit 1 and unit 2 set (the pack's tuning, core.hooksPath=/dev/null, no lazy fetch, the fd transport alone). The tests PR shows the rule for each git of the exchange.

Commits #

  • 30815a60d HUB-EXCHANGE-OVER-GIT unit 1: product changes (from lichen.page.view view/hub-exchange-over-git-u1)
  • b08e64afd HUB-EXCHANGE-OVER-GIT unit 2: product changes (from lichen.page.view view/hub-exchange-over-git-u2)
  • 70e5cbd89 HUB-EXCHANGE-OVER-GIT unit 3: product changes (from lichen.page.view view/hub-exchange-over-git-u3)
  • 0c6efcf56 HUB-EXCHANGE-OVER-GIT unit 4: product changes (from lichen.page.view view/hub-exchange-over-git-u4)

Count and shas #

count-product-code-lines 5cb6139d005e..0c6efcf56935: NET -372 · counted +583 -955 · moved out of the counted set +0 · comment-only deletions flagged 153 · macros flagged 0 · tests-folder code flagged 0

Base 5cb6139d005e592411f206aaf2dbfd350e8b1dc1 (tip of s/903-od-u3d-import-tests). Head 0c6efcf56935e0511b25c3d629d819fe9c090ae2. 25 files changed.

Check #

Files that differ from s/982-hxg-u4-site-hooks-fixup (later tests PRs or merges touched them, or the diff was resolved): crates/lichen-site/src/publish/run.rs, crates/lichen-site/src/site_process/host.rs.

Resolved/noted: The four source branches also contain merges of stack, which are not included here. Each unit was built from its own commits only (see the units' earlier PRs, now closed), and the four diffs were applied in order on the base. The two files listed above differ from the last source branch only by ONE-DOOR changes that came in with those merges (misconfigured in publish/run.rs, and the removal of settings_notice in site_process/host.rs).

Resolved/noted: Units 2 to 4 were originally built on later bases (s/967-od-u5b-runtime-tests, s/974-od-u6d-save-fixture-tests, s/983-od-u6g-ssr-error-tests); their diffs applied here without conflicts.

Commits 0