commit 62a1de45c519ad207b8c56ecb3c4d9570d412174
parent d4436ad75f04d63a0e797f9662e1bf4b3c0dc7f0
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 7 Sep 2026 17:11:09 -0400
plans: one-core Phase 0 is shipped, and the record says what it cost
STATE.md gets the dated entry, FACTS.md the anchors, and one-core.md a
"Phase 0, as shipped" section. Three things the survey had wrong or could not
know are recorded rather than left to be rediscovered:
- report-to-video had EIGHT import sites in umtool, not four.
- `page-${n}.json` existed SIX times, not three — lib/stats.ts, lib/posts.ts
and lib/digests.ts each had their own copy.
- An exports map with a fallback array (`["./x.tsx", "./x.ts"]`) type-checks
and does NOT build: Turbopack takes the first entry and stops. `tsc` is not
the test for an exports change; `next build` is.
And one blocker that is not ours: `editor/content` is an untracked symlink to
/home/user/Projects/recipe-content, Tailwind's source detection follows it out
of the project, and Turbopack panics compiling editor/app/globals.css
("leaves the filesystem root"). The editor dev server 500s, so `pnpm e2e` dies
before the first spec. Reproduced on main @ 42576a3 — environmental, not a
Phase 0 regression. The suite is green from a worktree: 511 passed, 0 failed,
23.8 min.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
| M | plans/FACTS.md | | | 102 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | plans/STATE.md | | | 62 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| M | plans/one-core.md | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 219 insertions(+), 1 deletion(-)
diff --git a/plans/FACTS.md b/plans/FACTS.md
@@ -3184,3 +3184,105 @@ around `:251-290`) while this change deleted four entries lower in the same file
`/duplicates` to `nav.ts` where main's `nav.test.ts` pins eleven, and still carries files main
deleted (`build/page.tsx` with its own `BUILD_KINDS`, `actionable/page.tsx`). Their merge —
recorded so nobody is surprised.
+
+---
+
+## one-core Phase 0 (verified 2026-09-07, branch `one-core/phase-0`)
+
+### The exports-map trap: a fallback array type-checks and does not build
+
+`common/package.json` now has an `exports` map. `"./components/*":
+["./components/*.tsx", "./components/*.ts"]` passes `tsc --noEmit` in all six packages and
+**fails `next build`**: Turbopack resolves the first entry of a fallback array and stops, so
+`components/statsCache`, `components/summariesCache` and `components/virtualizer` (all `.ts`)
+died with module-not-found. The map lists the **25** `.ts` modules under `components/`
+explicitly — an exact key beats a pattern — and keeps `"./components/*": "./components/*.tsx"`
+for the 30 `.tsx` ones. **A new `.ts` file under `components/` needs a new line in that map.**
+Corollary for any future exports work: an `exports` change is a RUNTIME resolution change, so
+`tsc` is not the test. `pnpm --filter editor exec next build` is.
+
+### `styles/tokens.css` is imported by relative path, not as a subpath
+
+`editor/app/globals.css:2`, `export/app/globals.css:2`, `homepage/app/globals.css:2` all do
+`@import "../../common/styles/tokens.css"`. Only `styles/fonts` is a package subpath
+(`editor/app/layout.tsx:10` and the two siblings). The exports map maps `"./styles/*.css"`
+anyway, but nothing uses it today.
+
+### `common/`'s back-edges: 12 remain, and the test is the ledger
+
+`common/architecture.test.ts:41-99` — the allow-list, one entry per edge with the phase that
+removes it. Rules: `lib/` ✗ `controller/` `jobs/`; `jobs/` ✗ `controller/`; `components/` ✗
+`controller/` `jobs/` `ytdlp/`. It fails on a NEW edge **and** on a stale entry, so the list
+can only shrink. The heaviest cluster is `lib/operations.ts` → four controllers (plus three in
+its test): the registry's `run()` closures call the engines, which inverts in phase 1 step 4.
+
+`common/lib/autoQueueTypes.ts` is new and holds the persisted auto-queue shape
+(`AutoQueueMode/MatchType/Order/Reach/Match/Leaf/Group/Node/Policy/Settings`, `isGroup`,
+`AutoQueueKind`). `jobs/autoQueuePolicy.ts:2-20` and `jobs/autoQueueState.ts:23-25` re-export
+every name, so `from "../jobs/autoQueuePolicy"` still resolves everywhere.
+
+### `page-${n}.json` existed six times, not three
+
+`common/lib/manifest.ts` had three identical copies (the survey's count) — **and** so did
+`lib/stats.ts:23`, `lib/posts.ts:164` and `lib/digests.ts:38`. All six are now
+`manifest.ts`'s single `pageFileName`; `statsPageFileName`, `postsPageFileName` and
+`digestPageFileName` are aliases of it, and `transcriptPageFileName` / `subsPageFileName` are
+**deleted** (12 call sites rewritten). A seventh copy at
+`common/components/searchIndex.worker.ts:39` is deliberate and stays: it is a bundled web
+worker whose only import is its sibling protocol module.
+
+### `CONTRACT` owns the published version numbers
+
+`common/lib/corpus.ts` — `CONTRACT` holds `corpusSpec 3`, `siteDescriptor 1`, `manifest 3`,
+`transcriptsManifest 1`, `subsManifest 4`, `subsChannelManifest 1`, `summariesPageSize 1000`,
+`pagePad 4`, and `layers ["transcripts","subs","posts","digests","summaries"]`. The seven
+existing named exports re-export its fields. Import direction is `corpus.ts` →
+`{manifest,siteDescriptor}.ts`; `corpus.ts` imports `siteDescriptor` **type-only**, so there
+is no runtime cycle, and `project.ts` (its only value import) imports nothing.
+
+### How to compose a fixture site offline, and what a clean diff looks like
+
+There is no existing harness for this — export's e2e route-mocks and the 2origin setup
+hand-writes its shards. What works:
+
+```sh
+# a corpus root + a one-channel site
+cp -r editor/e2e/fixtures/test-transcripts/one-youtube-channel-with-data/channels $F/
+mkdir -p $F/sites/testsite && cat > $F/sites/testsite/site.json # siteId/groups/channels
+cd common && env TRANSCRIPTS_DIR=$F SITES_DIR=$F/sites \
+ EXPORT_PUBLIC_DIR=$O/public EXPORT_INDEX_DIR=$O/index \
+ SETTINGS_FILE=$F/settings.json SITE_ID=testsite \
+ pnpm exec tsx bin/build-index.ts && ... bin/compose-site.ts
+```
+
+Output: **16 files** under `public/`, **7** under `index/`. Two runs differ ONLY in
+`generatedAt` — in `corpus.json`, `site.json`, the subs/posts/digests/summaries/transcripts
+manifests, and (pretty-printed, so normalize `"generatedAt": "…"` with a space too) the
+archive zip's `channel.json`. The zip's own bytes differ because entry mtimes do; its
+CONTENTS diff clean. Anything else differing is a real change to the wire.
+
+### `editor/content` is a symlink out of the repo and it panics Turbopack in dev
+
+`editor/content -> /home/user/Projects/recipe-content` (untracked, made 2026-08-31).
+Tailwind's automatic source detection follows it out of the project and Turbopack panics
+compiling `editor/app/globals.css`: `FileSystemPath("editor").join("../../recipe-content")
+leaves the filesystem root`. The dev server reports `Ready` and then FATALs on the first
+request, so **`pnpm e2e` in the primary checkout dies before the first spec**. Verified
+2026-09-07 on `one-core/phase-0` AND on `main` @ `5ffb0e1` — environmental, not a code
+regression. `next build` is unaffected. A `git worktree` of the same commit has no such
+symlink and runs the suite normally — **511 passed / 0 failed in 23.8 min** on `1691c4f`,
+one worker. A worktree needs one extra step first: its `export/public` holds only the tracked
+static assets, so the export webServer 500s and Playwright dies with "Timed out waiting
+120000ms from config.webServer". Copy a composed fixture site into it (16 files) and it
+serves 200.
+
+### The bake-off harnesses do not take `--help`
+
+`plans/tools/{digest-bakeoff,attribution-bakeoff,attribution-pilot}.ts` (moved here from
+`common/bin/`) ignore unknown flags and **start a real run against the corpus** — the first
+opens the LMDB index and writes `plans/bakeoff/round.partial.json`. Run them under
+`cd common && pnpm exec tsx ../plans/tools/<name>.ts` only when you mean to. They needed
+`common/bin/_lmdb.ts` (a re-export of `open`) because `plans/` is not a workspace package: a
+bare `import "lmdb"` resolves from the importing file upward and the repo root's
+`node_modules` holds only `tsx`.
+
diff --git a/plans/STATE.md b/plans/STATE.md
@@ -3,7 +3,67 @@
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.
-**Last updated:** 2026-08-31 — **the transcode operation is removed** (`904f1a9` → `f717a36`): it never fired in production. The census over 68 channels found the `untranscoded` bucket empty in every one, both `failed-transcodings` files 0 bytes, and only four channels even passing the stage's gate — and `resolveAudioFile` falls back to any real audio file anyway, so transcription never needed it. Gone: four controllers, four job kinds, the channel stage and station, the catalog entry and with it `appliesTo`/`operationApplies` (catalog 8 → 7). Kept: `transcodeAudio` and the download path, the per-file *Transcode …* rows, both wrong-format sweeps — whose bucket is `wrongFormatAudio` now. The slice-5 leftovers rode first (`measure-nav.mjs`, `BUILD_KINDS`, `EditorAliasesClient`). Plan: [`editor-transcode-removed.md`](editor-transcode-removed.md).
+**Last updated:** 2026-09-07 — **one-core Phase 0 shipped** on branch `one-core/phase-0`
+(`df5eb48` → `1691c4f`, six commits, not merged): **guardrails and dead weight.** The umbrella
+plan is [`one-core.md`](one-core.md); its "Phase 0, as shipped" section is the detail and
+[`FACTS.md`](FACTS.md#one-core-phase-0-verified-2026-09-07) the anchors. Every item is a
+deletion, a move or a guard. **Nothing on disk in a corpus changed and nothing on the wire
+changed** — proved, not asserted: a fixture corpus composed through `build-index` +
+`compose-site` before and after item 5 gives 16 public files and 7 index files that are
+**identical apart from `generatedAt`** (and the archive zip's entry mtimes; its contents diff
+clean).
+
+- **`scripts/report-to-video` → `umtool/report-to-video`**, package `umtool-report-to-video`.
+ A rename only. The survey undercounted: **eight** import sites, not four.
+- **`common/architecture.test.ts`** is the layering. Back-edges **17 lines / 16 edges → 13 /
+ 12**. The four burned down were type-only and now live in `common/lib/autoQueueTypes.ts`,
+ re-exported by `jobs/autoQueue{Policy,State}.ts` so no import site outside `common/lib`
+ changed. The allow-list fails on a NEW edge *and* on a stale entry, so it can only shrink;
+ each of the 12 names the slice that removes it. **Nine wait on phase 1** —
+ `lib/operations.ts` → four controllers is the registry's `run()` closures, THE structural
+ back-edge, and it inverts at phase 1 step 4.
+- **`common/package.json` has an `exports` map**; `mcp/package.json` finally declares
+ `yt-dlp-transcript-common` (it had been resolving by hoisting). One trap paid for:
+ `"./components/*": ["./components/*.tsx", "./components/*.ts"]` **type-checks and does not
+ build** — Turbopack takes the first entry of a fallback array and stops. The 25 `.ts`
+ modules under `components/` are listed explicitly; a new one needs a new line.
+- **Deleted**: root `sites/`, `common/bin/migrate-to-sites.ts` (the *controller* stays — the
+ Sites page's Migrate button calls it), `create-archives.sh`'s commented tail,
+ `export/.compose-cache/`, and `PARALLEL_TRANSCRIBE_LIMIT` from three doc tables.
+ `export/.r2-staging/` (2.5 GB) deliberately untouched. **Moved**: the three bake-off
+ harnesses (3,828 lines, 58% of `common/bin/`) to `plans/tools/`.
+- **One `pageFileName`, one `CONTRACT`.** The page-shard name existed **six** times, not
+ three. `CONTRACT` in `lib/corpus.ts` owns eight values plus the layer names; the seven
+ existing constants re-export its fields.
+
+**Decision taken here:** the bake-off move needed one new file, `common/bin/_lmdb.ts`, because
+`plans/` is not a workspace package and a bare `import "lmdb"` resolves upward from the
+importing file. Making `plans/tools` an eighth workspace member was rejected — it would add a
+`Dockerfile` COPY and a package to a workspace this plan exists to shrink.
+
+**BLOCKER FOR ANY EDITOR e2e IN THE PRIMARY CHECKOUT, and it is not ours.**
+`editor/content` is an untracked **symlink to `/home/user/Projects/recipe-content`** (made
+2026-08-31, after the last recorded e2e on 2026-08-30). Tailwind's automatic source detection
+follows it and Turbopack **panics** compiling `editor/app/globals.css`:
+`FileSystemPath("editor").join("../../recipe-content") leaves the filesystem root`. The dev
+server never serves a page, so `pnpm e2e` dies before the first spec. **Reproduced on `main`
+at `5ffb0e1` with the same panic**, so it is environmental, not a Phase 0 regression — and
+`pnpm --filter editor exec next build` is unaffected (production builds do not run the dev
+source scan). The operator should remove or move that symlink.
+
+**The suite itself is green.** Run from a detached `git worktree` of `1691c4f` (no such
+symlink there): **511 passed, 0 failed, 23.8 min**, one worker, serial behind the queue lock.
+Nothing to compare against FACTS.md's known-failing list — that list was superseded
+2026-07-30 and the run had no reds at all. One extra step a worktree needs: its
+`export/public` holds only the tracked static assets, so the export webServer 500s and
+Playwright times out after 120 s. Seed it with a composed fixture site (the same one used for
+the wire diff) and it serves 200.
+
+**Next:** phase 1 (dispatch: one scheduler), 4–5 slices, starting with persisting the arbiter.
+Read `common/architecture.test.ts`'s allow-list first — it is the shortest accurate statement
+of what is still tangled.
+
+**Previously:** 2026-08-31 — **the transcode operation is removed** (`904f1a9` → `f717a36`): it never fired in production. The census over 68 channels found the `untranscoded` bucket empty in every one, both `failed-transcodings` files 0 bytes, and only four channels even passing the stage's gate — and `resolveAudioFile` falls back to any real audio file anyway, so transcription never needed it. Gone: four controllers, four job kinds, the channel stage and station, the catalog entry and with it `appliesTo`/`operationApplies` (catalog 8 → 7). Kept: `transcodeAudio` and the download path, the per-file *Transcode …* rows, both wrong-format sweeps — whose bucket is `wrongFormatAudio` now. The slice-5 leftovers rode first (`measure-nav.mjs`, `BUILD_KINDS`, `EditorAliasesClient`). Plan: [`editor-transcode-removed.md`](editor-transcode-removed.md).
**Previously:** 2026-08-30 — **editor IA slice 5 shipped** (`140212a` → `df9b839`):
**a site has tabs, and the family has one page.** Charts, Search aliases, Deploy, Build and
diff --git a/plans/one-core.md b/plans/one-core.md
@@ -382,3 +382,59 @@ umbrella), each slice committed with its measurements in the commit body, `STATE
updated before context is cleared. The first concrete slice to write is **Phase 0** — it
is one session, deletes only dead weight, and its architecture test is what makes every
later deletion cheap to verify.
+
+## Phase 0, as shipped
+
+Branch `one-core/phase-0`, 2026-09-07, six commits (`df5eb48` → `1691c4f`). Every item is a
+deletion, a move or a guard; nothing on disk in a corpus changed and nothing on the wire
+changed.
+
+**report-to-video is under umtool.** `umtool/report-to-video/`, package
+`umtool-report-to-video`. Behaviour, bins and exports are untouched — `cues.mjs`'s `REPO_ROOT`
+is still two levels up, so `DEFAULT_CHANNELS_DIR` is unchanged. The survey undercounted the
+import sites: there were **eight**, not four (add `components/projects/ClaimBench{,Page}.tsx`,
+`lib/report/manifest.mjs`) plus three path references and `Dockerfile`'s manifest COPY.
+
+**The layering is a test.** `common/architecture.test.ts`. Back-edges went **17 import lines /
+16 distinct edges → 13 / 12**; the four burned down were all type-only and moved into a new
+`common/lib/autoQueueTypes.ts` (the persisted auto-queue shape, plus `AutoQueueKind`), which
+`jobs/autoQueuePolicy.ts` and `jobs/autoQueueState.ts` re-export so no import site outside
+`common/lib` changed. The allow-list can only shrink: a stale entry fails the suite too. Twelve
+remain, each with its reason in the file — nine of them wait on phase 1 (the registry's `run()`
+closures are THE structural back-edge), one on phase 3 slice 4 (the auto-queue sanitizers are
+settings-schema code), one on phase 3 slice 1 (`StreamActionResult` becomes a view-model).
+
+**The surface is declared.** `common/package.json` has an `exports` map; `mcp/package.json`
+declares `yt-dlp-transcript-common` (it had been resolving by hoisting). One trap paid for
+here: `"./components/*": ["./components/*.tsx", "./components/*.ts"]` **type-checks and does
+not build** — Turbopack takes the first entry of a fallback array and stops. The 25 `.ts`
+modules under `components/` are listed explicitly instead, and a new one needs a new line.
+
+**Deleted**: root `sites/`, `common/bin/migrate-to-sites.ts` (the controller stays — the Sites
+page's Migrate button calls it), `create-archives.sh`'s commented tail, `export/.compose-cache/`
+(`.r2-staging/` deliberately kept), and `PARALLEL_TRANSCRIBE_LIMIT` from three doc tables.
+**Moved**: the three bake-off harnesses (3,828 lines, 58% of `common/bin/`) to `plans/tools/`.
+That needed one new file — `common/bin/_lmdb.ts` — because `plans/` is not a workspace package
+and a bare `import "lmdb"` resolves from the importing file upward.
+
+**One `pageFileName`, one `CONTRACT`.** The page-shard name existed **six** times, not three:
+`lib/manifest.ts` ×3 plus `stats.ts`, `posts.ts`, `digests.ts` (a seventh copy in
+`components/searchIndex.worker.ts` is left alone — a bundled worker). `CONTRACT` in
+`lib/corpus.ts` owns eight values and the layer names; the seven existing constants are
+re-exports of its fields. Proved on the wire by composing a fixture site before and after:
+16 public files and 7 index files, **identical apart from `generatedAt`** (and the archive
+zip's entry mtimes; its contents diff clean).
+
+**What phase 1 should read first**: the allow-list in `common/architecture.test.ts`. It is now
+the shortest accurate statement of what is still tangled, and every entry names the slice that
+untangles it.
+
+**Verification, in full**: `tsc --noEmit` clean in common, editor, export, mcp, homepage and
+umtool after every item; `pnpm --filter yt-dlp-transcript-common test` **876/876** (874 + the
+2 new architecture tests), `pnpm --filter yt-dlp-transcript-mcp test` **205/205**,
+`pnpm test:scripts` **71 passed / 1 skipped**; a real `pnpm --filter editor exec next build`
+(the only test an `exports` map has); the compose byte-diff above; and the editor e2e
+**511 passed / 0 failed in 23.8 min**. The e2e had to run from a `git worktree` — the primary
+checkout's untracked `editor/content` symlink panics Turbopack in dev, on `main` as well as
+here. See `plans/STATE.md`.
+