fix(lbug): check PDG manifest rel-pair collision before node COPY (#2203)

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).
This commit is contained in:
Gergo Magyar 2026-06-16 08:30:27 +00:00
parent 917ae46ad2
commit ad195582dd

View file

@ -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.