Archilyzer · Source

archilyzer

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

commit ba0ae68bbe32118e9af35b422bda8169a32ff809
parent c80cd066474b267503fd2a2c8cb640b4e4dd2885
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 29 Sep 2026 21:18:15 -0400

plans: IG's review (SHIP AFTER FIXES) to its commits — M1 case (i), L3/L5 wording; FACTS L1 (a stats-only run between a drive's return and the next index build counts a held video as not indexable, healed by that build) and L2 (the processing-phase gap loses subs and digests too, until any tracked mtime moves), anchors refreshed; the changelog says where the override is set; common 2,220

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

Diffstat:
Meditor/CHANGELOG.md | 2+-
Mplans/FACTS.md | 53++++++++++++++++++++++++++++++++---------------------
Mplans/release-15.md | 60++++++++++++++++++++++++++++++++++++++++++++++--------------
3 files changed, 79 insertions(+), 36 deletions(-)

diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md @@ -3,7 +3,7 @@ ## [Unreleased] - **Transcripts that arrived after a video was first seen are counted.** The stats behind the homepage, the hub and every site's charts were cached per video and refreshed only when the video's metadata changed, so a transcript that came later — a Whisper run days after the download, or a video downloaded after the last index build — never reached them, and a video with YouTube captions alone had no transcription date. Counts and charts were low; the homepage could show a site with 0 transcripts, 0 channels and 0 hours while it served its videos. A stat is now also redone whenever the index re-reads the video, every transcript has a date, and a captioned video is dated by when its captions arrived rather than by a later Normalize run, so its place on "Transcribed over time" can move. **After updating, rebuild and restart the editor before anything else:** until then, **Build stats dataset** runs the old code and would undo the new stats, while a site, hub or homepage build already runs the new code — and the first stats build of any kind re-reads every video once (about 10–30 minutes on a large archive; it can be stopped and picks up where it stopped). Then build the index, the stats, the homepage, the hub, and the sites. - **A stats build keeps the stats of a channel whose drive is not mounted, and will not undo a newer version's stats.** A channel whose media is on a drive that is not mounted (or is being moved) is left as it was instead of being read as a channel with no videos; a stats rebuild that has to start over refuses until the drive is back. A stats build refuses to clear stats written by a newer version of the editor; set `ARCHILYZER_STATS_ALLOW_DOWNGRADE=1` to roll back on purpose. Its log also says apart how many videos were downloaded since the last index build (they catch up after the next one) and how many the index skipped (no upload date, or it failed on them). -- **An index build keeps a channel whose drive is not mounted, instead of dropping it from the sites.** **Build index**, a site build's data phase and `archilyzer index` read a channel whose media is on a drive that is not mounted (or is being moved, or whose link and config disagree) as a channel with no videos: they removed its videos from the index, and the next site build published the channel as gone. Such a channel is now left as the last build had it — its videos stay in the index, its pages stay as they were, and the sites built next still list it — and the log names it, with its storage location: one line per channel, ` Held: N channel(s), K video(s) kept.` at the end of the `Diff:` line, and the channels again on the last line. A data folder that fails to read is held the same way, and a channel with no data folder at all is said in the log instead of passed over. An index rebuild that has to start over (after an update that changes the index's format, or with no index yet) refuses while any channel is held and says which; mount the drive first, or set `ARCHILYZER_INDEX_ALLOW_HELD=1` to rebuild without that channel until its drive is back and the index is built again. +- **An index build keeps a channel whose drive is not mounted, instead of dropping it from the sites.** **Build index**, a site build's data phase and `archilyzer index` read a channel whose media is on a drive that is not mounted (or is being moved, or whose link and config disagree) as a channel with no videos: they removed its videos from the index, and the next site build published the channel as gone. Such a channel is now left as the last build had it — its videos stay in the index, its pages stay as they were, and the sites built next still list it — and the log names it, with its storage location: one line per channel, ` Held: N channel(s), K video(s) kept.` at the end of the `Diff:` line, and the channels again on the last line. A data folder that fails to read is held the same way, and a channel with no data folder at all is said in the log instead of passed over. An index rebuild that has to start over (after an update that changes the index's format, or with no index yet) refuses while any channel is held and says which; mount the drive first, or set `ARCHILYZER_INDEX_ALLOW_HELD=1` to rebuild without that channel until its drive is back and the index is built again — on the command for a command-line build (`ARCHILYZER_INDEX_ALLOW_HELD=1 pnpm archilyzer index`), or in the editor's own environment, with a restart, for **Build index** and the site builds started from the editor. - **Building the homepage now publishes the source: a read-only git mirror, its raw tree and a fresh tarball, behind a gate.** `archilyzer build homepage`, the `/sites` Homepage jobs and `pnpm ops build-homepage` run `archilyzer source publish` between compose and `next build`. It makes a fresh clone of the private `main` (the repository itself is never rewritten), rewrites that copy with git-filter-repo using your scrub rules (file contents and commit messages; your home directory becomes `/home/user` without a rule), and publishes it under `homepage/public` for `git clone https://archilyzer.pages.dev/source/archilyzer.git`, beside `/source/tree/` and the Downloads tarball. Before anything is written, every object of the rewritten history and every file about to be published is searched for every string you have denied; **one hit refuses the build**, and its log names the string only by where you wrote it (`denylist line 3 (len 5)`) and each hit by its object, field and byte offset — never a byte of the object. **A refusal withdraws the source**: the last publish is removed from `homepage/public` and the last build's copy from `homepage/out`, and **Deploy homepage refuses** a build whose source was not audited under today's rules and today's `main` ("run `archilyzer build homepage`, then deploy"). The rules live outside the repo, in `~/.config/archilyzer/source-scrub.txt` and `source-denylist.txt` (`ARCHILYZER_CONFIG_DIR`, `SOURCE_SCRUB_FILE`, `SOURCE_DENYLIST_FILE`); **without them the build refuses**, naming the missing file. **Put everything private in the denylist before any deploy, a preview included**: previews are public, and every deployment stays reachable at its own address until you delete it. Install git-filter-repo once (`pipx install git-filter-repo`; the editor's process needs `~/.local/bin` on its `PATH` to find it) — without it the build fetches it through `pipx run`, which needs the network — and gitleaks if you want its secret scan too. An unchanged `main` with unchanged rules is skipped, so a rebuild costs about 20 seconds only when something moved. A checkout with no git repository (the docker image, a tarball install) builds with the /source page's empty state. `archilyzer source publish --check` audits without writing, `archilyzer source audit <clone>/.git` checks any clone, `archilyzer build homepage --no-source` removes the published source instead, and `archilyzer doctor` reports the tools, the two files (rule counts and permissions, never their contents) and the last publish. `create-archives.sh` is gone. See PUBLISH.md, "The source mirror (homepage)". - **umtool reads the corpus from its checkout (or `TRANSCRIPTS_DIR`), and the song project's data defaults to `~/.local/share/archilyzer/song`.** If yours is elsewhere, link it there before restarting umtool: `mkdir -p ~/.local/share/archilyzer && ln -s <where the data is> ~/.local/share/archilyzer/song` (the data stays where it is). With no `CHANNELS_DIR`, umtool reads the corpus at `$TRANSCRIPTS_DIR/channels`, else the checkout's own `transcripts/channels`; it used to fall back to an absolute path that existed on one machine only. The song project's videos default to `~/reports/quartering-uh-song/videos`; `SONG_DIR` and `VIDEO_ROOT` still win. The song project's tracked manifests record their paths relative to the song folders, and the twenty one-off `umtool/song/*.sh` run logs, which only ever ran on the machine that wrote them, are gone. - **umtool's production build no longer reads the corpus folder.** Since umtool began finding the corpus from its checkout (the bullet above), `next build` treated the checkout's whole `transcripts/channels` as files to bundle. On a real archive it ran out of memory and was killed, so umtool could not be rebuilt. The build now ignores that folder and finishes in about 25 s at under 1 GB, the same as a checkout with no corpus. Nothing changes when umtool runs. diff --git a/plans/FACTS.md b/plans/FACTS.md @@ -7413,9 +7413,12 @@ on anchors elsewhere in this file: index it: - no `upload_date` (`buildIndex.ts:701`); - a processing failure; - - or the channel's media was unreachable during that build. Since release 15 (slice IG) that - happens only through a full rebuild run with `ARCHILYZER_INDEX_ALLOW_HELD=1`: an incremental - build keeps a held channel's records (see "The index build's hold"). + - or its channel was held during that build (its media unreachable). Since release 15 (slice + IG) an incremental build keeps a held channel's records, so this is a video that reached the + drive after the last index build that could read it. Once the drive is back, a stats-only run + (Build stats dataset, or a pool composer) before the next index build counts it here, and + that index build heals it. Under `ARCHILYZER_INDEX_ALLOW_HELD`, a full rebuild drops all of + the held channel's records the same way (see "The index build's hold"). It stays until fixed, and is logged as such rather than as pending (`:500`). - **A transcript always has a date, and a caption video takes its captions' arrival.** @@ -7489,7 +7492,7 @@ on anchors elsewhere in this file: The record is [`release-15.md`](release-15.md), "Slice IG, as shipped". Anchors are at the branch tip. The branch added lines to `buildIndex.ts` from `:10` on, so every `buildIndex.ts` anchor above -this section is stale, by +16 near the top and +197 at the end; they are not rewritten in place. +this section is stale, by +16 near the top and +203 at the end; they are not rewritten in place. - **An unmounted drive is not an empty channel, for the index either.** - `scanSource` (`common/controller/buildIndex.ts:309`) calls `inspectChannelMedia({ channelsDir }, @@ -7497,39 +7500,43 @@ this section is stale, by +16 near the top and +197 at the end; they are not rew `data/`. A status other than `ok` or `in-place` (`isMediaHeld`, `common/lib/channelMediaHold.ts:26`) puts the channel in `held` with a reason and no path, and it is not scanned. - - It calls it again after the walk (`:462`), so a drive that goes away mid-walk holds the - channel instead of dropping the videos after that point. + - It calls it again after the walk (`:465`), so a drive that goes away mid-walk holds the + channel instead of dropping the videos after that point (without it, `buildIndex.test.ts` + case (i) removes 3 of 4). - A failed `readdir(data/)` holds too (`its data directory could not be read (<code>)`), except ENOENT on an `in-place` channel, which is a channel with no downloads and is logged as such (`:362`). A per-video metadata `stat` failing with anything but ENOENT or ENOTDIR holds the - channel (`:387`). + channel too, logged as `a video in its data directory could not be read (<code>)` (`:387`). - **What a held channel keeps, on an incremental build:** - - its `mtimes` records: the removal pass skips its keys and counts them (`:709`), so `sums`, + - its `mtimes` records: the removal pass skips its keys and counts them (`:715`), so `sums`, `cues`, `subs`, `digests` and `byChannel` keep them too; - its shared transcript, subs and digest trees: not rewritten, not pruned, not removed - (`:1198`, `:1387`, `:1766`, and the top-level cleanups `:1507`, `:1873`); + (`:1204`, `:1393`, `:1772`, and the top-level cleanups `:1513`, `:1879`); - its subs and digest stats, carried from `channelStatsDb` / `channelDigestStatsDb`, so the per-site subs and digest manifests still list it; - - its availability states: the maybe-missing overlay skips it (`:1318`), and the last build's - `videoState` entries for it are carried over (`:1350`). Its `availability.json` files are on + - its availability states: the maybe-missing overlay skips it (`:1324`), and the last build's + `videoState` entries for it are carried over (`:1356`). Its `availability.json` files are on the missing drive, and a missing one reads as `maybe_missing`. - The per-site summaries come from LMDB, so the site built next still lists its videos. - **A curated-tag change while a channel is held:** the re-apply pass re-derives its records in LMDB (no disk read), but its pages are not written, so `curatedPagesPending` is NOT cleared while - a channel is held and the pass had pages pending (`:1545`). The first build with the drive back + a channel is held and the pass had pages pending (`:1551`). The first build with the drive back rewrites them. -- **A full rebuild with a channel held refuses** (`:646`). A full rebuild is a schema change or a +- **A full rebuild with a channel held refuses** (`:649`). A full rebuild is a schema change or a first build (no `meta.schema`); it clears every sub-DB, and a held channel cannot be re-read. - - The scan now runs BEFORE the clear (`:636`), so the refusal leaves the index untouched. + - The scan now runs BEFORE the clear (`:639`), so the refusal leaves the index untouched. `scanStartedAt` is still taken at the scan's start. - The message names each channel with its location's label, the ways out (`HELD_WAYS_OUT`, - shared with the stats build's refusal), and `ARCHILYZER_INDEX_ALLOW_HELD` (`:509`, declared in - `envVars.ts`). The CLI exits 1 on it, so a site build's data phase fails with it. + shared with the stats build's refusal), and `ARCHILYZER_INDEX_ALLOW_HELD` (`:512`, declared in + `envVars.ts`) with where it is set: the command's own environment for a CLI run; the editor's + own environment, and so a restart, for the editor's Build index job or a site build started + from the editor (their children inherit `process.env`). The CLI exits 1 on it, so a site + build's data phase fails with it. - With the variable set, the build proceeds: the held channel's records go with the clear, its shared trees are left on disk, and it is out of the index (the site lists it with 0 videos) until its media is back and an index build runs. -- **Who sees it:** `BuildIndexResult.heldChannels` (`:499`); the log's per-channel line, the - `Diff:` line's ` Held: N channel(s), K video(s) kept.` suffix (`:774`), and the `Done in` line's +- **Who sees it:** `BuildIndexResult.heldChannels` (`:502`); the log's per-channel line, the + `Diff:` line's ` Held: N channel(s), K video(s) kept.` suffix (`:780`), and the `Done in` line's ` Held, their media not readable: <slugs>.` suffix. The CLI (`archilyzer index`, the export's and homepage's `build:index`, so every site build's data phase) prints the log; the editor's **Build index** job (`buildIndexAction`) streams it into the job log, which `pnpm ops build-index @@ -7537,6 +7544,10 @@ this section is stale, by +16 near the top and +197 at the end; they are not rew - **The words are shared with the stats build** (`lib/channelMediaHold.ts`): `HELD_REASON` is a `Record<ChannelMediaStatus, string>`, so a new status added to `inspectChannelMedia` fails tsc until it has a reason; `isMediaHeld` treats any status but `ok` and `in-place` as held. -- **Not covered:** a drive that drops during the PROCESSING phase (after the scan). A transcript - read that fails there is caught as "no cues" (`cueList = undefined`), so that video's cues are - removed until it next changes. +- **Not covered:** a drive that drops during the PROCESSING phase (after the scan), for a changed + video whose metadata was read before the drop. A transcript read that fails after it is caught + as "no cues" (`cueList = undefined`, `:838`); a sub-track read that fails is skipped, and with + none left the video's subs are removed (`subs.remove`, `:895`); a digest load that fails leaves + no digest, which is removed (`digests.remove`, `:962`/`:965`). `mtimes` is then written with the + video's current mtimes, so the loss lasts until any of its tracked mtimes (metadata, transcript, + subs, availability, digest) moves. diff --git a/plans/release-15.md b/plans/release-15.md @@ -52,7 +52,8 @@ gone. Only the `Diff: … -R removed` line showed it. - The exception is ENOENT on an `in-place` channel: a channel with nothing downloaded (or its media deleted), which is emptied as before. The log now says it: `Channel <slug>: no data/ directory; indexed as a channel with no videos.` - - A per-video metadata `stat` failing with anything but ENOENT or ENOTDIR holds the channel. + - A per-video metadata `stat` failing with anything but ENOENT or ENOTDIR holds the channel, + logged as `a video in its data directory could not be read (<code>)`. - **A held channel keeps everything:** - its `mtimes` records, since the removal pass skips its keys, and so its `sums`, `cues`, `subs`, `digests` and `byChannel` entries; @@ -72,7 +73,9 @@ gone. Only the `Diff: … -R removed` line showed it. is still taken at the scan's start. - The message names each channel with its location's label and says why a clear would publish it as gone. It gives the ways out, mounting first (the same words as the stats build's - refusal), then the override by name. + refusal), then the override by name and where it is set: the command's own environment for a + CLI run, and the editor's own environment (which takes a restart) for its Build index job or a + site build started from it. Their children inherit the editor's `process.env`. - With `ARCHILYZER_INDEX_ALLOW_HELD=1` (1/true/yes/on, declared in `envVars.ts`, `ENVIRONMENT.md` regenerated), the build proceeds. The held channel's records go with the clear; they cannot be carried across a format change. Its shared trees are left on disk, and it is out of the index @@ -103,7 +106,10 @@ gone. Only the `Diff: … -R removed` line showed it. | `c6ae51b0` | `plans:` this record: the header, the slices, and empty Record and Rollout sections. | | `51328098` | `common:` the hold's words move to `lib/channelMediaHold.ts`; the stats build uses them, with its messages unchanged. | | `ffb01d8e` | `common:` the index build's hold, the refusal and its override, `heldChannels`, the log lines; `envVars.ts` + `ENVIRONMENT.md`; new `buildIndex.test.ts` (9 cases). | -| this commit | `plans:` this section; FACTS "The index build's hold" (and the two stale statements in "The stats cache key" corrected); the STATE follow-up closed; `stats-cache-key.md` "Left" marked closed; the editor changelog. | +| `702cd0da` | `plans:` this section; FACTS "The index build's hold" (and the two stale statements in "The stats cache key" corrected); the STATE follow-up closed; `stats-cache-key.md` "Left" marked closed; the editor changelog. | +| `9ec48351` | `common:` review L3 + L5: one unreadable video directory is logged apart from an unreadable `data/`; the refusal says where the override is set. | +| `5af516b7` | `common(test):` review M1: case (i), the drive lost mid-walk. | +| this commit | `plans:` the review's findings to their commits; FACTS L1, L2 and the anchors; the changelog's override sentence (L5). | **Tests** (`common/controller/buildIndex.test.ts`, the real `buildIndex` over a temp corpus). The drive channel is seeded with `buildStats.test.ts`'s `seedDriveChannel` shape and unmounted by @@ -120,24 +126,27 @@ node:fs writes. | (f) | A really empty in-place channel (an empty `data/`, and no `data/`) is emptied, not held; the missing `data/` is logged | the new log line only (the emptying matched, as it should) | | (g) | An unreadable `data/`, and an unreadable video dir mid-walk (mode 000), hold their channels with `EACCES` | removed 3 | | (h) | A tag rule added while held: the held pages untouched, the flag kept; the drive back writes the tag to them and clears the flag | the drive's pages emptied | +| (i) | The drive lost MID-WALK: `node:fs/promises` `stat` unmounts it right after the walk's first drive metadata stat, so every later stat in the channel is ENOENT. The second look holds the channel: 0 removed, records and pages unchanged, the log line | removed 3 of 4; the same with only the second look deleted from the new code | | (z) | No write outside the temp root | passes on both | The pre-change column was run with the old `buildIndex.ts` swapped in once, with the `heldChannels` assertions removed so each case reached its first substantive assertion. -#### Gates (at `ffb01d8e`; logs `$T/ig-*.log`) +#### Gates (at `ffb01d8e`, and after the review at `5af516b7`; logs `$T/ig-*.log`) -- **tsc** was clean before every commit: 78 s at the branch point, 48 s at `ffb01d8e`. +- **tsc** was clean before every commit: 78 s at the branch point, 48 s at `ffb01d8e`, 50 s at + `5af516b7`. - **Unit:** | Suite | Result | |---|---| - | common | 2,219/2,219 (the branch point's 2,210 plus the 9 new cases), 77 s | + | common | 2,219/2,219 at `ffb01d8e` (the branch point's 2,210 plus the 9 new cases), 77 s; **2,220/2,220** at `5af516b7` (case (i) added), 78 s | | editor unit | 87/87 | | `test:scripts` | 191 passed, 1 skipped (192) | | mcp | 271/271 | -- **Docs:** `docs env --check`, `docs files --check` and `settings example --check` all exit **0**. +- **Docs:** `docs env --check`, `docs files --check` and `settings example --check` all exit **0**, + at both points. - **Build:** the editor's `next build`, with the primary's `transcripts/` linked in and capped at 5 GB with no swap: 64 s, max RSS 1,642 MB. The link was removed after the build, and nothing ran through it. @@ -146,15 +155,21 @@ The pre-change column was run with the old `buildIndex.ts` swapped in once, with `regional-vtt-fallback`, `tags`, passed as `e2e/<name>.spec.ts` so `availability` does not also match `pre-clean-availability`): **35 passed, 0 failed, 3.6 min**, after 1 min 45 s in the queue. No e2e fixture has an unreachable channel, so these confirm the hold changes nothing for a - readable corpus. + readable corpus. Not rerun after the review: its two log-wording changes touch no spec (`git grep + "could not be read" editor/e2e` finds only the curated-tag preview and the title filter). - **Numbers tool:** none. #### Found and left -- **A drive that drops during the processing phase** (after the scan) is not covered. A transcript - read that fails there is caught as "no cues", so that video's cues are removed until its - transcript next changes. That is a per-video read inside the worker, and it is left to the slice - that handles drive stalls. +- **A drive that drops during the processing phase** (after the scan) is not covered, for a changed + video whose metadata was read before the drop. Its transcript read is caught as "no cues", so its + cues are removed. A failed sub-track read is skipped, and with none left its subs are removed. A + failed digest load leaves no digest, which is removed. `mtimes` is then written with the video's + current mtimes, so the loss lasts until any of its tracked mtimes (metadata, transcript, subs, + availability, digest) moves. These are per-video reads inside the worker, left to the slice that + handles drive stalls. +- **A drive that drops and comes back inside one walk** is not covered either: the second look + finds it, and the videos skipped in between are removed. - **`inspectChannelMedia` is asked twice per channel** (before and after the walk), three syscalls each today. Slice DS puts a health gate inside it, and that gate runs twice per channel per index build. @@ -172,13 +187,30 @@ The pre-change column was run with the old `buildIndex.ts` swapped in once, with | What I assumed | The alternative | |---|---| -| A held channel's shared pages are not written at all. | Rewrite them from the kept records: byte-identical on an incremental build, and a tag change would reach them at once. It would still need the skip after an override, when the records are gone. | +| A held channel's shared pages are not written at all. **Ruled at review: keep the skip.** | Rewrite them from the kept records: byte-identical on an incremental build, and a tag change would reach them at once. After an override rebuild the records are gone, and a rewrite would publish the channel empty. | | Under the override, the held channel's records go with the clear. | Carry them across the clear. A schema change means the stored format moved, so the old records cannot be trusted. | | An unreadable `data/`, or a per-video `stat` failing with anything but ENOENT, holds the channel. | Hold only for the statuses `inspectChannelMedia` reports, and keep treating other read errors as "no videos", as the old code did. | | A channel with no `data/` is logged, one line per build. | Stay silent, as before; the ruling asked for a log. | -| The CLI exits 0 on a hold, since the hold is the safe outcome. | Exit non-zero so scripts notice; a site build's data phase would then fail whenever a drive is out. | +| The CLI exits 0 on a hold, since the hold is the safe outcome; the refusal exits 1. **Ruled at review: both stay.** | Exit non-zero so scripts notice; a site build's data phase would then fail whenever a drive is out. | | The words live in a new `lib/channelMediaHold.ts` shared with the stats build. | Duplicate them in `buildIndex.ts` and leave `buildStats.ts` untouched. | +#### Review + +**Verdict: SHIP AFTER FIXES** (`ig-review.md` in the job's scratch). No High. Every write path a +held channel could reach was traced, and the refusal fires before anything is deleted. + +| Finding | Where | +|---|---| +| M1: the second look after the walk had no test | `5af516b7`: case (i). It fails with 3 of 4 removed when that look is deleted. | +| L1: FACTS said an unreachable drive reaches `notIndexable` only through the override | this commit: a video that reached the drive after the last index build that could read it is counted there by a stats-only run between the drive's return and the next index build, which heals it. | +| L2: the processing-phase gap also loses subs and digests, until any tracked mtime moves | this commit, in "Found and left" and FACTS. | +| L3: one unreadable video dir was logged as the whole data directory | `9ec48351`: `a video in its data directory could not be read (<code>)`; case (g) expects it. | +| L4 (optional): a narrower `HELD_REASON` type | **Left**, as the review allowed. The current contract already fails tsc on a new status. | +| L5: the refusal did not say where the override is set | `9ec48351` (message, case (c)) and this commit (changelog). | +| L6: check the held set before routine builds resume | A rollout note; the parent records it. | +| L7: stale trees under the override | Already in "Found and left". | +| Q2: the commit trailer | Ruled correct. | + **What runs which code, for the rollout.** Every CLI command and every spawned data phase runs the checkout's code, so they hold from the moment `main` has this branch. The editor's in-process **Build index** button runs its built bundle, so it holds only after the editor is rebuilt and