Conversation
- Delete fallback block that scanned wiki dir for .md files - Return [] when page-index is absent (silo is sole storage layer) - Remove unused imports: getStorage, FileEntry, wikiRelPath - Update lint-checks tests to expect [] without page-index - Seed page-index in lint, maintenance, and mcp tests that relied on fallback
Completes the cluster tracked by #892 — #890 (Part B), #889 (Part A), #874 (revisions) and #891 (Part C) — plus #905/#908, which turned out to be a prerequisite rather than a sibling. Both had been closed without the code change landing: `syncPageIndexForPage` still bailed when the index was absent. Why they had to land together: #892 sequenced B before A, but that ordering has no green state. Part B stops the flat dual-write while Part A still reads flat, so between them every page written is invisible to the reader that follows it — 8 lifecycle tests fail at exactly that point. B and A are one change. What the pieces do: - page-index (#905/#908): `syncPageIndexForPage` now seeds the map instead of no-opping. Silo-only reads derive a page's tenant from its index entry, so a page written before the first rebuild would resolve to DEFAULT_TENANT and read as missing. - Part B (#890, with #902/#903 cherry-picked): drops the three flat dual-write/delete blocks in lifecycle.ts and the flat-directory scan from `getOnDiskSlugs`. - Part A (#889): `readWikiPage`, `wikiPageExists` and `writeWikiPage` go through one resolver, `siloPathForSlug`. One page, one path. - #874: revision reads resolve the tenant internally, so no caller changes. They read silo-first and fall back to the flat ARCHIVE — the tenant migration only ever copied pages and raw sources, never `.revisions/`, so history from before revisions became tenant-aware still lives there. That is a data fallback, not the write-path fallback this epic removes. - Part C (#891): the reconcile forward pass copied flat → silo and is now dead. Rather than delete it, it repairs the failure silo-only reads actually introduce: a page under a different tenant than its index entry names is unreachable, so reconcile finds it and moves it back. Four regressions the issues do not mention, found by the suite: - An ownership change moves a page between silos. The revision snapshot read the destination, found nothing, and silently dropped the page's history; the old dual-write was masking it. Writes now snapshot from wherever the page actually is, and `moveRevisions` carries the archive across. - `writeWikiPage` without a tenant defaulted to DEFAULT_TENANT, which would RELOCATE an owned page on an ordinary content edit. An explicit tenant now asserts ownership and moves; an omitted one updates in place. - The delete path derived its tenant from frontmatter while reads derived it from the index. When those disagree the delete misses and the page stays live. - `migrateToTenants` enumerated through `listWikiPages` → `readWikiPage`, which no longer reads flat — a migration that cannot read the tree it migrates. It now reads flat directly, and seeds the page index from the tenants it just computed (owner lives inside the page, so a scan cannot bootstrap itself). Tests: 3530 pass, tsc and eslint clean. Two suites (ingest, source-index/ alias-index) set WIKI_DIR but never DATA_DIR or `_resetStorage()`; silo paths resolve against DATA_DIR, so their fixtures were escaping tmpDir and leaking across tests. Isolated them. Page fixtures now go through `__tests__/helpers/wiki-fixtures.ts` so they land where the read path looks. Closes #889, #890, #891, #874, #892, #902, #906 Refs #905, #908 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reverse pass only removed GHOSTS — silo files with no index entry at all. A page that moved silos leaves a copy behind whose slug IS indexed, so the ghost check kept it forever: unreachable (reads resolve to the other tenant) and drifting out of date. Found while verifying production data for the flat retirement: `reward-hacking` resolves to `yuanhao--yoyo` but a copy still sits under `yopedia` from the #930 repair. The forward pass has already placed every indexed page where its entry says, so anything left in another tenant is surplus by construction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes the cluster tracked by #892 — #890 (Part B), #889 (Part A), #874 (revisions), #891 (Part C) — plus #905/#908, which turned out to be a prerequisite rather than a sibling.
Why 13+ agent attempts produced nothing
#892's B→A ordering has no green state. Part B stops the flat dual-write while Part A still reads flat, so in between, every page written is invisible to the read that follows it — 8 lifecycle tests fail at exactly that seam. An agent that lands B, runs the suite and sees failures correctly concludes it broke something and backs out. B and A are one change.
#905/#908 was a missing dependency, and had been closed twice without landing.
syncPageIndexForPagestill hadif (idx === null) return. Silo-only reads derive a page's tenant from its index entry, so a page written before the first rebuild resolves toDEFAULT_TENANTand reads as missing.What landed
syncPageIndexForPageseeds the map instead of no-oppinglifecycle.tsand the flat scan ingetOnDiskSlugsreadWikiPage/wikiPageExists/writeWikiPageall route through one resolver,siloPathForSlug#874 without the caller churn. The issue plan threads
tenant?through 5 functions and ~15 call sites acrossmcp.ts, the revisions route,trail.ts,contributors.tsandlifecycle.ts. Resolving the tenant insiderevisions.ts— the same way reads resolve a page — gets the same result with zero caller changes.#891 does more than delete dead code. The forward pass copied flat → silo and is now meaningless. Rather than drop it, it repairs the failure silo-only reads actually introduce: a page under a different tenant than its index entry names is unreachable, so reconcile finds it and moves it back. The reverse pass also now clears stale cross-tenant duplicates, which the ghost check kept forever.
One flat fallback kept, deliberately
Revision reads still fall back to the flat archive. The tenant migration only ever copied pages and raw sources — it never moved
.revisions/, so every page edited before revisions became tenant-aware has its history there. Making those reads silo-only would have quietly erased the visible history of most of the wiki.That is a data fallback, not the write-path fallback this epic removes, and it can go once a migration moves
.revisions/into the silos.Four regressions the issues don't mention
Found by the suite, all pre-existing but masked by the dual-write:
moveRevisionscarries the archive across.writeWikiPagewithout a tenant would relocate an owned page. Flat retirement Part A: Make wiki.ts silo-only (read/write/exists) #889's plan defaults toDEFAULT_TENANT, which turns an ordinary content edit into a move. An explicit tenant now asserts ownership and moves; an omitted one updates in place.migrateToTenantscould no longer read the tree it migrates — it enumerated throughreadWikiPage. It now reads flat directly and seeds the page index from the tenants it just computed (owner lives inside the page, so a scan can't bootstrap itself after a move).Verification
tsc --noEmitandeslintclean._idx:pagesfrom KV (123 pages: 46yuanhao, 72yuanhao--yoyo, 5yopedia) and confirmed every one exists at the silo key its entry resolves to. 123/123 present, 0 misplaced.lint.test.ts,maintenance.test.tsandmcp.test.tsneeded.Two test suites set
WIKI_DIRbut neverDATA_DIRor_resetStorage(). Silo paths resolve againstDATA_DIR, so their fixtures were escaping tmpDir and leaking across tests — that's why a re-ingest test was deduping onto a page a previous test created. Isolated both; page fixtures now go through__tests__/helpers/wiki-fixtures.ts.Not done on purpose
The ~124 flat copies in R2 become orphans after this. They are the rollback net, so they should stay until the deploy is proven — then a separate cleanup can remove them. Deleting production files isn't part of this change.
Closes #889, #890, #891, #874, #892, #902, #906
Refs #905, #908
🤖 Generated with Claude Code