Skip to content

gitree: stamp jarvis node_key + :Node on Concept saves - #1622

Merged
Evanfeenstra merged 1 commit into
mainfrom
concept-node-key
Aug 30, 2026
Merged

gitree: stamp jarvis node_key + :Node on Concept saves#1622
Evanfeenstra merged 1 commit into
mainfrom
concept-node-key

Conversation

@Evanfeenstra

Copy link
Copy Markdown
Collaborator

Problem

jarvis's create-or-merge resolves Concepts by MERGE (node:Concept:Node {node_key, namespace}) — never by gitree's id slug — while gitree's saveConcept writes over a raw bolt session with neither the node_key property nor the :Node label. Each writer was blind to the other's nodes, so any concept touched from both sides forked a duplicate.

On one prod graph this produced 27 empty jarvis-side stubs shadowing real gitree concepts (85 READ_CONCEPT edges had landed on the empty copies — agents were being routed to bodiless concepts) plus 2 full-body duplicate pairs.

Fix

  • jarvisConceptNodeKey() (store/utils.ts) — byte-for-byte port of jarvis's sanitize_node_key + _compose_node_key (api/helper/schema_validation.py): trim → remove spaces → lowercase → strip non-[a-zA-Z0-9\s]concept- prefix, with the value portion replaced by sha256[:32] past 200 chars. Verified against 670 jarvis-assigned keys on the prod graph (670/670 match). Deliberately bug-compatible: non-space whitespace survives sanitization, as it does in jarvis.
  • saveConcept stamps SET f:Node, f.node_key = <computed> in a separate best-effort statement after the content write. Kept out of the main MERGE {id} on purpose: node_key carries a (node_key, namespace) uniqueness constraint, and a rename that computes an already-taken key must warn and skip rather than fail the save. NOT EXISTS guards skip the stamp when another node holds the key; a lost race with a concurrent writer throws on the constraint and is caught the same way.

Identity is unchanged — gitree still merges on {id}, so renames keep updating the same node; the stamp only makes the node addressable by jarvis's merge so future jarvis-side writes land on it instead of forking.

Prod state

The existing graph was repaired directly (duplicates merged via apoc.refactor.mergeNodes, all 223 bolt-written concepts backfilled with node_key + :Node, collision dry-run clean), so this change only needs to keep new writes consistent.

Tests

6 new unit tests in __tests__/jarvis-node-key.test.ts, 3 asserting against real prod-assigned keys; hash-path expectation generated from the actual Python. Full gitree suite 43/43.

🤖 Generated with Claude Code

jarvis's create-or-merge resolves Concepts by
MERGE (node:Concept:Node {node_key, namespace}) — never by gitree's id
slug — so Concepts written here were invisible to it and every
jarvis-side write of the same concept forked a duplicate node. On one
prod graph this produced 27 empty stubs shadowing real concepts (85
READ_CONCEPT edges landed on empty bodies) plus 2 full-body duplicates.

saveConcept now computes the key jarvis would assign
(jarvisConceptNodeKey, a byte-for-byte port of jarvis's
sanitize_node_key + _compose_node_key, verified against 670
jarvis-written keys) and stamps it with the :Node label in a separate
best-effort statement after the content write. Kept out of the main
MERGE deliberately: node_key carries a (node_key, namespace) uniqueness
constraint, and a rename that computes an already-taken key must warn
and skip rather than fail the save. Existing nodes were backfilled
directly on the prod graph; identity stays MERGE {id}, so renames keep
updating the same node.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Evanfeenstra
Evanfeenstra merged commit 6294a71 into main Aug 30, 2026
6 checks passed
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