ADR-0013 — The CMS worker fleet is the sole ship lane; the static assembler becomes internal tooling¶
Status: Proposed (drafted 2026-07-18; owner decision recorded same day, in-session) Date: 2026-07-18 Deciders: Cathal Dempsey Consulted: two independent blind structural reviews this session (deep-reasoner, Codex) — both flagged the lane divergence; one identified the corpus/lane incoherence this ADR resolves. Related: ADR-0008 (per-site worker fleet — confirmed and extended), ADR-0009 (measured near-match — its Slice 6 corpus vehicle is re-specced here), ADR-0003 (assembler-fulldev — its product role is superseded; the code remains as tooling), ADR-0004 (Path A adapters — unchanged), ADR-0011 (deterministic gate — unchanged)
Context¶
Two output lanes have coexisted since ADR-0003/0004: per-site static Astro builds
(lib/assembler-fulldev) and the per-site single-tenant CMS workers (apps/cms, ADR-0008).
The project docs still describe the static lane as the long-tail product ("per-site static
builds for the long tail" — CLAUDE.md), while ADR-0008 records that all ~1800 sites may
become self-service. The two stories imply opposite investment targets, and the ambiguity
was never resolved — it was worked around.
Three facts forced the decision:
- Every ADR-0009 fidelity dimension shipped CMS-lane-only. Verified in code
2026-07-18:
lib/assembler-fulldev/emit.jsandtranslate.jsconsume nosection-backgrounds.jsonand nomakeStyleLookup; nav is stillgroupNavByPrefix()(which provably fails on WCP). The static lane renders the pre-ADR-0009 house style that ADR-0009 exists to replace. - The Slice 6 corpus vehicle builds the wrong lane.
scripts/run-fulldev-batch.shbuilds static-lane output. Calibrating the ship/hold gate on a 30–50-site batch of it would fit thresholds against output the ship lane does not render — the calibration would be internally consistent and externally meaningless. - The owner decision (2026-07-18): most clients will not edit their site — but every client must know they can. The capability is part of the product promise. Therefore every site ships with its own admin, i.e. a CMS worker per site.
Decision¶
-
The per-site CMS worker fleet is the sole ship lane. Every replatformed site in the Astro lane ships as ADR-0008 specifies: its own worker, its own D1, its own Access app, sharded across accounts. No client site ships from the static assembler.
-
The static assembler is demoted to internal tooling. The code stays (its crawl inputs, theme emission, and the canonical block library all remain load-bearing for the ship lane), but it receives no further fidelity investment and nothing it emits is client-facing. Consequence for standing deferrals: every known-issues item gated on "the first per-site static build intended for launch" (static-lane capture consumption; static-lane nav grouping; team-grid/checklist static-lane eyeball; the static
noindexlaunch blocker) is void — that trigger can no longer fire. Closures land at acceptance. -
The Slice 6 corpus must score the CMS lane's render. The batch, per site: crawl → seed (
seed-from-build+ transformer, capture stamps included) → image migration (ADR-0005 path, per-domain R2 prefixes) → local per-site CMS build + serve (no worker deploy, no production writes) → symmetric ours-capture → scorecard. The runner implementing this is the next slice — scope:scope-2026-07-18-cms-batch-runner.md.run-fulldev-batch.shremains available as internal tooling but is no longer the corpus vehicle. -
Self-service editability is confirmed as a product promise. "They need to know they can" only holds if an edit actually persists. The emdash save race (known-issues, reproduced on 0.28.1) is therefore a product blocker, not an ops annoyance: file the already-drafted upstream repro, timebox an owned mitigation (serialize the admin save path via patch-package or equivalent), and if neither lands, this ADR's premise must be re-examined openly — not silently carried.
Consequences¶
Positive¶
- One topology for the whole portfolio: no per-tier fidelity split, no static→CMS migration machinery, no second render surface to keep honest.
- The ADR-0009 investment concentrates where every site ships; the corpus measures the shipped artifact, so the Slice 6 fit means what it claims to mean.
- ADR-0008's "N of everything" cost stops being a hedge and becomes a plannable certainty.
Negative / risks¶
- Slice 3 provisioning automation + cross-account proof are now hard prerequisites for scale — unchanged in content from ADR-0008, changed in certainty. Both remain unstarted/unproven at drafting.
- Fleet redeploy fan-out is a fact of life: a shared block fix means ~1800 worker redeploys. The loop is trivial; it must exist before the fleet does.
- Verify early — per-account limits and pricing wherever the fleet multiplies them: Workers script count (~500/account, known), D1 database count/storage, and especially Cloudflare Access / Zero Trust seats at ~450 owner logins per account. Unverified at drafting; fold into the ADR-0008 Slice 3 second-account spike. Cheaper to learn at site 3 than at site 500.
- The static lane's sunk product cost is written off. Bounded: the pipeline underneath it (dom-pipeline, capture, theme, matchers, canonical blocks) is the same pipeline the ship lane consumes.
When to revisit¶
- Access/D1/Workers economics or limits fail at fleet scale → Workers-for-Platforms dispatch namespaces (ADR-0008's stated fallback) lift the script ceiling; re-evaluate account sharding wholesale.
- The save race proves unfixable upstream AND unpatchable locally → the editing promise cannot be honoured as specced; re-open the lane/tier question rather than shipping a door that doesn't open.
- A client segment materialises that explicitly wants no admin → a static tier may return as a deliberate product decision with its own fidelity plan — never as a silent default.
On acceptance (checklist)¶
- Pointer note in ADR-0003: product role superseded by ADR-0013; code remains as internal tooling.
- Pointer note in ADR-0009: Slice 6 vehicle re-specced (§Decision 3); "both lanes" phrasing scoped to "canonical library shared; ship lane is CMS".
- Note in ADR-0008: "all ~1800 may become self-service" hardens to "every site ships with its own admin".
- CLAUDE.md project description corrected (the "per-site static builds for the long tail" line).
- known-issues: close/annotate the void static-lane deferrals (Decision 2 list).
- mkdocs nav entry (added at drafting).
References¶
- ADR-0003 / ADR-0008 / ADR-0009 (the three records this ADR reconciles)
lib/assembler-fulldev/{emit,translate}.js(verified capture-blind, 2026-07-18)scripts/run-fulldev-batch.sh(static-lane batch, EC2-bound)- known-issues: "Per-site static lane consumes no section-background capture", "The per-site static lane's nav is still URL-prefix-grouped", the emdash save-race entry
- Memory:
per_site_worker_model(one single-tenant worker per site, all 1800 may self-manage — now hardened by this ADR)