Skip to content

Give every symbol a real identity and kill the zombie nodes for good - #31

Merged
Phoenixrr2113 merged 4 commits into
mainfrom
fix/batch-five-node-identity
Aug 22, 2026
Merged

Phoenixrr2113 merged 4 commits into
mainfrom
fix/batch-five-node-identity

Conversation

@Phoenixrr2113

Copy link
Copy Markdown
Owner

What this fixes

Batch five: the node-identity redesign, the root cause behind zombie findings in sixteen consecutive adversarial review rounds. Symbols were keyed on name, file, and line number, so any edit that shifted a line created a duplicate node while cross-file edges pinned the stale one forever, surviving even force reindex.

The new identity

Every symbol carries an opaque sym:v1 id: SHA-256 over a length-prefixed canonical tuple of label, normalized path, lexical scope chain, declared name, and disambiguator. Scope is part of identity (owner chains for members, signature hashes for overloads, one id for declaration-merged forms, bounded ordinals as last resort). Line numbers are mutable properties. A fixed-vector test locks the exact bytes-to-hash contract.

What changed everywhere

  • All language plugins emit identity fields; per-language scope fidelity documented with honest gaps.
  • The pipeline builds a project symbol catalog and resolves every edge to endpoint ids before writes; unresolved edges are dropped, never guessed; incremental runs combine fresh extraction with persisted symbols.
  • The graph merges by id with exact-match indexes, sweeps stale ids even when inbound edges pin them, notices deleted files by set-difference, and deletes projects by ownership stamp, so force reindex genuinely resets.
  • Ids flow unchanged through search (including the text fallback), references (now id-addressed; name lookup removed since nothing used it), file relationships, embeddings (line-only shifts skip re-embedding), and the dashboard, which no longer synthesizes ids.
  • TypeScript class overload declarations are extracted for the first time, with calls binding to the runtime implementation only.
  • Legacy idless knowledge entities backfill on write and are tolerated on read.

The kill-shot test

Index two files with cross-file CALLS and IMPORTS_SYMBOL edges, insert lines above the target, reindex incrementally: one node, same id, moved startLine, both edges intact. Force reindex: zero prior-generation survivors, verified by the acceptance queries.

Review process

A scout produced the design work map (three candidates; scope-qualified opaque identity chosen); four implementation groups worked a frozen contract; an integration wave caught a silently skipped seam and a phantom index label; two adversarial rounds found six more defects (deleted files invisible to incremental indexing, overload collapse, fallback rows without ids, legacy Entity crashes, calls binding to every overload, File rows rejected by the converter), four of them integration seams between individually verified components. All fixed red-first and re-verified against the reviewers' own reproductions.

Verification

pnpm turbo build 21/21, test 36/36, typecheck 20/20, release:check green. Core 264 tests, plugin-typescript 116, graph 107 plus 53 lite-integration.

Ledger

  • Per-language scope fidelity gaps (Python locals, Rust nested modules, generic unnamed containers) are documented, not hidden.
  • The occurrence-fallback ordinal can churn one id when an identical declaration is inserted above it; bounded to the colliding peer group and swept clean.

🤖 Generated with Claude Code

Symbol identity is no longer a line number in disguise. Every Function,
Class, Interface, Variable, Type, and Component now carries an opaque
sym:v1 id hashed from its label, normalized path, lexical scope chain,
declared name, and disambiguator, with length-prefixed encoding so no
name or path can collide. Line numbers become mutable properties:
editing a file no longer changes what a symbol IS.

Scope is part of identity: members carry their owner chain, nested
declarations their lexical chain, overloads a signature hash (class
method overload declarations are now extracted at all, with calls
binding to the one runtime implementation), declaration-merged forms
share one id, and true duplicates fall back to a bounded ordinal.

The pipeline builds a project symbol catalog and resolves every edge to
endpoint ids before writing; unresolved edges are dropped, never
guessed. The graph merges by id, sweeps stale ids from changed files
even when inbound edges pin them, notices deleted files by
set-difference, and deletes projects by ownership stamp instead of
reachability, so force reindex genuinely resets. Ids flow unchanged
through search (including the no-embedding text fallback), references,
file relationships, embeddings, and the dashboard, which no longer
synthesizes ids at all. Legacy idless knowledge entities are backfilled
on write and tolerated on read.

The proof the whole campaign was building toward now passes: shift a
function with inbound CALLS and IMPORTS_SYMBOL edges down the file,
reindex incrementally, and it is still one node with the same id, a new
line number, and both edges intact. Two adversarial review rounds drove
out six defects before merge, four of them integration seams between
individually verified components.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
v0-landing-page-build Skipped Skipped Aug 22, 2026 12:01am

The remote FalkorDB client rejects undefined parameter values; the
embedded driver tolerated them, which hid the gap from local testing.
Entities seeded without canonical ids now get them derived at the write
boundary, and a regression test drives all six legacy symbol shapes
through the real params path asserting no undefined survives at any
depth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The strict embedding update was right; the fixtures were still passing
the legacy name-and-path shape while discarding the canonical ids their
own upserts had produced. Production never hits this path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…opping every row

The vector Cypher never returned node.id, and the strict candidate
mapper skipped rows without one, so remote search returned zero hits on
a fully indexed graph. The projection now includes the id, the row
mapping throws a diagnostic error on a missing or malformed id instead
of silently filtering, and a Linux temp-directory prefix in one test is
made portable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Phoenixrr2113
Phoenixrr2113 merged commit 9a9a750 into main Aug 22, 2026
12 checks passed
@Phoenixrr2113
Phoenixrr2113 deleted the fix/batch-five-node-identity branch August 22, 2026 00:06

This branch was previously deployed

1 inactive deployment
Preview — fa1f88d1 Deployed Aug 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant