commit 6b5638671c099c6abd2ecf88dd5ac03dcecc3838
parent 3eeec47ffaae52677beca6a0562f031a6af59820
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 12 Sep 2026 11:50:09 -0400
plans: S2c shipped — one shipsPwa, one _headers generator, one hub entry type
Appended at the END of one-core-phase-2.md §Record, so S2a and S3 can append
theirs beside it without a conflict. Sha range 46c1b05..3eeec47, every gate's
actual number, the `_headers` before/after for both site and hub, and four
divergences with the reason each one was taken.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
1 file changed, 132 insertions(+), 0 deletions(-)
diff --git a/plans/one-core-phase-2.md b/plans/one-core-phase-2.md
@@ -382,3 +382,135 @@ 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.
+
+### S2c — shipped 2026-09-12
+
+Branch `one-core/phase-2-s2c`, off `7f86aef` (the
+`integrate/2026-09-storage-priority` tip, S1 merged). Four commits,
+`706a9ed` → `d1b3903`, plus this note, unmerged. **One deliberate wire
+change — two CORS lines, described below — and nothing else moved: no URL
+shape, no `corpus.json` byte, no CONTRACT version, no architecture
+allow-list entry, no new `components/*.ts`.**
+
+| commit | what |
+|---|---|
+| `706a9ed` | compose-hub's local `HubSiteEntry` deleted; it uses S1's `HubMemberInput` |
+| `674aeb4` | one `shipsPwa()` — compose-site.ts's and mode.ts's copies deleted |
+| `a168358` | `lib/archive/headers.ts`: one `_headers` generator, byte-identical output |
+| `d1b3903` | **wire change**: `/digests/*` and `/duplicates.json` get CORS |
+
+#### The `_headers` before/after
+
+The two additions are the whole diff, for the site. Composing the FACTS.md
+fixture recipe and diffing against
+`plans/tools/compose-fixture-one-youtube-channel/public/_headers`:
+
+```
+5a6,7
+> /duplicates.json
+> Access-Control-Allow-Origin: *
+12a15,16
+> /digests/*
+> Access-Control-Allow-Origin: *
+```
+
+Four added lines; every other line byte-identical, and the rest of the
+composed dir IDENTICAL modulo the build clock (16 files under `public/`,
+7 under `index/`). The **hub** block does not move at all: compose-hub at
+`7f86aef` and at the tip produce a byte-identical `_headers` and an
+identical `hub-sites.json`, with `corpus.json` differing only in
+`generatedAt`.
+
+Why the gap existed and why no test had caught it: `_headers` only exists on
+the CDN, and every local server in this repo is more permissive than
+Cloudflare. `serve` — what the export, hub and 2-origin suites all run
+behind — gives `**/*.json` a blanket `Access-Control-Allow-Origin: *`
+(`export/serve.json`). So a cross-origin viewer could read a federated
+site's transcripts, subs, posts, summaries, stats and archives, and got a
+CORS failure on its digests and its duplicates report, and nothing local
+could reproduce it.
+
+**`curl -I` cannot show this, and the reason is worth recording** rather
+than quoting a run that proves nothing. `wrangler pages dev` is the only
+local server here that reads `_headers` at all, and it adds
+`Access-Control-Allow-Origin: *` to every response of its own accord — a
+file named in no rule whatsoever still comes back with the header (checked
+with a `zzz-not-in-headers.json` dropped into the same composed dir). Before
+and after are indistinguishable over HTTP locally. What wrangler does report
+is its own parse of the file, on the composed fixture:
+
+```
+before: ✨ Parsed 12 valid header rules.
+after: ✨ Parsed 14 valid header rules.
+```
+
+Cloudflare's own parser, counting the two new rules as valid. The real
+before/after is a deploy.
+
+#### Divergences from the S2c brief
+
+**1. The generator is `lib/archive/headers.ts`, not `contract.ts`.** The
+brief allowed either. It is its own module because it carries two ordered
+path LISTS, not just a renderer: `_headers` is matched top-down, the file is
+diffed against a committed fixture, and the existing order is not the order
+`ROOT_FILES` + `CONTRACT.layers` would produce — so deriving the list would
+have reordered every line and buried the two-line wire change in noise. The
+contract still gets the last word: `contractCorsPaths()` + a test assert
+every `CONTRACT.layers` tree and every `ROOT_FILES` document has a line, so
+adding a layer and forgetting its CORS entry now fails a test rather than a
+deploy. No `node:*` import either way.
+
+**2. The hub does NOT get the two new lines.** The brief's wire change is
+"add `/digests/*` and `/duplicates.json` to the CORS set"; applied to the
+hub that would declare headers for paths a hub never serves. A hub holds no
+shard data — it reads every member cross-origin at runtime — so its block is
+unchanged, and the hub half of the fixture diff is empty. (`HUB_CORS_PATHS`
+does still list four shard trees the hub does not serve either; those are
+pre-existing and left alone, since removing them would be a second,
+unrelated change to the same file.)
+
+**3. `mode.ts`'s hub short-circuit is deleted, not kept.** The brief said to
+replace the copy with a call. The replacement made
+`if (instanceMode() === "hub") return true;` dead weight: `currentSite()`
+already returns `hubSite()`, which sets `pwa: true`, and the contract
+predicate reads `INSTANCE_MODE` itself. Same answer in both modes, so
+`shipsPwa()` is now one line.
+
+**4. `shipsPwa` stays server-called, and `contract.ts`'s comment about it is
+corrected.** No `typeof process` guard was added, per S1's divergence 3 — the
+callers are `compose-site.ts` (a build script) and `export/app/lib/mode.ts`,
+whose only caller is `export/app/layout.tsx`, a server component;
+`currentSite()` reads the sites dir, so `mode.ts` could not be client-side
+regardless. The comment S1 left on `shipsPwa` still asserted that Next
+inlines `process.env.INSTANCE_MODE` into the client bundle and that a guard
+would therefore break hub mode; the S1 review established that is false.
+That comment is rewritten to say why the bare read is actually safe and what
+a future client caller would owe. **`contract.ts`'s diff in this slice is
+comment-only** — verified by diffing with comment lines filtered out.
+
+#### Gates
+
+- `pnpm -r exec tsc --noEmit` — clean, exit 0, all six packages, after every
+ commit.
+- `pnpm --filter yt-dlp-transcript-common test` — **1083 passed / 0 failed**
+ (baseline 1077 at `7f86aef`, + 6 `headers.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` (site mode) — **compiled
+ successfully**, 11 static pages, 9 routes. The only warning is the
+ pre-existing NFT trace S1 documented.
+- `pnpm --filter export run build:hub` — **compiled successfully in 4.2s**,
+ TypeScript finished, then dies at exactly the known step:
+ `Error: useSearchSession must be used within a SearchSessionProvider`
+ prerendering `/ask`. Red on the base commit for a reason in
+ `common/components/SearchSessionContext.tsx`, which this slice does not
+ open; see S1's note. Compile and type-check are the part this slice needs,
+ and they pass.
+- **compose-site / compose-hub fixture diffs** — above.
+- **e2e**, behind the queue lock from the worktree (port block #12 —
+ editor 4201 / export-e2e 4220): export `e2e` **172 passed / 0 failed** (5.5 min),
+ `e2e:hub` **5 passed / 0 failed**. `e2e:2origin` was **not run**: it shells
+ `build:hub`, which is red on the base for the reason above, so it cannot
+ reach a spec — exactly as S1 recorded.