commit 195a9d6e67e499496dab4da28925a26155924ff8
parent 0a6ca1ddf93fc94fef4a059d9d7e593b8f428331
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 15:12:44 -0400
plans: release 12 merged, not rolled out — release-12.md "Merged" and the owed Rollout, STATE's "Now", FACTS' source-mirror section
release-12.md: the merge facts (Q `cc9e97a4` + the changelog fix `6a769e92`,
R `0a6ca1dd`; the operator files 3 rules / 3 literals by kind; filter-repo via
pipx; the gate passes at the merge) and "Rollout" as the ordered checklist of
what is owed — the denylist first (a preview is public), the song link, the
editor rebuild and restart with ~/.local/bin on its PATH, a fresh build
homepage, the preview and its live checks (single-valued Content-Type), the
production deploy, the edge and leak contingencies, the cut and the worktrees
— with the runbook's scripts (~/reports/release-12/scripts/). STATE: a "Now"
block added above release 11's. FACTS: a "Never publish a path segment named
`.git`" naming hazard and a release 12 section — the step, the withdrawal, the
deploy key, filter-repo's `#` lines and binaries, dumb HTTP (the loose ref),
`_headers` appending, the nearest-404 rule, previews being permanent,
Tailwind/tsc/eslint/docker, and the measured numbers.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
| M | plans/FACTS.md | | | 149 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | plans/STATE.md | | | 37 | +++++++++++++++++++++++++++++++++++++ |
| M | plans/release-12.md | | | 147 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 333 insertions(+), 0 deletions(-)
diff --git a/plans/FACTS.md b/plans/FACTS.md
@@ -153,6 +153,11 @@ THROWS at declaration — module load — on a name `SUB_FILE_RE` matches;
Never name the curated field `tags`. Never assume a `tags.json` is the keyword list:
`transcripts/tags.json` and `sites/<id>/tags.json` are curated tags.
+**Never publish a path segment named `.git`.** wrangler's Pages upload drops `**/.git` (and
+`**/node_modules`) SILENTLY, and Cloudflare's managed rules block `/.git/` requests. The source
+mirror is `/source/archilyzer.git/` for that reason (release 12, below). A mirror named `.git` would
+deploy "successfully" and 404.
+
---
## Reusable helpers (do not rewrite these)
@@ -7168,3 +7173,147 @@ out. O3's facts are the section just above ("O3 — runner lows"). Anchors are a
byte-identical after every run): the export suite replaces the `sw.js` link with its own file;
`e2e:2origin` (`compose hub`, twice) replaces `sw.js`, `hub-sites.json`, `corpus.json`, `llms.txt`,
`robots.txt` and `_headers`. `e2e:hub` writes none. Re-seed before the next export run.
+
+## Release 12 — the source mirror (verified 2026-09-28, `main` @ `ffdeb2cd`)
+
+Slices Q (`4855f70b`) and R (`ffdeb2cd`): [`release-12.md`](release-12.md), the plan
+[`source-mirror.md`](source-mirror.md), the operator-facing doc `PUBLISH.md` "The source mirror
+(homepage)". Anchors are at `ffdeb2cd`.
+
+### The step (`common/publish/source.ts`)
+
+- **`publishSource`** (`:611`) → `publish` (`:647`) runs these steps in order:
+ 1. The git COMMON dir's `refs/heads/main`: a worktree build mirrors the primary's main. No
+ repository at all (`commonDir` `:543`) says `NO_REPOSITORY` (`:510`).
+ 2. The operator files (`loadSourceRules` `:216`).
+ 3. The tools: `resolveFilterRepo` `:321` and `gitleaksIdentity` `:483`.
+ 4. The skip.
+ 5. A `--no-local --bare --single-branch --no-tags` clone into
+ `mkdtemp(ARCHILYZER_SOURCE_SCRATCH/archilyzer-source-)`. The scratch root is refused inside the
+ checkout or the public dir (`scratchRootProblem` `:582`).
+ 6. filter-repo, then `repack -a -d --max-pack-size=20m`, `prune-packed`, `pack-refs`,
+ `update-server-info`.
+ 7. The object audit.
+ 8. The tree and the tarball.
+ 9. The allowlisted stage.
+ 10. The file audit.
+ 11. The limits: 15,000 files, 24 MiB a file.
+ 12. The link-safe install: the manifest removed first and written LAST.
+
+ `buildHomepage` (`build.ts:991`) runs it between compose and `next build`, with `noRepository:
+ "empty"`. So `archilyzer build homepage`, `/sites` Build homepage and `pnpm ops build-homepage`
+ all do.
+- **A refusal withdraws the source.**
+ - Once the rules are loaded, any non-zero outcome runs `removePublishedSource` (`:520`). `--check`
+ writes nothing, this included.
+ - `buildHomepage` then removes `out/source` and the two download files (`withdrawBuiltSource`,
+ `build.ts:1026`). `out/source/index.html`, the `/source` PAGE, goes with them.
+- **The deploy key.** `homepage/.source-publish.json` sits beside `public/`, NEVER inside it (a
+ published rules hash would confirm a guess at the denylist). It holds `sourceCommit`,
+ `mirrorHead`, `rulesHash`, `filterRepo`, `gitleaks` and `contentDigest`:
+ - `rulesHash` is `rulesHashOf(scrub lines, literals, SOURCE_STEP_VERSION)` (`:243`).
+ **`SOURCE_STEP_VERSION` (`:86`, now 3) must be bumped whenever the scrub or the audit
+ changes**, so an unchanged main is re-published and an old `out/` refuses to deploy.
+ - `gitleaks` is the version line plus the sha256 of the binary. This machine's gitleaks prints
+ `version is set by build process` for every release.
+ - `contentDigest` is `sourceDigest` (`:447`): the sorted path, size and streamed sha256 of every
+ published file — `source/archilyzer.git/**`, `source/tree/**`, `source/manifest.json`, the
+ tarball, `snapshot.json`. It takes about 0.5 s over 2,459 files.
+- **`publishedSourceProblem`** (`:985`), asked by `deployHomepage` (`build.ts:1079`) before every
+ deploy, preview included:
+ - no `/source` page in `out/` refuses: a refused build, or one from before release 12;
+ - the page with no artefacts beside it is a `--no-source` build, and deploys;
+ - otherwise it binds: a valid manifest, a complete state, `rulesHash` = today's, `mirrorHead` =
+ `out`'s, main = the state's and the manifest's, the tarball's sha256, the gitleaks identity, and
+ the content digest.
+
+ A hand-written state file is the only way past it, and the file is the operator's own record.
+- **The report never prints a literal or an object's bytes.**
+ - A literal is `denylist line N (len L)`, `scrub line N lhs (len L)` or `built-in home rule (len
+ L)`.
+ - A hit is its kind, object id, the blob's path in history, the byte offset and a commit/tag
+ field (`objectField`, `sourceAudit.ts:229`).
+ - Every refusal message, and every path it prints, goes through `maskLiterals`
+ (`sourceAudit.ts:144`).
+ - A literal spanning path components (`a/b`) is invisible to the object walk, which reads tree
+ entry names one at a time. The staged-path sweep catches it.
+- **The operator files** are `${ARCHILYZER_CONFIG_DIR:-~/.config/archilyzer}/source-{scrub,denylist}.txt`,
+ read through `getPaths()` (`sourceScrubFile`, `sourceDenylistFile`). The two readers are
+ `operatorLines` (`sourceAudit.ts:72`) and `parseScrubRules` (`source.ts:161`):
+ - they drop a BOM and CRLF;
+ - the home dir loses a trailing `/`;
+ - an empty left side refuses;
+ - every literal left side is denied too — its exact bytes; a new spelling is caught only by the
+ denylist.
+
+### git-filter-repo (2.47.0, pipx `~/.local/bin`)
+
+- **`--replace-text` has no comments.** `get_replace_text` treats a `#` line as a literal to replace
+ with `***REMOVED***`. The step writes `replace.txt` without comments or blank lines.
+ `--replace-message` reads the same syntax. Lines split at the LAST `==>`, then
+ `regex:`/`glob:`/`literal:`.
+- **`git filter-repo --version` prints a hash (`a40bce548d2c`), not `2.47.0`.**
+ `commit-map`/`ref-map` under `<repo>/filter-repo/` hold the PRIVATE ids, and are deleted before
+ staging. The default `--replace-refs` is `update-no-add`; the step passes `delete-no-add`.
+- **A blob with a NUL in its first 8 KiB is never scrubbed** (`_tweak_blob`): a binary is audited,
+ never scrubbed. Compressed content (a zip member, PNG text chunks, a PDF stream) is opaque to the
+ byte search. At release 12 every such blob in history was decompressed by the reviewer: 0 hits.
+- **Deterministic:** the same main, rules and filter-repo gave the same `mirrorHead` on every run
+ (`20c367613f75` for main `e56101fdee5d`). Pack BYTES differ run to run, which is why the digest is
+ taken per publish.
+
+### git's dumb HTTP, and Cloudflare Pages
+
+- **The published file set** is `HEAD`, `packed-refs`, a LOOSE `refs/heads/main`, `info/refs`,
+ `objects/info/packs` and `objects/pack/pack-*.{pack,idx}`, from an allowlist: never `config`
+ (the clone's origin path), `hooks/`, `description`, `logs/`, `filter-repo/`, `*.rev` (git 2.55
+ writes them by default) or `*.bitmap`.
+- **The loose ref is load-bearing.** git treats a directory as a repository only if it has a
+ `refs/` directory, so a `file://` clone or `source audit` of the published dir fails without it.
+ An empty `refs/` would not survive a deploy.
+- **The `?service=git-upload-pack` probe falls back to dumb** when the server returns plain
+ `info/refs`. Verified against `python3 -m http.server`. The live Pages proof is the rollout's
+ preview.
+- **`_headers`** (wrangler 4.88 `attachHeaders`, re-read in 4.142 by the review):
+ - EVERY matching rule applies, in file order. A header a LATER rule sets again is **APPENDED**
+ (`text/plain; charset=utf-8, text/html; charset=utf-8`), so an override must detach it first
+ with `! Content-Type`.
+ - A splat is `(?<splat>.*)` matched against the ENCODED pathname. A rule with two `*` compiles to
+ duplicate group names and is dropped silently; wrangler's parser also refuses it.
+ - The limits are 100 rules and 2,000 characters a line. Only the ROOT `_headers` is read.
+ - `homepage/app/lib/headers.test.ts` holds a replica and pins the source-tree rules.
+- **Pages answers a missing path with the NEAREST `404.html`**, walking up, with the REQUEST path's
+ headers. So a tracked `404.html` in the raw tree would run as HTML on the origin, and the tree
+ step refuses one (`sourceTree.ts:142`), as it refuses a tracked `index.html` or a symlink.
+- **A Pages PREVIEW is a public publication, and every deployment stays reachable at
+ `<hash>.<project>.pages.dev` until that deployment is DELETED.** A newer deploy does not remove
+ an older one. A preview branch name is guessable (`source`, named in the mirrored plans).
+- **`next dev` serves `public/` but not a directory's `index.html` at `/<dir>/`,** and ignores
+ `_headers`. A static export always writes the `/source` page into `out/source/index.html`, so
+ `out/source` exists in every build, `--no-source` included.
+
+### Tailwind, tsc, eslint and docker must not read the mirror
+
+- `/homepage/public/source` and `homepage/.source-publish.json` are gitignored, because Tailwind v4
+ scans every file `.gitignore` does not exclude, and a binary pack yields "class names" that break
+ the stylesheet.
+- `homepage/tsconfig.json` excludes **`public` AND `out`**. `next build` copies `public/` into
+ `out/`, and the SECOND build with a mirror failed its type check on
+ `out/source/tree/common/jobs/registry.ts`: a duplicate `declare global var __yttJobRegistry__`.
+- `homepage/eslint.config.mjs` ignores `public/source/**`, and `.dockerignore` excludes both paths.
+
+### Measured (2026-09-28, the real repo)
+
+- **`main` `e56101fdee5d` publishes:**
+ - 1,703 commits and 21,447 objects;
+ - 2,459 staged files, 69–71 MB;
+ - 2 packs (about 21 and 16 MB);
+ - 2,035 tree files in 412 directories;
+ - a tarball of about 7.2 MB.
+- **`homepage/out`:** 2,640 files, about 78 MB. The largest file is 19.99 MiB, against the step's
+ 24 MiB limit and Pages' 25 MiB.
+- **Time:** the step adds about 19 s to `build homepage` (filter-repo about 5 s, gitleaks about 7 s).
+ An unchanged main, rules, tools and files skip it. The deploy check takes 0.6–0.8 s.
+- **The live :3001 editor runs its BUILT bundle.** Until it is rebuilt on a tree with release 12,
+ its `/sites` Homepage jobs have no source step, no withdrawal and no deploy check.
+
diff --git a/plans/STATE.md b/plans/STATE.md
@@ -3,6 +3,43 @@
The working memory for the local-AI derived-corpus work. Rewritten at the end of every
session, before context is cleared. See [`README.md`](README.md) for the protocol.
+**Now (2026-09-28, evening): release 12 — the source mirror — is merged to `main` and NOT rolled
+out.** [`release-12.md`](release-12.md) holds Q's and R's records, their reviews, "Merged" and
+"Rollout". The operator's runbook is `~/reports/release-12/RUNBOOK.html`, with its scripts in
+`~/reports/release-12/scripts/`. The plan is [`source-mirror.md`](source-mirror.md).
+- **What merged:**
+ - **Q** (`4855f70b`, and the changelog fix `e6c5d2e3`) fixes the hardcoded umtool paths.
+ - **R** (`ffdeb2cd`) adds `archilyzer source publish`:
+ - a fresh bare clone of `main` is rewritten by git-filter-repo with the operator's scrub rules,
+ then repacked for git's dumb HTTP;
+ - an audit gate refuses a denied literal anywhere;
+ - the raw tree at `/source/tree/` and the tarball are published beside the mirror, and the
+ `/source/` page shows them;
+ - a refusal withdraws the source from `public/` and `out/`;
+ - `deployHomepage` refuses any `out/` it cannot vouch for.
+- **The operator's side is prepared:**
+ - `~/.config/archilyzer/` holds a scrub file (3 rules) and a denylist (3 literals: the user name,
+ the host name, an email address). NEVER print them;
+ - `git-filter-repo` 2.47.0 is installed with pipx;
+ - `source publish --check` exits 0. With `main` at `ffdeb2cd` it would publish `8188e02a7d04`:
+ 2,473 files, 69.8 MB. The parent's first check, on `e56101fdee5d`, gave `20c367613f75`.
+- **Owed, in order** (release-12.md "Rollout"):
+ 0. The operator completes the denylist (real name, handles), then runs `source publish --check`.
+ **No deploy of any kind before this: a Pages preview is public and permanent until it is
+ deleted.**
+ 1. The song link, before any umtool restart.
+ 2. Rebuild and restart :3001 with `~/.local/bin` on PATH. It runs 0.10.0 (`BUILD_ID`
+ `vWCJb87ktCy5akih_pM9X`), which has no source step, no withdrawal and no deploy check.
+ 3. A FRESH `archilyzer build homepage` in the primary. The current `out/` predates `/source`, so
+ the deploy check refuses it.
+ 4. The preview `--preview source` and its live checks.
+ 5. Production.
+ 6–7. What to do if the edge refuses the clone, and if anything private ever ships: delete that
+ deployment.
+ 8. The cut (`release cut editor …`: 2 bullets; export has none) and the worktrees.
+- **Baselines now:** common **2,149**, homepage unit **7**, homepage e2e **36**; editor unit 85,
+ `test:scripts` 185 + 1 and mcp 269 are unchanged.
+
**Now (2026-09-28, afternoon): release 11 is LIVE as 0.10.0, and Jasolyzer is launched.** The
rollout ran 12:11–14:05 ([`release-11.md`](release-11.md), "Rollout, as done", every deploy and job
id): the cut (editor `24c8352e`, export `bf6904e8`), ONE :3001 restart (`BUILD_ID`
diff --git a/plans/release-12.md b/plans/release-12.md
@@ -775,3 +775,150 @@ accepted as built, and that the four new Lows be closed before the merge:
- Nothing was deployed. `main` had not moved.
## Rollout
+
+**Merged** (by the parent, in the primary, `git merge --no-ff` on a clean tree):
+- **Slice Q** merged as `4855f70b`, then the changelog fix `e6c5d2e3`. The 0.10.0 cut landed between
+ Q's branch point and its merge. A clean textual merge filed Q's bullet inside the released
+ `[0.10.0]` section, and `e6c5d2e3` moved it back under `[Unreleased]`.
+- **Slice R** merged as `ffdeb2cd`. The tree is identical to R's tip `484952ed`.
+- **The parent then prepared the operator's side:**
+ - it created `~/.config/archilyzer/` (mode 700) with the two files (mode 600): the scrub file
+ holds **3 rules**, the denylist **3 literals** — the Unix user name, the host name and one email
+ address. The contents are never printed;
+ - it installed `git-filter-repo` 2.47.0 with pipx (`~/.local/bin`);
+ - it ran `pnpm archilyzer source publish --check`: **exit 0**, it would publish main
+ `e56101fdee5d` as `20c367613f75`, with 2,459 files, 69.3 MB, 2 packs, 412 tree dirs and a
+ 6.9 MB tarball.
+ - The runbook's `r12-check.sh`, run in the primary while this record was written with `main` at
+ `ffdeb2cd`, also exits **0**. It would publish `ffdeb2cd770e` as `8188e02a7d04`, with 2,473
+ files, 69.8 MB, 2 packs, 413 tree dirs and a 7.0 MB tarball; its log holds 0 user-name and 0
+ host-name occurrences.
+- **Nothing is rolled out.** Nothing was deployed. The live :3001 editor runs 0.10.0 (`BUILD_ID`
+ `vWCJb87ktCy5akih_pM9X`, built on `e6c5d2e3`): it has no source step, no withdrawal and no deploy
+ check. The primary's `homepage/out` predates release 12 (it has no `/source` page), so the deploy
+ check refuses it until it is rebuilt.
+- **A side effect of the scrub, cosmetic and in the mirror only:** the bare user name is scrubbed to
+ `user`. The `plans/` text in the mirror (the grep gates, one risk sentence) therefore reads
+ differently from the private repository.
+
+**What is OWED, in order.** The operator's runbook is `~/reports/release-12/RUNBOOK.html`, rendered
+by `~/reports/release-12/make-runbook.py`. Its scripts are in `~/reports/release-12/scripts/` and
+log to `~/reports/release-12/tmp/`. Every script refuses unless the primary's `HEAD` contains
+`ffdeb2cd`.
+
+**Before ANY deploy, a preview included, the denylist must hold everything private.** A Pages
+preview is public, and every deployment stays reachable at its own `<hash>.archilyzer.pages.dev`
+until it is deleted.
+
+0. **The operator completes the denylist**, then runs the check. Add your real name, other handles
+ and anything else that must never appear to `~/.config/archilyzer/source-denylist.txt`: one per
+ line, `i:` for any case. Then:
+ ```
+ cd ~/Projects/yt-dlp-transcript-browser && pnpm archilyzer source publish --check
+ ```
+ (or `sh ~/reports/release-12/scripts/r12-check.sh`, which also counts user-name occurrences in its
+ log: expect 0).
+ - **Expect:** `[source] audit clean: …` and `[source] check passed — would publish main <12 hex>
+ as <12 hex>: 2459 files …; nothing written`.
+ - **If it refuses:** the report names the source only by position (`denylist line N (len L)`) and
+ each hit only by object, field and byte offset. Add a scrub rule to
+ `~/.config/archilyzer/source-scrub.txt` (`<text>==>user`), or drop the file from history. Then
+ re-run.
+1. **The song link, before any umtool restart** (slice Q's operator step, with the review's I1
+ correction):
+ ```
+ mkdir -p ~/.local/share/archilyzer && ln -s ~/.claude/jobs/efbe67a7/tmp/song ~/.local/share/archilyzer/song
+ ```
+ - The target holds only umtool's rebuildable `.cache/umtool`. The song project's bulk data is in
+ `~/reports/quartering-uh-song/data`; pointing the link there instead is a separate choice.
+ - `~/.local/share/archilyzer` did not exist on 2026-09-28. If a restarted :3050 created
+ `…/song/.cache` as a real directory first, remove that directory before linking, or the link
+ lands inside it.
+2. **Rebuild and restart the live editor, with `~/.local/bin` on its PATH.** The precedent is
+ release 11's `r11-build.sh` + `r11-restart.sh` in `~/reports/overnight-2026-09-28/scripts/`.
+ Release 12's copies are `r12-build.sh` and `r12-restart.sh`:
+ - `r12-build.sh` builds into the live `.next`. Run the restart right after it.
+ - `r12-restart.sh` refuses while `BUILD_ID` is still `vWCJb87ktCy5akih_pM9X`. It starts the
+ editor with `PATH=$HOME/.local/bin:$PATH` and checks that the new server's environment has it.
+ Without it, `/sites` Build homepage falls back to `pipx run`, which needs the network.
+ - Run the md5 sweep around the restart, and the smoke after it. `r12-md5.sh` and `r12-smoke.sh`
+ wrap `plans/tools/rollout/md5.sh` and `smoke.sh` with `CLAUDE_JOB_DIR=~/reports/release-12
+ REL=r12`:
+ ```
+ sh ~/reports/release-12/scripts/r12-md5.sh before
+ sh ~/reports/release-12/scripts/r12-build.sh
+ sh ~/reports/release-12/scripts/r12-md5.sh pre-restart
+ sh ~/reports/release-12/scripts/r12-restart.sh
+ sh ~/reports/release-12/scripts/r12-md5.sh after-boot
+ sh ~/reports/release-12/scripts/r12-smoke.sh
+ cd ~/Projects/yt-dlp-transcript-browser && pnpm archilyzer doctor
+ ```
+ - **Expect:** `BUILD_OK`, then `RESTART_DONE editor / 200`, `/sites` carries release 12's
+ sentence ("also publishes the source mirror") and the editor's PATH has `.local/bin`. `r12-md5.sh after-boot` ends `MD5_SAME`, and
+ the smoke ends `SMOKE_FAIL=0`. A `PAIR_DIFF` on a pair that moves live (auto-queue status) is
+ drift, as it was at release 11.
+ - **Doctor** has a **source publish** block:
+ - `filter-repo git filter-repo a40bce548d2c`;
+ - `gitleaks` ok;
+ - `scrub rules … (3 rules, mode 600)` and `denylist … (N literals, mode 600)`;
+ - `published` says nothing is published in the primary yet.
+3. **A FRESH homepage build in the primary.** The deploy check refuses any `out/` built before
+ release 12, because it has no `/source` page.
+ ```
+ sh ~/reports/release-12/scripts/r12-home-build.sh
+ ```
+ - It runs `pnpm archilyzer build homepage`, then checks
+ `homepage/out/source/archilyzer.git/info/refs`, the file count and the deploy check, read-only.
+ - **Expect:** `[source] published main … as …: 2459 files` (about 19 s for the step), `out: ~2,640
+ files`, `info/refs: <40 hex>\trefs/heads/main`, and `deploy check: ok (would deploy)`.
+ - **If the build refuses:** it has already WITHDRAWN the source from `public/` and `out/`. Fix the
+ rule (step 0) and rebuild.
+4. **The preview** (`source.archilyzer.pages.dev`; public):
+ ```
+ sh ~/reports/release-12/scripts/r12-preview.sh
+ sh ~/reports/release-12/scripts/r12-live-check.sh https://source.archilyzer.pages.dev
+ ```
+ - The first script runs `pnpm archilyzer deploy homepage --preview source`, which asks the deploy
+ check first. Its log ends `[preview] https://source.archilyzer.pages.dev (this deployment:
+ https://<hash>.archilyzer.pages.dev)`.
+ - The live check is `source-mirror.md` Rollout step 2, as commands. Each line prints OK or FAIL:
+ - `git clone https://source.archilyzer.pages.dev/source/archilyzer.git` works;
+ - the clone's HEAD = `manifest.json`'s `mirrorHead`;
+ - `…/source/tree/common/lib/paths.ts` is `content-type: text/plain; charset=utf-8`, ONE value,
+ with `x-content-type-options: nosniff`;
+ - `…/source/tree/common/` is `text/html; charset=utf-8`, ONE value. The appended form
+ `text/plain…, text/html…` is the bug `f218ed86` fixed;
+ - `…/source/tree/umtool/report-to-video/fonts/Archivo%5Bwdth%2Cwght%5D.ttf` is 200 `font/ttf`;
+ - `…/source/tree/homepage/app/docs/%5Bslug%5D/page.tsx` is 200 `text/plain; charset=utf-8`;
+ - the tarball's sha256 = `snapshot.json` = `manifest.json` = the one on `/source/`;
+ - `pnpm archilyzer source audit <clone>/.git` is clean;
+ - user-name and host-name counts in the clone's history are 0.
+5. **Production:**
+ ```
+ sh ~/reports/release-12/scripts/r12-prod.sh
+ sh ~/reports/release-12/scripts/r12-live-check.sh https://archilyzer.pages.dev
+ ```
+ - The first script runs `pnpm archilyzer deploy homepage`: branch `main`, the log ends
+ `[deployed] https://<hash>.archilyzer.pages.dev`.
+ - Or use the editor's path, which also proves the step inside its job: `pnpm ops build-homepage
+ --json '{"deploy":true}' --wait` with `WORKER_TOKEN` from `editor/.env`.
+ - The same checks must pass on `archilyzer.pages.dev`.
+6. **If the edge refuses the dumb clone** (`source-mirror.md` Rollout step 4): the tree and the
+ tarball still stand. The mirror would need another host (R2 behind a custom domain, out of scope).
+ Record it, do not improvise.
+7. **If something private ever ships:**
+ - DELETE THAT DEPLOYMENT in the Cloudflare dashboard (Workers & Pages → `archilyzer` →
+ Deployments). A newer deploy does not remove it; for a preview, delete every deployment on
+ that branch.
+ - Then add the literal (and a scrub rule) and run `pnpm archilyzer build homepage`, which refuses
+ and withdraws. Rebuild clean, and deploy again.
+8. **Housekeeping:**
+ - `pnpm archilyzer release show` has 2 editor bullets pending; export has none, so `all` would
+ refuse. When the operator chooses: `pnpm archilyzer release cut editor next --commit` (0.10.1)
+ or `next-minor` (0.11.0).
+ - Remove the worktrees when done: `pnpm wt rm r12-paths-fix` and `pnpm wt rm r12-source-mirror`,
+ from the primary. Removing them re-sorts the index-based port blocks, so do it only when no
+ dev server or e2e runs in any worktree; the parallel session's `r13-*` worktrees are active.
+ The seven `r11-*` wait on these.
+ - Optional: `git branch -d r12/paths-fix r12/source-mirror`.
+