Repeatable operator procedures, how to recover from a bad release, and where to look when something fails. Local fixture work needs none of this; live operations need the maintainer's separately scoped authority.

Reference Value
Subject Publishing, image mirrors, schema and permission operations, rollback and recovery, credential rotation, observability; for operators
Verified source apps/cms/scripts, cms.yml, apps/cms/README.md on main at f40e8704, checked 2026-11-07; image publishing, the article editor deployment and permission lanes re-verified on main at 0461e045 (apps/cms/README.md, build-media.ts, cms.yml), checked 2026-11-10
Support state Publishing procedures checked against main f40e8704. Production builds read live CMS content; provider operations and approval receipts have their own evidence

Publish content

  1. Save content with the appropriate publication status or visibility.
  2. Open the Publish site item (build_queue), Flows, Publish site, and confirm once.
  3. The asynchronous flow reads the public build version, attempts one production hook POST, records context, waits ten minutes, then reads the version again.
  4. Read the CMS notification and Vercel deployment, then verify the changed public page.
Feedback Meaning
Updated The public version changed between reads; this request is not proven to be its cause
Not updated The two readable versions match; a slow build may complete after the check
Could not verify A version read or script failed; inspect Vercel before retrying
Hook rejected Immediate dedicated notification; the normal wait is bypassed

Saving CMS content does not itself start a build. A production build uses live CMS content, whether triggered by Publish site or a merge to main. A merge after a CMS content write therefore publishes that content when the production build succeeds; the code diff does not bound the content published. Each successful hook path makes two public JSON reads, one hook POST and one notification. The wait is in memory, not a durable queue; restarting the CMS loses that notice. A stalled Request has no per-operation timeout. Flow links must be unique separately within resolve and reject columns, including across flows; each failure path owns its own notice.

Source: flows.json, operations.json. ‣ owns the dated successful version-pair and notification receipt, plus unobserved error/overlap scenarios.

Publish or replace an image

Upload the image in Directus, reference it in published content, then run Publish site. The production build publishes each referenced PNG, JPEG, WebP, GIF or AVIF to /images/cms/; no manifest, ID map or code PR is needed. For a theme pair in an image block, pick the light file under Light version in the Article Block Editor, or upload example.dark.png and example.light.png and select only the dark file. Review file contents, placement and any pair before publishing. A missing, unreadable or invalid image fails the build and the last successful site keeps serving. Build Bot reads only files in the seven delivery folders (about, blog, brand, illustrations, og, projects, services) or unfiled.

A committed mirror is optional reviewed pinning and the only path for a new SVG. To pin one:

  1. Register a new image in fixtures/assets-manifest.json and the UUID map through the reviewed asset workflow.
  2. Preview the mirror from the CMS (read credential injected), then apply only the reviewed digest:
# in apps/cms
bun scripts/mirror-asset.ts --path=images/about/headshot.webp
bun scripts/mirror-asset.ts --path=images/about/headshot.webp --apply-sha256=<reviewed-digest>
  1. Update that image's sha256 in fixtures/assets-manifest.json to the reviewed digest and commit it with the new bytes. The script doesn't change the manifest, and the export fails on a digest mismatch.
  2. Review rights and provenance, the image diff and mapping; let the build regenerate variants; ship it as a normal code PR.

The script reads one file and writes local bytes only. It fails before replacing anything on an unregistered path, an invalid image, a format mismatch or a changed digest. Deleting a file in the CMS does not remove committed or deployed bytes; removing a committed mirror is its own reviewed code change. A deleted upload that published content still references fails the next live build.

Production configuration writes