feat(graph): add staticGated edge property

Adds an optional `staticGated?: boolean` field to `GraphRelationship`
that flags edges originating in code branches known at index time to
be unreachable in production — e.g. `if (CONST_FALSE)` blocks where
the condition reduces to a comptime-known `false`.

Schema + persistence wiring:

- `gitnexus-shared/src/graph/types.ts` — additive optional field on
  `GraphRelationship`; absent edges read identically to live ones.
- `gitnexus/src/core/lbug/schema.ts` — `staticGated BOOLEAN` column
  on the `CodeRelation` REL table.
- `gitnexus/src/core/lbug/csv-generator.ts` — appends a
  `staticGated` column (0/1) to the `relations.csv` written for
  bulk COPY ingest.
- `gitnexus/src/core/lbug/lbug-adapter.ts` — fallback per-row
  `MATCH ... CREATE` insert reads the optional column and threads
  it into the relationship properties.

No language has populated this field yet — the Zig hookup lands in
the next commit. Existing DBs need a re-index for the new column to
appear; existing readers are unchanged because the field is
optional and absent on every other language's edges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit c8cd5efe27a77a5d1b3f05e3f4e89069b5c6b19e)
This commit is contained in:
Garrett Griffin-Morales 2026-04-26 19:47:34 -04:00
parent 75bfa26d5f
commit 3ec546be62
4 changed files with 30 additions and 5 deletions

View file

@ -306,4 +306,18 @@ export interface GraphRelationship {
readonly weight: number;
readonly note?: string;
}[];
/**
* When `true`, the edge originates inside a code branch known at
* index time to be unreachable in production — e.g. a Zig
* `if (CONST_FALSE)` block where the condition reduces to a
* comptime-known `false`. Consumers of impact / context queries
* can filter these edges out to suppress paper-tiger callers
* caused by feature-flag-gated dead code paths.
*
* Optional and additive: edges from languages that don't compute
* static gating leave this `undefined`, which existing readers
* treat identically to "live". Currently set only by the Zig
* call-extractor.
*/
staticGated?: boolean;
}

View file

@ -396,7 +396,7 @@ class BufferedCSVWriter {
/** Canonical relationship CSV header — shared by the emit pass and the
* `splitRelCsvByLabelPair` differential oracle. */
export const REL_CSV_HEADER = 'from,to,type,confidence,reason,step';
export const REL_CSV_HEADER = 'from,to,type,confidence,reason,step,staticGated';
/** Build the escaped CSV row (no trailing newline) for one relationship.
* Single source of the relationship row bytes — used by the emit pass and by
@ -409,6 +409,10 @@ export const buildRelRow = (rel: GraphRelationship): string =>
escapeCSVNumber(rel.confidence, 1.0),
escapeCSVField(rel.reason),
escapeCSVNumber(rel.step, 0),
// `staticGated` persists as 0/1 (LadybugDB BOOLEAN COPY accepts both).
// Every edge that does not carry the flag writes 0, so readers treat
// absence and `false` identically.
rel.staticGated === true ? '1' : '0',
].join(',');
/** Canonical BasicBlock node CSV header — taint/PDG substrate (issue #2080).

View file

@ -1466,22 +1466,28 @@ export const fallbackRelationshipInserts = async (
for (let i = 1; i < validRelLines.length; i++) {
const line = validRelLines[i];
try {
const match = line.match(/"([^"]*)","([^"]*)","([^"]*)",([0-9.]+),"([^"]*)",([0-9-]+)/);
// CSV layout: from,to,type,confidence,reason,step[,staticGated]
// The trailing `staticGated` column (0/1) is optional so we remain
// tolerant of legacy CSVs written before the column existed.
const match = line.match(
/"([^"]*)","([^"]*)","([^"]*)",([0-9.]+),"([^"]*)",([0-9-]+)(?:,([01]))?/,
);
if (!match) continue;
const [, fromId, toId, relType, confidenceStr, reason, stepStr] = match;
const [, fromId, toId, relType, confidenceStr, reason, stepStr, gatedStr] = match;
const fromLabel = getNodeLabel(fromId);
const toLabel = getNodeLabel(toId);
if (!validTables.has(fromLabel) || !validTables.has(toLabel)) continue;
const confidence = parseFloat(confidenceStr) || 1.0;
const step = parseInt(stepStr) || 0;
const staticGated = gatedStr === '1';
await queryAndDrain(
conn,
`
MATCH (a:${escapeLabel(fromLabel)} {id: ${formatCypherValue(fromId)} }),
(b:${escapeLabel(toLabel)} {id: ${formatCypherValue(toId)} })
CREATE (a)-[:${REL_TABLE_NAME} {type: ${formatCypherValue(relType)}, confidence: ${confidence}, reason: ${formatCypherValue(reason)}, step: ${step}}]->(b)
CREATE (a)-[:${REL_TABLE_NAME} {type: ${formatCypherValue(relType)}, confidence: ${confidence}, reason: ${formatCypherValue(reason)}, step: ${step}, staticGated: ${staticGated}}]->(b)
`,
);
} catch {

View file

@ -657,7 +657,8 @@ ${generatedPairDdl()},
type STRING,
confidence DOUBLE,
reason STRING,
step INT32
step INT32,
staticGated BOOLEAN
)`;
// ============================================================================