Context
Part B of 3 in the flat retirement split (parent: #869, grandparent: #866).
No dependencies on Part A or C — can be built independently. After Part A lands, the dual-write becomes a redundant double-write to the same silo path; but even before Part A, removing it is safe because silo-primary reads (#867/#868) ensure all reads try the silo first.
This issue removes the flat transition code from lifecycle.ts (3 call sites) and the flat directory listing fallback from lint-checks.ts.
Step-by-step Implementation Plan
Step 1: Remove flat write on the WRITE path (src/lib/lifecycle.ts, lines 236–239)
Delete these 4 lines:
// Also write flat copy (transition — removable after #869) so readWikiPage
// can find the page without a page index. writeWikiPage without tenant
// targets the flat path.
await writeWikiPage(slug, op.content, op.author);
The silo write on line 235 (await writeWikiPage(slug, op.content, op.author, undefined, writeTenant)) stays — it is the primary write.
Step 2: Remove flat delete on the DELETE path (src/lib/lifecycle.ts, lines 264–272)
Delete these 9 lines:
// Also delete flat copy (transition cleanup — removable after #869).
try {
await getStorage().deleteFile(wikiRelPath(`${slug}.md`));
} catch (err: unknown) {
// Flat copy may already be gone — swallow ENOENT.
if (!(err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT')) {
throw err;
}
}
The silo delete (lines 258–263) stays — it is the primary delete.
Step 3: Remove flat write on the BACKLINK STRIP path (src/lib/lifecycle.ts, lines 638–639)
Delete these 2 lines:
// Also write flat copy (transition — removable after #869).
await writeWikiPage(entry.slug, updated, "system", "backlink strip");
The silo write on line 637 (await writeWikiPage(entry.slug, updated, "system", "backlink strip", stripTenant)) stays.
Step 4: Remove unused wikiRelPath import (src/lib/lifecycle.ts, line 15)
After Steps 1–3, wikiRelPath is no longer used in lifecycle.ts. Remove it from the import block:
// BEFORE (lines 5-20):
import {
validateSlug,
writeWikiPage,
readWikiPage,
readWikiPageWithFrontmatter,
listWikiPages,
updateIndexUnsafe,
findRelatedPages,
updateRelatedPages,
appendToLog,
wikiRelPath, // ← REMOVE THIS LINE
tenantForOwner,
tenantWikiRelPath,
isArtifactType,
enrichEntry,
} from "./wiki";
// AFTER:
import {
validateSlug,
writeWikiPage,
readWikiPage,
readWikiPageWithFrontmatter,
listWikiPages,
updateIndexUnsafe,
findRelatedPages,
updateRelatedPages,
appendToLog,
tenantForOwner,
tenantWikiRelPath,
isArtifactType,
enrichEntry,
} from "./wiki";
Step 5: Remove flat fallback from getOnDiskSlugs (src/lib/lint-checks.ts)
Function: getOnDiskSlugs (line 50)
Replace lines 50–70 with:
export async function getOnDiskSlugs(): Promise<string[]> {
try {
const idx = await getPageIndex();
if (idx) return Object.keys(idx);
} catch (err) {
logger.warn("lint", "page-index read failed", err);
}
// No page-index available — return empty. The flat fallback is retired;
// the page-index is the single source of truth for slug enumeration.
return [];
}
Step 6: Remove unused imports from lint-checks.ts (line 3)
After Step 5, wikiRelPath and FileEntry are no longer used. Update line 3:
// BEFORE:
import { getStorage } from "./storage";
import type { FileEntry } from "./storage";
import { readWikiPage, readWikiPageWithFrontmatter, listWikiPages, wikiRelPath } from "./wiki";
// AFTER:
import { getStorage } from "./storage";
import { readWikiPage, readWikiPageWithFrontmatter, listWikiPages } from "./wiki";
Check whether getStorage is still used elsewhere in the file before removing it. (It likely is — keep it if so.)
Step 7: Update lint-checks tests (src/lib/__tests__/lint-checks.test.ts)
The test at ~line 87 ("returns slugs from .md files excluding index.md and log.md (fallback)") writes .md files to the flat wiki/ directory and expects getOnDiskSlugs to find them via listFiles. Now the function returns [] without a page-index. Update:
it("returns empty array when page-index is not seeded (no flat fallback)", async () => {
// Write files to flat wiki/ — should NOT be found anymore
const storage = (await import("../storage")).getStorage();
await storage.writeFile("wiki/hello.md", "# Hello");
await storage.writeFile("wiki/world.md", "# World");
const slugs = await getOnDiskSlugs();
expect(slugs).toEqual([]);
});
Similarly update ~line 103 ("ignores non-.md files (fallback)") — this test is now redundant since the fallback is gone. Either delete it or convert it to verify empty return.
Gotchas
- Do NOT remove
getStorage import from lifecycle.ts — it is still used for the silo delete (line 258).
- Do NOT touch the silo-primary write/delete — only the flat transition copies are removed.
- Line numbers may shift slightly if Part A lands first — identify the code blocks by their comment markers (
"removable after #869", "transition", "Flat fallback").
- Verify
FileEntry is not used elsewhere in lint-checks.ts before removing the type import.
Acceptance Criteria
Size Estimate
Small — 2 production files + 1 test file, ~25 lines deleted, ~5 lines added
Context
Part B of 3 in the flat retirement split (parent: #869, grandparent: #866).
No dependencies on Part A or C — can be built independently. After Part A lands, the dual-write becomes a redundant double-write to the same silo path; but even before Part A, removing it is safe because silo-primary reads (#867/#868) ensure all reads try the silo first.
This issue removes the flat transition code from
lifecycle.ts(3 call sites) and the flat directory listing fallback fromlint-checks.ts.Step-by-step Implementation Plan
Step 1: Remove flat write on the WRITE path (
src/lib/lifecycle.ts, lines 236–239)Delete these 4 lines:
The silo write on line 235 (
await writeWikiPage(slug, op.content, op.author, undefined, writeTenant)) stays — it is the primary write.Step 2: Remove flat delete on the DELETE path (
src/lib/lifecycle.ts, lines 264–272)Delete these 9 lines:
The silo delete (lines 258–263) stays — it is the primary delete.
Step 3: Remove flat write on the BACKLINK STRIP path (
src/lib/lifecycle.ts, lines 638–639)Delete these 2 lines:
The silo write on line 637 (
await writeWikiPage(entry.slug, updated, "system", "backlink strip", stripTenant)) stays.Step 4: Remove unused
wikiRelPathimport (src/lib/lifecycle.ts, line 15)After Steps 1–3,
wikiRelPathis no longer used in lifecycle.ts. Remove it from the import block:Step 5: Remove flat fallback from
getOnDiskSlugs(src/lib/lint-checks.ts)Function:
getOnDiskSlugs(line 50)Replace lines 50–70 with:
Step 6: Remove unused imports from
lint-checks.ts(line 3)After Step 5,
wikiRelPathandFileEntryare no longer used. Update line 3:Check whether
getStorageis still used elsewhere in the file before removing it. (It likely is — keep it if so.)Step 7: Update lint-checks tests (
src/lib/__tests__/lint-checks.test.ts)The test at ~line 87 ("returns slugs from .md files excluding index.md and log.md (fallback)") writes
.mdfiles to the flatwiki/directory and expectsgetOnDiskSlugsto find them via listFiles. Now the function returns[]without a page-index. Update:Similarly update ~line 103 ("ignores non-.md files (fallback)") — this test is now redundant since the fallback is gone. Either delete it or convert it to verify empty return.
Gotchas
getStorageimport from lifecycle.ts — it is still used for the silo delete (line 258)."removable after #869","transition","Flat fallback").FileEntryis not used elsewhere in lint-checks.ts before removing the type import.Acceptance Criteria
#869remain in lifecycle.ts comments"transition"remain in lifecycle.tswikiRelPathis NOT in lifecycle.ts importswikiRelPathis NOT in lint-checks.ts importsgetOnDiskSlugs()returns[](not flat directory contents) when no page-index existspnpm buildpassespnpm testpassesSize Estimate
Small — 2 production files + 1 test file, ~25 lines deleted, ~5 lines added