Skip to content

@ruvector/graph-node binding has no delete API — blocks downstream re-indexing #427

@ruvnet

Description

@ruvnet

Summary

The @ruvector/graph-node native binding (probed via @ruvector/graph-node-darwin-arm64@0.1.0 / etc.) exposes write-side primitives but no delete:

> Object.getOwnPropertyNames(GraphDatabase.prototype)
[ 'createEdge', 'createHyperedge', 'createNode', 'searchHyperedges' ]

That makes the binding a write-only graph store from a consumer's perspective — there's no way to remove a node, an edge, or a hyperedge once it's persisted.

Concrete downstream impact

In ruvnet/ruflo (issue #1784, PR #1789) the agentdb_hierarchical-store and agentdb_causal-edge MCP tools route writes through the bridge into either:

  • (a) the AgentDB SQL fallback (memory_entries table — soft-deletable), or
  • (b) the @ruvector/graph-node native backend when available (this binding).

When (b) is the active backend, the cli has no way to propagate a delete. The ruflo-adr plugin's /adr-index skill ends up with stale ADR nodes and dangling supersedes / amends / related / depends-on edges every time a docs/adr/*.md file is removed from disk. There's no first-class workaround at the cli's MCP layer.

We shipped a partial fix (PR #1789) that surfaces controller: "native-unsupported" honestly when delete is requested on the native backend, but the real fix has to happen here in the binding.

What's needed in @ruvector/graph-node

Three new methods on GraphDatabase.prototype:

// Delete a node and (optionally) cascade to its incident edges.
// Returns the count of deleted edges so callers can audit.
deleteNode(id: string, opts?: { cascade?: boolean }): Promise<{ deletedNode: boolean; deletedEdges: number }>;

// Delete a single edge by id.
deleteEdge(id: string): Promise<{ deleted: boolean }>;

// Delete a hyperedge by id.
deleteHyperedge(id: string): Promise<{ deleted: boolean }>;

Symmetric to the existing createNode / createEdge / createHyperedge. Cascade semantics on deleteNode are the most important — without them, callers have to manually enumerate edges before deleting the node, which is racy in any concurrent context.

Bonus (lower priority)

If feasible at the storage layer:

  • deleteEdgesByEndpoints(from: string, to: string, label?: string) — delete edges by (source, target, label) tuple. The cli currently has to materialize full edge IDs; a tuple-based delete would let the bridge avoid stale-ID lookups.
  • deleteHyperedgesContaining(nodeId: string) — useful when removing an agent from all swarm-team hyperedges in a single call.

Verification once landed

The cli's downstream PR (#1789) has stub branches at bridgeDeleteCausalNode / bridgeDeleteCausalEdge that try causalGraph.removeEdge() / db.deleteNode() etc. before falling back to SQL. Once the binding ships these, the cli can re-target those branches at the native API and the SQL fallback becomes a true fallback (only fires when the binding isn't loaded).

Affected versions

Probed against:

  • @ruvector/graph-node-darwin-arm64@0.1.0
  • @ruvector/graph-node@0.1.0 (parent meta)

Both are the latest at time of this report.

Repro

node -e "
const m = require('@ruvector/graph-node-darwin-arm64');
console.log(Object.getOwnPropertyNames(m.GraphDatabase.prototype));
// → [ 'createEdge', 'createHyperedge', 'createNode', 'searchHyperedges' ]
// (no delete*, no remove*)
"

References

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