Files
gbrain/test/migrations-v0_13_0.test.ts
T
6f26d5e4df v0.41.37.0 fix: critical fix wave — reindex tag wipe, grandfather hang, Windows migration spawn, sync ReDoS (#1621 #1581 #1605 #1569) (#1665)
* fix(reindex): add-only tag reconciliation + DB-only re-chunk preserves frontmatter (#1621)

reindex --markdown and re-import no longer wipe DB-side enrichment tags.
Tag reconciliation is now ADD-ONLY (import-file.ts): re-import adds current
frontmatter tags and never deletes, so auto/dream/signal-detector tags survive.
The reindex DB-only fallback reconstructs full markdown via serializeMarkdown
so re-chunking a page with no on-disk source preserves frontmatter/title/timeline.

* fix(migrations): v0.13.1 grandfather chunked, source-safe, soft-delete-filtered (#1581)

phaseCGrandfather rewritten from a per-page getPage+putPage loop (which hung
70+ min on an 82K-page PGLite brain) to a chunked bulk SQL pass keyed on
pages.id (NOT slug — slug isn't globally unique), filtering deleted_at IS NULL,
with a batched rollback log carrying source identity.

* fix(migrations): run schema phases in-process to fix Windows getaddrinfo ENOTFOUND (#1605)

The 9 'gbrain init --migrate-only' execSync spawns died on Windows+bun+Supabase
(child DNS resolution). runMigrateOnlyCore (extracted from initMigrateOnly) runs
the schema bring-up in-process for all engines, unblocking schema_version
advancement. Includes async-call-site audit, a wall-clock guard, and a
runGbrainSubprocess stderr-capture wrapper for the remaining backfill spawns.

* fix(sync): ReDoS hardening + diagnostics for schema-pack regexes (#1569)

Input-length cap in runRegexBounded + route the unbounded link-inference path
through it (closes the only no-timeout ReDoS hole); star-height lint rule warns
on nested-quantifier patterns; --no-schema-pack sync escape hatch; GBRAIN_SYNC_TRACE
per-file begin heartbeat; PGLite serve/sync concurrency doc. Defensive hardening +
diagnostics — the deterministic ~3100-file wedge root cause remains open (no repro).

* docs(todos): file v0.41.37.0 fix-wave follow-ups (#1621/#1605/#1569)

* chore: bump version and changelog (v0.41.37.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: sync README + CLAUDE.md for v0.41.37.0 critical fix wave

Add reindex add-only tag reconciliation (#1621), v0.13.1 grandfather +
Windows in-process migration (#1581/#1605), and schema-pack ReDoS
hardening + sync --no-schema-pack / GBRAIN_SYNC_TRACE triage (#1569)
to CLAUDE.md key-files annotations and README Troubleshooting.
Regenerated llms-full.txt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ci): bump llms-full.txt budget 700KB→750KB (CLAUDE.md crossed 700KB after master merge)

The build-llms size-budget test failed: llms-full.txt is 703,244 bytes after the
v0.41.37.0 key-files annotations merged on top of master's v0.41.34/35/36 CLAUDE.md
additions. Matches the v0.41.9.0 precedent (600→700); the single-fetch bundle still
fits comfortably in modern long-context models.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 14:56:26 -07:00

84 lines
3.9 KiB
TypeScript

/**
* Tests for the v0.13.0 frontmatter relationship indexing migration.
*
* Iron rule (regression guard for Bug 1, v0.14.0 upgrade night): phase
* handlers must shell out to the bare string `gbrain`, NOT to
* `process.execPath`. On bun-installed trees execPath is the bun runtime;
* `bun extract ...` gets interpreted as `bun run extract` and the upgrade
* crashes mid-migration. The canonical shim on PATH is the right target.
*/
import { describe, test, expect } from 'bun:test';
import { readFileSync } from 'fs';
import { join } from 'path';
const SRC_PATH = join(__dirname, '..', 'src', 'commands', 'migrations', 'v0_13_0.ts');
describe('v0.13.0 — Frontmatter relationship indexing migration', () => {
test('registered in the TS migration registry', async () => {
const { migrations, getMigration } = await import('../src/commands/migrations/index.ts');
const versions = migrations.map(m => m.version);
expect(versions).toContain('0.13.0');
const m = getMigration('0.13.0');
expect(m).not.toBeNull();
expect(typeof m!.orchestrator).toBe('function');
});
test('phase functions exported for unit testing', async () => {
const { __testing } = await import('../src/commands/migrations/v0_13_0.ts');
expect(typeof __testing.phaseASchema).toBe('function');
expect(typeof __testing.phaseBBackfill).toBe('function');
expect(typeof __testing.phaseCVerify).toBe('function');
});
test('dry-run skips all side-effect phases', async () => {
const { v0_13_0 } = await import('../src/commands/migrations/v0_13_0.ts');
const result = await v0_13_0.orchestrator({ yes: true, dryRun: true, noAutopilotInstall: true });
expect(result.version).toBe('0.13.0');
for (const phase of result.phases) {
expect(phase.status).toBe('skipped');
expect(phase.detail).toBe('dry-run');
}
});
// ── Regression guards (Bug 1) ──────────────────────────────
test('source does NOT reference process.execPath (Bug 1 regression)', () => {
// process.execPath on a bun install is the bun runtime itself, so
// `${process.execPath} extract` becomes `bun run extract` and dies.
// See v0.14.0 upgrade-night postmortem.
const src = readFileSync(SRC_PATH, 'utf-8');
expect(src).not.toContain('process.execPath');
});
test('source does NOT build commands from a GBRAIN constant (Bug 1 regression)', () => {
// Earlier revisions used `const GBRAIN = process.execPath` and built
// commands as `${GBRAIN} extract ...`. The constant was the vector.
const src = readFileSync(SRC_PATH, 'utf-8');
expect(src).not.toMatch(/const\s+GBRAIN\s*=/);
expect(src).not.toMatch(/\$\{GBRAIN\}/);
});
test('phases use in-process schema + bare `gbrain` subprocess (v0.41.37.0 #1605)', () => {
const src = readFileSync(SRC_PATH, 'utf-8');
// Schema bring-up is now IN-PROCESS (was execSync('gbrain init
// --migrate-only'), which died with getaddrinfo ENOTFOUND on Windows).
expect(src).toContain('runMigrateOnlyCore()');
expect(src).not.toContain("execSync('gbrain init --migrate-only'");
// Backfill extract goes through the stderr-capturing wrapper (still bare
// `gbrain` so the canonical shim on PATH wins).
expect(src).toContain("runGbrainSubprocess('gbrain extract links --source db --include-frontmatter'");
// Stats readback still shells out (reads stdout); bare gbrain.
expect(src).toContain("execSync('gbrain call get_stats'");
});
test('phase commands never reference `bun` or `.ts` paths (Bug 1 regression)', () => {
// Belt-and-suspenders: even if someone reintroduces a runtime-path
// helper, they must not produce `bun ...` or `<path>.ts` as the spawn
// target.
const src = readFileSync(SRC_PATH, 'utf-8');
expect(src).not.toMatch(/execSync\([^)]*\bbun\b/);
expect(src).not.toMatch(/execSync\([^)]*\.ts/);
});
});