Skip to content

graph-node 2.1.0: createEdge/batchInsert silently discard metadata, blocking lossless recovery #984

Description

@sparkling

Reproduced on the current published native binding

@ruvector/graph-node@2.1.0 (npm latest checked 2026-09-13), Node 24.14.1, macOS arm64. Resolved from @claude-flow/cli@3.41.2.

Both createEdge() and batchInsert() accept the documented metadata: Record<string,string>, return successful edge IDs, and retain endpoints/connectivity after a fresh-process reopen. However, all supplied edge metadata is absent from query results before and after reopen. Node properties round-trip in the same fixture.

Source inspection confirms this is not merely a result projection: create_edge and batch_insert build persisted edge properties from only __confidence and __embedding, never copying edge.metadata. run_query returns non-internal properties, so public metadata would be observable if stored.

Minimal disposable reproduction

Use a new temporary directory, never a live managed graph. Run writer and reader in separate processes against that temporary path.

const db = new GraphDatabase({
  storagePath: temporaryGraphPath, dimensions: 8, distanceMetric: 'Cosine'
});
const embedding = new Float32Array([1,0,0,0,0,0,0,0]);
for (const id of ['mem:a', 'mem:b', 'mem:c'])
  await db.createNode({ id, embedding, properties: { diagnostic: 'true' } });
const edge = {
  from: 'mem:a', to: 'mem:b', description: 'supports', embedding,
  confidence: 0.75,
  metadata: {
    sourceEdgeId: 'fixture-source-edge',
    weight: '0.8',
    metadata: JSON.stringify({ witness: 'fixture-witness' })
  }
};
await db.createEdge(edge);
await db.batchInsert({ nodes: [], edges: [{ ...edge, to: 'mem:c' }] });
console.log(await db.query('MATCH (a)-[r]->(b) RETURN a,r,b'));
// nodes: diagnostic property retained
// both edges: properties: {}

A fresh reader process with the same options returns the same endpoints/types and empty edge properties. Counts remain 3 nodes / 2 edges. This issue does not dispute the connectivity/persistence fixes in #809/#879/#938.

Impact and bounded proposed fix

This blocks lossless reconstruction of a native graph from retained managed SQL relationships: weights carried as public metadata, witnesses, original SQL edge identity and other metadata cannot be verified or retained by this API. Retrying causal-edge writes in Ruflo is not an acceptable workaround because that handler also writes SQL.

  • Copy public JsEdge.metadata into stored edge properties in both single and batch insertion, with an explicit collision policy for reserved internal keys.
  • Preserve native confidence/embedding handling; do not repurpose relation type or node IDs to smuggle metadata.
  • Test exact public metadata before and after a separate-process reopen, including Unicode, JSON strings and reserved-key behavior.
  • Audit createHyperedge() separately: source appears to ignore its metadata too, but the executed reproduction above covers ordinary edges only.
  • Keep missing metadata observable; do not return successful import/recovery receipts without fidelity verification.

Related downstream persistence adapter fix: ruvnet/ruflo#3313 . The constructor patch enables durable future graph writes but cannot repair this compiled native metadata omission. No production databases were changed during these tests.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions