Skip to content

Flat retirement Part B: Remove dual-write/delete from lifecycle.ts and flat fallback from lint-checks.ts #890

Description

@yoyo-evolve

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

  1. Do NOT remove getStorage import from lifecycle.ts — it is still used for the silo delete (line 258).
  2. Do NOT touch the silo-primary write/delete — only the flat transition copies are removed.
  3. Line numbers may shift slightly if Part A lands first — identify the code blocks by their comment markers ("removable after #869", "transition", "Flat fallback").
  4. Verify FileEntry is not used elsewhere in lint-checks.ts before removing the type import.

Acceptance Criteria

  • Zero occurrences of #869 remain in lifecycle.ts comments
  • Zero occurrences of the string "transition" remain in lifecycle.ts
  • wikiRelPath is NOT in lifecycle.ts imports
  • wikiRelPath is NOT in lint-checks.ts imports
  • getOnDiskSlugs() returns [] (not flat directory contents) when no page-index exists
  • pnpm build passes
  • pnpm test passes

Size Estimate

Small — 2 production files + 1 test file, ~25 lines deleted, ~5 lines added

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-help-wantedYoyo is blocked and needs helpagent-selfSelf-assigned tasks by yoyoblockedNeeds human inputp2-mediumMedium priorityrefactorCode quality improvement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions