commit d4998ac63b2ca741f3acf1503e3230bfe20fda30
parent d2170bfb377377ccc4fba23afd7c83a28c593707
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 12 Sep 2026 02:20:07 -0400
plans: S1 recorded — sha range, the four divergences, and every gate number
`plans/one-core-phase-2.md` §Record now carries what S2a, S2b, S2c, S3 and
S0-pause need before they branch off this tip.
The four divergences, each because the code said so rather than the plan:
`contract.ts` OWNS `CONTRACT` and `pageFileName` instead of re-exporting them
(the alternative is a TDZ ReferenceError through `manifest.ts`'s module-scope
read, and the note says so with the trace); the four hub spellings collapse to
two types plus a `Pick`, because `corpus.json` spells a published member
`title`/`url` and the input spells it `siteTitle`/`siteUrl` and the wire is
frozen; `shipsPwa` reads `process.env.INSTANCE_MODE` bare, because a `typeof
process` guard would defeat Next's build-time inlining and silently turn the hub
PWA off in the browser; and `stats/` gets URLs through a wider `ArchiveTree`
rather than joining the published `CONTRACT.layers`.
Gate numbers as measured, not paraphrased: common 1077/1077 (from 1051), mcp
205/205, test:scripts 71+1 skipped, tsc clean in six packages, `next build`
compiled, the bench's eight structural rows identical byte for byte on a 1.3 GB
corpus, and compose-site identical modulo the build clock.
`e2e:2origin` gets its own subsection because it is RED AND NOT OURS: it dies in
globalSetup's `pnpm run build:hub`, prerendering `/ask` with "useSearchSession
must be used within a SearchSessionProvider". Re-run on `8fd36c5` itself in this
worktree — same error, same page, same chunks. Export e2e was 172/172 and hub
5/5. Recorded with what the failure still proves: the hub-mode build compiled
and type-checked before the prerender, so `lib/archive/*` resolves in both modes
and `reader-fs.ts` reaches no client chunk.
Also corrected in place: four file paths in §Corrections point at directories
that do not exist (`SearchSessionContext.tsx` / `SearchDataContext.tsx` are under
`common/components/`, `useAskChat.ts` is `export/app/ask/`, `siteRegistry.ts` is
`common/components/`, and the deliberate worker copy is
`common/components/searchIndex.worker.ts`). S2a and S3 would each have burned a
grep on that.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
1 file changed, 140 insertions(+), 0 deletions(-)
diff --git a/plans/one-core-phase-2.md b/plans/one-core-phase-2.md
@@ -238,3 +238,143 @@ Monitor, commit incrementally, add by path, never `next build` in the primary ch
## Record
Filled in as slices ship: sha range, actual gate numbers, every divergence from this plan.
+
+### S1 — shipped 2026-09-12
+
+Branch `one-core/phase-2-s1`, off `c7f7b90` (the `integrate/2026-09-storage-priority`
+tip). Five commits, `8d60ad9` → `f085667`, unmerged. **No URL shape moved, no
+`corpus.json` byte moved, no CONTRACT version moved, no architecture allow-list
+entry added or burned.**
+
+| commit | what |
+|---|---|
+| `8d60ad9` | `archive/contract.ts` + `archive/io-stats.ts`; `buildSiteCorpus` calls the URL builders |
+| `3c631a2` | the reader: `reader.ts` / `reader-fs.ts` / `reader-hub.ts` + `reader.test.ts` |
+| `43be5f9` | `mcp/src/source.ts`: 1,386 lines → 47, a re-export |
+| `f2f688d` | the five `lib/search/*` stubs S3 fills |
+| `f085667` | `plans/tools/compose-fixture-one-youtube-channel/` |
+
+#### Four divergences from the plan above, each because the code said so
+
+**1. `contract.ts` OWNS `CONTRACT` and `pageFileName`; it does not re-export
+them.** The plan (and the umbrella) said re-export from `lib/corpus.ts` /
+`lib/manifest.ts`. That is a hard ESM failure, not a style question: `corpus.ts`
+must import the URL builders (that is the point — one definition of the shape it
+emits), `contract.ts` needs `CONTRACT.pagePad` for `pageFileName`, and
+`manifest.ts` reads `CONTRACT.manifest` **at module scope**. Any arrangement that
+leaves `CONTRACT` in `corpus.ts` closes the loop `corpus → archive/contract →
+manifest → corpus`, and the first module to be imported gets
+`ReferenceError: Cannot access 'CONTRACT' before initialization`. So the contract
+module is the BOTTOM of the stack — it imports only `lib/duplicates.ts` — and
+`corpus.ts` / `manifest.ts` re-export the names. Every existing import site
+(`from "./corpus"`, `from "./manifest"`, the three `*PageFileName` aliases) is
+byte-identical. Verified by importing each of the ten modules in the cycle under
+`tsx`, entry-point by entry-point.
+
+**2. The four hub spellings collapse to TWO types plus one `Pick`, not one.**
+The reason is on the wire. `HubMemberInput` and compose-hub's `HubSiteEntry` are
+the same direction and the same vocabulary (`siteTitle`/`siteUrl`) — those
+genuinely become one, and S2c deletes the local copy. But `HubCorpusSite` is the
+entry **published** in a hub `corpus.json`, and it spells the same member
+`title`/`url` with two derived pointers beside it. `corpus.json` is frozen, so
+the emitted shape cannot be renamed to match the input shape. What the slice does
+instead is make mcp's `HubSite` a `Pick<HubCorpusSite, "siteId"|"title"|"url">`,
+so the read-back spelling can no longer drift from the published one.
+
+**3. `shipsPwa` reads `process.env.INSTANCE_MODE` bare, with no `typeof process`
+guard** — unlike `io-stats.ts` and the page-cache knob, which are guarded. That
+is deliberate and the guard would be a BUG: Next inlines the literal member
+expression `process.env.INSTANCE_MODE` into the client bundle at build time, but
+a browser has no `process` global, so `typeof process !== "undefined" &&
+process.env.INSTANCE_MODE === "hub"` evaluates to `false` in a hub build's
+browser and silently turns the PWA off. The two knobs that ARE guarded use the
+dynamic `process.env?.[name]` form, which Next does not inline — correct for a
+server-only setting whose browser answer is "take the default".
+
+**4. `stats/` gets URLs without joining `CONTRACT.layers`.** It follows the same
+manifest → page walk, but `corpus.json`'s `shardScheme` does not document it, so
+adding it to the published layer list would be a wire change. The builders take a
+wider `ArchiveTree = ContractLayer | "stats"` instead and the layer list stays
+frozen.
+
+One thing the plan did not mention and that had to change: **common's test glob
+only reached one directory deep**, so `lib/archive/*.test.ts` would have been
+collected by nobody. `common/package.json`'s `test` script now also globs
+`{lib,…}/*/*.test.ts`. Nothing else lives two deep today, so no existing test
+moved in or out.
+
+#### Gates
+
+- `pnpm -r exec tsc --noEmit` — clean in all six packages, after every commit.
+- `pnpm --filter yt-dlp-transcript-common test` — **1077 passed / 0 failed**
+ (baseline 1051 at `c7f7b90`, + 9 `contract.test.ts`, + 17 `reader.test.ts`;
+ none lost). The architecture test passes with its allow-list untouched.
+- `pnpm --filter yt-dlp-transcript-mcp test` — **205 passed / 0 failed**,
+ unchanged.
+- `pnpm test:scripts` — **71 passed / 1 skipped**, unchanged.
+- `pnpm --filter export exec next build` — **compiled successfully**, 11 static
+ pages. This is the only test that `reader-fs.ts` is unreachable from a client
+ module. (The one warning is the pre-existing NFT trace on
+ `next.config.ts → channelMedia.ts → controller/channels.ts → app/offline/page.tsx`
+ — the documented reason this app does not use `output: "standalone"`.)
+- `pnpm --filter yt-dlp-transcript-mcp bench --repeat 1 --force`, before at
+ `c7f7b90` and after, both `--local` the same composed 1.3 GB site, identical
+ fingerprint (3,358 summaries videos; stats, duplicates and digests all
+ present). **Every structural counter identical, byte for byte:**
+
+ | case | reads | bytes parsed |
+ |---|---|---|
+ | cold-channels | 0 → 0 | 0 → 0 |
+ | rare, whole corpus | 170 → 170 | 1,335,885,512 → 1,335,885,512 |
+ | common, whole corpus | 170 → 170 | 1,364,679,296 → 1,364,679,296 |
+ | channel-scoped | 4 → 4 | 28,847,054 → 28,847,054 |
+ | date-scoped (filter-first) | 32 → 32 | 213,451,503 → 213,451,503 |
+ | state-scoped (filter-first) | 9 → 9 | 73,399,988 → 73,399,988 |
+ | enumerate, whole corpus | 170 → 170 | 1,364,679,296 → 1,364,679,296 |
+ | get_transcripts × 20 ids | 4 → 4 | 28,847,054 → 28,847,054 |
+
+ The per-case scan notes match too, `pruned` flags included (date-scoped 28
+ pages pruned, state-scoped 9). Wall ms is noise and is not quoted.
+- **compose-site byte-identity** over the FACTS.md fixture recipe, at `c7f7b90`
+ and at the tip: 16 files under `public/`, 7 under `index/`, identical modulo
+ the build clock. The artifact and the normalising diff are committed at
+ `plans/tools/compose-fixture-one-youtube-channel/`; re-running the README's own
+ commands into two fresh temp dirs reproduces it.
+- **e2e**, behind the queue lock from the worktree (port block #9 —
+ 3901/3911/3920): export `e2e` **172 passed / 0 failed**, `e2e:hub`
+ **5 passed / 0 failed**. `e2e:2origin` **could not run**, and the reason is
+ not this slice — see below.
+
+#### `e2e:2origin` is red on the base commit, for a reason S1 does not touch
+
+It never reaches a spec. `playwright.2origin.config.ts:16` calls
+`e2e-2origin/globalSetup.ts:142`, which shells `pnpm run build:hub`, and the hub
+build dies prerendering `/ask`:
+
+```
+Error: useSearchSession must be used within a SearchSessionProvider
+Export encountered an error on /(workspace)/ask/page: /ask, exiting the build.
+```
+
+**Verified on `c7f7b90` itself** — `git switch --detach c7f7b90` in this
+worktree, `pnpm --filter export run build:hub`: the same error, on the same
+page, from the same two chunks (`SearchSessionContext`, `AskChat`). This is the
+"hub `/ask` broken on main blocks `e2e:2origin`" failure already recorded with
+the export responsive redesign, and nothing in S1 is in that path: the reader
+never enters a React tree, and the provider is
+`common/components/SearchSessionContext.tsx` — S2a/S3's file, untouched here.
+(Note for those slices: four paths in §Corrections above are wrong, checked
+2026-09-12 — `SearchSessionContext.tsx` and `SearchDataContext.tsx` are under
+`common/components/`, not `export/app/lib/`; `useAskChat.ts` is
+`export/app/ask/useAskChat.ts`; `siteRegistry.ts` is `common/components/`, not
+`common/lib/`; and the deliberate worker copy is
+`common/components/searchIndex.worker.ts`, not `export/app/lib/`. The files all
+exist; only the directories in the plan are wrong.)
+
+Worth noting what it DOES prove: the site-mode `next build` passed here and the
+HUB-mode one compiled and type-checked before the prerender — so the bundle
+resolves `lib/archive/*` in both modes and never pulls `reader-fs.ts` into a
+client chunk. The prerender is the only step that fails, and it fails at base.
+
+`plans/tools/jeralyzer-corpus-2026-09-12.json` was not re-fetched; the live diff
+is S3's gate, after the search pipeline lands.