Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 2a6906654fee3611f697e56428a849c1916beb81
parent debce0f3c493e33d751ee89ecf21549a4cb38f8a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 13:42:53 -0400

plans: slice R's record — `archilyzer source publish` + /source, as shipped

What shipped (R1–R6), the corrections running it found (`_headers` appends,
the tsconfig must exclude `out`, no rules hash in the published manifest, a
loose refs/heads/main so the mirror is a git dir, filter-repo reads `#` lines
as literals), the operator files' refusal with its redacted report and the
one scrub rule that clears it, every R7 gate with its numbers (common 2,138,
homepage e2e 36/36 published and 16/16 empty, 2,640 files / 78.1 MB out,
largest 19.93 MiB, both clones = mirrorHead, the user-name grep 0), the commit
table, and what was found and left.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mplans/release-12.md | 269+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 269 insertions(+), 0 deletions(-)

diff --git a/plans/release-12.md b/plans/release-12.md @@ -281,4 +281,273 @@ No High or Medium findings. The coordinator asked for two of the Lows to be fixe - **After the fixes:** tsc is clean (41 s, all seven packages), and the grep gate is empty at the new tip. Per the coordinator, e2e was not re-run for a type comment and a changelog line. +### Slice R, as shipped — `archilyzer source publish` + `/source` (2026-09-28) + +Branch `r12/source-mirror` off `main` `e6c5d2e3` (slice Q merged), worktree +`~/Projects/r12-source-mirror` (worktree #10: editor 4001, homepage e2e 4040), one Opus +implementer. Plan: [`source-mirror.md`](source-mirror.md), "Slice R", R1–R7. Why: the operator +asked for the repo on the project site, read-only, clonable from static files, with a gate that +refuses to publish a denied literal. `main` did not move during the slice (`git merge main`: +already up to date). Scratch files `r-m-*` in the job's `tmp`. + +**What shipped.** +- **R1, `common/publish/source.ts`: `publishSource(opts)`** — 0 published / skipped / checked, + 1 refused or cancelled; a `SourceRefusal` is logged as `[source] REFUSED: …`. The steps are the + plan's 1–17: + - `git rev-parse` of the git COMMON dir's `refs/heads/main`; + - the two operator files, parsed; a missing one is a refusal naming it (`~/`-relative); + - the skip; + - `resolveFilterRepo()`: `git filter-repo`, else `pipx run --spec git-filter-repo==2.47.0`, + else the install line; + - `git clone --no-local --bare --single-branch --no-tags --branch main` into + `mkdtemp(<ARCHILYZER_SOURCE_SCRATCH>/archilyzer-source-)`, origin removed; + - filter-repo `--force --quiet --replace-refs delete-no-add --replace-text R --replace-message R`, + then `filter-repo/` deleted; + - `repack -a -d -q --max-pack-size=20m`, `prune-packed`, `pack-refs --all`, `update-server-info`. + It refuses on loose objects, no `P` line, any ref but `refs/heads/main`, or a HEAD that is not + main. + - the object gate; the tree (`git archive` → `tar -x` → pages); the tarball (`--format=tar.gz + -9 --prefix=archilyzer/`) and `snapshot.json` in the `Snapshot` shape; + - the mirror staged from an allowlist; the manifest staged; the file gate; the limits; + `--check` stops; the link-safe install (manifest removed first, written last); one summary line. + + Every child goes through `runChildIntoLog` with `AbortSignal.any([signal, timeout])`, in an + environment with the `GIT_DIR`-family variables cleared. The one exception is the audit's + binary `cat-file` stream (a `spawn`, with the signal). `clearPublishedSource` is what + `--no-source` runs. `common/lib/sourceManifest.ts` is the leaf contract: `MIRROR_DIR`, + `CLONE_URL`, `TREE_HREF`, `TARBALL_HREF`, `SourceManifest` and `parseSourceManifest`. +- **R2, `sourceAudit.ts`.** + - `parseDenylist` (`i:`, `#` comments, trimmed, deduped). + - `scanBuffer`, and `redactHit`: ±24 bytes, every byte any hit covers masked, a half-shown + occurrence included. + - `maskLiterals` for every path or line quoted from the repo. + - `auditObjects`: ONE `cat-file --batch-all-objects --unordered --batch` stream with a framing + parser — blobs and commits/tags whole, trees by entry NAME (not their binary ids). + - `auditFiles`: contents, paths, a `.gz` decompressed; a symlink is a refusal. + - `runGitleaks`: cwd = scratch, so no checkout's ignore file applies. 0 is clean, 3 is parsed + findings, anything else refuses; not on PATH is "skipped" with a WARNING. + - `auditBare`: blob hits get their path in history, and gitleaks runs only when the literal + audit is clean. + - `formatAuditReport`: `#n (x…, len L)`, counts per literal and kind, the first 20 contexts, + "… and N more", then the plan's closing line. +- **R3, `sourceTree.ts`** (`hrefFor`, `escapeHtml`, `renderTreeIndex`, `writeTreeIndexes`, which + refuses a tracked `index.html` or a symlink), and the `_headers` block — with the correction + below. +- **R4, the homepage.** + - `app/lib/source.ts` `loadSourceManifest()` (also needs `info/refs` and the tarball). + - Nav: Source after Docs. + - `app/source/page.tsx`: the plan's copy verbatim; `source-clone`, `source-mirror-head`, + `source-tree-link`, `source-tarball-link`, and `source-tarball-sha` for the spec; the empty + state `source-empty`. + - A shared `components/Fact.tsx`. + - Downloads: one sentence linking "read-only git mirror", and the empty state names + `archilyzer source publish`; every phrase `downloads.spec.ts` asserts is kept. + - `marketing.spec.ts`' nav list gains Source. +- **R5, the docs.** `create-archives.sh` is deleted. README and SETUP lead with the clone and keep + the tarball as the no-git path. PUBLISH.md gains "The source mirror (homepage)". The homepage's + *Install*, FAQ, *What is Archilyzer* and `content/README.md` stop saying "there is no public + repository". `grep -rn 'create-archives' README.md SETUP.md homepage/` is empty. +- **R6 and the rest.** + - `getPaths()` gains `configDir`, `sourceScrubFile`, `sourceDenylistFile` and + `sourceScratchDir`, from `ARCHILYZER_CONFIG_DIR`, `SOURCE_SCRUB_FILE`, `SOURCE_DENYLIST_FILE` + and `ARCHILYZER_SOURCE_SCRATCH`, declared as `paths` in `envVars.ts`. + - `HOMEPAGE_PUBLIC_DIR`'s `readBy` names `source.ts`, and `TRANSCRIPTS_DIR`'s doc says umtool + reads `<it>/channels` too (review Q(c)). `ENVIRONMENT.md` is regenerated. + - The CLI rows `build homepage [--no-source]`, `source publish [--force] [--check] + [--keep-scratch]` and `source audit [<git dir>]`. + - `buildHomepage`: compose, then the source step (a lazy import; `publishSource` and + `clearSource` are seams), then `next build`. + - doctor's "source publish" block. It is never a failure; it WARNs when the operator files exist + but no filter-repo is installed, or a file is readable by others. It prints rule counts and + modes, never contents. + +**Corrections to the plan** (each found by running it): +- **`_headers`: later rules APPEND, they do not win** (`f218ed86`). + - wrangler 4.88's `attachHeaders` (the Pages asset server's code, read in its `cli.js`) `set`s + a header on the first matching rule and `append`s it on every later one. The plan's exact + text served a directory page as `text/plain; charset=utf-8, text/html; charset=utf-8` and a + font as `text/plain; charset=utf-8, font/ttf`. + - Each override now starts with `! Content-Type`. A replica of wrangler's parse and attach + (`$T/r-m-headers-sim.mjs`) gives single values for `/source/tree/`, `…/common/`, a `.ts`, the + bracketed `.ttf` and `page.tsx`, the `.onnx` and `README.md`. + - The preview deploy is still the live proof. +- **The tsconfig must exclude `out` too** (`3fa5ff18`). + - `next build` copies `public/` into `out/`. The SECOND build with a mirror failed its type check + on `out/source/tree/common/jobs/registry.ts`, a second `declare global var __yttJobRegistry__`. + - With `out` excluded, the homepage program is 871 files, none from the mirror; homepage tsc + takes 11 s with a mirror in both dirs. + - The homepage's `eslint.config.mjs` ignores `public/source/**` (it already ignored `out/**`). + Lint itself was not run. +- **The published manifest carries no `rulesHash`.** + - `plans/` ships in the mirror, and so does the plan's description of the two files. What is + left unknown is the hostname and the address, so a published hash of the rules would confirm + a guess at them. + - The skip key (`{sourceCommit, rulesHash}`) is `homepage/.source-publish.json`, beside + `public/` and gitignored. A missing key rebuilds; the round trip asserts it is absent from the + manifest. +- **The mirror has a loose `refs/heads/main` beside `packed-refs`.** git treats a directory as a + repository only with a `refs/` dir, so without it the `file://` clone and `source audit` of the + published dir fail. An empty dir would not survive a deploy. +- **filter-repo's `--replace-text` does not skip `#` lines** (it would replace a comment as a + literal; `get_replace_text` in 2.47). The step writes `replace.txt` without comments or blank + lines. +- **The step adds three safety flags:** `--no-tags`, `--replace-refs delete-no-add` (2.47's + default is `update-no-add`, stated for determinism), and a check that the mirror holds exactly + `refs/heads/main`. +- **`--no-source` removes the published source** (manifest first; a linked `downloads/` goes as a + link, its target untouched). It does not leave the source there: a copy audited against older + rules would ship ungated. That removal gives the empty state R7 expects. +- **The header nav moves from `sm` to `md`:** five labels do not fit beside the wordmark at 640 px. +- **The tree has 4 `.ttf`**, not 3 (IBM Plex Mono Bold, O5); `*.ttf` covers them. +- **No `branch` option:** `SOURCE_BRANCH = "main"`, an operator decision. `now` is `() => Date`. +- **The packs are 37.5 MB in two** (20,897,277 + 16,633,904 bytes), not the plan's 21.5 MB in one. + The history grew by about 150 commits since the plan, and the 20 MB split costs deltas across + the two packs. That is well inside the limits. + +**The operator's files, as created, refuse the publish** (an action for the rollout, not the +slice): +- **The run:** `pnpm archilyzer source publish --check` with `~/.config/archilyzer/*` as the + parent created them, at `main` `e6c5d2e3`. It ended `AUDIT REFUSED: 8 hits in 21,441 objects + (1,702 commits) against 6 denied literals`, with every hit `#1 (r…, len 5)` in a blob: + - `plans/source-mirror.md`, two versions, 3 hits each: the plan's own grep gates (`git grep -c + -i '<it>' …`, `git -C <clone> grep -c -i <it> …`) and the "a future transcript quote "<it> + that"" risk line; + - `plans/release-12.md`, two versions, 1 hit each: slice Q's grep-gate line. +- **Why the scrub rules miss them.** They cover the backticked spelling and the `/run/media/` path + (review I2: no hit came from there), not the bare, single-quoted or double-quoted name. +- **The operator's step:** add a rule for the bare name (`<name>==>user` covers every spelling, + the backticked one included) or one per spelling. Then `archilyzer source publish --check` + (about 25 s) must end `check passed`. +- **How R7 ran anyway,** with the operator files untouched and the gate not weakened: + - `SOURCE_SCRUB_FILE` = a scratch file of ONE rule, `<name>==>user`, written with `$(id -un)`, + with the REAL denylist. The check was clean: 4 literals, gitleaks clean. + - `source audit` of both clones and of the published dir with the REAL files (6 literals) is + clean, below. + +**Gates** (from the worktree root; logs `$T/r-m-*.log`). Every log of a run over the real files was +first scanned by `$T/r-m-leakcheck.mts` (counts per literal label, any ASCII case) and read through +`$T/r-m-maskview.mts`. The step's own lines carried 0 literals; the only hits were pnpm's and +doctor's home-directory paths. +- **tsc** was clean before every commit (`r-m-tsc-{0..3}.log`): + - 215 s at the first run, 168 s, 59 s, and 73 s with a mirror in `public/` AND `out/`. Another + session's tsc was running during the first and the last. + - The homepage alone: 11 s, 871 files, none from the mirror. +- **Unit:** + - common **2,138/2,138** (2,114 + 24: sourceAudit 7, sourceTree 5, source 7, build +1, `_cli` + +3, doctor +1). The two filter-repo tests RAN (`ok 1832` round trip, `ok 1833` planted-literal + refusal); 0 skipped. + - `test:scripts` **185 + 1 skipped**; editor unit **85/85**; mcp **269/269**; homepage unit + **2/2**. +- `pnpm archilyzer docs env --check` exits **0**. The editor's `next build` is **ok** (43 s); it + bundles `buildHomepage`'s lazy import. +- **`archilyzer build homepage`** (from `common/`, `SOURCE_SCRUB_FILE` as above): + - **ok in 38 s**, against 19 s for `--no-source`. The source step took **19 s**: filter-repo + 5.3 s, gitleaks 7.3 s, `[source] published main e6c5d2e322b3 as 78dc0126382b: 2459 files, + 69.5 MB (mirror 2 packs, tree 412 dirs), tarball 6.9 MB sha256 c4ceb6d110ee`. + - A rebuild with nothing changed logged `[source] up to date at e6c5d2e322b3; skipping`. + - **Deterministic:** two runs gave the same `mirrorHead` and tarball sha; the real files' two + runs gave `9b880270c49d` both times. +- **What it published** (`homepage/out`): + - **2,640 files, 78.1 MB** in all; `source/` 2,465 files, 65.8 MB; + - the mirror: 9 files, 38.1 MB, 2 packs; + - the tree: 2,035 files + 412 pages, 27.5 MB; + - the tarball: 7,246,992 bytes. + - **Against the limits:** 2,459 staged of the step's 15,000 (2,640 of Pages' 20,000); the + largest file is 19.93 MiB, against 24 MiB (25 MiB). +- **Two clones, both HEAD = `manifest.mirrorHead` `78dc0126382b…`:** + - `git clone file://$WT/homepage/out/source/archilyzer.git`: 2 s. + - `git clone http://127.0.0.1:8765/source/archilyzer.git` from `python3 -m http.server 8765 + --bind 127.0.0.1` in `homepage/out`: 1 s. The protocol was dumb: `info/refs?service=…` 200, + then `HEAD`, `objects/info/packs`, both `.idx` and both `.pack`; the loose-object probes 404. + - `:8765` was free before, the server was killed, and it was free after. +- **The user name:** `git grep -c -i -F "$(id -un)" $(git rev-list --all) | wc -l` is **0** in both + clones (1,702 revisions). It is 0 in the identities and messages and 0 in the paths too; the + hostname is 0. +- **`archilyzer source audit`** with the real files (6 literals) is **clean** on the file clone + (13 s), the http clone (11 s) and the published dir (10 s): 21,441 objects, 1,702 commits, + gitleaks "1530 commits scanned … no leaks found". +- **The gate, exercised:** `SOURCE_DENYLIST_FILE` = a scratch file of `Co-Authored-By`, then + `source publish --check`: + - exit **1** in 14 s, `AUDIT REFUSED: 1476 hits … #1 (C…, len 14): 8 in blobs, 1468 in + commits`, 20 context lines and "… and 1456 more"; + - the log holds the literal **0** times; + - `public/source/manifest.json`'s mtime and size are unchanged (`1790616152 1057`). +- **`build homepage --no-source`:** `out/source/index.html` holds `data-testid="source-empty"` + and "No source published in this build."; `/downloads/` says "no source snapshot attached". +- **No filter-repo:** with PATH = a scratch dir of two symlinks (`git`, `node`; nothing + uninstalled), `git filter-repo` is "not a git command", and `source publish --check` exits 1 with + `REFUSED: git-filter-repo is not installed and pipx is not on PATH — install it once: \`pipx + install git-filter-repo\` …`. +- **The resolved filter-repo** is `git filter-repo` (`~/.local/bin/git-filter-repo`, pipx-installed + 2.47.0), whose `--version` prints `a40bce548d2c`; git is 2.55.0. The `pipx run` fallback was not + run (it needs the network). +- **`archilyzer doctor`** prints the block: + ``` + source publish + ok filter-repo git filter-repo a40bce548d2c + ok gitleaks gitleaks version is set by build process + ok scrub rules ~/.config/archilyzer/source-scrub.txt (2 rules, mode 600) + ok denylist ~/.config/archilyzer/source-denylist.txt (3 literals, mode 600) + -- published main e6c5d2e322b3 as 78dc0126382b, 2026-09-28T17:22:29.301Z (2458 files) + ``` + The real output prints the absolute paths. `2458` is the manifest's `files`, which does not + count the manifest itself. +- **homepage e2e** (`node scripts/worktree.mjs run -- pnpm --filter homepage run e2e`; the queue + was free): + - **36 passed, 0 skipped, 0 failed, 1.1 min**, WITH the manifest present: + `loadSourceManifest()` from `homepage/` gave mirror head `78dc0126382b`, so the published + branch of every `source.spec` test ran. + - The empty state too: with `public/source` and `public/downloads` moved aside and restored + after, `source.spec` + `downloads.spec` + `marketing.spec` gave **16 passed** (30 s). + - `downloads.spec.ts` is unchanged. +- **Numbers tool:** none. + +**They bite:** +- The planted-literal test fails if the object walk misses the blob. +- The redaction test pins a half-shown second occurrence. +- `build.test.ts`' fake refusal proves `next build` never runs. +- The restricted-PATH run proves the install line. +- The `Co-Authored-By` run proves the gate refuses a literal the scrub does not touch, in commits + AND blobs. + +| sha | what | +|---|---| +| `25f5e241` | `homepage:` `/homepage/public/source` gitignored (the Tailwind note), tsconfig excludes `public` — first, before any mirror | +| `7fdbe2dc` | `common:` `sourceAudit.ts`, `sourceTree.ts`, `sourceManifest.ts` + tests | +| `fc31aab5` | `common:` `publishSource` (`source.ts`) + tests; `getPaths()` / `envVars.ts` / `ENVIRONMENT.md` | +| `2f08cb47` | `common:` `buildHomepage` runs the step; the CLI rows; doctor's block + tests | +| `1a11a2bb` | `homepage:` `/source/`, nav, `Fact`, Downloads, `_headers`, docs content, `source.spec.ts`, marketing nav | +| `2cd189f3` | `docs:` README, SETUP, PUBLISH "The source mirror (homepage)"; `create-archives.sh` deleted | +| `c448b392` | `changelog:` the editor and homepage `[Unreleased]` bullets | +| `f218ed86` | `homepage:` `_headers` overrides detach `Content-Type` first (correction) | +| `3fa5ff18` | `homepage:` tsconfig excludes `out`; eslint ignores `public/source/**` (correction) | +| _this_ | `plans:` this record | + +**Found and left** (not this slice's files): +- **`.dockerignore:20-22`** still names `create-archives.sh`, and it does not exclude + `homepage/public/source/`. A `docker build` from a checkout that has published would send about + 66 MB of mirror and tree in the build context. +- **`common/lib/project.ts:37-38`** (`PROJECT_DOWNLOADS_URL`'s comment) says "there is no public git repository". +- **The editor's /sites Homepage section** (`HomepageBuildButtons.tsx:170`) does not say that the + build publishes the source, or that it can refuse. +- **`export/CHANGELOG.md`'s released entry** names `create-archives.sh`. It is history, and is left. +- **The label `#n (x…, len L)`** shows a literal's first character and length in the logs. This is + the plan's format; nothing of it is published. +- **gitleaks** scans 1,530 of 1,702 commits: its `git log -p` skips merges. The literal audit reads + every object. + +**Changelog.** +- **`editor/CHANGELOG.md`:** one `[Unreleased]` bullet. The build step and the gate; the operator + files, and that the build refuses without them; `pipx install git-filter-repo`; the skip, the + CLI rows and doctor; `create-archives.sh` gone. +- **`homepage/CHANGELOG.md`:** one `[Unreleased]` bullet. `/source/` and its nav entry, the empty + state, the tarball regenerated per build, the docs, the nav at `md`, and `_headers`. + +**For the parent and the operator:** +1. Add the scrub rule above, then run `archilyzer source publish --check`. +2. The editor's /sites homepage job finds `git filter-repo` only if the editor's PATH includes + `~/.local/bin`. Otherwise it falls back to `pipx run`, which needs the network on first use. +3. The worktree's `homepage/public/source`, `homepage/out` and `homepage/.source-publish.json` were + made with the scratch rule. They are disposable, and nothing here deployed them. + ## Rollout