commit 53d1d4a75ab642a5e62897ac13b23fa48a8353f2
parent c7d6311ec4fa8a6b81017e15d6ec7e3bf5a81105
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 1 Oct 2026 23:03:45 -0400
plans: slice XP — the review, its fixes, the rollout steps (the hub rebuild, old Pages deployments) and the gates after it
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 90 insertions(+), 2 deletions(-)
diff --git a/plans/FACTS.md b/plans/FACTS.md
@@ -8265,6 +8265,12 @@ phase deletes from the destination.
(`resolveHubUrl`), its `corpus.json` says `site.audience: "private"`, and every deploy path
refuses it or its bundle before any upload (`lib/builtExport.ts` `deployAudienceProblem`; the
bulk deploys skip it). Build & deploy all still BUILDS it.
+- **The hub carries no site's data** (release 17 XP review): `public/` is shared, so a hub built after
+ a site's compose used to ship that site's data trees. `compose-hub` now removes every per-site entry
+ first (`SITE_ONLY_PUBLIC_ENTRIES`) and writes the global `search-aliases.json`; `builtHubProblem`
+ refuses a hub bundle carrying a data tree. **compose-site's own stages (summaries, stats,
+ duplicates) are trusted from its cache only when `public/site.json` names the site**; the
+ per-channel trees keep their signatures across sites.
- **gallery-dl 1.32.9** takes `--cookies-from-browser BROWSER[/DOMAIN][+KEYRING][:PROFILE][::CONTAINER]`
(yt-dlp's syntax plus `/DOMAIN`) and reads every browser it supports, Chromium's encrypted store
included, on each run. The spec is passed verbatim (`galleryDlCookieChoice`).
diff --git a/plans/release-17.md b/plans/release-17.md
@@ -435,8 +435,14 @@ loop (`buildIndex.ts` ~1943 and the fingerprint), a different hunk from T1's `sc
pnpm archilyzer index # or any editor build: the index build writes the per-site manifests
BUILD_ARCHIVES=0 EXPORT_PUBLIC_DIR="$HOME/archives/<private-id>" \
EXPORT_INDEX_DIR="$PWD/export/.export-index" pnpm archilyzer compose site <private-id>
- claude mcp add archilyzer -- pnpm --silent -C "$PWD" archilyzer mcp --local "$HOME/archives/<private-id>"
+ claude mcp remove archilyzer -s local # the name must be free; use the scope it was added in
+ claude mcp add archilyzer \
+ --env ARCHILYZER_EDITOR_URL=http://localhost:3001 \
+ --env WORKER_TOKEN=… \
+ -- pnpm --silent -C "$PWD" archilyzer mcp --local "$HOME/archives/<private-id>"
```
+ The two `--env` lines are what `fetch_clip` needs (the editor's own `WORKER_TOKEN`, from
+ `editor/.env`); the name stays `archilyzer` for `/ask` and `/sweep` (AGENTS.md).
(`EXPORT_PUBLIC_DIR` must be absolute — the command runs in `common/`; the compose cache lands beside
it, in `$HOME/archives/.compose-cache/`.) The site's editor **Build** works too: it composes into
`export/public` and builds into `export/out`. **The current registration, `--local
@@ -457,7 +463,11 @@ loop (`buildIndex.ts` ~1943 and the fingerprint), a different hunk from T1's `sc
| `75ccbef3` | `editor(e2e), export(e2e):` `x-session` and `sites-crud` cases; `export/e2e/x-posts-private.spec.ts` |
| `30c14aa0` | `common:` compose never ships a posts tree the index withheld; the cache trusted only over the site's own last compose |
| `63002de3` | `common:` the per-channel trees keep their signatures across sites |
-| this commit | `plans:` this section, the ruling, the slices row; FACTS; the editor changelog |
+| `4ed7d410` | `plans:` this section, the ruling, the slices row; FACTS; the editor changelog |
+| `6466a68e` | `common:` the hub carries no site's data; `builtHubProblem` refuses one that does (review HIGH 1) |
+| `385e4eb1` | `common:` a compose over a stale index lists no withheld channel; the comments (LOW 3, NIT 7) |
+| `8e448fde` | `editor:` the changelog (LOW 5) |
+| this commit | `plans:` the review, its record, the rollout steps and the gates after it |
#### Gates (logs `$T/XP-*.log`)
@@ -502,6 +512,20 @@ loop (`buildIndex.ts` ~1943 and the fingerprint), a different hunk from T1's `sc
content. The posts tree, the posts manifest, the channel list and `corpus.json` follow the index build
and do not carry it.
- The `/sites` list does not mark a private site; its form and every deploy refusal do.
+- **The posts reconcile ships only the channels the site's posts manifest lists, on every build**: a
+ social channel with 0 posts no longer gets a posts folder. Nothing advertised that folder (no posts
+ manifest entry, no `corpus.json` posts link), so nothing reads the difference.
+- **A private site changes the homepage's and the hub's totals.** A private site is unlisted, and
+ `channelsOnlyOnUnlistedSites` keeps a channel only unlisted sites expose out of every public total. A
+ private site holding every channel turns every pool-only channel (on no public site) into "only on
+ unlisted sites", so the homepage's and the hub's instance-wide totals drop by those channels at the
+ next homepage and hub build. Channels a public site also has are unaffected.
+- **Cloudflare Pages keeps old builds reachable.** A production redeploy replaces what the production
+ URL serves, nothing else: every earlier deployment stays live at its own
+ `<hash>.<project>.pages.dev`, and a preview alias (`<branch>.<project>.pages.dev`) keeps its last
+ build. The review found `tags-exclude.anilyzer.pages.dev/posts/manifest.json` still listing an X
+ channel. So after the rollout, X posts are gone from the production URLs only; whether to delete
+ the old deployments is the operator's call (rollout step 5).
- `queue-lock.test.mjs`'s two timing cases failed under the machine's load (below); not this slice's file.
#### Decisions the operator could overturn
@@ -513,5 +537,63 @@ loop (`buildIndex.ts` ~1943 and the fingerprint), a different hunk from T1's `sc
| A bundle whose `corpus.json` says private is refused even when the site is public now | Trust the site's current audience only (a stale private build could ship) |
| `docker/publish-site.sh` refuses a private site (the `site` service is the host's public face) | Let it publish locally behind Caddy |
| `social.x.visibility` lives in `xCookieSource.ts` with the rest of the social block | A module of its own |
+| The hub's `search-aliases.json` is the global dictionary (it was whichever site composed last) | Ship none: hub-wide search in a reader (`reader-hub.ts`) would have no aliases |
+
+#### Rollout for this slice (the parent's, through the editor's own writers)
+
+1. Rebuild and restart the editor (the setting, the Audience field, the deploy refusals, the compose
+ and hub fixes).
+2. Create the private site (Sites → New, **Audience: Private**, every channel), then set **Where X posts
+ appear: Private** on `/settings`.
+3. Rebuild and deploy every public site that has an X channel (each by name; Build & deploy all skips the
+ private site's deploy and says why).
+4. **Rebuild and deploy the hub** — the live hub serves the last-built site's data, X posts included,
+ until it is rebuilt from this branch (the review's HIGH 1). Then the homepage, for the totals.
+5. **Old deployments** (the operator decides): every earlier production deployment and every preview
+ alias of a site that had X posts still serves them. To remove them: the Cloudflare dashboard →
+ Workers & Pages → the project → Deployments → a deployment's ⋯ menu → Delete deployment (a preview
+ alias's branch deployments are listed there too); or `pnpm dlx wrangler pages deployment list
+ --project-name <project>` then `pnpm dlx wrangler pages deployment delete <deployment-id>
+ --project-name <project>` (check `--help` for the force flag an aliased deployment needs). The
+ current production deployment cannot be deleted, and needs none.
+6. Build the private site (its **Build**, or the compose-to-a-directory commands above) and point the
+ MCP at it.
+
+#### Review
+
+**Verdict: SHIP AFTER FIXES** (`XP-review.md` in the job's scratch): two Highs, four Lows, three nits.
+
+| Finding | Where |
+|---|---|
+| HIGH 1: the hub bundle carried the last-composed site's data trees (the live hub serves jeralyzer's posts manifest, two X channels), through no gate | `6466a68e`: `compose-hub` removes every per-site entry from `public/` first (`SITE_ONLY_PUBLIC_ENTRIES`: summaries, transcripts, subs, posts, digests, stats, archives, `site.json`, `tags.json`, `duplicates.json`, `search-aliases.json`, `chart-templates.json`, `sitemap.xml`; a worktree link by the link only) and writes the global alias dictionary as the hub's; `builtHubProblem` refuses a hub bundle that still carries a data tree, so Deploy hub refuses one. Tests: `compose-hub.test.ts` +1, `builtExport.test.ts` extended. Rollout step 4 |
+| HIGH 2: Pages preview aliases and old deployments keep serving X posts | Record: "Found and left" and rollout step 5 (the operator decides; the dashboard and wrangler paths) |
+| LOW 3: a compose over an index built before the flip listed the X channel's name and count | `385e4eb1`: the served posts manifest and summaries manifest are narrowed to the published members (`site.json`, `corpus.json` follow); a narrowed summaries copy is not trusted by the next compose. Test: "a compose over an index built before the setting flipped lists no X channel anywhere" |
+| LOW 4: the MCP line lost `fetch_clip`'s env, and `add` fails over a registered name | This commit: `claude mcp remove archilyzer -s local` first, the two `--env` lines kept (`WORKER_TOKEN=…`) |
+| LOW 5: the changelog missed the compose-cache fix and the restart notes | `8e448fde`: a bullet for the compose-cache fix, one for the hub, and the restart/redeploy notes |
+| LOW 6: a private site holding every channel moves the homepage's and hub's totals | Record: "Found and left" |
+| NIT 7: the deploy-all comment said the bundle check runs first | `385e4eb1`: the comment says the audience check runs first and what that means for a private wrong-site bundle |
+| NIT 8: the posts reconcile drops a 0-post social channel's folder | Record: "Found and left" |
+| NIT 9: `/sites` does not mark a private site | Already listed as left |
+
+#### Gates after the review
+
+- **tsc** (all workspaces) clean at `385e4eb1`'s tree (`XP-tsc6.log`).
+- **common:** 2,500 passed, 1 failed of 2,501 (`XP-common4.log`; +2 since the first gates: the hub
+ clearing and the stale-index compose). The one failure is `relocateChannelMedia.test.ts`'s "reconcile:
+ an extra and a changed file on the destination are settled" (its diff listing counted `./` as
+ changed — a directory mtime); 3/3 alone, and the file is not this slice's. **Export unit:** 98/98.
+ **Homepage unit:** 23/23. **Editor unit:** 109/109.
+- **No rebuild:** the fixes touch two bins (`compose-hub`, `compose-site`), `builtExport.ts` and
+ comments in `publish/build.ts`; no Next app bundles a changed module beyond `builtExport` (the
+ editor's hub action reads `builtHubProblem`, a pure function, tsc-checked).
+
+ | Run | At | Specs | Result |
+ |---|---|---|---|
+ | 3 (editor) | `8e448fde` | the run-1 list | **62 passed**, 0 failed, 3.0 min (after 45 min in the queue) |
+ | 4 (export) | `8e448fde` | the run-2 list | **20 passed**, 0 failed, 1.1 min (after 50 min in the queue) |
+ | 5 (hub) | `8e448fde` | `e2e:hub`, the whole suite | **39 passed**, 0 failed, 1.6 min (after 12 min in the queue) |
+
+ The hub suite runs `next dev` in hub mode over `export/public` and never composes, so it shows the
+ hub app is unchanged; the clearing itself is pinned by `compose-hub.test.ts`.
## Rollout