From ad195582dd7ed9ddf44dd80b63edc7c8f2b5f064 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Tue, 16 Jun 2026 08:30:27 +0000 Subject: [PATCH] fix(lbug): check PDG manifest rel-pair collision before node COPY (#2203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by Codex in tri-review. The manifest rel-pair collision guard ran after the FK barrier (after node COPY committed), so on that should-never-happen error branch the overlap path left orphan node rows AND the GITNEXUS_SERIAL_LBUG_LOAD escape hatch diverged from the legacy 'validate manifest before any COPY' behavior. Move the rel merge + collision check ahead of beginNodeCopy/the barrier: the serial path now detects a collision before committing any node rows (legacy parity restored — the escape hatch is a faithful oracle again), and the overlap path detects it as early as csvResult is available. The node-collision guard already ran before node COPY (in the hook). --- gitnexus/src/core/lbug/lbug-adapter.ts | 35 +++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/gitnexus/src/core/lbug/lbug-adapter.ts b/gitnexus/src/core/lbug/lbug-adapter.ts index b645e5fab..e0ddd0de4 100644 --- a/gitnexus/src/core/lbug/lbug-adapter.ts +++ b/gitnexus/src/core/lbug/lbug-adapter.ts @@ -1028,21 +1028,13 @@ export const loadGraphToLbug = async ( } const tCsv = mark(); - // Serial path: all CSVs are on disk and node COPY has not started — start it - // here so the barrier below blocks on it exactly as the legacy path did. - if (SERIAL) beginNodeCopy(csvResult.nodeFiles); - - // FK barrier: node rows must exist before the relationship COPY resolves their - // endpoints. In overlap mode most of node COPY was hidden behind rel emit, so - // this await is the *residual* node-COPY time (≈0 when fully overlapped). - if (nodeCopyPromise) await nodeCopyPromise; - if (nodeCopyError) { - throw nodeCopyError instanceof Error ? nodeCopyError : new Error(String(nodeCopyError)); - } - const tCopyNodes = mark(); - - // Merge the streamed PDG-emit per-pair rel CSVs (#2202) — collision-guarded, - // before the relationship COPY consumes relsByPair. + // Merge the streamed PDG-emit per-pair rel CSVs (#2202) into the COPY plan — + // collision-guarded. Done BEFORE node COPY so the serial escape hatch detects a + // manifest/structural pair collision before committing any node rows (legacy + // parity with the pre-overlap path), and the overlap path detects it as early + // as csvResult is available. When a manifest is present, streaming was on and + // the in-memory graph held zero BasicBlocks, so a structural collision means a + // streaming-invariant violation — fail loudly rather than load corrupt data. if (pdgEmitManifest) { for (const [pairKey, meta] of pdgEmitManifest.relsByPair) { if (csvResult.relsByPair.has(pairKey)) { @@ -1056,6 +1048,19 @@ export const loadGraphToLbug = async ( } } + // Serial path: all CSVs are on disk and node COPY has not started — start it + // here so the barrier below blocks on it exactly as the legacy path did. + if (SERIAL) beginNodeCopy(csvResult.nodeFiles); + + // FK barrier: node rows must exist before the relationship COPY resolves their + // endpoints. In overlap mode most of node COPY was hidden behind rel emit, so + // this await is the *residual* node-COPY time (≈0 when fully overlapped). + if (nodeCopyPromise) await nodeCopyPromise; + if (nodeCopyError) { + throw nodeCopyError instanceof Error ? nodeCopyError : new Error(String(nodeCopyError)); + } + const tCopyNodes = mark(); + // Bulk COPY relationships. They were already routed to per-FROM→TO-label-pair // files during the emit pass (#2203 U2) — there is no monolithic relations.csv // to re-read/re-split here; we COPY each pair file directly.