lichen.page.review from the reviewed point 0b92cb61 to stack eb5a1653, as one PR per subsystem (review set)

full 08: Publishing in the site: standard.site, the publish build and the publish settings #71

targeting full/07-git-sync from full/08-publish-site
Open opened by and assigned to notplants-bot.bsky.social
targeting full/07-git-sync from full/08-publish-site

The site's half of publishing. On 09-23 the front server held all of it: the publish hook, the GitHub Pages, Codeberg Pages and wisp.place publishers, the standard.site records and the PDS client. PER-SITE-PROCESS moved that code into lichen-site. SITE-FRONT-BOUNDARY unit 9 then gave the credentialed steps to the front server, and FULL-SITE-PROCESS gave them to the site process's admin thread (#25), so what is here is what the site's main runtime does: it reads its publish settings, discovers and keys its standard.site posts, and makes a publish's build (the source and a refs/dist/latest commit of dist) in its hub.


AST-verified changes

  • The front server's publish module moved into lichen-site (PER-SITE-PROCESS P2c-2b commit 3, cf1f8e96): 24 files renamed from lichen-server/src/publish/ to lichen-site/src/publish/ (settings, the three publishers, standard_site/* and their tests), and build_publish_hook into lichen-site/src/publish.rs. Checked by ast-checker 9a9c4144 (run id not recorded): "commit 3 move ok: 1 moved, 241 carried, 24 renamed; plant refused"; adversary rerun (A11333): equal=true, "3 1/0" (moved/visibility). Allowed: lichen_site:: → crate:: in the 24 moved files, one path rewrite (get_url_path_from_dist_path), lichen-site/Cargo.toml as an other file.
  • publish/router.rs moved whole to lichen-site (P2c-2b commit 4b, c2eabe5a). Checked (run id not recorded): "4b ok"; adversary rerun (A11334): "equal=true whole-file rename". Allowed: lichen_site:: → crate:: in the file and one path rewrite (publish_record::run).
  • publish/helpers.rs (with its tests) moved to lichen-site (P2c-2b commit 1d, 10a747b0), with git/settings.rs (#22). Checked (run id not recorded): "3 moved, 32 carried, 5 visibility"; adversary rerun (A11333): equal=true, "1d 3/5".
  • The PDS XRPC core moved from lichen-server/src/auth/atproto/pds.rs to lichen-site/src/atproto/pds.rs (P2c-2b commit 1e, e3ed8ab6): 17 items, send_dpop_request, put_record, create_record, delete_record, list_records, upload_blob and their helpers. Builder count not recorded; adversary rerun (A11333): equal=true, "1e 17/1". Allowed: one visibility change (delete_record_with_context to pub); both crates' Cargo.toml as other files.
  • publish/standard_site/mod.rs renamed to standard_site.rs (OPERATOR-REVIEW-FIXES U1, N8, f6a10c32), before commit 3 moved it. Checked over 8b30050a..f6a10c32 (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. The same commit renamed publish/mod.rs to publish.rs; both halves of that one are in #25.
  • The publication renames of OPERATOR-REVIEW-REVISIONS R7 (d52675cd): PublishResult → StandardSitePublication, PublishHook/Attempt/Record → Publication*, publish_staged → install_dist, and others. Checked by substituting the names back: run notify-392a0b96, "names substituted back AST EQUAL, control RED"; adversary (A11506): "rename AST-equal (control RED)".
  • Leaving this code: six functions of standard_site/publish.rs and standard_site/remote.rs went to the front server's publish/records.rs (SITE-FRONT-BOUNDARY 9b, inside 98c16447, checked on a rebuilt pure move); see #25.

Roughly 920 of this PR's 3,293 changed lines are code these moves carried verbatim, mostly the PDS client and the deleted publish module's lines that reappear here; the manifest, the publish build and the publication report are new code.

On this forge the 6 renamed files here show as a whole deletion and a whole addition, so its diff reads 6,689 changed lines; with rename detection (git diff -M full/07-git-sync full/08-publish-site) it is 3,293.


What the site does now

  • The publish build (git/publish_build.rs, unit 9a): asked by the site process's admin thread, which runs the publish since FULL-SITE-PROCESS 6e (POST /cms/api/git/publish-build, admitted only on the admin thread's own connection: access::admin_host, D6), the site commits what is saved, builds dist with the standard.site links in it, makes a parentless refs/dist/latest commit of the build with each link resolved to its bytes (a link that resolves outside the site refuses the publish), and pushes main and that commit into the hub through the admin thread's one receive, which the front server relays to. It sends the editor's publish events; the admin thread asks it to send finished or failed (/cms/api/git/publish-event).
  • standard.site, the site's part (publish/standard_site.rs, standard_site/manifest.rs, unit 9b): the site discovers its posts and gives each a record key before the build (so one build carries every link), then writes a manifest of the records it wants into the build's commit. The admin thread checks the manifest and writes the records with the owner's session; the main runtime that builds holds no session. standard_site/publish.rs keeps the site's keys, its publication identity and the counts, not the writing.
  • Settings and checks: publish/settings.rs (the [publish] and [standard_site] sections), publish/helpers.rs (the checks of a remote URL and of a Pages repository and branch, which the admin thread uses before it pushes), publish/router.rs (GET /cms/api/publish/config, whether publishing is configured).
  • The report and record formats the admin thread fills: publication.rs (one outcome per destination, operator note N4) and publish_record.rs (the last publish and the recent ones).
  • The PDS client (atproto/pds.rs): DPoP-signed record CRUD and blob upload, now shared by both crates; create_record_at writes a chosen key with createRecord, which the PDS refuses where a record exists, so it never replaces one.
  • Deleted from the front server: publish/github_pages.rs, codeberg_pages.rs, wisp.rs (the deploy is now git/deploy.rs, #25), router.rs, helpers.rs, standard_site/mod.rs; auth/atproto/pds.rs keeps the site-index records, the git-state record and the session check, and re-exports the client from lichen-site (+5/−393).

Files The build, read this first:

  • crates/lichen-site/src/git/publish_build.rs — new: the site's half of a publish.
  • crates/lichen-site/src/publish/standard_site/manifest.rs — new: the keys and the manifest.

standard.site (crates/lichen-site/src/publish/):

  • standard_site.rs — prepare: discovery and keys for the build.
  • standard_site/publish.rs — the site's standard.site state; the diff-and-sync is publish/records.rs's, the admin thread's (#25).
  • standard_site/discovery.rs, inject.rs, markdown.rs, identity.rs, types.rs — renamed from lichen-server/src/publish/standard_site/; small changes (crate paths, test-support gates).

Settings, checks and formats:

  • crates/lichen-site/src/publish.rs, publish/settings.rs (renamed from lichen-server), publish/helpers.rs, publish/router.rs.
  • crates/lichen-site/src/publication.rs, crates/lichen-site/src/publish_record.rs.
  • crates/lichen-site/src/atproto/pds.rs — the PDS client; crates/lichen-server/src/auth/atproto/pds.rs — what the front server kept.

Deleted: crates/lichen-server/src/publish/{router,helpers,github_pages,codeberg_pages,wisp}.rs, publish/standard_site/mod.rs.

Moved in: the lichen-server publish deletions above (their code went to lichen-site in P2c-2b), and lichen-server/src/auth/atproto/pds.rs (round 1's map would put it under front-server auth; most of its change is the PDS client leaving for lichen-site/src/atproto/pds.rs).

Lives elsewhere

  • The publish run, the records, the deploy and the publish record in the area (lichen-site's publish/run.rs, publish/records.rs, git/deploy.rs, admin_area/publish.rs, run by the admin thread), and the front server's forwarding of the publish routes: #25.
  • The build itself (the rebuild lane, SSGs) and its scripts: #27, #26.
  • The hub's receive that takes the build: #21.
  • lichen-site/src/ipc/publish.rs, the site process's publish ask: #19.
  • Tests (publish/tests/, standard_site/tests/, tests/publish_record.rs, e2e publish tests) are in the base commit.

Category: Publishing in the site: standard.site, the publish build and the publish settings Size: 23 files, +2,268/−1,025 Rounds: round 0 at stack ae42e3c0; at 29a7c15b and at 6b650b2a none of this PR's files changed; round 1 at b66397c0 (FULL-SITE-PROCESS 6e): the publish build is asked by the site process's admin thread, which now runs the publish, and is admitted only on its own connection (access::admin_host); the build's own push holds TriggerPushToRemote (the O4 split) where it held PushToRemote; publish.rs declares the moved dns, records and run; comments name the admin thread where they named the front server. Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/24/round/1/interdiff Plans and ledger: PER-SITE-PROCESS-2026-10-09.md (P2c-2b commits 1d, 1e, 3, 4b); SITE-FRONT-BOUNDARY-2026-10-17.md ("Publishing", decision 10, units 9a and 9b) with SITE-FRONT-BOUNDARY-U9-DESIGN-2026-10-23.md; OPERATOR-REVIEW-REVISIONS-2026-10-13.md (N4, R7); OPERATOR-REVIEW-FIXES-2026-10-07.md (U1, N8). Ledger: lichen-builder B2352, B2363, B2367, B2777, B3310; lichen-adversary A11224, A11333, A11334, A11506. Inline #[cfg(test)] modules: none inline; 11 files point to a test file with #[path], 1 has test-only items (publish/standard_site.rs). Bucket moves: round 1's Publishing PR is split: the site's half here, the front server's in #25. The deleted lichen-server publish files and auth/atproto/pds.rs are placed here with the code they became. Round 1: #7 (Publishing: the front server publishes, the site builds) covered most of these files from 0b92cb61; standard_site/discovery.rs, inject.rs and markdown.rs and the lichen-server deletions are new to the set.

Commits 0