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

product: 07 Publishing: the front server publishes, the site builds #54

targeting oct/06-git-sync from oct/07-publishing
Closed opened by and assigned to notplants-bot.bsky.social
targeting oct/06-git-sync from oct/07-publishing

superseded by the full set from 09-23, #17–#37

description: Review set for lichen.page.review, 0b92cb61 (reviewed) → eb5a1653 (stack). PR 7 of 16. 23 files, +3951/−1662. One commit, 887f24d9fda8, which sets these files to their content at eb5a1653. Targets #6 (oct/06-git-sync). #8 stacks on this one. Tests and generated files are not in any PR; they are in main.

The front server publishes; the site only builds (SITE-FRONT-BOUNDARY decision 10, unit 9). The site holds no deployment credential and no atproto session any more.

A publish now runs like this (one function, publish::run, for the editor's Publish, the dashboard's, and a save with auto_publish):

  1. The front server checks the caller may publish, takes the site's publish lock, and asks the site for its publish build. The site commits what is saved, builds dist with the standard.site links already in it (the record keys are known before the build), writes a manifest of the records it wants, and pushes main and a parentless refs/dist/latest commit of the build into the hub.
  2. The front server then does each step with its own credentials, whatever the step before did (operator note N4): writes the standard.site records from the manifest, after checking it; pushes the source if push_on_publish; deploys refs/dist/latest to GitHub Pages, Codeberg Pages or wisp.place. The front server never runs anything from dist.
  3. The report keeps the PublicationReport shape, now with one outcome per destination (publication.rs), and the publish record lives in the site's area.

The records check is strict: the front server's own record of the site's keys is the authority; only records of this site's publication are written or deleted (several sites share one PDS); new keys use createRecord, which never replaces a record. The site's GitHub/Codeberg/wisp publishers are deleted.


info: Plans: SITE-FRONT-BOUNDARY-2026-10-17.md ("Publishing", decision 10, unit 9a deployment, 9b records), SITE-FRONT-BOUNDARY-U9-DESIGN-2026-10-23.md (§4, §9, §11, §12); OPERATOR-REVIEW-REVISIONS-2026-10-13.md N4 (explicit destinations, best-effort reports).

Files

Front server:

  • crates/lichen-server/src/publish/run.rs — new: the one publish path, its lock, the build ask, then the steps. Read first.
  • crates/lichen-server/src/publish/records.rs — new (1068 lines): the record of keys (standard-site.json in the area), its migration, the manifest check, writing and sweeping records. The diff-and-sync code moved here from the site's standard_site/publish.rs.
  • crates/lichen-server/src/publish/records/remote.rs — moved from lichen-site/src/publish/standard_site/remote.rs: remote inventories; a publication is this site's only when the front server's record holds it.
  • crates/lichen-server/src/git/deploy.rs — new: deploy refs/dist/latest: a parentless commit with the domain file pushed from the hub to Pages (no owner credential: the target is the site's to write), or wisp.
  • crates/lichen-server/src/site_area/publish.rs — new: the publish record, the Pages deploy key and wisp's credential, in the area; no route answers the key or the credential.
  • crates/lichen-server/src/publish.rs — module list.

Site:

  • crates/lichen-site/src/git/publish_build.rs — new: the site's half: commit, build with records, dist_commit (links resolved to bytes; a link outside the site refuses), push both refs.
  • crates/lichen-site/src/publish/standard_site/manifest.rs — new: what the build asks of the records.
  • crates/lichen-site/src/publish/standard_site.rs, standard_site/publish.rs (−831), identity.rs, types.rs — the site keeps discovery, keys and the manifest; writing moved out.
  • crates/lichen-site/src/publication.rs — new: the per-destination report (N4).
  • crates/lichen-site/src/publish_record.rs — the record's format and rule, now written by the front server.
  • crates/lichen-site/src/publish.rs, publish/router.rs, publish/helpers.rs (−166), publish/settings.rs — settings and the "is publishing configured" route kept; target checks kept for the front server's use.
  • crates/lichen-site/src/publish/github_pages.rs, codeberg_pages.rs, wisp.rs, api/publish.rs — deleted.
  • crates/lichen-site/src/atproto/pds.rs — create_record, which the PDS refuses where a record exists.

Moved in (from where the starting map put them): git/deploy.rs and git/publish_build.rs (Git), site_area/publish.rs (Site area), atproto/pds.rs (iroh/sync, by its atproto path) and publication.rs (site core). Each is a step of a publish.

Lives elsewhere

  • The build itself (the rebuild lane, scripts, SSGs): #8. The source push used by push_on_publish: git/outbound.rs in #6.
  • The dashboard's trigger-publish route: api.rs in #11. The owner's session the records use: credentials.rs in #10.
  • Tests (publish__records.rs, publish__run.rs, standard_site tests, e2e publish tests) are in main.
Commits 0