Archilyzer · Source

archilyzer

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

commit d6aa38431bbd97845456cf2cfef17e192997eebc
parent 1fce53985d71a0e79aa59ed98afc5320a70e25c1
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sat, 12 Sep 2026 11:54:35 -0400

merge: one-core/phase-2-s2c — one shipsPwa, one _headers generator, one hub member type

S2c of Phase 2, reviewed clean: the Cloudflare _headers block is generated
once for site and hub from ordered path lists, byte-identical to the old
hand-written blocks, and the site block now grants CORS to /digests/* and
/duplicates.json (the wire change is its own commit, 3eeec47, with
Cloudflare's parser counting 12 → 14 rules). shipsPwa has one definition;
compose-hub's local HubSiteEntry is gone. Gates on the slice tip: tsc clean,
common 1083, mcp 205, scripts 71/1, both export builds compile, export e2e
172/172, hub 5/5.

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

Diffstat:
Mcommon/bin/compose-hub.ts | 54++++++++++++++----------------------------------------
Mcommon/bin/compose-site.ts | 47++++++-----------------------------------------
Mcommon/lib/archive/contract.ts | 16++++++++++++----
Acommon/lib/archive/headers.test.ts | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommon/lib/archive/headers.ts | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mexport/app/lib/mode.ts | 31++++++++++++++++++++-----------
Mplans/one-core-phase-2.md | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 390 insertions(+), 96 deletions(-)

diff --git a/common/bin/compose-hub.ts b/common/bin/compose-hub.ts @@ -21,44 +21,9 @@ import { buildHubCorpus, renderHubLlmsTxt, renderRobotsTxt, + type HubMemberInput, } from "../lib/corpus"; - -// CORS for the hub's own JSON (hub-sites.json). The hub is primarily a reader, -// but keeping its endpoints CORS-open lets a hub-of-hubs federate it too. Same -// rationale as compose-site.ts. -const CORS_HEADERS = `# Generated by compose-hub.ts — do not edit by hand. -/hub-sites.json - Access-Control-Allow-Origin: * -/site.json - Access-Control-Allow-Origin: * -/summaries/* - Access-Control-Allow-Origin: * -/subs/* - Access-Control-Allow-Origin: * -/transcripts/* - Access-Control-Allow-Origin: * -/stats/* - Access-Control-Allow-Origin: * -/corpus.json - Access-Control-Allow-Origin: * -/llms.txt - Access-Control-Allow-Origin: * -/robots.txt - Access-Control-Allow-Origin: * -`; - -// One entry the hub registry loads at boot to seed its trusted built-in pool. -// The origin is derived from siteUrl on the client (coerceBuiltin), so a pool -// site without a siteUrl can't be federated and is dropped. -type HubSiteEntry = { - siteId: string; - siteTitle: string; - siteUrl: string; - accent?: string; - hubUrl?: string; - pwa: boolean; - contract: number; -}; +import { HUB_CORS_PATHS, renderHeadersFile } from "../lib/archive/headers"; async function exists(p: string): Promise<boolean> { try { @@ -73,8 +38,14 @@ async function main(): Promise<void> { const paths = getPaths(); const publicDir = paths.exportPublicDir; - // Built-in pool: every configured site that publishes a public URL. - const builtins: HubSiteEntry[] = []; + // Built-in pool: every configured site that publishes a public URL. The entry + // the hub registry loads at boot to seed its trusted built-in pool, and the + // input buildHubCorpus maps — ONE type for both (lib/archive/contract.ts + // HubMemberInput), where this file used to restate it as a local + // `HubSiteEntry` with the same fields under a different name. The origin is + // derived from siteUrl on the client (coerceBuiltin), so a pool site without + // a siteUrl can't be federated and is dropped — by both consumers. + const builtins: HubMemberInput[] = []; for (const site of listSites(paths)) { if (!site.siteUrl) continue; builtins.push({ @@ -114,7 +85,10 @@ async function main(): Promise<void> { renderRobotsTxt({ siteUrl: hub.siteUrl }), ); - await writeFile(path.join(publicDir, "_headers"), CORS_HEADERS); + await writeFile( + path.join(publicDir, "_headers"), + renderHeadersFile("compose-hub.ts", HUB_CORS_PATHS), + ); // The hub always ships a PWA. Copy the hub service worker into place. Until // the dedicated hub SW lands (Phase 7), fall back to the site SW so the PWA diff --git a/common/bin/compose-site.ts b/common/bin/compose-site.ts @@ -32,6 +32,8 @@ import type { Manifest, SubsManifest } from "../lib/manifest"; import type { PostsManifest } from "../lib/posts"; import type { DigestsManifest } from "../lib/digests"; import { buildSiteDescriptor, type PublicSiteDescriptor } from "../lib/siteDescriptor"; +import { shipsPwa } from "../lib/archive/contract"; +import { renderHeadersFile } from "../lib/archive/headers"; import { effectiveSiteAliases } from "../lib/aliasesStore"; import { buildSiteCorpus, @@ -52,39 +54,6 @@ import { type ArchiveManifestEntry, } from "../lib/archiveOptions"; -// CORS + cache headers for Cloudflare Pages (a static `_headers` file at the -// deploy root). All served data is public static JSON with no credentials, so -// `Access-Control-Allow-Origin: *` is correct and lets a federating hub on any -// origin read it. Default GETs with no custom headers are CORS-simple → no -// preflight needed. `output: "export"` can't emit these via next.config, and -// `serve`/`next dev` ignore this file (local/e2e CORS lives in serve.json). -const CORS_HEADERS = `# Generated by compose-site.ts — do not edit by hand. -/site.json - Access-Control-Allow-Origin: * -/search-aliases.json - Access-Control-Allow-Origin: * -/summaries/* - Access-Control-Allow-Origin: * -/subs/* - Access-Control-Allow-Origin: * -/transcripts/* - Access-Control-Allow-Origin: * -/posts/* - Access-Control-Allow-Origin: * -/stats/* - Access-Control-Allow-Origin: * -/archives/* - Access-Control-Allow-Origin: * -/corpus.json - Access-Control-Allow-Origin: * -/llms.txt - Access-Control-Allow-Origin: * -/robots.txt - Access-Control-Allow-Origin: * -/sitemap.xml - Access-Control-Allow-Origin: * -`; - // Emit the public federation contract: /site.json (branding + channels + // freshness) and the CORS _headers file. Emitted for EVERY site regardless of // whether it ships a PWA — a dumb instance is still federatable. @@ -92,7 +61,10 @@ async function emitFederationFiles( site: Site, paths: ReturnType<typeof getPaths>, ): Promise<void> { - await writeFile(path.join(paths.exportPublicDir, "_headers"), CORS_HEADERS); + await writeFile( + path.join(paths.exportPublicDir, "_headers"), + renderHeadersFile("compose-site.ts"), + ); const manifestPath = path.join(paths.exportSummariesDir, "manifest.json"); if (!(await exists(manifestPath))) return; // no composed data → no descriptor @@ -201,13 +173,6 @@ async function emitAiFiles(paths: ReturnType<typeof getPaths>): Promise<void> { } } -// Whether this build ships an installable PWA. Resolved from the site's `pwa` -// config flag (site builds are dumb instances by default) or forced on in hub -// mode. Keep in sync with export/app/lib/mode.ts shipsPwa(). -function shipsPwa(site: Site): boolean { - return site.pwa === true || process.env.INSTANCE_MODE === "hub"; -} - // Compose the service worker into the served public dir ONLY when this instance // ships a PWA. The SW source lives outside public/ (export/service-worker/) so // a dumb instance emits no /sw.js at all — not just an unregistered one. Mirrors diff --git a/common/lib/archive/contract.ts b/common/lib/archive/contract.ts @@ -143,10 +143,18 @@ export function pageUrl( // installable) and opts in per site via the `pwa` config flag. // // Was two copies with "keep in sync" comments on each (compose-site.ts and -// export/app/lib/mode.ts); this is the one. `process.env.INSTANCE_MODE` is read -// here exactly as both copies read it — Next inlines that member expression -// into the client bundle at build time, so a `typeof process` guard around it -// would turn hub mode OFF in the browser rather than make it safer. +// export/app/lib/mode.ts); S2c deleted both, and this is the one. +// +// `process.env.INSTANCE_MODE` is read bare, exactly as both copies read it, and +// the reason that is safe is NOT that Next inlines it — it does not: +// INSTANCE_MODE is neither `NEXT_PUBLIC_` nor listed in a next.config `env:` +// block, so a client bundle would read `undefined` here. (An earlier draft of +// this comment claimed the opposite; the S1 review checked.) It is safe because +// no browser evaluates it: the only caller in the export app is +// export/app/lib/mode.ts, whose only caller is export/app/layout.tsx, a SERVER +// component, and the other caller is compose-site.ts, a build script. If this +// ever becomes reachable from a client module it needs a +// `typeof process !== "undefined"` guard AND an env var the client can see. export function shipsPwa(site: { pwa?: boolean }): boolean { return site.pwa === true || process.env.INSTANCE_MODE === "hub"; } diff --git a/common/lib/archive/headers.test.ts b/common/lib/archive/headers.test.ts @@ -0,0 +1,113 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + HUB_CORS_PATHS, + SITE_CORS_PATHS, + contractCorsPaths, + renderHeadersFile, +} from "./headers"; + +// The whole generated site block, as a snapshot. `_headers` is a wire artifact: +// a line lost here is a cross-origin reader that stops working on the CDN and +// nowhere else (local `serve` blankets `**/*.json` with CORS). So the file is +// asserted in full — an edit has to be deliberate enough to update this string. +const SITE_HEADERS = `# Generated by compose-site.ts — do not edit by hand. +/site.json + Access-Control-Allow-Origin: * +/search-aliases.json + Access-Control-Allow-Origin: * +/duplicates.json + Access-Control-Allow-Origin: * +/summaries/* + Access-Control-Allow-Origin: * +/subs/* + Access-Control-Allow-Origin: * +/transcripts/* + Access-Control-Allow-Origin: * +/posts/* + Access-Control-Allow-Origin: * +/digests/* + Access-Control-Allow-Origin: * +/stats/* + Access-Control-Allow-Origin: * +/archives/* + Access-Control-Allow-Origin: * +/corpus.json + Access-Control-Allow-Origin: * +/llms.txt + Access-Control-Allow-Origin: * +/robots.txt + Access-Control-Allow-Origin: * +/sitemap.xml + Access-Control-Allow-Origin: * +`; + +const HUB_HEADERS = `# Generated by compose-hub.ts — do not edit by hand. +/hub-sites.json + Access-Control-Allow-Origin: * +/site.json + Access-Control-Allow-Origin: * +/summaries/* + Access-Control-Allow-Origin: * +/subs/* + Access-Control-Allow-Origin: * +/transcripts/* + Access-Control-Allow-Origin: * +/stats/* + Access-Control-Allow-Origin: * +/corpus.json + Access-Control-Allow-Origin: * +/llms.txt + Access-Control-Allow-Origin: * +/robots.txt + Access-Control-Allow-Origin: * +`; + +test("renderHeadersFile: the site block, in full", () => { + assert.equal(renderHeadersFile("compose-site.ts"), SITE_HEADERS); + assert.equal( + renderHeadersFile("compose-site.ts", SITE_CORS_PATHS), + SITE_HEADERS, + ); +}); + +test("renderHeadersFile: the hub block, in full", () => { + assert.equal(renderHeadersFile("compose-hub.ts", HUB_CORS_PATHS), HUB_HEADERS); +}); + +test("the two paths that used to be served without CORS are declared", () => { + // The wire change. A cross-origin viewer could read every other tree and got + // a CORS failure on exactly these two. + assert.ok(SITE_CORS_PATHS.includes("/digests/*")); + assert.ok(SITE_CORS_PATHS.includes("/duplicates.json")); +}); + +test("every contract layer and root file has a CORS line", () => { + for (const p of contractCorsPaths()) { + assert.ok( + SITE_CORS_PATHS.includes(p), + `${p} is on the wire but has no _headers entry`, + ); + } +}); + +test("the hub surface is the site surface plus its own pool file", () => { + for (const p of HUB_CORS_PATHS) { + if (p === "/hub-sites.json") continue; + assert.ok( + SITE_CORS_PATHS.includes(p), + `${p} is declared by the hub but not by a site`, + ); + } +}); + +test("every rendered entry is one path line and one indented header", () => { + const lines = renderHeadersFile("x.ts").split("\n").slice(0, -1); + assert.equal(lines[0], "# Generated by x.ts — do not edit by hand."); + const body = lines.slice(1); + assert.equal(body.length, SITE_CORS_PATHS.length * 2); + for (let i = 0; i < body.length; i += 2) { + assert.ok(body[i].startsWith("/")); + assert.equal(body[i + 1], " Access-Control-Allow-Origin: *"); + } +}); diff --git a/common/lib/archive/headers.ts b/common/lib/archive/headers.ts @@ -0,0 +1,94 @@ +// The Cloudflare Pages `_headers` file, generated from the contract. +// +// A deployed archive is static JSON on a CDN, so the only place CORS can be +// declared is a `_headers` file at the deploy root. `output: "export"` cannot +// emit it from next.config, so the compose steps write it — and until now they +// wrote it TWICE, as two hand-maintained string literals (compose-site.ts and +// compose-hub.ts) that had already drifted apart. This module is the one +// renderer; each compose step supplies the surface it actually serves. +// +// Browser-safe like the rest of `lib/archive/`: pure string building over +// `CONTRACT` / `ROOT_FILES`, no `node:*`. + +import { CONTRACT, ROOT_FILES } from "./contract"; + +// The single header every served document gets. All served data is public +// static JSON with no credentials, so `*` is correct and is what lets a +// federating hub — or an MCP reader — on any origin read it. A default GET with +// no custom headers is CORS-simple, so no preflight is involved. +const CORS_HEADER = "Access-Control-Allow-Origin: *"; + +// Everything a SITE build serves, in the order the file has always listed it. +// The order is preserved deliberately: `_headers` is matched top-down by +// Cloudflare, the file is diffed against a committed fixture, and reordering it +// would be a wire change with no benefit. +// +// `/digests/*` and `/duplicates.json` were the two gaps — both composed into +// every site's public dir, neither declared here, so a cross-origin viewer got +// a CORS failure on the digest shards and on the duplicates report while every +// other tree read fine. Local `serve` hands `**/*.json` a blanket +// `Access-Control-Allow-Origin: *` (export/serve.json), which is exactly why no +// e2e run ever saw it. +// +// Kept honest by headers.test.ts: every `CONTRACT.layers` tree and every +// `ROOT_FILES` document must appear here, so adding a layer to the contract and +// forgetting its CORS line fails a test rather than a deploy. +export const SITE_CORS_PATHS: readonly string[] = [ + "/site.json", + "/search-aliases.json", + "/duplicates.json", + "/summaries/*", + "/subs/*", + "/transcripts/*", + "/posts/*", + "/digests/*", + "/stats/*", + "/archives/*", + "/corpus.json", + "/llms.txt", + "/robots.txt", + "/sitemap.xml", +]; + +// What a HUB build serves. The hub holds no shard data of its own — it reads +// every member cross-origin at runtime — so this is a subset of the site +// surface plus `/hub-sites.json`, the built-in trusted pool compose-hub writes. +// The shard-tree entries it does list are vestigial but harmless: a header rule +// for a path this origin never serves costs nothing, and a hub-of-hubs should +// not have to care which of the two shapes it was pointed at. Nothing here +// moves when the SITE list gains a tree — the hub serves no digest shards and +// no duplicates report, so it declares neither. +export const HUB_CORS_PATHS: readonly string[] = [ + "/hub-sites.json", + "/site.json", + "/summaries/*", + "/subs/*", + "/transcripts/*", + "/stats/*", + "/corpus.json", + "/llms.txt", + "/robots.txt", +]; + +// Render a `_headers` file: a banner naming the generator, then one path / +// indented-header pair per served surface. `generator` is the script name so a +// reader of a deploy artifact knows what to edit instead of the file. +export function renderHeadersFile( + generator: string, + paths: readonly string[] = SITE_CORS_PATHS, +): string { + const out = [`# Generated by ${generator} — do not edit by hand.`]; + for (const p of paths) { + out.push(p, ` ${CORS_HEADER}`); + } + return `${out.join("\n")}\n`; +} + +// The contract surfaces a site must declare, for the drift test. Exported so +// the assertion lives with the data rather than being re-derived in the test. +export function contractCorsPaths(): string[] { + return [ + ...ROOT_FILES.map((f) => `/${f}`), + ...CONTRACT.layers.map((l) => `/${l}/*`), + ]; +} diff --git a/export/app/lib/mode.ts b/export/app/lib/mode.ts @@ -1,4 +1,5 @@ import { currentSite } from "./site"; +import { shipsPwa as contractShipsPwa } from "yt-dlp-transcript-common/lib/archive/contract"; // The export app renders one of two shells, selected at build time: // site (default): a single-site archive, exactly as before. @@ -13,17 +14,25 @@ export function instanceMode(): InstanceMode { } // Whether this build ships an installable PWA — the "dangerous permissions" -// surface: a service worker, a web manifest, and installability. This is an -// axis INDEPENDENT of the shell: -// - hub mode always ships the PWA (Archilyzer IS the installable app); -// - site mode is a dumb instance by default (federatable JSON only, not -// installable) and opts in per site via the `pwa` config flag. +// surface: a service worker, a web manifest, and installability. See +// lib/archive/contract.ts shipsPwa() for what the axis means; this wrapper only +// supplies the site, which the export app resolves at build/render time. // -// Resolved purely from config that is already available at build/render time -// (INSTANCE_MODE env + the selected site's `pwa`), so the app shell and the -// compose step agree without threading an extra env var. Keep this in sync with -// compose-site.ts's shipsPwa(). +// Was a second copy of the predicate with a "keep in sync" comment pointing at +// compose-site.ts's copy. Both are gone; there is one. +// +// Hub mode needs no branch of its own any more: currentSite() already returns +// hubSite(), which sets `pwa: true` (export/app/lib/site.ts), and the contract +// predicate reads INSTANCE_MODE itself — so the old +// `if (instanceMode() === "hub") return true` short-circuit was true twice over. +// +// SERVER-ONLY, deliberately: the contract predicate reads +// `process.env.INSTANCE_MODE`, which is not a `NEXT_PUBLIC_` var and is not in a +// next.config `env:` block, so Next does NOT inline it into a client bundle. +// The only caller is export/app/layout.tsx, a server component — and +// currentSite() is server-only regardless, since it reads the sites dir. +// Calling this from a client component would read `undefined` and silently turn +// hub mode off; that would need a guard there, not an assumption here. export function shipsPwa(): boolean { - if (instanceMode() === "hub") return true; - return currentSite().pwa === true; + return contractShipsPwa(currentSite()); } diff --git a/plans/one-core-phase-2.md b/plans/one-core-phase-2.md @@ -507,3 +507,134 @@ one TypeScript file touched. 4. **Test fixtures use `tmpdir()`, not the agent scratch dir.** A test that hard-coded a job scratch path would not survive the job. `CUES_TEST_DIR` overrides the root, and that is what the scratch-dir runs used. +### 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.