From 14f0674bcf60ffdc83eb369297aedb0964855bec Mon Sep 17 00:00:00 2001 From: zsimovanforgeops Date: Mon, 27 Jul 2026 15:29:37 -0500 Subject: [PATCH] fix(synthesize): normalize Postgres receipt job ids (#3414) Co-authored-by: Forge (Ron) --- scripts/e2e-test-map.ts | 5 +- src/core/cycle/synthesize.ts | 9 ++- test/cycle-synthesize-slug-collection.test.ts | 25 ++++++ .../synthesize-bigint-job-id-postgres.test.ts | 80 +++++++++++++++++++ 4 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 test/e2e/synthesize-bigint-job-id-postgres.test.ts diff --git a/scripts/e2e-test-map.ts b/scripts/e2e-test-map.ts index cca6fb6b4..c32a34142 100644 --- a/scripts/e2e-test-map.ts +++ b/scripts/e2e-test-map.ts @@ -42,7 +42,10 @@ export const E2E_TEST_MAP: Record = { // phase, extract, integrity, embed, or migrate-engine change. "src/core/cycle/extract-takes.ts": ["test/e2e/multi-source-bug-class.test.ts"], "src/core/cycle/patterns.ts": ["test/e2e/multi-source-bug-class.test.ts"], - "src/core/cycle/synthesize.ts": ["test/e2e/multi-source-bug-class.test.ts"], + "src/core/cycle/synthesize.ts": [ + "test/e2e/multi-source-bug-class.test.ts", + "test/e2e/synthesize-bigint-job-id-postgres.test.ts", + ], "src/commands/embed.ts": ["test/e2e/multi-source-bug-class.test.ts"], "src/commands/extract.ts": ["test/e2e/multi-source-bug-class.test.ts"], "src/commands/migrate-engine.ts": ["test/e2e/multi-source-bug-class.test.ts"], diff --git a/src/core/cycle/synthesize.ts b/src/core/cycle/synthesize.ts index bf680dbc7..34361c77f 100644 --- a/src/core/cycle/synthesize.ts +++ b/src/core/cycle/synthesize.ts @@ -1251,7 +1251,7 @@ async function collectChildPutPageSlugs( // cycle's resolved source via SubagentHandlerData.source_id, and stamps // the SAME source here so reverseWriteRefs / provenance reads target the // correct (source_id, slug) row. Unset → legacy 'default'. - const rows = await engine.executeRaw<{ job_id: number; slug: string }>( + const rows = await engine.executeRaw<{ job_id: number | bigint; slug: string }>( `SELECT job_id, COALESCE(input->>'slug', (input #>> '{}')::jsonb->>'slug') AS slug FROM subagent_tool_executions @@ -1265,10 +1265,13 @@ async function collectChildPutPageSlugs( const rewritten = new Map(); for (const r of rows) { if (typeof r.slug !== 'string' || r.slug.length === 0) continue; - const ci = chunkInfo.get(r.job_id); + // Postgres decodes the BIGINT FK as bigint; both metadata maps are keyed + // by the INTEGER minion job id represented as a JavaScript number. + const jobId = Number(r.job_id); + const ci = chunkInfo.get(jobId); const slug = ci ? rewriteChunkedSlug(r.slug, ci.hash6, ci.idx) : r.slug; if (!rewritten.has(slug) || rewritten.get(slug) === undefined) { - rewritten.set(slug, jobRawSource?.get(r.job_id)); + rewritten.set(slug, jobRawSource?.get(jobId)); } } return Array.from(rewritten.keys()).sort().map(slug => { diff --git a/test/cycle-synthesize-slug-collection.test.ts b/test/cycle-synthesize-slug-collection.test.ts index d83ad4b3d..99709a64d 100644 --- a/test/cycle-synthesize-slug-collection.test.ts +++ b/test/cycle-synthesize-slug-collection.test.ts @@ -127,6 +127,31 @@ describe('C6: collectChildPutPageSlugs survives double-encoded jsonb (#745)', () expect(ref?.raw_source).toBe('/transcripts/2026-07-01-standup.md'); }); + test('normalizes bigint job ids before number-keyed metadata lookups', async () => { + const bigintEngine = { + executeRaw: async () => [{ + job_id: 1001n, + slug: 'wiki/agents/test/bigint-job-abc123', + }], + }; + const chunkInfo = new Map([[1001, { idx: 2, hash6: 'abc123' }]]); + const jobRawSource = new Map([[1001, '/transcripts/bigint-source.md']]); + + const refs = await collectChildPutPageSlugs( + bigintEngine as any, + [1001], + chunkInfo, + 'default', + jobRawSource, + ); + + expect(refs).toEqual([{ + slug: 'wiki/agents/test/bigint-job-abc123-c2', + source_id: 'default', + raw_source: '/transcripts/bigint-source.md', + }]); + }); + test('omits raw_source when no map entry exists for the job (#1978)', async () => { const refs = await collectChildPutPageSlugs(engine as any, [1001], new Map(), 'default', new Map()); const ref = refs.find((r: { slug: string }) => r.slug === 'wiki/agents/test/normal-shape'); diff --git a/test/e2e/synthesize-bigint-job-id-postgres.test.ts b/test/e2e/synthesize-bigint-job-id-postgres.test.ts new file mode 100644 index 000000000..3c64687bb --- /dev/null +++ b/test/e2e/synthesize-bigint-job-id-postgres.test.ts @@ -0,0 +1,80 @@ +/** + * Real-Postgres regression for synthesis receipt correlation. + * + * `minion_jobs.id` is INTEGER and decodes as a JavaScript number, while + * `subagent_tool_executions.job_id` is BIGINT and the Postgres engine decodes it + * as a JavaScript bigint. `collectChildPutPageSlugs` must normalize that driver + * boundary before consulting number-keyed chunk and raw-source maps. + * + * Run: DATABASE_URL=... bun test test/e2e/synthesize-bigint-job-id-postgres.test.ts + */ + +import { afterAll, beforeAll, describe, expect, test } from 'bun:test'; +import { __testing } from '../../src/core/cycle/synthesize.ts'; +import { + getConn, + getEngine, + hasDatabase, + setupDB, + teardownDB, +} from './helpers.ts'; + +const { collectChildPutPageSlugs } = __testing; +const skip = !hasDatabase(); +const describeE2E = skip ? describe.skip : describe; + +if (skip) { + console.log('Skipping synthesis bigint job-id E2E (DATABASE_URL not set)'); +} + +describeE2E('synthesis receipt job-id correlation on Postgres', () => { + beforeAll(async () => { await setupDB(); }); + afterAll(async () => { await teardownDB(); }); + + test('matches bigint receipt ids to number-keyed child metadata', async () => { + const engine = getEngine(); + const conn = getConn(); + const [job] = await conn` + INSERT INTO minion_jobs (queue, name, data, status) + VALUES ('default', 'subagent', ${conn.json({})}, 'completed') + RETURNING id + `; + const jobId = job.id as number; + + try { + await conn` + INSERT INTO subagent_tool_executions ( + job_id, message_idx, tool_use_id, tool_name, status, input + ) VALUES ( + ${jobId}, 0, 'bigint_job_id_tool', 'brain_put_page', 'complete', + ${conn.json({ slug: 'wiki/agents/test/postgres-bigint-abc123' })} + ) + `; + + const [receipt] = await engine.executeRaw<{ job_id: unknown }>( + `SELECT job_id + FROM subagent_tool_executions + WHERE job_id = $1`, + [jobId], + ); + expect(typeof jobId).toBe('number'); + expect(typeof receipt.job_id).toBe('bigint'); + + const refs = await collectChildPutPageSlugs( + engine, + [jobId], + new Map([[jobId, { idx: 2, hash6: 'abc123' }]]), + 'default', + new Map([[jobId, '/transcripts/postgres-bigint.md']]), + ); + + expect(refs).toEqual([{ + slug: 'wiki/agents/test/postgres-bigint-abc123-c2', + source_id: 'default', + raw_source: '/transcripts/postgres-bigint.md', + }]); + } finally { + await conn`DELETE FROM minion_jobs WHERE id = ${jobId}`; + } + }, 30_000); +});