commit 417afda560d3dfc9ce24d83f21a98ea126696598
parent ab9bfa2063bfb6df0b1d8125e9ba1f854eed410b
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 30 Sep 2026 09:43:42 -0400
plans: slice SG — the parent's four rulings applied (the cap at the newest 10,000 commits by stagit -l, read in stagit.c and measured; the render cache in the XDG cache dir; the diff tokens and the themeConfig re-export stand), the merge of main with slice DT and doctor's drive-health line; the re-gates (common 2,341, the three source specs 12/12, source publish and --check); FACTS for the cap, the cache and doctor, anchors refreshed
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
| M | plans/FACTS.md | | | 67 | ++++++++++++++++++++++++++++++++++++++++++------------------------- |
| M | plans/release-15.md | | | 73 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
2 files changed, 104 insertions(+), 36 deletions(-)
diff --git a/plans/FACTS.md b/plans/FACTS.md
@@ -7782,66 +7782,83 @@ source mirror (homepage)". Anchors are at the branch.
- **stagit** (codemadness.org, C over libgit2; built from `519cae2f`, "bump version to 1.3",
2026-09-14, against the system libgit2 1.9.7) renders the scrubbed mirror into `/source/git/`.
It is operator-installed like git-filter-repo: never vendored. `resolveStagit`
- (`publish/sourceHistory.ts:104`): `paths.stagitBin` (`STAGIT_BIN`, else `"stagit"`, `lib/paths.ts:304`)
+ (`publish/sourceHistory.ts:115`): `paths.stagitBin` (`STAGIT_BIN`, else `"stagit"`, `lib/paths.ts:314`)
— a name with a slash is that file or nothing; a bare name is PATH, then `~/.local/bin/<name>`.
- It has no version flag: `stagitIdentity` (`:118`) is the first 12 hex of its binary's sha256,
+ It has no version flag: `stagitIdentity` (`:129`) is the first 12 hex of its binary's sha256,
never the path (the manifest is published, and a home-dir path is a denied literal).
- **What stagit does** (read in `stagit.c`): it writes into its CWD; the repository's NAME is its
directory's basename minus `.git` — so the step's scratch clone is `<scratch>/archilyzer.git`
- (`source.ts:792`), no longer `bare`; `description` (read with `fgets`, the newline KEPT — write it
+ (`source.ts:831`), no longer `bare`; `description` (read with `fgets`, the newline KEPT — write it
without one) and `url` (newline stripped) come from the repository directory; the header links
`<relpath>file/README.md.html` and `LICENSE` when HEAD has them, and the logo `<a href="../<relpath>">`
lands on `/source/` from every depth; `file/` pages are written for HEAD's whole tree on EVERY run;
every text from the repository is xmlencoded (`"` → `"`), so no page text can fake an
- `href="…"`; a diff's paths are percent-encoded (`/` and `,-.` kept); a commit over 1,000 files or
+ `href="…"`; `-l N` keeps the NEWEST N log lines (the revwalk is newest first) and ends "M more
+ commits remaining, fetch the repository", but still writes a page for EVERY commit (it only skips
+ the diffstat of a page that exists); `-c` with `-l` is a usage error; a diff's paths are percent-encoded (`/` and `,-.` kept); a commit over 1,000 files or
100,000 added or deleted lines prints "Diff is too large, output suppressed"; `-c <file>` walks from
HEAD to the commit the cache file names, KEEPS every `commit/<sha>.html` already in the CWD, and
appends the cached log lines — so the cache is the whole output directory, and a cache whose commit
is not an ancestor duplicates the log. Its temp cache file (`cache.XXXXXXXXXXXX`) is made in the CWD.
-- **The step** (`source.ts`, step 12b, `:920`) runs after the object audit and the mirror's staging,
- before the manifest and the file audit: `stageHistoryPages` (`:1070`) → `renderHistory`
- (`sourceHistory.ts:454`). **An allowlist is staged** (`TOP_FILES`, `COMMIT_PAGE`, `:76`): `log.html`,
+- **The cap** (`SOURCE_HISTORY_MAX_COMMITS`, 10,000; ruled 2026-09-30): up to it, `-c`; past it,
+ `-l 10000`. Either way the pages published are exactly the commits the log links
+ (`loggedCommits`: every `href="commit/<sha>.html"` in log.html), each of which must exist, and the
+ first must be the head. The manifest's `history.total` is every commit, `commits` those with a
+ page; `/source/` says "the latest N of M" when they differ. Past the cap each publish computes N
+ diffstats (no `-c`): 8.75 s at 1,834 commits with every page present, against 0.58 s with `-c`.
+ The oldest published page's parent link is then a 404.
+- **The step** (`source.ts`, step 12b) runs after the object audit and the mirror's staging,
+ before the manifest and the file audit: `stageHistoryPages` (`:1109`) → `renderHistory`
+ (`sourceHistory.ts:497`). **An allowlist is staged** (`TOP_FILES`, `COMMIT_PAGE`, `:87`): `log.html`,
`files.html`, `refs.html`, `atom.xml`, `tags.xml`, `commit/<40 hex>.html`, plus `style.css`. Never
`file/`, never a leftover. Pages are rewritten byte for byte (latin1 in and out; every injection is
ASCII: the back link says `·`).
-- **The post-pass** `rewriteHistoryPage` (`:285`), pages only: `<script>` + the homepage's pre-paint
- script (`homepageThemeScript`, `:70` = `buildThemeScript({ defaultBase: HOMEPAGE_DEFAULT_BASE })`)
- before `</head>`; `HISTORY_BACK_LINK` (`:67`) after `<body>`; `href="(../)*file/<p>.html"` →
+- **The post-pass** `rewriteHistoryPage` (`:296`), pages only: `<script>` + the homepage's pre-paint
+ script (`homepageThemeScript`, `:81` = `buildThemeScript({ defaultBase: HOMEPAGE_DEFAULT_BASE })`)
+ before `</head>`; `HISTORY_BACK_LINK` (`:78`) after `<body>`; `href="(../)*file/<p>.html"` →
`href="$1../tree/<p>"`; `logo.png`/`favicon.png` → `/icons/icon-32.png` (a route the homepage renders
at build). The theme config moved to `lib/themeConfig.ts` for this (publish may not import
components/; `components/themeConfig.ts` re-exports it); `HOMEPAGE_DEFAULT_BASE` (`:62`) is what the
homepage layout passes. The attribute the script sets is `data-base`, not `data-theme`.
-- **`style.css`** is `historyStylesheet` (`:197`) over `common/styles/tokens.css`: the light block
+- **`style.css`** is `historyStylesheet` (`:208`) over `common/styles/tokens.css`: the light block
(`:root, html[data-base="light"]`), the dark block for `html[data-base="dark"]` AND for
`:root:not([data-base])` under `prefers-color-scheme: dark` (no JS); `HISTORY_TOKENS` and whatever
they reach through `var()`. Diff `+` lines are `--success`, `−` lines `--destructive`; there is no
chart-safe red in the tokens (the chart palette is blue, green, violet, amber, magenta, rust).
-- **Never a failed build over the history.** No stagit: one line with the install (`source.ts:931`).
+- **Never a failed build over the history.** No stagit: one line with the install (`source.ts:970`).
A render that fails (`HistoryProblem`), a stagit timeout (600 s), unreadable tokens, more files
than the step's 15,000 in all, or a page over 24 MiB: a `[source] WARNING: …` and no history. A
cancel still cancels; any other error is a crash as before.
-- **The manifest's `history`** (`lib/sourceManifest.ts:88`, parsed at `:137`, all-or-nothing):
+- **The manifest's `history`** (`lib/sourceManifest.ts:88`, parsed at `:140`, all-or-nothing):
`href`, `commits` (`git rev-list --count`), `head`, `files`, `bytes`, `sha256` (`historyDigest`,
- `source.ts:500`: `sourceDigest`'s walk over `source/git/**` alone), `tool`. `sourceDigest` (`:484`)
+ `source.ts:505`: `sourceDigest`'s walk over `source/git/**` alone), `tool`. `sourceDigest` (`:489`)
now walks `source/git` too. The state (`.source-publish.json`) gains `stagit` and `history:
{files, digest} | null`; the skip needs both to match, and a stagit present with no history never
skips. `SOURCE_STEP_VERSION` is 4 (`:109`).
-- **The deploy check** (`publishedSourceProblem`, `:1204`) names the history on its own (`:1286`):
+- **The deploy check** (`publishedSourceProblem`, `:1244`) names the history on its own (`:1326`):
`out/source/git`'s digest must be the state's (null when none) and the manifest's.
-- **The render cache** is `<scratch root>/archilyzer-source-history/` (`historyCacheDir`, `:556`):
- `key.json`, `stagit.cache`, `out/`, and `lock` while held. `holdHistoryCache` (`:408`): the lock
+- **The render cache** is `${XDG_CACHE_HOME:-~/.cache}/archilyzer/source-history/`
+ (`getPaths().sourceHistoryCacheDir`; an empty `XDG_CACHE_HOME` is unset; ruled 2026-09-30):
+ `key.json`, `stagit.cache`, `out/`, and `lock` while held. `historyCacheFor` (`source.ts`) gives
+ none for `--check` and none, with a line, inside the checkout or the public dir. `holdHistoryCache` (`sourceHistory.ts:443`): the lock
names the holder's pid; a live holder → render without the cache; a dead one → emptied and taken.
- Usable only when the key (`historyCacheKey`, `:565`: rules hash, filter-repo, stagit, description,
- clone URL, base URL) matches, `out/log.html` exists, and the cache's commit is an ancestor of the head
- (`git merge-base --is-ancestor`). The key is removed before a render and written after it. A cached
- render whose commit pages ≠ commits is rendered again from nothing, once. `--force` renders
- afresh; `--check` renders in its own scratch; a refusal removes the cache (`source.ts:676`).
+ Its pages are kept when the key (`historyCacheKey`: rules hash, filter-repo, stagit, description,
+ clone URL, base URL) matches and `out/log.html` exists; its `-c` file only when the commit it names
+ is an ancestor of the head (`git merge-base --is-ancestor`), else that file alone goes. A page of a
+ commit no longer in history is never published (only the log's commits are). The key is removed
+ before a render and written after it. A cached render whose log names a page that is not there
+ is rendered again from nothing, once. `--force` renders
+ afresh; `--check` renders in its own scratch; a refusal removes the cache (`source.ts:713`).
- **Measured (2026-09-30, `main` `6b8aa450` → mirror `8c924ca31701`):** 1,872 commits; 1,878 files,
141,299,532 bytes; the largest `commit/7ddfc955….html`, 5,017,646 bytes; stagit 8 s of a 32 s
publish (every page); the whole publish 4,396 files, `homepage/out` 4,576. On the published mirror
- at 1,834 commits: 8.0 s every page, 2.0 s from the cache with nothing new. The cache is 138 MB — on
- this machine's tmpfs `/tmp`, memory. One file per commit: at about 100 commits a day (September
- 2026) the step's 15,000 is some three months away, and the history is what is dropped then.
+ at 1,834 commits: 8.0 s every page, 2.0 s from the cache with nothing new. The cache is 138 MB.
+ At `main` `4dfe21e5`: 1,882 commits, 1,888 files, 4,409 staged in all. One file per commit: the cap
+ keeps the history at 10,006 files at most; the 15,000-file drop is the last resort.
+- **Doctor** (`bin/doctor.ts`): the stagit line ends `; cache: <path>, <size>`. Since the merge of
+ slice DT it reads the settings before the corpus and applies `settings.storage.health`
+ (`applyHealthTimings`) before it inspects any drive, and prints the timings in force on a
+ `drive health` line.
- **The homepage** believes `history` only beside `source/git/log.html` (`homepage/app/lib/source.ts:43`;
without it the manifest comes back without the block). Its e2e reads a fixture publish from
`E2E_SOURCE_PUBLIC_DIR` while it holds a manifest (`sourcePublicDir`, `:69`; never in a production
diff --git a/plans/release-15.md b/plans/release-15.md
@@ -21,7 +21,7 @@ prompt carries its ruling, and this record carries what was built. Rules:
| UT | `r15/umtool-trace` | umtool's build stops tracing the whole `umtool/` folder | per its prompt |
| SS | `r15/site-scope` | The editor's site picker paints the stored site at once: the selection is a cookie | `editor/app/lib/activeSite{,Server,Actions}.ts` + `activeSite.test.ts`, `editor/app/components/SiteScope{Provider,Select}.tsx`, `editor/app/layout.tsx`, the scope lines of `editor/app/page.tsx` and `editor/app/channels/page.tsx`, a comment in `editor/next.config.ts`, `editor/e2e/site-scope.spec.ts`; records: `plans/FACTS.md` |
| DT | `r15/drive-timings` | The drive-health timings are settings (`settings.storage.health`), edited on `/storage` | new `common/lib/storageHealthTimings.ts` + test; `lib/{storageHealth,storageVolumes,channelMedia,storageLocations,settingsSchema,settingsDocs}.ts`, `controller/storageWatch.ts`, the `index` and `build stats` bins, `SETTINGS.md`; `/storage` (a form, its action and parse), the stall wording on `/channels` and the videos pages; `storage-locations.spec.ts`; records: `plans/FACTS.md` |
-| SG | `r15/stagit` | The source's history and diffs on the homepage, rendered by stagit at `/source/git/` | new `common/publish/sourceHistory.ts` + test; `common/publish/source.ts` (step 12b, the key, the digest, the deploy check) + test; `common/lib/{sourceManifest,paths,envVars}.ts`, `common/lib/themeConfig.ts` (moved from `components/`, which re-exports it); `common/bin/doctor.ts`; `homepage/app/{source/page.tsx,lib/source.ts,layout.tsx}`, `homepage/e2e/{source,source-history}.spec.ts`, `homepage/e2e/fixture-source.ts`, `homepage/playwright.config.ts`; `ENVIRONMENT.md`, `PUBLISH.md`; records: `plans/FACTS.md` |
+| SG | `r15/stagit` | The source's history and diffs on the homepage, rendered by stagit at `/source/git/` | new `common/publish/sourceHistory.ts` + test, `common/publish/__fixtures__/fakeStagit.ts`; `common/publish/source.ts` (step 12b, the key, the digest, the deploy check) + test; `common/lib/{sourceManifest,paths,envVars}.ts`, `common/lib/themeConfig.ts` (moved from `components/`, which re-exports it); `common/bin/doctor.ts`; `homepage/app/{source/page.tsx,lib/source.ts,layout.tsx}`, `homepage/e2e/{source,source-history}.spec.ts`, `homepage/e2e/fixture-source.ts`, `homepage/playwright.config.ts`; `ENVIRONMENT.md`, `PUBLISH.md`; records: `plans/FACTS.md` |
**Order:** IG → DS. DS adds a health gate inside `inspectChannelMedia`, which IG's hold calls
through its public signature. UT is independent. The shared files are `editor/CHANGELOG.md`'s
@@ -1438,11 +1438,9 @@ against libgit2 1.9.7. The binary was copied to `~/.local/bin/stagit` (sha256 `8
#### Found and left
-- **The file limit is months away, not years.** There is one file per commit, and main gained about
- 100 commits a day in September 2026. The publish is 4,396 files, and the step refuses at 15,000:
- about 10,600 commits of headroom. When the history would cross it, the history is dropped with a
- WARNING and the rest is published. The choices then (raise `MAX_FILES` toward Pages' 20,000, or
- publish only recent commits' pages) are a later ruling.
+- ~~**The file limit is months away, not years.**~~ **Ruled and applied below: the history is capped
+ at the newest 10,000 commits**, so it is at most 10,006 files; the 15,000-file drop stays as the
+ last resort.
- **`/source/git/` has no index page**, so the directory itself is a 404; `/source/` links
`log.html`. A copy of `log.html` as `index.html` would double 0.6 MB.
- **No `_headers` rule for `/source/git/*`.** The pages are HTML by extension, and stagit encodes
@@ -1457,23 +1455,76 @@ against libgit2 1.9.7. The binary was copied to `~/.local/bin/stagit` (sha256 `8
| What I assumed | The alternative |
|---|---|
-| Diff lines use `--success` and `--destructive`, with no new tokens: the chart palette has no red (it is categorical: blue, green, violet, amber, magenta, rust); both are text-grade on both grounds; every changed line keeps its `+`/`−`, so hue is never the only signal. | Two new tokens (`--diff-add`, `--diff-del`), validated as a pair. A green/red pair cannot be told apart by hue for every colour-blind reader, so the sign column carries it either way. |
-| A failed render, a timeout, unreadable tokens or pages over the limits are a WARNING, with no history. | Refuse the publish: louder, but it takes the mirror down with the history. |
+| **Ruled (2026-09-30): stands.** Diff lines use `--success` and `--destructive`, with no new tokens: the chart palette has no red (it is categorical: blue, green, violet, amber, magenta, rust); both are text-grade on both grounds; every changed line keeps its `+`/`−`, so hue is never the only signal. | Two new tokens (`--diff-add`, `--diff-del`), validated as a pair. A green/red pair cannot be told apart by hue for every colour-blind reader, so the sign column carries it either way. |
+| A failed render, a timeout, unreadable tokens or pages over the limits are a WARNING, with no history. **Ruled: the history is capped by commit count instead (below); the file-limit drop stays only as the last resort.** | Refuse the publish: louder, but it takes the mirror down with the history. |
+| **Ruled (2026-09-30): the `components/themeConfig.ts` re-export stands**; its importers move to `lib/themeConfig` when a slice next touches them. | Move the 11 importers now. |
| stagit's logo and favicon point at `/icons/icon-32.png`. | Leave them broken, or drop the `<img>`. The ruling said "nothing else is injected"; this is a link rewrite, like `file/`. |
| The History block's head is 12 characters, linking to its commit page. The full sha is the Mirror head fact just above. | The full 40-character sha, as text. |
-| The cache lives under the scratch root, a tmpfs `/tmp` by default here (138 MB of memory). | A cache dir of its own, on disk. |
+| ~~The cache lives under the scratch root, a tmpfs `/tmp` by default here (138 MB of memory).~~ **Ruled (2026-09-30): `${XDG_CACHE_HOME:-~/.cache}/archilyzer/source-history/`** — applied, below. | — |
| `--force` empties the cache. | Keep it, and force only the publish. |
-| `--check` renders without the cache (8 s rather than 2 s): it writes nothing outside its own scratch. | Use the cache. |
+| `--check` renders without the cache (8 s rather than 2 s): it writes nothing outside its own scratch. **Ruled: stays cache-less.** | Use the cache. |
| The e2e shows the page with and without a history from a fixture publish, through a non-production-only `E2E_SOURCE_PUBLIC_DIR`. The real pages are walked by `source.spec` when the checkout has a history. | Only the checkout's real state, as the other source specs do. |
**For the rollout** (the parent's):
- **Install stagit on the publishing machine**, the same way (`~/.local/bin/stagit`).
- **Rebuild and restart the editor** before any `/sites` Homepage job; its built bundle runs the
old step.
-- The first build publishes the source again (step 4), with the history.
+- The first build publishes the source again (step 4), with the history, and makes the render
+ cache at `~/.cache/archilyzer/source-history` (about 140 MB on disk).
- A review should read one live page after the deploy; the preview's `_headers` do not touch
`/source/git/`.
+#### The parent's rulings, applied (2026-09-30), and the merge of `main` with slice DT
+
+| Ruling | What was done |
+|---|---|
+| 1. The history is capped by commit count: stagit `-l <SOURCE_HISTORY_MAX_COMMITS>`, 10,000, the newest first; `/source/` says "the latest N of M"; the 15,000-file drop only as the last resort | `d8c9e5fe`, `4409ab9d`. **Read in `stagit.c` and measured:** `-l N` keeps the NEWEST N log lines (the revwalk is newest first) and ends "M more commits remaining, fetch the repository". But it still writes a page for EVERY commit (only the diffstat of a page that exists is skipped), and stagit refuses `-c` with `-l` (usage error). So: up to the cap, `-c` as before; past it, `-l 10000`. The pages published are, in both, exactly the commits the log links (`loggedCommits`), and every one must be there. The manifest's `history` gains `total` (`commits` is how many have a page). Measured on the published mirror (1,834 commits, pages present): `-l 10000` 8.75 s, `-c` 0.58 s. Past the cap every publish computes 10,000 diffstats (~5 ms each, ~48 s). |
+| 2. The render cache is `~/.cache/archilyzer/source-history/` (`XDG_CACHE_HOME` honoured), made with a recursive mkdir, never inside the repo; `--check` stays cache-less; PUBLISH.md and doctor ("cache: <path>, <size>") | `d8c9e5fe`, `68670703`, `2acbd080`. `getPaths().sourceHistoryCacheDir`, from `XDG_CACHE_HOME` (empty = unset) else `~/.cache` (`XDG_CACHE_HOME` declared, `ENVIRONMENT.md`). `historyCacheFor` gives none for `--check` and none — with a line — inside the checkout or the public dir. Its pages are kept by key; its `-c` log lines only when they end at an ancestor (the pages stay otherwise). Doctor's stagit line ends `; cache: <path>, <size>` ("none yet"). The old `/tmp` cache was removed. |
+| 3. `--success` / `--destructive` stand | Recorded (the decisions table). |
+| 4. The `components/themeConfig.ts` re-export stands; importers move when a slice next touches them | Recorded (the decisions table). |
+| After DT merged: merge `main`; doctor's `applyHealthTimings(...)` line (DT's Found and left) | `2b2b5305` merges `main` `4dfe21e5`; the conflicts were `release-15.md` (DT's row, then SG's; DT's section, then SG's, before the Rollout) and the editor changelog (DT's bullet, then SG's). `53c1849d`: doctor reads the settings before the corpus and applies `settings.storage.health` before it inspects any drive, as the index and stats bins do; a "drive health" line in the corpus block names the timings in force and whether they are the defaults. |
+
+| Commit | What |
+|---|---|
+| `d8c9e5fe` | `common:` the cap (`SOURCE_HISTORY_MAX_COMMITS`, `-l`, `loggedCommits`, `history.total`); the cache in the XDG cache dir (`sourceHistoryCacheDir`, `historyCacheFor`, `XDG_CACHE_HOME`); one fake stagit (`__fixtures__/fakeStagit.ts`, `-c` and `-l` as `stagit.c` has them); tests +4 (the cap twice, `loggedCommits`, the cache's place) and the cache test rewritten. |
+| `68670703` | `common:` doctor's stagit line ends with the cache's path and size. |
+| `4409ab9d` | `homepage:` "the latest N of M commits"; the fixture can be capped (+1 e2e); `source.spec` checks the log lists exactly the commits with a page. |
+| `2acbd080` | `docs:` PUBLISH.md — the cap, the cache's place, doctor's line. |
+| `44ddb6b2` | `changelog:` the cap and the cache's place in both `[Unreleased]` bullets. |
+| `2b2b5305` | Merge `main` (DT). |
+| `53c1849d` | `common:` doctor applies the drive-health timings, and prints them (+1 test). |
+| this commit | `plans:` this subsection, the ruled rows, FACTS. |
+
+**Re-gates** (after the merge, at `53c1849d`; logs `$T/sg-*-2.log`, `sg-tsc-{5,6,7}.log`, `sg-pub2.log`):
+- **tsc** clean before each commit and on the merge.
+- **common 2,341/2,341**, 0 skipped: SG's 22 over `main` `4dfe21e5`'s (2,319 by difference; `sourceHistory` 13,
+ `source` +6, `sourceManifest` +2, doctor +1).
+- **homepage unit 22/22**; **test:scripts 195 + 1 skipped**; `docs env --check` 0,
+ `docs files --check` 0.
+- **The three source specs** (`source`, `source-history`, `downloads`; `E2E_EXPECT_SOURCE=1`, over
+ the worktree's fresh publish): **12 passed, 0 failed, 17.5 s** (+1: the capped block).
+- **`source publish`** (main `4dfe21e5`) in the worktree: `[source] history: stagit (sha256
+ 898752011b07) — 1882 commits, 1882 pages rendered; 1888 files, 135.3 MB, the largest
+ git/commit/7ddfc955….html 4.8 MB`; `audit clean: 23,128 objects (1,882 commits), 4,409 staged
+ files against 10 denied literals; gitleaks clean`; published as `f81edb46f7e3`, 4,409 files. The
+ cache was made at `~/.cache/archilyzer/source-history` (138 MB, mode 700).
+- **`source publish --check`**: `check passed — would publish main 4dfe21e5714a as f81edb46f7e3: 4409
+ files, 210.3 MB (…, history 1882 commits in 1888 files) …; nothing written (32 s)`. The cache's
+ key was untouched.
+- **doctor**: `ok stagit ~/.local/bin/stagit; cache: ~/.cache/archilyzer/source-history, 134.1 MB`
+ and `-- drive health a read may take 3 s, a check every 15 s (3 s each), a stall clears on a
+ clean check twice in a row, 4 reads in flight per drive — the defaults`.
+- Not re-run after the rulings: the full homepage suite (103 before), the builds. The rulings change
+ `source.ts`, `sourceHistory.ts`, doctor and one page component; the source specs cover the page.
+
+**Found and left, from the rulings:**
+- **Past the cap, the oldest published commit page links its parent's page, which is not
+ published** (a 404). Every other link resolves.
+- **Past the cap the render is not incremental**: stagit cannot combine `-l` with `-c`, so each
+ publish computes 10,000 diffstats (about 48 s by the per-commit rate measured here). The pages
+ themselves are still kept.
+
+
## Rollout
Release 15 is slices IG (`r15/index-hold`, merged `ccf90892`), UT (`r15/umtool-trace`, `07c991be`),