From 781cc13779a8992e1be4bb613d85297c739b0d3b Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Tue, 1 Sep 2026 16:08:05 +0000 Subject: [PATCH] refactor(memory-ingest): reunite preparePages with its docblock; pin disambiguateSlugs wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #2724 disambiguateSlugs block was inserted between preparePages' docblock and the function, orphaning the secret-scanning policy doc onto the wrong symbol. Reordered. A call-site pin now asserts the prepare→stage flow actually invokes disambiguateSlugs, so a refactor can't drop the call while every unit test stays green. Co-Authored-By: Claude Fable 5 --- bin/gstack-memory-ingest.ts | 44 +++++++++---------- ...gression-transcript-slug-collision.test.ts | 37 ++++++++++++++++ 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/bin/gstack-memory-ingest.ts b/bin/gstack-memory-ingest.ts index 3445f9b26..c6f518c33 100644 --- a/bin/gstack-memory-ingest.ts +++ b/bin/gstack-memory-ingest.ts @@ -1303,28 +1303,6 @@ async function probeMode(args: CliArgs): Promise { }; } -/** - * Prepare phase: walk sources, apply incremental + optional-secret-scan filters, - * parse transcripts/artifacts into PageRecord, render bodies with - * frontmatter. Returns the PreparedPage[] to stage + counts of files - * filtered at each gate. - * - * Secret scanning policy (post 2026-05-10 perf review): - * - * The actual cross-machine exfiltration boundary is `gstack-brain-sync`, - * which runs a regex-based secret scanner on the staged diff before - * `git commit` (see bin/gstack-brain-sync:78-110: AWS keys, GitHub - * tokens, OpenAI keys, PEM blocks, JWTs, bearer-token-in-JSON). That's - * the right place — it gates content leaving the machine. - * - * memory-ingest, by contrast, moves data from one local file to a - * local PGLite database. Scanning every source file at ingest time - * doesn't change exposure (the secret already lives in plaintext - * where the user keeps their transcripts and artifacts) but costs - * ~470s on cold runs. We removed the per-file gitleaks gate as - * redundant defense-in-depth and made it opt-in via `--scan-secrets` - * for users who want belt-and-suspenders. - */ /** * Disambiguate colliding page slugs before staging (#2724). * @@ -1362,6 +1340,28 @@ export function disambiguateSlugs(pages: PreparedPage[]): void { } } +/** + * Prepare phase: walk sources, apply incremental + optional-secret-scan filters, + * parse transcripts/artifacts into PageRecord, render bodies with + * frontmatter. Returns the PreparedPage[] to stage + counts of files + * filtered at each gate. + * + * Secret scanning policy (post 2026-05-10 perf review): + * + * The actual cross-machine exfiltration boundary is `gstack-brain-sync`, + * which runs a regex-based secret scanner on the staged diff before + * `git commit` (see bin/gstack-brain-sync:78-110: AWS keys, GitHub + * tokens, OpenAI keys, PEM blocks, JWTs, bearer-token-in-JSON). That's + * the right place — it gates content leaving the machine. + * + * memory-ingest, by contrast, moves data from one local file to a + * local PGLite database. Scanning every source file at ingest time + * doesn't change exposure (the secret already lives in plaintext + * where the user keeps their transcripts and artifacts) but costs + * ~470s on cold runs. We removed the per-file gitleaks gate as + * redundant defense-in-depth and made it opt-in via `--scan-secrets` + * for users who want belt-and-suspenders. + */ function preparePages( args: CliArgs, ctx: WalkContext, diff --git a/test/regression-transcript-slug-collision.test.ts b/test/regression-transcript-slug-collision.test.ts index 659150449..69b472258 100644 --- a/test/regression-transcript-slug-collision.test.ts +++ b/test/regression-transcript-slug-collision.test.ts @@ -9,6 +9,8 @@ * ("accounted for N-1 of N staged ... Refusing to advance state"). */ import { describe, it, expect } from "bun:test"; +import { readFileSync } from "fs"; +import { join } from "path"; import { disambiguateSlugs } from "../bin/gstack-memory-ingest"; const mk = (slug: string, source_path: string) => ({ @@ -62,4 +64,39 @@ describe("regression: disambiguateSlugs resolves colliding staged slugs", () => disambiguateSlugs(pages); expect(pages.map((p) => p.slug)).toEqual(before); }); + + it("call-site wiring: the prepare/stage flow actually invokes disambiguateSlugs (source pin)", () => { + // The unit tests above prove the function works; nothing else proves the + // flow CALLS it — a refactor could drop the invocation and every test + // would stay green while #2724 regresses. Anchor narrowly: extract the + // preparePages function body (and, as an accepted alternate home, the + // main-flow stretch between the preparePages call and writeStaged) and + // require a disambiguateSlugs( invocation inside — cosmetic changes + // (argument rename, comment edits) don't trip this; moving the call out + // of the prepare→stage flow entirely does. + const src = readFileSync( + join(import.meta.dir, "..", "bin", "gstack-memory-ingest.ts"), + "utf-8", + ); + + // preparePages body: from its declaration to the next top-level function. + const defStart = src.indexOf("function preparePages("); + expect(defStart).toBeGreaterThan(-1); + const afterDef = src.slice(defStart + "function preparePages(".length); + const endRel = afterDef.search(/\n(?:export )?(?:async )?function /); + const prepareBody = endRel === -1 ? afterDef : afterDef.slice(0, endRel); + + // Alternate home: the stage flow between the preparePages call site and + // the writeStaged call that consumes its output. + const callSite = src.indexOf("= preparePages("); + const stageSite = callSite === -1 ? -1 : src.indexOf("writeStaged(", callSite); + const stageFlow = + callSite !== -1 && stageSite !== -1 ? src.slice(callSite, stageSite) : ""; + + const invokes = (text: string) => + // An invocation, not the `function disambiguateSlugs(` definition. + /(?