ADR-001 — MPowerUP Circle Graph Data Model and Database Selection¶
| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-04-30 |
| Project | MPowerUP |
| Deciders | Kevin Crump |
Context¶
MPowerUP organises users into trusted circles — groups of people who share safety information, send help requests, and vouch for each other during recovery and emergency situations. The data model for circles must support:
- A user belonging to multiple overlapping circles
- Facilitator roles within specific circles
- Trust relationships between individual users that exist independently of circle membership
- Operations that are inherently graph problems: circle vouch recovery (find 2+ members who can confirm identity), Sybil detection (identify suspiciously clustered fake accounts), shortest trust path between users, and revocation quorum tracking
- A decentralised, offline-first architecture where the social graph must never be centralised on a server
- A relay layer that coordinates cross-device operations but must not persist social graph data
The existing stack uses WatermelonDB (SQLite) on device and a Node.js relay on Fly.io. Any data store must be open source and runnable without paid managed services.
Decision¶
Model circles as a hybrid graph: explicit Circle nodes for access control, combined with direct TRUSTS edges between users that exist independently of circle membership.
Device layer — Yjs-encoded graph + SQLite adjacency tables¶
The social graph on device is encoded in two complementary ways:
-
Yjs CRDT graph encoding —
Y.Mapfor nodes (users, circles),Y.MapofY.Arrayfor adjacency lists. Because MPowerUP already uses Yjs for state synchronisation over libp2p, this gives distributed, offline-first graph sync with zero additional dependencies. The sync problem is already solved. -
SQLite adjacency tables via WatermelonDB — for queryable persistence. The Yjs state is mirrored into SQLite tables so that graph traversal can be performed with SQL recursive CTEs without loading the full graph into memory. This is the query layer; Yjs is the sync layer.
This combination avoids introducing a new native module dependency into the React Native / Expo build, which would complicate EAS builds and increase the native surface area.
Kuzu (embedded graph DB, C++ core, MIT licensed, Cypher query language) is identified as the preferred long-term replacement for the SQLite query layer once stable React Native bindings exist. The architecture is designed so that Kuzu can be slotted in as a drop-in replacement for the adjacency table queries when that time comes.
Relay layer — FalkorDB (Redis module)¶
The relay uses FalkorDB (Redis graph module, Server Side Public License) for structural graph operations that require cross-device coordination:
- Revocation quorum vote tracking
- Sybil detection (community detection on invitation graphs)
- Circle size cap enforcement
- Cross-circle trust path queries
FalkorDB is in-memory by default, which aligns directly with the relay's data retention policy: no persistent social graph data, all structural metadata purged after session close. Cypher query language is shared with the long-term device-side target (Kuzu), reducing the query language surface area.
Schema¶
Nodes:
User { did, display_name, joined_at, trust_tier }
Circle { id, name, created_at, type }
Org { did, name, verified, badge_level }
Edges:
(User)-[:MEMBER_OF { role, joined_at, invited_by_did }]->(Circle)
(User)-[:TRUSTS { weight, since, source }]->(User)
(User)-[:INVITED { at, accepted }]->(User)
(Org) -[:MEMBER_OF { role: 'facilitator', verified_at }]->(Circle)
(User)-[:CREATED { at }]->(Circle)
The facilitator role is a property on the MEMBER_OF edge, not a separate node type. One user can be a regular member in one circle and a facilitator in another.
Consequences¶
Positive¶
- Graph traversal operations (vouch recovery, Sybil detection, revocation quorum) are expressed as native graph queries rather than application-layer loops over relational results
- No new native module in the React Native build — Yjs and SQLite are already present
- The relay's in-memory graph naturally enforces the no-persistence policy without additional configuration
- Cypher is used at both layers, reducing the total query language surface area
- Kuzu provides a clear upgrade path for the device layer when bindings are available
Negative / trade-offs¶
- SQLite recursive CTEs for graph traversal are verbose and harder to read than Cypher; this cost is accepted as a temporary measure until Kuzu bindings are available
- FalkorDB's Server Side Public License (SSPL) restricts offering FalkorDB itself as a managed service — not a concern for BNI's use case, but worth noting
- Two representations of the graph on device (Yjs + SQLite) must be kept in sync; a sync bug would produce inconsistent query results
Neutral¶
- Derived circles (communities computed from the trust graph rather than explicitly declared) are deferred to Phase 3. The explicit
Circlenode model does not prevent this — derived communities would be written back asCirclenodes with atype: 'derived'flag - The relay graph is scoped to structural metadata only (DIDs, edge existence, timestamps); no content, display names, or message data ever enters the relay graph store
Alternatives Considered¶
Neo4j Community (relay)¶
Well-documented, mature Cypher support, large ecosystem. Rejected because it is JVM-based (significant memory overhead for a lightweight relay service) and the community edition has limited clustering support. Good choice if analytics tooling is added on top of the graph in a later phase.
ArangoDB (relay or device)¶
Multi-model (document + graph + key-value), Apache 2.0. Capable and flexible, but more than needed for the relay's narrow use case. Would be reconsidered if the relay needs to store mixed document and graph data in the same store.
Apache AGE (PostgreSQL extension)¶
Adds Cypher to PostgreSQL. Attractive because it eliminates a separate graph service. Rejected for the relay because the relay's in-memory requirement is harder to satisfy with PostgreSQL, and AGE's performance on real-time graph operations is behind FalkorDB and Memgraph.
Memgraph (relay)¶
In-memory, C++ core, Cypher-compatible, fast. Strong alternative to FalkorDB. FalkorDB preferred because it runs as a Redis module (simpler operational profile — Redis is already a common relay sidecar), but Memgraph is the preferred fallback if FalkorDB's SSPL becomes a concern.
SurrealDB (device)¶
Multi-model, Rust core, WASM-capable, graph-native query language. Most credible candidate to replace both WatermelonDB and the Yjs graph encoding. Rejected for now because it would replace the entire device data layer (significant migration scope) and the React Native wrapper is not yet mature. Revisit at Phase 3.
Realm / Atlas Device SDK (device)¶
Object-oriented mobile DB with offline sync. Rejected because sync requires MongoDB Atlas (commercial dependency) and it is not a graph DB natively — relationships are modelled as object references, not first-class graph edges.
Review trigger¶
This decision should be revisited when any of the following occur:
- Kuzu releases stable React Native / Expo bindings
- SurrealDB's React Native wrapper reaches production stability
- The relay's data requirements expand beyond structural metadata (e.g., storing aggregated analytics)
- FalkorDB's licensing changes in a way that affects self-hosted use