test(bootstrap): real end-to-end coverage — cross-session recall, per-turn content, Codex door, realistic corpus

Closes the seven e2e gaps a coverage audit surfaced: the plumbing was
well-unit-tested but the product claims ("Codex works, context shows up every
turn with real content, it remembers across restarts, machine two recovers,
Postgres works") were unproven end to end. Test-only wave — zero src changes.

- Hermetic synthetic corpus (test/fixtures/bootstrap-corpus/ + a loader helper):
  12 interlinked pages (52 edges, timelines), 12 world/private beliefs, 8 gold
  queries — curated from the gbrain-evals synthetic corpora, 100% placeholder
  names, so recall is asserted on a real multi-entity brain instead of a
  2-node self-planted probe.
- GAP1 magic moment: author a fact via the real write path, disconnect the
  engine, reopen against the same DB, recall it — a real session boundary, not
  verify.ts's same-connection SQL read-back. Plus a source-isolation assertion.
- GAP2 per-turn content: hook-under-serve Pin 1 now seeds a known fact and
  asserts its text lands in the injected block AND private beliefs never do
  (was: empty brain, empty_block accepted as a pass).
- GAP3 Codex door: assert the rendered AGENTS.md carries the Gate-3 brain-first
  pull protocol; make the fake codex shim implement `mcp get` so the [FIX7]
  target-verification can actually fail; the Docker cold-machine harness now
  exercises the hooks/MCP registration step instead of skipping it.
- GAP4 corpus recall: turn-context + verify graph-floor/qrels run on the real
  multi-entity brain with real edges.
- GAP5 attach: machine-two now re-ingests the cloned brain/ into a fresh DB and
  recalls a fact authored only on machine one — the multi-device payoff.
- GAP6 keyed + Postgres (env-gated): real embeddings prove semantic recall a
  paraphrase query can reach but keyless BM25 cannot; bootstrap verify drives a
  real Postgres engine (skipIf DATABASE_URL/keys absent).
- GAP7 persistence: session-end runs the REAL push (not the mocked seam) to a
  local bare remote and the remote receives the content; a planted secret is
  blocked at the gate; the 15-min cron installs and fires a scan-gated push.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-11 11:30:07 -07:00
co-authored by Claude Fable 5
parent 5183e9f039
commit 6fb6790b2a
27 changed files with 1810 additions and 14 deletions
+117
View File
@@ -0,0 +1,117 @@
/**
* Codex door, end to end (GAP 3a). The Codex harness installs NO per-turn
* hooks — the pull protocol in AGENTS.md IS the per-message seam, and the MCP
* registration is what wires the tools. Two contracts are load-bearing and
* were previously only spot-checked by grepping the "Per-message gates"
* heading (a template edit could gut Gate 3's brain-first prose silently):
*
* 1. The RENDERED AGENTS.md carries Gate 3 ("Entity lookup (brain first)") in
* full — search-the-brain-before-answering with the actual brain tools —
* so a Codex agent with no hooks still pulls context every turn.
* 2. `registerCodexMcp` argv pins `serve --surface full` (bootstrap needs the
* whole op surface, not a narrowed `verbs`) and binds GBRAIN_SOURCE so a
* GUI-spawned serve (which inherits no shell env) writes to the workspace
* source [G1, CX-P1.4].
*/
import { describe, test, expect } from 'bun:test';
import { mkdtempSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { renderWorkspace } from '../src/core/bootstrap/render.ts';
import { setAnswer } from '../src/core/bootstrap/interview.ts';
import { registerCodexMcp } from '../src/core/bootstrap/hooks.ts';
const REQUIRED_ANSWERS: Record<string, string> = {
AGENT_NAME: 'Codexeer',
PRINCIPAL_NAME: 'Alice Example',
AGENT_PURPOSE: 'Maintain the research corpus and draft the weekly memo.',
AGENT_TOP_JOBS: '- corpus upkeep\n- weekly memo\n- meeting prep',
PRINCIPAL_CONTEXT: 'Runs a small research lab; ships a memo every Friday.',
VOICE_REGISTER: 'Direct. Three options, the second one wins.',
};
function answeredWs(): string {
const ws = mkdtempSync(join(tmpdir(), 'gbrain-codex-door-'));
for (const [key, value] of Object.entries(REQUIRED_ANSWERS)) {
const r = setAnswer(ws, key, value);
if (!r.ok) throw new Error(`setup failed for ${key}: ${r.message}`);
}
return ws;
}
describe('Codex door — rendered AGENTS.md pull protocol (Gate 3)', () => {
test('rendered AGENTS.md carries the full brain-first Entity-lookup gate, not just the heading', () => {
const ws = answeredWs();
renderWorkspace(ws);
const agents = readFileSync(join(ws, 'AGENTS.md'), 'utf8');
// The heading everyone already greps — necessary but far from sufficient.
expect(agents).toContain('Per-message gates');
// Gate 3 body — the actual pull-protocol prose. If a template edit guts
// any of this, a hookless Codex agent stops pulling context and this fails.
expect(agents).toContain('Gate 3 — Entity lookup (brain first)');
expect(agents).toContain('search the brain before answering');
// Names the concrete brain tools, in order (recall → query → get_page).
expect(agents).toContain('`recall` for hot');
expect(agents).toContain('`query` for synthesis');
expect(agents).toContain('`get_page` for the record');
// Push-context path: already-injected context is used rather than re-fetched.
expect(agents).toContain('If context was already\ninjected this turn, use it.');
// The anti-narration + no-generic-grep rule that keeps retrieval silent.
expect(agents).toContain('never narrate retrieval');
expect(agents).toContain('Never use generic file-grep where a brain tool');
// The identity token resolved (this is a real render, not the template).
expect(agents).toContain('Codexeer');
expect(/\{\{[A-Z0-9_]+\}\}/.test(agents)).toBe(false);
});
test('Gate 3 sits inside the Per-message gates block, ahead of the write-back gate', () => {
const ws = answeredWs();
renderWorkspace(ws);
const agents = readFileSync(join(ws, 'AGENTS.md'), 'utf8');
const gatesIdx = agents.indexOf('## Per-message gates');
const gate3Idx = agents.indexOf('Gate 3 — Entity lookup (brain first)');
const gate7Idx = agents.indexOf('Gate 7 — Write-back');
expect(gatesIdx).toBeGreaterThanOrEqual(0);
expect(gate3Idx).toBeGreaterThan(gatesIdx);
expect(gate7Idx).toBeGreaterThan(gate3Idx);
});
});
describe('Codex door — registerCodexMcp argv (--surface full + GBRAIN_SOURCE)', () => {
const BIN = '/opt/gbrain/bin/gbrain';
test('user-global stdio registration binds the source and pins the full op surface', () => {
const argvs = registerCodexMcp({ gbrainBin: BIN, sourceId: 'workspace' });
expect(argvs.length).toBe(1);
const argv = argvs[0]!;
// Exact shape — codex has no scope flag (user-global), env rides the add.
expect(argv).toEqual([
'codex', 'mcp', 'add', 'gbrain',
'--env', 'GBRAIN_SOURCE=workspace',
'--', BIN, 'serve', '--surface', 'full',
]);
// The two contracts, asserted independently so a reshuffle can't hide a drop.
const joined = argv.join(' ');
expect(joined).toContain('--env GBRAIN_SOURCE=workspace');
expect(joined).toContain('serve --surface full');
// `serve` argv must NOT narrow to the verbs surface.
expect(joined).not.toContain('--surface verbs');
});
test('isolated install also threads GBRAIN_HOME onto the registration [CX2-8]', () => {
const argv = registerCodexMcp({ gbrainBin: BIN, sourceId: 'workspace', gbrainHome: '/home/me/agent' })[0]!;
expect(argv).toContain('GBRAIN_HOME=/home/me/agent');
// GBRAIN_HOME is a second --env, GBRAIN_SOURCE still present, surface intact.
expect(argv.filter((a) => a === '--env').length).toBe(2);
expect(argv).toContain('GBRAIN_SOURCE=workspace');
expect(argv.join(' ')).toContain('serve --surface full');
});
test('a non-absolute gbrain binary is refused (GUI hosts inherit no PATH) [CX-P1.4]', () => {
expect(() => registerCodexMcp({ gbrainBin: 'gbrain', sourceId: 'workspace' })).toThrow(/absolute path/);
});
});
+58
View File
@@ -26,6 +26,8 @@ import {
} from '../src/core/bootstrap/verify.ts';
import { listVerifyRuns } from '../src/core/bootstrap/status.ts';
import type { CapabilityReport } from '../src/core/capability.ts';
import { operations, type OperationContext } from '../src/core/operations.ts';
import { loadCorpusPages, loadCorpusQueries } from './helpers/bootstrap-corpus.ts';
const KEYLESS: CapabilityReport = {
embeddings: { available: false },
@@ -389,3 +391,59 @@ describe('verifyWorkspace — source_id collision resolution', () => {
}
}, 240_000);
});
describe('verifyWorkspace — real corpus graph floor + qrels recall', () => {
test('auto-link builds real multi-entity edges; verify passes on the populated brain; gold queries recall the expected pages', async () => {
// Seed the synthetic world into the SAME workspace source verify checks,
// through the real put_page handler so auto-link builds REAL edges — a
// 12-page graph, not the 2-node self-planted probe pair verify writes.
const loaded = await loadCorpusPages(engine, { sourceId: 'workspace' });
expect(loaded).toContain('people/alice-example');
expect(loaded).toContain('companies/ridge-platform');
// Real edges exist from the wikilinks in the page bodies (not the probe):
// alice-example → ridge-platform, with multiple outbound edges on a real
// entity, and the reverse edge answers through the backlink table.
const aliceLinks = await engine.getLinks('people/alice-example', { sourceId: 'workspace' });
const aliceTargets = aliceLinks.map((l) => l.to_slug);
expect(aliceTargets).toContain('companies/ridge-platform');
expect(aliceTargets.length).toBeGreaterThanOrEqual(2);
const ridgeBacklinks = await engine.getBacklinks('companies/ridge-platform', { sourceId: 'workspace' });
expect(ridgeBacklinks.map((l) => l.from_slug)).toContain('people/alice-example');
// verify still runs green end-to-end on the now-populated brain, and its
// graph_floor / roundtrip checks pass alongside the real corpus graph.
const res = await verifyWorkspace(engine, ws, {
sourceId: 'workspace',
gbrainHomeDir: home,
capabilities: KEYLESS,
skipHooksSmoke: true,
});
if (!res.ok) console.error(res.report);
expect(res.ok).toBe(true);
expect(check(res.checks, 'graph_floor')[0].ok).toBe(true);
for (const c of check(res.checks, 'roundtrip')) expect(c.ok).toBe(true);
// qrels: each gold page-recall case returns its expected slug through the
// REAL keyword-search query op (keyless), over the multi-page corpus.
const queryOp = operations.find((o) => o.name === 'query')!;
const ctx: OperationContext = {
engine,
config: { engine: 'pglite' } as never,
logger: { info: () => {}, warn: () => {}, error: () => {} },
dryRun: false,
remote: false,
sourceId: 'workspace',
};
const goldPageCases = loadCorpusQueries().filter((q) => q.kind === 'page');
expect(goldPageCases.length).toBeGreaterThan(0);
const misses: string[] = [];
for (const q of goldPageCases) {
const result = await queryOp.handler(ctx, { query: q.query, limit: 10, expand: false });
if (!JSON.stringify(result).includes(q.expect_slug!)) {
misses.push(`${q.id}: "${q.query}" did not recall ${q.expect_slug}`);
}
}
expect(misses).toEqual([]);
}, 240_000);
});
+64 -1
View File
@@ -9,7 +9,8 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readFileSync
import { join } from 'path';
import { tmpdir } from 'os';
import { execFileSync } from 'child_process';
import { hardenBrainRepo } from '../src/core/brain-repo-durability.ts';
import { hardenBrainRepo, unhardenBrainRepo } from '../src/core/brain-repo-durability.ts';
import { runPull } from '../src/commands/sources-harden.ts';
// #2943 root cause: `env: process.env` is REQUIRED here. Bun snapshots
// process.env at startup, so without it the spawned git — and any post-commit
@@ -192,3 +193,65 @@ describe('post-commit hook (D9 local, D7 self-contained)', () => {
expect(found).toBe(true);
}, 60_000);
});
// The persistence schedule is the DB-free PULL cron (D2/D12) — the push side
// is the post-commit hook proved above. beforeEach hardens with
// installCron:false; this block re-hardens the SAME repo with installCron:true
// and proves the scheduled job is registered with the right command + interval,
// then invokes that exact command to prove it performs a real pull. It always
// unregisters the launchd/cron job afterward so no scheduled job survives.
describe('durability schedule (installCron:true) [D2/D12]', () => {
test('registers the DB-free pull job with the right command + interval, and the job performs a real pull', async () => {
const sourceId = 'wiki';
const report = await hardenBrainRepo({
repoPath: work, sourceId, pat: 'ghp_x', installCron: true, intervalSec: 900, verify: false,
});
const toplevel = git(work, 'rev-parse', '--show-toplevel');
try {
const cronStep = report.steps.find((s) => s.step === 'cron')!;
expect(cronStep).toBeDefined();
expect(cronStep.status).not.toBe('skipped'); // installCron:true → it ran
// The scheduled COMMAND: a DB-free `sources pull` wrapper for THIS repo,
// written to <home>/brain-pull-<sourceId>.sh regardless of platform.
const wrapper = join(process.env.HOME!, '.gbrain', `brain-pull-${sourceId}.sh`);
expect(existsSync(wrapper)).toBe(true);
const body = readFileSync(wrapper, 'utf-8');
expect(body).toContain(`sources pull --path '${toplevel}' --branch 'main'`);
// The REGISTERED job + its 15-minute INTERVAL. launchd (darwin) is
// deterministic — assert the plist directly; the step detail names the
// interval on the darwin path.
if (process.platform === 'darwin') {
const plist = join(process.env.HOME!, 'Library', 'LaunchAgents', `com.gbrain.brain-pull.${sourceId}.plist`);
expect(existsSync(plist)).toBe(true);
const xml = readFileSync(plist, 'utf-8');
expect(xml).toContain('<key>StartInterval</key><integer>900</integer>'); // 900s = 15m
expect(xml).toContain(wrapper); // ProgramArguments points at our wrapper
expect(cronStep.detail).toContain('900s');
}
// Invoke the scheduled command directly to PROVE it performs the pull.
// Advance origin from a second clone, then run the exact DB-free pull the
// wrapper execs (`gbrain sources pull --path <repo> --branch main`).
const other = mkdtempSync(join(root, 'other-'));
execFileSync('git', ['-c', 'protocol.file.allow=always', 'clone', '-q', bare, other], { stdio: 'ignore', env: process.env });
git(other, 'config', 'user.email', 'o@o.o'); git(other, 'config', 'user.name', 'other');
writeFileSync(join(other, 'from-remote.md'), 'landed via a second device\n');
git(other, 'add', 'from-remote.md'); git(other, 'commit', '-qm', 'remote advance'); git(other, 'push', '-q', 'origin', 'main');
const remoteHead = originHead(bare);
expect(git(work, 'rev-parse', 'HEAD')).not.toBe(remoteHead); // local is behind
await runPull(null, ['--path', work, '--branch', 'main']);
// The scheduled pull fast-forwarded the local checkout to the remote and
// the remote-authored file is now present locally.
expect(git(work, 'rev-parse', 'HEAD')).toBe(remoteHead);
expect(existsSync(join(work, 'from-remote.md'))).toBe(true);
} finally {
// Unregister while HOME is still the temp home (afterEach restores it).
await unhardenBrainRepo({ repoPath: work, sourceId });
}
}, 60_000);
});
+106
View File
@@ -35,6 +35,26 @@ import { attachWorkspace } from '../../src/core/bootstrap/attach.ts';
import { readManifest, readReceipt } from '../../src/core/bootstrap/format.ts';
import { initState, setAnswer, confirm, readBackHash } from '../../src/core/bootstrap/interview.ts';
import { realpathOrResolve } from '../../src/core/path-confine.ts';
import { PGLiteEngine } from '../../src/core/pglite-engine.ts';
import { addSource } from '../../src/core/sources-ops.ts';
import { importFromFile } from '../../src/core/import-file.ts';
import { verifyWorkspace } from '../../src/core/bootstrap/verify.ts';
import { operations, type OperationContext } from '../../src/core/operations.ts';
import type { CapabilityReport } from '../../src/core/capability.ts';
import { CORPUS_DIR } from '../helpers/bootstrap-corpus.ts';
/** Keyless capability report — machine 2 re-ingests + verifies with ZERO keys. */
const KEYLESS: CapabilityReport = {
embeddings: { available: false },
extraction: { available: false },
search: 'keyword-only',
mode: 'keyless',
};
/** A page authored ONLY on machine 1, committed to the repo, cloned to machine 2. */
const MACHINE1_PAGE_SLUG = 'companies/summit-robotics';
/** A distinctive fact that exists nowhere but machine 1's authored repo page. */
const MACHINE1_MARKER = 'The flagship warehouse pilot runs at the Rivermouth fulfillment center.';
const SAVED_ENV: Record<string, string | undefined> = {};
const ENV_KEYS = [
@@ -129,6 +149,17 @@ beforeAll(async () => {
expect(renderCode).toBe(0);
expect(readManifest(ws1).state).toBe('initialized');
// Author a corpus page into machine 1's brain/ BEFORE the commit — a real
// synthetic page carrying a machine-1-only fact (the Rivermouth marker). It
// lives ONLY in the repo, so machine 2 can only learn it by re-ingesting the
// clone [multi-device promise: hot facts arrive via the repo].
const corpusPage = readFileSync(join(CORPUS_DIR, 'pages', 'companies__summit-robotics.md'), 'utf8');
mkdirSync(join(ws1, 'brain', 'companies'), { recursive: true });
writeFileSync(
join(ws1, 'brain', 'companies', 'summit-robotics.md'),
`${corpusPage.trimEnd()}\n\n## Operations\n\n${MACHINE1_MARKER}\n`,
);
// git init + commit + local bare 'origin' + clone → machine 2.
git(ws1, ['init', '-q', '-b', 'main']);
git(ws1, ['config', 'user.email', 'test@example.com']);
@@ -239,6 +270,81 @@ describe('bootstrap attach (machine-2 adoption, serial e2e)', () => {
]);
}, 60_000);
test('machine-2 full round-trip: repo page re-ingests into a FRESH brain, verify passes, and a machine-1-only fact is recalled', async () => {
// A fresh machine-2 PGLite brain — nothing pre-seeded. The only path a fact
// can reach it is the cloned repo tree. Hermetic in-memory engine (the
// proven in-process verify pattern) so teardown is clean.
const engine = new PGLiteEngine();
await engine.connect({});
await engine.initSchema();
try {
// Register the cloned brain/ as the workspace source (what `bootstrap
// attach` → `gbrain sources add` does on machine 2).
await addSource(engine, { id: 'workspace', localPath: join(ws2, 'brain'), force: true });
// Precondition: the machine-1-authored page is NOT in the fresh DB — it
// exists ONLY as a committed repo file cloned from machine 1.
expect(await engine.getPage(MACHINE1_PAGE_SLUG, { sourceId: 'workspace' })).toBeNull();
const clonedFile = join(ws2, 'brain', 'companies', 'summit-robotics.md');
expect(existsSync(clonedFile)).toBe(true);
expect(readFileSync(clonedFile, 'utf8')).toContain(MACHINE1_MARKER);
// Re-ingest through the REAL per-file import path (the primitive `gbrain
// sync` runs on each changed file), scoped to the workspace source.
const rel = join('companies', 'summit-robotics.md');
const imp = await importFromFile(engine, clonedFile, rel, { sourceId: 'workspace', noEmbed: true });
expect(imp.status).toBe('imported');
expect(imp.slug).toBe(MACHINE1_PAGE_SLUG);
// The page + its machine-1-authored content now live in machine 2's DB.
const page = await engine.getPage(MACHINE1_PAGE_SLUG, { sourceId: 'workspace' });
expect(page).not.toBeNull();
expect(page!.title).toBe('Summit Robotics');
expect(page!.compiled_truth ?? '').toContain(MACHINE1_MARKER);
// The clone's origin is a local bare path gh cannot prove private; drop it
// so repo_privacy resolves to the honest local-only pass. The multi-device
// fact transport under test is orthogonal to remote-privacy verification.
execFileSync('git', ['-C', ws2, 'remote', 'remove', 'origin']);
// `bootstrap verify` core runs GREEN on machine 2, keyless.
const res = await verifyWorkspace(engine, ws2, {
sourceId: 'workspace',
gbrainHomeDir: join(machine2Home, '.gbrain'),
capabilities: KEYLESS,
skipHooksSmoke: true,
});
if (!res.ok) console.error(res.report);
expect(res.ok).toBe(true);
expect(res.checks.find((c) => c.id === 'roundtrip')!.ok).toBe(true);
// Recall the machine-1-only fact through the REAL keyword-search query op.
// The Rivermouth marker was authored on machine 1 and reached machine 2
// ONLY via the repo, so a hit proves the multi-device promise end to end.
const queryOp = operations.find((o) => o.name === 'query')!;
const ctx: OperationContext = {
engine,
config: { engine: 'pglite' } as never,
logger: { info: () => {}, warn: () => {}, error: () => {} },
dryRun: false,
remote: false,
sourceId: 'workspace',
};
const markerHit = await queryOp.handler(ctx, { query: 'Rivermouth fulfillment center warehouse', limit: 10, expand: false });
expect(JSON.stringify(markerHit)).toContain(MACHINE1_PAGE_SLUG);
const goldHit = await queryOp.handler(ctx, { query: 'warehouse navigation robots startup', limit: 10, expand: false });
expect(JSON.stringify(goldHit)).toContain(MACHINE1_PAGE_SLUG);
} finally {
// The query op fires retrieval telemetry (last_retrieved_at / search
// stats) as a background write. PGLite serializes one connection, so a
// yield-then-flush drains that in-flight write BEFORE close() — issuing
// db.close() with a query still queued otherwise deadlocks disconnect.
await new Promise((r) => setTimeout(r, 100));
await engine.executeRaw('SELECT 1').catch(() => {});
await engine.disconnect();
}
}, 240_000);
test('template clone (initialized: false) is REFUSED with the agent-readable render pointer [CX2-1]', async () => {
const ws3 = mkdtempSync(join(tmpdir(), 'gb-att-ws3-'));
try {
@@ -39,6 +39,12 @@ import { resolveSocketPath, ipcSecretPath } from '../../src/core/context/resolve
import { LiveServeLockError } from '../../src/core/pglite-lock.ts';
import { createEngine } from '../../src/core/engine-factory.ts';
import { addSource } from '../../src/core/sources-ops.ts';
import {
loadCorpusPages,
loadCorpusBeliefs,
loadCorpusBeliefData,
loadCorpusQueries,
} from '../helpers/bootstrap-corpus.ts';
const REPO_ROOT = resolve(import.meta.dir, '..', '..');
const TRANSCRIPT_FIXTURE = join(REPO_ROOT, 'test', 'fixtures', 'conversation-formats', 'claude-code.jsonl');
@@ -120,12 +126,20 @@ beforeAll(async () => {
mkdirSync(join(ws, 'brain'), { recursive: true });
// Pre-init the brain in-process (schema + source) so the serve subprocess
// boots fast and doesn't spend its boot budget on migrations.
// boots fast and doesn't spend its boot budget on migrations. Also SEED the
// synthetic corpus (pages + world/private beliefs) into the serve's source
// BEFORE the serve takes the single-writer lock — the serve then reads this
// committed data when it assembles turn context, so Pin 1 can assert on real
// recalled content (world belief present, private belief fenced out) instead
// of accepting an empty block [GAP 2].
const engineConfig = { engine: 'pglite' as const, database_path: dbDir };
const engine = await createEngine(engineConfig);
await engine.connect(engineConfig);
await engine.initSchema();
await addSource(engine, { id: 'workspace', localPath: join(ws, 'brain'), force: true });
await loadCorpusPages(engine, { sourceId: 'workspace' });
const seededBeliefs = await loadCorpusBeliefs(engine, { sourceId: 'workspace' });
if (seededBeliefs < 1) throw new Error('corpus beliefs failed to seed — GAP 2 has nothing to recall');
await engine.disconnect();
// REAL serve subprocess. stdin stays open (a stdin EOF is serve's shutdown
@@ -198,17 +212,25 @@ describe('bootstrap hook under a live serve (serial e2e) [A7]', () => {
expect(existsSync(ipcSecretPath(dbDir))).toBe(true);
}, 30_000);
test('Pin 1: user-prompt hook with a LIVE serve → exit 0 + context JSON or a documented degradation heartbeat', async () => {
test('Pin 1: user-prompt hook with a LIVE serve → exit 0 + additionalContext carrying the recalled WORLD belief, never the PRIVATE one', async () => {
// Transcript fixture under the confinement seam root.
const projRoot = join(tmpParent, 'projects');
mkdirSync(join(projRoot, 'p1'), { recursive: true });
const transcript = join(projRoot, 'p1', 'session.jsonl');
copyFileSync(TRANSCRIPT_FIXTURE, transcript);
// Drive the turn off a real gold recall case: its query is the prompt, its
// expected substring is a WORLD belief we seeded, and its must_not is the
// PRIVATE sibling belief the visibility fence must keep out of the block.
const beliefCase = loadCorpusQueries().find((q) => q.id === 'belief-recall-alice-standups');
expect(beliefCase).toBeDefined();
expect(beliefCase!.expect_substring).toBeDefined();
expect(beliefCase!.must_not_substring).toBeDefined();
const out = collectStdout();
const code = await runHook(['user-prompt'], {
stdin: JSON.stringify({
prompt: 'what do we know about widget-co?',
prompt: beliefCase!.query,
session_id: 'hook-under-serve-1',
transcript_path: transcript,
}),
@@ -224,16 +246,28 @@ describe('bootstrap hook under a live serve (serial e2e) [A7]', () => {
expect(hb.event).toBe('user-prompt');
expect(hb.outcome).not.toBe('error');
// Every distinctive fragment of a PRIVATE belief that must NEVER cross the
// IPC boundary (the meta-hook pins visibility=['world'] for the hook path).
const privateFragments = loadCorpusBeliefData()
.filter((b) => b.visibility === 'private')
.map((b) => b.text);
if (payload.length > 0) {
// Non-empty additionalContext JSON — the full happy-path contract.
// Happy path: assert the REAL recalled content, not length>0. The
// assembled block must carry the seeded WORLD belief the gold case
// expects, and NONE of the private beliefs.
const parsed = JSON.parse(payload) as {
hookSpecificOutput: { hookEventName: string; additionalContext: string };
};
expect(parsed.hookSpecificOutput.hookEventName).toBe('UserPromptSubmit');
expect(parsed.hookSpecificOutput.additionalContext.length).toBeGreaterThan(0);
const ctx = parsed.hookSpecificOutput.additionalContext;
expect(ctx).toContain(beliefCase!.expect_substring!);
expect(ctx).not.toContain(beliefCase!.must_not_substring!);
for (const frag of privateFragments) expect(ctx).not.toContain(frag);
} else {
// Empty stdout MUST be a documented degradation recorded in the
// heartbeat — never a silent nothing, never a wiring-broken reason.
// heartbeat — never a silent nothing, never a wiring-broken reason. A
// degraded turn also, trivially, leaked no private content.
expect(hb.reason).toBeDefined();
expect(DOCUMENTED_LIVE_SERVE_REASONS.has(hb.reason!)).toBe(true);
}
@@ -0,0 +1,332 @@
/**
* GAP 6 — keyed mode + Postgres bootstrap coverage.
*
* Every other bootstrap test pins the KEYLESS posture (embeddings.available:
* false) against embedded PGLite with DATABASE_URL stripped. Nothing proved
* that (a) a keyed brain actually does SEMANTIC recall that BM25 can't, that
* (b) a real chat key drives auto fact-extraction the keyless path can't, or
* that (c) the bootstrap verify probes run on a real Postgres/Supabase engine
* instead of only PGLite. This file closes all three, each env-gated with
* `skipIf` so a CI run without secrets stays green while the path runs for
* real when the key / DATABASE_URL is present.
*
* Serial: mutates process.env.GBRAIN_HOME and the process-global AI gateway.
* Deliberately does NOT strip DATABASE_URL (the Postgres describe needs it).
*/
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { PGLiteEngine } from '../../src/core/pglite-engine.ts';
import { PostgresEngine } from '../../src/core/postgres-engine.ts';
import { addSource } from '../../src/core/sources-ops.ts';
import { loadCorpusPages } from '../helpers/bootstrap-corpus.ts';
import { runEmbedCore } from '../../src/commands/embed.ts';
import { hybridSearch } from '../../src/core/search/hybrid.ts';
import { runSchemaTransition } from '../../src/core/retrieval-upgrade-planner.ts';
import { extractTakesFromPages } from '../../src/core/extract-takes-from-pages.ts';
import { configureGateway, resetGateway } from '../../src/core/ai/gateway.ts';
import {
verifyWorkspace,
VERIFY_PROBE_SLUG,
VERIFY_PROBE_ENTITY_SLUG,
VERIFY_MAGIC_TOKEN,
} from '../../src/core/bootstrap/verify.ts';
import type { CapabilityReport } from '../../src/core/capability.ts';
const OPENAI = process.env.OPENAI_API_KEY;
const VOYAGE = process.env.VOYAGE_API_KEY;
const ANTHROPIC = process.env.ANTHROPIC_API_KEY;
const DATABASE_URL = process.env.DATABASE_URL;
const KEYLESS: CapabilityReport = {
embeddings: { available: false },
extraction: { available: false },
search: 'keyword-only',
mode: 'keyless',
};
/** Resolve the embedding provider the same way `gbrain init` would from env.
* OpenAI (1536d) matches the committed `embedding` column width, so no schema
* transition is needed; Voyage (1024d) requires resizing the column exactly
* the way the provider-agnostic migration does. */
function resolveEmbedProvider():
| { model: string; dims: number; env: Record<string, string>; cfg: Record<string, string> }
| null {
if (OPENAI) {
return {
model: 'openai:text-embedding-3-large',
dims: 1536,
env: { OPENAI_API_KEY: OPENAI },
cfg: { openai_api_key: OPENAI },
};
}
if (VOYAGE) {
return {
model: 'voyage:voyage-3-large',
dims: 1024,
env: { VOYAGE_API_KEY: VOYAGE },
cfg: { voyage_api_key: VOYAGE },
};
}
return null;
}
/** Chat provider for the LLM fact-extraction path (embeddings not required). */
function resolveChatProvider():
| { model: string; env: Record<string, string> }
| null {
if (ANTHROPIC) return { model: 'anthropic:claude-haiku-4-5', env: { ANTHROPIC_API_KEY: ANTHROPIC } };
if (OPENAI) return { model: 'openai:gpt-4o-mini', env: { OPENAI_API_KEY: OPENAI } };
return null;
}
// ───────────────────────────────────────────────────────────────────────────
// (a) Keyed semantic recall: a PARAPHRASE query (no shared keyword with the
// target page) recalls the right page via real embeddings — something the
// keyless BM25 path structurally cannot do (disjoint tokens ⇒ zero lexical
// hits). The contrast is asserted directly: the keyword arm returns the
// page NOT AT ALL, the vector arm surfaces it.
// ───────────────────────────────────────────────────────────────────────────
describe.skipIf(!OPENAI && !VOYAGE)('keyed semantic recall (real embeddings)', () => {
let engine: PGLiteEngine;
let home: string;
let root: string;
let prevHome: string | undefined;
let embedded = 0;
beforeAll(async () => {
const prov = resolveEmbedProvider()!;
root = mkdtempSync(join(tmpdir(), 'gb-keyed-'));
home = join(root, '.gbrain');
mkdirSync(home, { recursive: true });
writeFileSync(
join(home, 'config.json'),
JSON.stringify({
engine: 'pglite',
embedding_model: prov.model,
embedding_dimensions: prov.dims,
...prov.cfg,
}),
);
prevHome = process.env.GBRAIN_HOME;
process.env.GBRAIN_HOME = root;
engine = new PGLiteEngine();
await engine.connect({});
await engine.initSchema();
// Size the primary embedding column to the provider's width (OpenAI is
// already 1536; Voyage needs the resize the real migration performs).
if (prov.dims !== 1536) await runSchemaTransition(engine, prov.dims);
await addSource(engine, { id: 'workspace', localPath: join(root, 'brain'), force: true });
// Load the corpus KEYLESS (no gateway yet, so put_page never auto-embeds),
// then configure the real gateway and run the embed sweep for real.
mkdirSync(join(root, 'brain'), { recursive: true });
await loadCorpusPages(engine, { sourceId: 'workspace' });
configureGateway({ embedding_model: prov.model, embedding_dimensions: prov.dims, env: prov.env });
const res = await runEmbedCore(engine, { stale: true, sourceId: 'workspace', quiet: true });
embedded = res.embedded;
}, 300_000);
afterAll(async () => {
try { await engine.disconnect(); } catch { /* noop */ }
resetGateway();
if (prevHome === undefined) delete process.env.GBRAIN_HOME;
else process.env.GBRAIN_HOME = prevHome;
rmSync(root, { recursive: true, force: true });
});
test('the embed sweep actually produced vectors', () => {
// Guards against a false-green semantic assertion below: if nothing
// embedded, a "recall" could only be lexical.
expect(embedded).toBeGreaterThan(0);
});
// Each case: a query whose tokens are DISJOINT from the target page so BM25
// cannot reach it; only a real embedding can. Asserted both ways.
const cases = [
{
name: 'summit-robotics',
slug: 'companies/summit-robotics',
paraphrase: 'company making self-guided fulfillment-center machinery',
},
{
name: 'hybrid-retrieval',
slug: 'concepts/hybrid-retrieval',
paraphrase: 'combining vector similarity with lexical token matching for lookup',
},
];
for (const c of cases) {
test(`paraphrase "${c.paraphrase.slice(0, 32)}…" recalls ${c.name} via vectors, not keywords`, async () => {
// The keyless arm: pure BM25. Disjoint tokens ⇒ the target is unreachable.
const kw = await engine.searchKeyword(c.paraphrase, { limit: 10, sourceId: 'workspace' });
expect(kw.map((r) => r.slug)).not.toContain(c.slug);
// The keyed arm: real query embedding + vector search surfaces it.
const results = await hybridSearch(engine, c.paraphrase, {
limit: 10,
sourceId: 'workspace',
mode: 'balanced',
});
const slugs = results.map((r) => r.slug);
expect(slugs).toContain(c.slug);
// And it ranks as a confident hit, not a tail match.
expect(slugs.slice(0, 5)).toContain(c.slug);
}, 120_000);
}
});
// ───────────────────────────────────────────────────────────────────────────
// (b) Keyed auto fact-extraction: a real chat key drives the takes classifier
// over concept prose and lands a gradeable claim. The keyless path returns
// `llm_unavailable` with zero claims — the exact capability a key unlocks.
// ───────────────────────────────────────────────────────────────────────────
describe.skipIf(!OPENAI && !ANTHROPIC)('keyed auto fact-extraction (LLM takes)', () => {
let engine: PGLiteEngine;
let home: string;
let root: string;
let prevHome: string | undefined;
const SLUG = 'concepts/keyed-extraction-probe';
// >200 chars of concept prose carrying clear gradeable claims (facts/takes),
// so the classifier has something real to extract.
const PROSE = [
'Hybrid retrieval systems consistently outperform pure keyword search on',
'recall-sensitive corpora because dense embeddings capture paraphrase',
'relationships that sparse lexical matching misses. The reranker stage is',
'the single highest-leverage component: teams that skip it leave roughly a',
'third of achievable precision on the table. In 2026 most production memory',
'stacks will standardize on a reranked hybrid pipeline as the default, and',
'brains that stay keyword-only will feel noticeably worse to their users.',
].join(' ');
beforeAll(async () => {
root = mkdtempSync(join(tmpdir(), 'gb-extract-'));
home = join(root, '.gbrain');
mkdirSync(home, { recursive: true });
writeFileSync(join(home, 'config.json'), JSON.stringify({ engine: 'pglite' }));
prevHome = process.env.GBRAIN_HOME;
process.env.GBRAIN_HOME = root;
engine = new PGLiteEngine();
await engine.connect({});
await engine.initSchema();
await engine.putPage(SLUG, {
type: 'concept',
title: 'Keyed Extraction Probe',
compiled_truth: PROSE,
});
}, 120_000);
afterAll(async () => {
try { await engine.disconnect(); } catch { /* noop */ }
resetGateway();
if (prevHome === undefined) delete process.env.GBRAIN_HOME;
else process.env.GBRAIN_HOME = prevHome;
rmSync(root, { recursive: true, force: true });
});
test('keyless → llm_unavailable, zero claims; keyed → a real fact/take lands', async () => {
// Keyless contrast: no chat gateway ⇒ the sweep cannot extract anything.
resetGateway();
const keyless = await extractTakesFromPages(engine, { bootstrapEnabled: true });
expect(keyless.llm_unavailable).toBe(true);
expect(keyless.claims_extracted).toBe(0);
const beforeRows = await engine.executeRaw<{ n: number }>(
`SELECT COUNT(*)::int AS n FROM takes t JOIN pages p ON p.id = t.page_id WHERE p.slug = $1`,
[SLUG],
);
expect(beforeRows[0].n).toBe(0);
// Keyed: configure the real chat model and run the same sweep.
const chat = resolveChatProvider()!;
configureGateway({ chat_model: chat.model, env: chat.env });
const keyed = await extractTakesFromPages(engine, { bootstrapEnabled: true, holder: 'test' });
expect(keyed.llm_unavailable).toBe(false);
expect(keyed.claims_extracted).toBeGreaterThan(0);
// The claim physically landed in the takes table on OUR probe page.
const rows = await engine.executeRaw<{ claim: string; kind: string }>(
`SELECT t.claim, t.kind FROM takes t JOIN pages p ON p.id = t.page_id WHERE p.slug = $1`,
[SLUG],
);
expect(rows.length).toBeGreaterThan(0);
expect(rows.every((r) => r.claim.trim().length > 0)).toBe(true);
expect(rows.every((r) => ['fact', 'take', 'bet', 'hunch'].includes(r.kind))).toBe(true);
}, 180_000);
});
// ───────────────────────────────────────────────────────────────────────────
// (c) Postgres bootstrap: drive the verify core's roundtrip + graph_floor +
// magic_moment probes against a REAL Postgres engine. First bootstrap
// coverage off PGLite — catches PGLite-only assumptions in the probes
// (tsvector recall, graph-edge materialization, probe cleanup DDL).
// ───────────────────────────────────────────────────────────────────────────
describe.skipIf(!DATABASE_URL)('Postgres bootstrap verify (real Postgres)', () => {
let engine: PostgresEngine;
let home: string;
let root: string;
let ws: string;
let prevHome: string | undefined;
beforeAll(async () => {
root = mkdtempSync(join(tmpdir(), 'gb-pg-verify-'));
home = join(root, '.gbrain');
mkdirSync(join(home, 'bootstrap'), { recursive: true });
writeFileSync(join(home, 'config.json'), JSON.stringify({ engine: 'postgres' }));
ws = mkdtempSync(join(tmpdir(), 'gb-pg-verify-ws-'));
mkdirSync(join(ws, 'brain'), { recursive: true });
prevHome = process.env.GBRAIN_HOME;
process.env.GBRAIN_HOME = root;
engine = new PostgresEngine();
await engine.connect({ database_url: DATABASE_URL! });
await engine.initSchema();
await addSource(engine, { id: 'workspace', localPath: join(ws, 'brain'), force: true });
}, 60_000);
afterAll(async () => {
try { await engine.disconnect(); } catch { /* noop */ }
if (prevHome === undefined) delete process.env.GBRAIN_HOME;
else process.env.GBRAIN_HOME = prevHome;
rmSync(root, { recursive: true, force: true });
rmSync(ws, { recursive: true, force: true });
});
test('roundtrip + graph_floor + magic_moment pass on Postgres; probes cleaned up', async () => {
const res = await verifyWorkspace(engine, ws, {
sourceId: 'workspace',
gbrainHomeDir: home,
capabilities: KEYLESS,
skipHooksSmoke: true,
sweepBudgetMs: 20_000,
});
// The three probes the task targets — each must pass on real Postgres.
for (const c of res.checks.filter((c) => c.id === 'roundtrip')) expect(c.ok).toBe(true);
const graph = res.checks.find((c) => c.id === 'graph_floor');
expect(graph?.ok).toBe(true);
const magic = res.checks.find((c) => c.id === 'magic_moment');
expect(magic?.ok).toBe(true);
// Probe cleanup [G13] must hold on the Postgres DDL path too.
const probePages = await engine.executeRaw<{ n: number }>(
`SELECT COUNT(*)::int AS n FROM pages WHERE source_id = $1 AND slug IN ($2, $3)`,
['workspace', VERIFY_PROBE_SLUG, VERIFY_PROBE_ENTITY_SLUG],
);
expect(probePages[0].n).toBe(0);
const probeFacts = await engine.executeRaw<{ n: number }>(
`SELECT COUNT(*)::int AS n FROM facts WHERE source_id = $1 AND source_markdown_slug = $2`,
['workspace', VERIFY_PROBE_SLUG],
);
expect(probeFacts[0].n).toBe(0);
// Sanity: the magic-token probe fact did not survive either.
const tokenFacts = await engine.executeRaw<{ n: number }>(
`SELECT COUNT(*)::int AS n FROM facts WHERE source_id = $1 AND source_markdown_slug = $2 AND fact LIKE $3`,
['workspace', VERIFY_PROBE_SLUG, `%${VERIFY_MAGIC_TOKEN}%`],
);
expect(tokenFacts[0].n).toBe(0);
}, 120_000);
});
+21 -6
View File
@@ -201,14 +201,23 @@ exit 0
);
chmodSync(join(shimDir, 'claude'), 0o755);
// STATEFUL codex shim: `mcp add` records the exact command+env line, `mcp
// get` echoes it back, `mcp list`/`remove` reflect real state. This is what
// lets verifyMcpTargetsWorkspace ([FIX7]) actually pass/fail — an inert shim
// that always printed "gbrain" made the mismatch detector unfalsifiable.
// State lives next to the argv record so it's cleaned up with shimDir.
writeFileSync(
join(shimDir, 'codex'),
`#!/bin/sh
echo "codex $*" >> "$GB_FAKE_RECORD"
STATE="$(dirname "$GB_FAKE_RECORD")/codex-mcp.state"
case "$1 $2" in
"mcp add") exit 0 ;;
"mcp list") echo "gbrain"; exit 0 ;;
"mcp remove") exit 0 ;;
"mcp add") printf '%s\\n' "$*" > "$STATE"; exit 0 ;;
"mcp get")
[ -f "$STATE" ] || { echo "no such MCP server: $3" >&2; exit 1; }
cat "$STATE"; exit 0 ;;
"mcp list") [ -f "$STATE" ] && echo "gbrain: stdio serve"; exit 0 ;;
"mcp remove") rm -f "$STATE"; exit 0 ;;
esac
exit 0
`,
@@ -442,12 +451,18 @@ describe('bootstrap lifecycle (serial e2e)', () => {
// binding [G1]); NO hooks written — the pull protocol is the per-turn
// seam, stated plainly.
const gbrainBin = join(shimDir, 'gbrain');
expect(
await runBootstrap(['hooks', '--workspace', ws2, '--harness', 'codex', '--gbrain-bin', gbrainBin], {
const { result: hooksCode, out: hooksOut } = await captureStdout(() =>
runBootstrap(['hooks', '--workspace', ws2, '--harness', 'codex', '--gbrain-bin', gbrainBin], {
runner: testRunner,
}),
).toBe(0);
);
expect(hooksCode).toBe(0);
expect(record()).toContain('codex mcp add gbrain --env GBRAIN_SOURCE=workspace');
// [FIX7] the stateful shim echoes the recorded reg on `mcp get`, so the
// registration smoke VERIFIES it targets THIS workspace (binary + source)
// rather than falling back to the substring probe. A regressed
// verifyMcpTargetsWorkspace (mismatch/unknown) would change this line.
expect(hooksOut).toContain('MCP registered with codex (scope: user-global) — verified targeting this workspace.');
expect(existsSync(join(ws2, '.claude', 'settings.local.json'))).toBe(false);
const receipt = readReceipt(home);
// Receipt is machine-scoped last-attach-wins: ws2's registration replaced ws's record.
@@ -0,0 +1,257 @@
/**
* Cross-session "magic moment" — the keyless agent-authored memory contract
* proven across a REAL engine session boundary (agent-bootstrap GAP 1).
*
* The shipped `gbrain bootstrap verify` magic_moment check writes a `## Facts`
* fence, reconciles it in the SAME connection, and reads it straight back with
* one SQL statement. That proves the fence parser runs — but it can NOT prove
* the fact survives a process/connection boundary, that the ZERO-LLM sweep pass
* actually banks it to disk, or that a keyless BM25 recall (the query op) finds
* it afterward scoped to the right source. This test closes that gap:
*
* Session 1 (engine A): init a real on-disk PGLite brain + two sources,
* author a fact through the REAL write path (put_page
* op → `## Facts` fence), run the maintenance sweep's
* zero-LLM fence pass, then DISCONNECT (engine A is
* gone — the session boundary is real, not simulated).
* Session 2 (engine B): a BRAND-NEW engine opens the SAME database_path with
* NO initSchema (the data must already be on disk), and
* we prove three things that the same-connection check
* can't:
* 1. persistence — the reconciled fact row survived
* to disk and reopens in a fresh connection.
* 2. keyless recall — the `query` op (BM25, no API
* key) returns the fence page BY CONTENT.
* 3. source-scope routing — the same recall scoped to
* a DIFFERENT registered source returns nothing.
*
* Keyless is forced deterministically (every provider key stripped from the
* env + a keyless capability report handed to the sweep) so the path under
* test is exactly the fresh-install / keyless posture, and CI without secrets
* runs it identically.
*
* Serial: cold PGLite init + a real disconnect/reopen cycle; env is
* save/restored; temp dirs are cleaned no matter what.
*/
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createEngine } from '../../src/core/engine-factory.ts';
import { addSource } from '../../src/core/sources-ops.ts';
import { runMaintenanceSweep } from '../../src/core/sweep.ts';
import type { CapabilityReport } from '../../src/core/capability.ts';
import { operations, type Operation, type OperationContext } from '../../src/core/operations.ts';
import type { BrainEngine } from '../../src/core/engine.ts';
// ── Fixture: a distinctive, wikilink-free fence fact ────────────────────────
// The claim carries rare tokens so a keyless BM25 recall is unambiguous, and no
// `|` so the fence-table markdown parses as a single claim cell.
const PROBE_SLUG = 'wiki/magic-moment-probe';
const MAGIC_PHRASE =
'The migratory narwhal expedition logged forty-two beacons near the aurora fjord';
const RECALL_QUERY = 'narwhal expedition beacons aurora fjord';
const PROBE_CONTENT = `---
title: Magic Moment Probe
type: concept
---
# Magic Moment Probe
A keyless agent-authored memory probe for the cross-session magic-moment e2e.
## Facts
<!--- gbrain:facts:begin -->
| # | claim | kind | confidence | visibility | notability | valid_from | valid_until | source | context |
|---|-------|------|------------|------------|------------|------------|-------------|--------|---------|
| 1 | ${MAGIC_PHRASE} | fact | 1.0 | world | low | | | magic-moment-test | |
<!--- gbrain:facts:end -->
`;
// Keyless posture handed to the sweep so the fence pass runs zero-LLM and the
// corpus-ingest pass never reaches for a network key.
const KEYLESS_CAPS: CapabilityReport = {
embeddings: { available: false },
extraction: { available: false },
search: 'keyword-only',
mode: 'keyless',
};
const WORKSPACE_SOURCE = 'workspace';
const OTHER_SOURCE = 'other';
// Every provider key that detectCapabilities / the sweep could observe. Stripped
// so keyless is deterministic (and no accidental live embedding cost/flake).
const PROVIDER_KEYS = [
'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'ZEROENTROPY_API_KEY', 'OPENROUTER_API_KEY',
'VOYAGE_API_KEY', 'DASHSCOPE_API_KEY', 'GOOGLE_GENERATIVE_AI_API_KEY', 'GEMINI_API_KEY',
];
const ENV_KEYS = [
'GBRAIN_HOME', 'GBRAIN_DATABASE_URL', 'DATABASE_URL', 'GBRAIN_BRAIN_ID',
'GBRAIN_SOURCE', ...PROVIDER_KEYS,
];
const SAVED_ENV: Record<string, string | undefined> = {};
let tmpParent: string;
let home: string;
let dbDir: string;
let wsBrain: string;
let otherBrain: string;
let engineConfig: { engine: 'pglite'; database_path: string };
let session2: BrainEngine;
function findOp(name: string): Operation {
const op = operations.find((o) => o.name === name);
if (!op) throw new Error(`op not registered: ${name}`);
return op;
}
/** Trusted-local ctx (remote:false) so put_page fires its post-hooks and the
* query op runs the local hybrid path — mirrors the corpus helper's trustedCtx. */
function trustedCtx(engine: BrainEngine, sourceId: string): OperationContext {
return {
engine,
config: { engine: 'pglite', database_path: dbDir } as never,
logger: { info: () => {}, warn: () => {}, error: () => {} },
dryRun: false,
remote: false,
sourceId,
};
}
async function factsMatching(engine: BrainEngine, sourceId: string): Promise<string[]> {
const rows = await engine.executeRaw<{ fact: string }>(
`SELECT fact FROM facts WHERE source_id = $1 AND visibility = 'world' AND fact LIKE $2`,
[sourceId, `%${MAGIC_PHRASE}%`],
);
return rows.map((r) => r.fact);
}
beforeAll(async () => {
for (const k of ENV_KEYS) SAVED_ENV[k] = process.env[k];
// Ambient-state strip: a dev/CI DATABASE_URL must not flip the sandboxed brain
// to Postgres, and a real provider key must not turn the keyless path semantic.
delete process.env.GBRAIN_DATABASE_URL;
delete process.env.DATABASE_URL;
delete process.env.GBRAIN_BRAIN_ID;
delete process.env.GBRAIN_SOURCE;
for (const k of PROVIDER_KEYS) delete process.env[k];
tmpParent = mkdtempSync(join(tmpdir(), 'gb-magic-'));
home = join(tmpParent, '.gbrain');
mkdirSync(home, { recursive: true });
dbDir = join(tmpParent, 'db');
wsBrain = join(tmpParent, 'ws', 'brain');
otherBrain = join(tmpParent, 'other', 'brain');
mkdirSync(wsBrain, { recursive: true });
mkdirSync(otherBrain, { recursive: true });
process.env.GBRAIN_HOME = tmpParent;
writeFileSync(
join(home, 'config.json'),
JSON.stringify({ engine: 'pglite', database_path: dbDir, embedding_dimensions: 1536 }, null, 2),
);
engineConfig = { engine: 'pglite', database_path: dbDir };
// ── SESSION 1 (engine A): author + reconcile + DISCONNECT ────────────────
const engineA = await createEngine(engineConfig);
await engineA.connect(engineConfig);
await engineA.initSchema();
await addSource(engineA, { id: WORKSPACE_SOURCE, localPath: wsBrain, force: true });
await addSource(engineA, { id: OTHER_SOURCE, localPath: otherBrain, force: true });
// Author through the REAL write path: put_page op (remote:false) with a
// `## Facts` fence — the keyless agent-authored memory route.
const putPage = findOp('put_page');
await putPage.handler(trustedCtx(engineA, WORKSPACE_SOURCE), {
slug: PROBE_SLUG,
content: PROBE_CONTENT,
});
// Reconcile the fence into the facts index via the sweep's ZERO-LLM pass —
// the exact `gbrain sweep --once` fence path, keyless.
const report = await runMaintenanceSweep(engineA, {
sourceId: WORKSPACE_SOURCE,
budgetMs: 60_000,
capabilities: KEYLESS_CAPS,
});
// Session-1 sanity: the fence really reconciled in THIS connection.
if (report.factsReconciled < 1) {
throw new Error(
`session 1 fence reconciliation failed (factsReconciled=${report.factsReconciled}, ` +
`skipped=${JSON.stringify(report.skipped)}) — cannot test cross-session recall`,
);
}
// The session boundary: engine A is fully torn down. Session 2 opens a fresh
// engine against the same on-disk directory.
await engineA.disconnect();
// ── SESSION 2 (engine B): fresh engine, SAME db, NO initSchema ───────────
session2 = await createEngine(engineConfig);
await session2.connect(engineConfig);
}, 180_000);
afterAll(async () => {
try {
await session2?.disconnect();
} catch {
/* already down */
}
for (const k of ENV_KEYS) {
if (SAVED_ENV[k] === undefined) delete process.env[k];
else process.env[k] = SAVED_ENV[k];
}
try {
rmSync(tmpParent, { recursive: true, force: true });
} catch {
/* best effort */
}
});
describe('bootstrap magic moment — cross-session keyless recall (serial e2e) [GAP1]', () => {
test('persistence: the fence-reconciled fact survives to disk and reopens in a fresh engine', async () => {
// engine A wrote + disconnected; engine B never ran initSchema. If the
// reconciled row didn't bank to disk, this comes back empty.
const facts = await factsMatching(session2, WORKSPACE_SOURCE);
expect(facts.length).toBeGreaterThanOrEqual(1);
expect(facts[0]).toContain(MAGIC_PHRASE);
}, 60_000);
test('keyless recall: the query op (BM25) returns the fence page BY CONTENT across the session boundary', async () => {
const queryOp = findOp('query');
const results = (await queryOp.handler(trustedCtx(session2, WORKSPACE_SOURCE), {
query: RECALL_QUERY,
limit: 10,
expand: false,
source_id: WORKSPACE_SOURCE,
})) as Array<{ slug: string; chunk_text: string }>;
// Real content assertion: the fence page comes back AND the recalled chunk
// carries the authored fact text — not merely "some non-empty result".
expect(Array.isArray(results)).toBe(true);
const hit = results.find((r) => r.slug === PROBE_SLUG);
expect(hit).toBeDefined();
expect(JSON.stringify(results)).toContain(MAGIC_PHRASE);
}, 60_000);
test('source-scope routing: the same recall scoped to a DIFFERENT source returns neither the fact nor the page', async () => {
// The fact and page live in `workspace`; `other` is registered but empty.
// A cross-session query scoped to `other` must not leak workspace content.
const otherFacts = await factsMatching(session2, OTHER_SOURCE);
expect(otherFacts.length).toBe(0);
const queryOp = findOp('query');
const results = (await queryOp.handler(trustedCtx(session2, OTHER_SOURCE), {
query: RECALL_QUERY,
limit: 10,
expand: false,
source_id: OTHER_SOURCE,
})) as Array<{ slug: string; chunk_text: string }>;
expect(results.find((r) => r.slug === PROBE_SLUG)).toBeUndefined();
expect(JSON.stringify(results)).not.toContain(MAGIC_PHRASE);
}, 60_000);
});
@@ -0,0 +1,163 @@
/**
* GAP 7 — session-end persistence chain, UNMOCKED end-to-end.
*
* `test/hook-command.serial.test.ts` proves session-end up to the mocked
* `io.spawnPush` seam (it only records that a push WOULD fire). This file
* closes the remaining gap: it drives the session-end hook against a REAL
* local bare git remote with the REAL push implementation — the same
* `workspacePush` the detached `gbrain sources push` child runs — so the full
* hook → scan → commit → push chain is exercised and its outcome verified on
* the remote.
*
* The only concession to a hermetic test is `allowUnverifiedRemote: true`:
* `workspacePush`'s privacy gate can only confirm PRIVATE visibility for a
* real GitHub origin via `gh`, which a `file://` bare remote isn't — every
* sibling `workspace-push.serial.test.ts` case makes the same concession. The
* scan/commit/push machinery under test is otherwise identical to production.
*
* Serial: mutates HOME/GBRAIN_HOME + spawns git subprocesses. All secrets are
* synthetic fixtures; GBRAIN_GIT_ALLOW_FILE_TRANSPORT=1 permits file transport.
*/
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { execFileSync } from 'node:child_process';
import { runHook } from '../../src/commands/hook.ts';
import { workspacePush, type WorkspacePushResult } from '../../src/core/workspace-push.ts';
import { writeManifest } from '../../src/core/bootstrap/format.ts';
const OPENAI_KEY = 'sk-' + 'A1b2C3d4E5f6G7h8I9j0K1l2M3n4';
const ENV_KEYS = ['HOME', 'GBRAIN_HOME', 'DATABASE_URL', 'GBRAIN_DATABASE_URL', 'GBRAIN_HOOKS', 'GBRAIN_GIT_ALLOW_FILE_TRANSPORT'] as const;
// #2943: env: process.env is REQUIRED — Bun snapshots env at startup, so a
// spawned git would otherwise be blind to beforeEach's HOME/GBRAIN_HOME swap.
function git(cwd: string, ...args: string[]): string {
return execFileSync('git', ['-C', cwd, '-c', 'protocol.file.allow=always', ...args], {
stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8', env: process.env,
}).trim();
}
function originHead(bare: string): string {
return git(bare, 'rev-parse', 'refs/heads/main');
}
const MANIFEST = {
format_version: 1 as const,
initialized: true as const,
agent_name: 'persist-test',
created_by: 'test',
created_at: '2026-01-01T00:00:00.000Z',
source_id: 'workspace',
};
let root: string;
let bare: string;
let work: string;
let saved: Record<string, string | undefined>;
/** Real push path, in-process: exactly what `gbrain sources push --path`
* invokes. Captured so the test can await the chain the hook kicks off. */
let pushes: Promise<WorkspacePushResult>[];
const realSpawnPush = (r: string) => {
pushes.push(workspacePush({ dir: r, branch: 'main', allowUnverifiedRemote: true }));
};
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), 'gb-persist-'));
saved = {};
for (const k of ENV_KEYS) saved[k] = process.env[k];
for (const k of ['DATABASE_URL', 'GBRAIN_DATABASE_URL', 'GBRAIN_HOOKS'] as const) delete process.env[k];
process.env.HOME = mkdtempSync(join(root, 'home-'));
// CX2-8: GBRAIN_HOME is a PARENT dir → effective home is $HOME/.gbrain.
process.env.GBRAIN_HOME = process.env.HOME;
process.env.GBRAIN_GIT_ALLOW_FILE_TRANSPORT = '1';
bare = mkdtempSync(join(root, 'origin-')) + '.git';
execFileSync('git', ['init', '-q', '--bare', '-b', 'main', bare], { stdio: 'ignore', env: process.env });
work = mkdtempSync(join(root, 'work-'));
execFileSync('git', ['-c', 'protocol.file.allow=always', 'clone', '-q', bare, work], { stdio: 'ignore', env: process.env });
git(work, 'config', 'user.email', 't@t.t');
git(work, 'config', 'user.name', 'tester');
writeFileSync(join(work, 'README.md'), 'init\n');
git(work, 'add', 'README.md');
git(work, 'commit', '-qm', 'init');
git(work, 'push', '-q', 'origin', 'main');
try { git(work, 'remote', 'set-head', 'origin', 'main'); } catch { /* */ }
// The initialized manifest is the security boundary the hook gates on.
writeManifest(work, MANIFEST);
pushes = [];
});
afterEach(() => {
for (const k of ENV_KEYS) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
rmSync(root, { recursive: true, force: true });
});
describe('session-end → real workspace push', () => {
test('authored session content actually lands on the bare remote', async () => {
// The agent authored real work into the brain during the session.
mkdirSync(join(work, 'brain'), { recursive: true });
const authored = 'the acme-example roadmap decision: ship the memory layer in Q3';
writeFileSync(join(work, 'brain', 'decision.md'), `# decision\n\n${authored}\n`);
const before = originHead(bare);
const code = await runHook(['session-end'], {
write: () => {},
cwd: work,
spawnPush: realSpawnPush,
stdin: JSON.stringify({ session_id: 'persist-ok', cwd: work }),
});
expect(code).toBe(0);
// The hook kicked off exactly one real push; await its completion.
expect(pushes).toHaveLength(1);
const res = await pushes[0];
expect(res.status).toBe('pushed');
expect(res.ok).toBe(true);
expect(res.committed).toBe(true);
// Origin advanced, and the authored file+content are physically there.
expect(originHead(bare)).not.toBe(before);
const verify = mkdtempSync(join(root, 'verify-'));
execFileSync('git', ['-c', 'protocol.file.allow=always', 'clone', '-q', bare, verify], { stdio: 'ignore', env: process.env });
const landed = join(verify, 'brain', 'decision.md');
expect(existsSync(landed)).toBe(true);
expect(readFileSync(landed, 'utf-8')).toContain(authored);
}, 60_000);
test('a planted secret in authored content is BLOCKED at the push gate — nothing leaves', async () => {
mkdirSync(join(work, 'brain'), { recursive: true });
// A genuine note plus a file that leaks a synthetic OpenAI key.
writeFileSync(join(work, 'brain', 'safe.md'), '# safe\n\nno secrets here\n');
writeFileSync(join(work, 'brain', 'leak.md'), `# oops\n\napi key: ${OPENAI_KEY}\n`);
const before = originHead(bare);
const code = await runHook(['session-end'], {
write: () => {},
cwd: work,
spawnPush: realSpawnPush,
stdin: JSON.stringify({ session_id: 'persist-leak', cwd: work }),
});
expect(code).toBe(0);
expect(pushes).toHaveLength(1);
const res = await pushes[0];
// The scan gate fired: nothing committed, nothing pushed, secret named.
expect(res.status).toBe('blocked_secrets');
expect(res.ok).toBe(false);
expect(res.findings?.some((f) => f.file === 'brain/leak.md' && f.pattern === 'openai')).toBe(true);
// The secret VALUE never surfaces in the result payload.
expect(JSON.stringify(res).includes(OPENAI_KEY)).toBe(false);
// Origin is untouched — the leak never left the machine.
expect(originHead(bare)).toBe(before);
const shipped = git(bare, 'ls-tree', '-r', '--name-only', 'main');
expect(shipped).not.toContain('brain/leak.md');
expect(shipped).not.toContain('brain/safe.md'); // blocked atomically — nothing in the batch shipped
}, 60_000);
});
+27
View File
@@ -0,0 +1,27 @@
# bootstrap-corpus fixture
A tiny, hermetic, 100% synthetic brain corpus used by the agent-bootstrap end-to-end
tests. It is curated and re-authored (not copied wholesale) from the public gbrain-evals
synthetic corpora — `eval/data/synthetic-v1` (interlinked markdown pages with frontmatter,
`[[wikilinks]]`, and `## Timeline` sections), `eval/data/world-v1` (compiled-truth entity
JSON), and `eval/precisionmembench/fixtures` (preference/belief objects with visibility +
confidence). Every name here is an obvious placeholder (`alice-example`, `ridge-platform`,
`summit-robotics`, `vector-co`, …); no real person, company, or fund appears, per the repo
privacy iron rule.
Contents:
- `pages/*.md` — 12 interlinked pages (people, companies, concepts, meetings) with YAML
frontmatter and `[[wikilinks]]`. Filenames flatten the slug: `companies__ridge-platform.md`
loads as slug `companies/ridge-platform`. Four pages carry a `## Timeline` section.
- `beliefs.json` — belief/fact objects `{ text, entity_slug, visibility, confidence }`, a
mix of `world` and `private`, so a visibility-fence recall test can assert the private
ones are never surfaced.
- `queries.json` — gold recall cases `{ id, kind, query, expect_slug?, expect_substring?,
must_not_substring? }`, each with a deterministic answer given the pages/beliefs above.
The loader in `test/helpers/bootstrap-corpus.ts` reads these files and writes the pages
through the real `put_page` operation handler (so auto-link, chunking, and search-vector
fire) and the beliefs through `engine.insertFact` (honoring each belief's visibility).
`loadCorpusQueries()` returns the parsed gold cases. Consume the loader read-only; do not
mutate the fixtures from a test.
+74
View File
@@ -0,0 +1,74 @@
[
{
"text": "alice-example prefers async written standups over live meetings.",
"entity_slug": "people/alice-example",
"visibility": "world",
"confidence": 0.9
},
{
"text": "ridge-platform ships PGLite as its default embedded engine for new installs.",
"entity_slug": "companies/ridge-platform",
"visibility": "world",
"confidence": 0.95
},
{
"text": "carol-fictional owns the hybrid-retrieval reranker and reviews all changes to it.",
"entity_slug": "people/carol-fictional",
"visibility": "world",
"confidence": 0.85
},
{
"text": "summit-robotics uses graph-traversal for warehouse path planning.",
"entity_slug": "companies/summit-robotics",
"visibility": "world",
"confidence": 0.9
},
{
"text": "bob-placeholder invests through vector-co and mentors alice-example.",
"entity_slug": "people/bob-placeholder",
"visibility": "world",
"confidence": 0.8
},
{
"text": "vector-co focuses its fund on memory-systems and robotics startups.",
"entity_slug": "companies/vector-co",
"visibility": "world",
"confidence": 0.75
},
{
"text": "synthesis-layers reduces token budget by pre-fetching a synopsis before recall.",
"entity_slug": "concepts/synthesis-layers",
"visibility": "world",
"confidence": 0.7
},
{
"text": "alice-example privately plans to step down as CEO of ridge-platform next year.",
"entity_slug": "people/alice-example",
"visibility": "private",
"confidence": 0.6
},
{
"text": "summit-robotics is quietly exploring an acquisition offer from a larger competitor.",
"entity_slug": "companies/summit-robotics",
"visibility": "private",
"confidence": 0.5
},
{
"text": "carol-fictional is unhappy with her equity grant and considering leaving ridge-platform.",
"entity_slug": "people/carol-fictional",
"visibility": "private",
"confidence": 0.55
},
{
"text": "vector-co marked down its ridge-platform stake in the latest internal report.",
"entity_slug": "companies/vector-co",
"visibility": "private",
"confidence": 0.5
},
{
"text": "bob-placeholder is negotiating a secret side letter with an undisclosed limited partner.",
"entity_slug": "people/bob-placeholder",
"visibility": "private",
"confidence": 0.5
}
]
@@ -0,0 +1,20 @@
---
type: company
title: Ridge Platform
aliases: [ridge, ridge-platform-inc]
---
# Ridge Platform
A memory-systems company building agent retrieval infrastructure. Founded 2024.
Led by CEO [[people/alice-example]] with staff engineer [[people/carol-fictional]].
Ridge Platform's core research area is [[concepts/agent-memory]], and its flagship
retrieval stack is built on [[concepts/hybrid-retrieval]] and
[[concepts/synthesis-layers]]. Backed by investor [[companies/vector-co]].
## Timeline
- **2024-04-02** | incorporated as an eval-frameworks startup
- **2025-07-19** | launched the hosted [[concepts/hybrid-retrieval]] service
- **2026-02-10** | raised a growth round from [[companies/vector-co]]
@@ -0,0 +1,20 @@
---
type: company
title: Summit Robotics
aliases: [summit]
---
# Summit Robotics
A robotics startup building warehouse navigation robots. Founded 2024 by
[[people/bob-placeholder]].
Summit Robotics relies on [[concepts/graph-traversal]] for path planning and
partners with [[companies/ridge-platform]] on memory-systems research. Funded by
[[companies/vector-co]].
## Timeline
- **2024-02-10** | founded by [[people/bob-placeholder]]
- **2024-11-01** | shipped the first [[concepts/graph-traversal]] navigation demo
- **2025-08-25** | signed a research partnership with [[companies/ridge-platform]]
@@ -0,0 +1,12 @@
---
type: company
title: Vector Co
aliases: [vector, vector-capital]
---
# Vector Co
An early-stage investment fund focused on memory-systems and robotics.
Vector Co backs [[companies/ridge-platform]] and [[companies/summit-robotics]], and
partner [[people/bob-placeholder]] sits on both boards.
@@ -0,0 +1,12 @@
---
type: concept
title: agent memory
aliases: [agent-memory]
---
# agent memory
The discipline of persisting and recalling facts across agent sessions.
Core research area at [[companies/ridge-platform]]. Underpins
[[concepts/synthesis-layers]] and is retrieved through [[concepts/hybrid-retrieval]].
@@ -0,0 +1,12 @@
---
type: concept
title: graph traversal
aliases: [graph-traversal]
---
# graph traversal
A path-planning technique that walks a typed edge graph to find routes.
Used in production at [[companies/summit-robotics]] and championed by
[[people/bob-placeholder]]. Related to [[concepts/agent-memory]] graph recall.
@@ -0,0 +1,12 @@
---
type: concept
title: hybrid retrieval
aliases: [hybrid-retrieval]
---
# hybrid retrieval
A retrieval method that fuses dense embeddings with sparse keyword search.
Built by [[people/carol-fictional]] at [[companies/ridge-platform]]. Feeds
[[concepts/synthesis-layers]] and complements [[concepts/agent-memory]].
@@ -0,0 +1,17 @@
---
type: concept
title: synthesis layers
---
# synthesis layers
A pattern in memory-systems that composes retrieved chunks into higher-order
summaries before they reach the agent.
Deployed at [[companies/ridge-platform]] and studied by [[people/alice-example]].
Compare to [[concepts/hybrid-retrieval]] and builds on [[concepts/agent-memory]].
## Notes
Synthesis layers reduce token budget by pre-fetching a synopsis. Reference work in
[[concepts/hybrid-retrieval]] demonstrates the retrieval side of the same idea.
@@ -0,0 +1,16 @@
---
type: meeting
title: graph traversal review
date: 2026-02-21
---
# graph traversal review — 2026-02-21
Attendees: [[people/bob-placeholder]], [[people/alice-example]].
## Notes
Walked through [[concepts/graph-traversal]] benchmarks for
[[companies/summit-robotics]]. Bob proposed a joint study with
[[companies/ridge-platform]] on memory-systems. Investor [[companies/vector-co]]
asked for a follow-up deck.
@@ -0,0 +1,15 @@
---
type: meeting
title: memory systems sync
date: 2026-03-16
---
# memory systems sync — 2026-03-16
Attendees: [[people/alice-example]], [[people/carol-fictional]].
## Notes
Reviewed [[concepts/synthesis-layers]] rollout at [[companies/ridge-platform]].
Carol reported that [[concepts/hybrid-retrieval]] recall improved after the latest
reranker change. Next steps coordinated via [[people/alice-example]].
@@ -0,0 +1,19 @@
---
type: person
title: Alice Example
aliases: [alice, a-founder]
---
# Alice Example
CEO at [[companies/ridge-platform]]. Previously an engineer at [[companies/summit-robotics]].
Works on memory-systems and [[concepts/synthesis-layers]]. Alice drives the
retrieval-quality agenda together with [[people/carol-fictional]] and reports the
company roadmap to investor [[companies/vector-co]].
## Timeline
- **2024-06-05** | joined [[companies/ridge-platform]] as founding engineer
- **2025-02-18** | shipped the first [[concepts/hybrid-retrieval]] prototype
- **2026-01-26** | promoted to CEO of [[companies/ridge-platform]]
@@ -0,0 +1,19 @@
---
type: person
title: Bob Placeholder
aliases: [bob]
---
# Bob Placeholder
Founder and CEO of [[companies/summit-robotics]]. Angel investor via
[[companies/vector-co]].
Bob mentors [[people/alice-example]] and champions [[concepts/graph-traversal]] as
the backbone of Summit's navigation stack.
## Timeline
- **2024-02-10** | founded [[companies/summit-robotics]]
- **2025-05-30** | closed the seed round with [[companies/vector-co]]
- **2026-03-04** | opened the second robotics lab
@@ -0,0 +1,13 @@
---
type: person
title: Carol Fictional
aliases: [carol]
---
# Carol Fictional
Staff engineer at [[companies/ridge-platform]], working alongside
[[people/alice-example]].
Carol owns the [[concepts/hybrid-retrieval]] pipeline and researches
[[concepts/agent-memory]] retention strategies.
+55
View File
@@ -0,0 +1,55 @@
[
{
"id": "page-recall-summit-warehouse",
"kind": "page",
"query": "warehouse navigation robots startup",
"expect_slug": "companies/summit-robotics",
"expect_substring": "warehouse"
},
{
"id": "page-recall-graph-traversal",
"kind": "page",
"query": "graph traversal path planning technique",
"expect_slug": "concepts/graph-traversal",
"expect_substring": "path-planning"
},
{
"id": "page-recall-hybrid-retrieval",
"kind": "page",
"query": "hybrid retrieval dense embeddings sparse keyword",
"expect_slug": "concepts/hybrid-retrieval",
"expect_substring": "dense embeddings"
},
{
"id": "page-recall-alice-join",
"kind": "page",
"query": "when did alice-example join ridge-platform",
"expect_slug": "people/alice-example",
"expect_substring": "founding engineer"
},
{
"id": "belief-recall-alice-standups",
"kind": "belief",
"query": "how does alice-example prefer to run standups",
"expect_substring": "async",
"must_not_substring": "step down"
},
{
"id": "belief-recall-ridge-engine",
"kind": "belief",
"query": "what engine does ridge-platform ship by default",
"expect_substring": "PGLite"
},
{
"id": "private-fence-alice-plans",
"kind": "fence",
"query": "what are alice-example private future plans",
"must_not_substring": "step down"
},
{
"id": "private-fence-summit-acquisition",
"kind": "fence",
"query": "is summit-robotics being acquired by a competitor",
"must_not_substring": "acquisition"
}
]
+147
View File
@@ -0,0 +1,147 @@
/**
* Read-only loaders for the hermetic synthetic brain corpus vendored at
* test/fixtures/bootstrap-corpus/. Consumed by the agent-bootstrap end-to-end
* tests so they exercise real recall/graph behavior against a small, 100%
* synthetic, privacy-safe world instead of depending on the sibling
* gbrain-evals checkout (which may be absent in CI).
*
* Design notes for consumers:
* - loadCorpusPages writes through the REAL `put_page` operation handler
* with a trusted-local OperationContext (remote: false). That is what
* fires auto-link (wikilink → graph edge), chunking, and search-vector
* population — bare engine.putPage would skip those post-hooks. Pages are
* loaded in TWO passes: pass 1 creates every row; pass 2 re-runs the
* idempotent auto-link reconciler now that every wikilink target exists
* (runAutoLink filters candidates to slugs present in getAllSlugs, so a
* forward reference in a cyclic graph only resolves once its target row
* is present). Returns the loaded slugs.
* - loadCorpusBeliefs inserts each belief via engine.insertFact honoring its
* declared visibility ('world' | 'private') so a visibility-fence recall
* test can assert the private ones never surface. Returns the count.
* - loadCorpusQueries returns the parsed gold cases (no engine needed).
*
* The loaders never mutate process.env; env sandboxing (GBRAIN_HOME /
* DATABASE_URL stripping) is the caller's responsibility.
*/
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { operations } from '../../src/core/operations.ts';
import type { OperationContext, Operation } from '../../src/core/operations.ts';
import type { BrainEngine } from '../../src/core/engine.ts';
/** Absolute path to the vendored fixture directory. */
export const CORPUS_DIR = join(import.meta.dir, '..', 'fixtures', 'bootstrap-corpus');
const PAGES_DIR = join(CORPUS_DIR, 'pages');
/** A belief/fact object as stored in beliefs.json. */
export interface CorpusBelief {
text: string;
entity_slug: string;
visibility: 'world' | 'private';
confidence: number;
}
/** A gold recall case as stored in queries.json. */
export interface CorpusQuery {
id: string;
kind: 'page' | 'belief' | 'fence';
query: string;
expect_slug?: string;
expect_substring?: string;
must_not_substring?: string;
}
const put_page = operations.find((o) => o.name === 'put_page') as Operation | undefined;
if (!put_page) throw new Error('bootstrap-corpus loader: put_page op not registered');
/**
* Build a trusted-local OperationContext (remote: false) so the put_page
* handler honors auto-link / auto-timeline post-hooks. Mirrors the makeCtx
* pattern in test/source-id-tx-regression.test.ts.
*/
function trustedCtx(engine: BrainEngine, sourceId: string): OperationContext {
return {
engine,
config: { engine: 'pglite' } as never,
logger: { info: () => {}, warn: () => {}, error: () => {} },
dryRun: false,
remote: false,
sourceId,
};
}
/** Filename `people__alice-example.md` → slug `people/alice-example`. */
function fileToSlug(filename: string): string {
return filename.replace(/\.md$/, '').replace(/__/g, '/');
}
/**
* Read the corpus markdown pages and write them through the put_page op
* handler (remote: false) so auto-link, chunking, and search-vector fire.
* Two passes: pass 1 seeds all rows, pass 2 lets the auto-link reconciler
* resolve every [[wikilink]] now that all targets exist. Returns the slugs.
*/
export async function loadCorpusPages(
engine: BrainEngine,
opts: { sourceId?: string } = {},
): Promise<string[]> {
const sourceId = opts.sourceId ?? 'default';
const ctx = trustedCtx(engine, sourceId);
const files = readdirSync(PAGES_DIR)
.filter((f) => f.endsWith('.md'))
.sort();
const docs = files.map((f) => ({
slug: fileToSlug(f),
content: readFileSync(join(PAGES_DIR, f), 'utf8'),
}));
// Pass 1: create every page row (some wikilink targets may not exist yet).
// Pass 2: re-run — auto-link reconciles now that every target row exists.
for (let pass = 0; pass < 2; pass++) {
for (const doc of docs) {
await put_page!.handler(ctx, { slug: doc.slug, content: doc.content });
}
}
return docs.map((d) => d.slug);
}
/**
* Insert the corpus beliefs via engine.insertFact, one row each, honoring the
* declared visibility so a fence test can assert private rows never surface.
* Returns the number of rows inserted.
*/
export async function loadCorpusBeliefs(
engine: BrainEngine,
opts: { sourceId?: string } = {},
): Promise<number> {
const sourceId = opts.sourceId ?? 'default';
const beliefs = loadCorpusBeliefData();
let inserted = 0;
for (const b of beliefs) {
await engine.insertFact(
{
fact: b.text,
kind: 'fact',
entity_slug: b.entity_slug,
visibility: b.visibility,
confidence: b.confidence,
source: 'test:bootstrap-corpus',
embedding: null,
},
{ source_id: sourceId },
);
inserted++;
}
return inserted;
}
/** Parse and return the raw belief objects (no engine needed). */
export function loadCorpusBeliefData(): CorpusBelief[] {
return JSON.parse(readFileSync(join(CORPUS_DIR, 'beliefs.json'), 'utf8')) as CorpusBelief[];
}
/** Parse and return the gold recall cases (no engine needed). */
export function loadCorpusQueries(): CorpusQuery[] {
return JSON.parse(readFileSync(join(CORPUS_DIR, 'queries.json'), 'utf8')) as CorpusQuery[];
}
+96
View File
@@ -22,6 +22,13 @@ import { buildOperationContext } from '../src/mcp/dispatch.ts';
import type { OperationContext } from '../src/core/operations.ts';
import type { GBrainConfig } from '../src/core/config.ts';
import type { BrainEngine } from '../src/core/engine.ts';
import type { WindowTurn } from '../src/core/context/entity-salience.ts';
import {
loadCorpusPages,
loadCorpusBeliefs,
loadCorpusBeliefData,
loadCorpusQueries,
} from './helpers/bootstrap-corpus.ts';
let engine: PGLiteEngine;
@@ -239,3 +246,92 @@ describe('dispatch typed session identity [CX2-11]', () => {
expect(buildOperationContext(fakeEngine, { _meta: { session_id: '' } }, { sourceId: 'default' }).sessionId).toBeUndefined();
});
});
describe('corpus recall on a multi-entity brain [S3#1 fence, real recall]', () => {
// Deterministic proper-case surface for a slug tail so the zero-LLM,
// proper-case-biased entity resolver fires on a page-kind query.
// 'concepts/graph-traversal' → 'Graph Traversal'
const nameFromSlug = (slug: string): string =>
slug
.split('/')
.pop()!
.split('-')
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ');
test('every gold query: world answer surfaces, private beliefs are fenced, named pages resolve to pointers', async () => {
// beforeEach cleared pages/facts/aliases. Seed the WHOLE synthetic world
// through the real handlers: put_page fires auto-link + search vector,
// insertFact honors each belief's declared visibility.
const slugs = await loadCorpusPages(engine, { sourceId: 'default' });
expect(slugs.length).toBeGreaterThanOrEqual(12);
const inserted = await loadCorpusBeliefs(engine, { sourceId: 'default' });
expect(inserted).toBe(loadCorpusBeliefData().length);
// Beliefs were inserted after any prior meta read this test — start the
// hot-memory cache clean so the world/private tiers are (re)computed.
__resetHotMemoryCacheForTests();
const privateTexts = loadCorpusBeliefData()
.filter((b) => b.visibility === 'private')
.map((b) => b.text);
expect(privateTexts.length).toBeGreaterThanOrEqual(3);
const queries = loadCorpusQueries();
expect(queries.length).toBeGreaterThan(0);
// Collect every violation with its gold-case id so a regression names the
// exact query + assertion that broke, instead of a bare boolean.
const violations: string[] = [];
for (const q of queries) {
// Page-kind: name the entity so the reflex resolver runs against the real
// 12-page graph. belief / fence: the raw query — only the world/private
// hot-memory fence decides what surfaces.
const window: WindowTurn[] =
q.kind === 'page' && q.expect_slug
? [{ role: 'user', text: `the latest on ${nameFromSlug(q.expect_slug)}` }]
: [{ role: 'user', text: q.query }];
const r = await assembleTurnContext(engine, { sourceId: 'default', window });
// Fence (S3#1): NO private belief text ever crosses into the block,
// regardless of what the user asked.
for (const pt of privateTexts) {
if (r.text.includes(pt)) violations.push(`${q.id}: leaked private belief "${pt.slice(0, 40)}…"`);
}
if (q.must_not_substring && r.text.includes(q.must_not_substring)) {
violations.push(`${q.id}: fenced substring surfaced "${q.must_not_substring}"`);
}
if (q.kind === 'belief' && q.expect_substring && !r.text.includes(q.expect_substring)) {
violations.push(`${q.id}: world belief not recalled "${q.expect_substring}"`);
}
if (q.kind === 'page' && q.expect_slug) {
if (!r.pointers.some((p) => p.slug === q.expect_slug)) {
violations.push(`${q.id}: entity did not resolve to a pointer (${q.expect_slug})`);
}
if (!r.text.includes(q.expect_slug)) {
violations.push(`${q.id}: expected slug missing from the block (${q.expect_slug})`);
}
}
}
expect(violations).toEqual([]);
}, 120_000);
test('reflex pointer synopsis carries real page-body content, not a generic stub', async () => {
await loadCorpusPages(engine, { sourceId: 'default' });
// Named entity → pointer, and the pointer's synopsis is drawn from THAT
// page's body (the world-fenced first prose line), so distinctive body
// content reaches the assembled block.
const cases: Array<[name: string, slug: string, needle: string]> = [
['Summit Robotics', 'companies/summit-robotics', 'warehouse'],
['Graph Traversal', 'concepts/graph-traversal', 'path-planning'],
['Hybrid Retrieval', 'concepts/hybrid-retrieval', 'dense embeddings'],
];
for (const [name, slug, needle] of cases) {
const r = await assembleTurnContext(engine, {
sourceId: 'default',
window: [{ role: 'user', text: `context on ${name}` }],
});
expect(r.pointers.map((p) => p.slug)).toContain(slug);
expect(r.text).toContain(needle);
}
}, 120_000);
});
+66 -1
View File
@@ -103,6 +103,58 @@ EOF
chmod +x "$SCRATCH/bin/gh"
export PATH="$SCRATCH/bin:$PATH"
# ── Fake codex shim (stateful MCP registry, offline) ────────────────────────
# The container has no real codex, but the registration step must still be
# EXERCISED (not skipped). `mcp add` records the exact command+env line; `mcp
# get` echoes it back so verifyMcpTargetsWorkspace ([FIX7]) can genuinely
# confirm the serve targets this workspace (binary path + GBRAIN_SOURCE).
# State path rides an exported env var so it survives the bun->codex spawn.
step "fake codex shim"
export GB_CODEX_STATE="$SCRATCH/codex-mcp.state"
cat > "$SCRATCH/bin/codex" <<'EOF'
#!/bin/sh
STATE="${GB_CODEX_STATE:-/tmp/codex-mcp.state}"
case "$1 $2" in
--version*) echo "codex 0.0.0 (offline-fake)"; exit 0 ;;
"mcp add") printf '%s\n' "$*" > "$STATE"; exit 0 ;;
"mcp get")
[ -f "$STATE" ] || { echo "no such MCP server: $3" >&2; exit 1; }
cat "$STATE"; exit 0 ;;
"mcp list") [ -f "$STATE" ] && echo "gbrain: stdio serve"; exit 0 ;;
"mcp remove") rm -f "$STATE"; exit 0 ;;
esac
exit 0
EOF
chmod +x "$SCRATCH/bin/codex"
# Absolute gbrain binary for the registration argv (never executed — the codex
# path writes no hooks and the MCP add is faked).
FAKE_GBRAIN="$SCRATCH/bin/gbrain-bin"
printf '#!/bin/sh\nexit 0\n' > "$FAKE_GBRAIN"
chmod +x "$FAKE_GBRAIN"
# ── Fake git push shim (offline) ────────────────────────────────────────────
# The fake gh adds an https origin, so repo.ts's real `git push` would try to
# reach github.com — impossible under --network none (and it prompts for
# credentials when network IS allowed for the local smoke). Intercept `push`
# (absorbed — nothing leaves the container) and `ls-remote` (reports the branch
# exists) exactly like the in-repo lifecycle e2e's git shim; delegate every
# other subcommand (init/add/commit/remote/rev-parse) to real git. Capture the
# real git path BEFORE this shim shadows it on PATH.
REAL_GIT="$(command -v git)"
[ -n "$REAL_GIT" ] || fail "git not on PATH — the e2e needs a real git to delegate to"
cat > "$SCRATCH/bin/git" <<EOF
#!/bin/sh
for a in "\$@"; do
if [ "\$a" = "push" ]; then exit 0; fi
if [ "\$a" = "ls-remote" ]; then
echo "0000000000000000000000000000000000000000 refs/heads/main"
exit 0
fi
done
exec "$REAL_GIT" "\$@"
EOF
chmod +x "$SCRATCH/bin/git"
# ── Engine phase artifact: keyless PGLite config ─────────────────────────────
step "engine config"
cat > "$GBRAIN_HOME/.gbrain/config.json" <<EOF
@@ -177,5 +229,18 @@ grep -Fq '"ok": true' "$SCRATCH/verify.json" || fail "verify did not pass"
grep -Fq '"mode": "keyless"' "$SCRATCH/verify.json" || fail "capability report is not keyless"
grep -Fq 'smoke not applicable' "$SCRATCH/verify.json" || fail "hooks_smoke degradation not named"
# ── Codex door: exercise MCP registration through the fake stateful codex ────
# No real codex binary, but the argv/registration step MUST run (not skip). The
# stateful shim lets the [FIX7] smoke actually verify the serve targets this
# workspace; the codex path writes NO .claude hooks (pull protocol covers it).
step "codex MCP registration (fake stateful codex)"
codex_out="$(gbrain bootstrap hooks --workspace "$WS" --harness codex --gbrain-bin "$FAKE_GBRAIN" 2>&1)"
printf '%s\n' "$codex_out"
printf '%s\n' "$codex_out" | grep -Fq "verified targeting this workspace" \
|| fail "codex MCP registration was not verified as targeting this workspace"
[ -f "$WS/.claude/settings.local.json" ] && fail "codex path must not write Claude hooks"
grep -Fq "GBRAIN_SOURCE=" "$GB_CODEX_STATE" || fail "codex registration did not bind GBRAIN_SOURCE"
grep -Fq "serve --surface full" "$GB_CODEX_STATE" || fail "codex registration did not pin the full op surface"
echo
echo "PASS: offline bootstrap e2e (interview -> render -> repo -> abort/resume -> keyless verify)"
echo "PASS: offline bootstrap e2e (interview -> render -> repo -> abort/resume -> keyless verify -> codex MCP)"