This library presents a mature-NextGraph SDK face to consumers while compensating for the current SDK's gaps via a shared-wallet simulation. It therefore OWNS all current-state + simulation knowledge — moved here out of the Festipod app repo, which must treat this library as a finished SDK. New docs/: - nextgraph-current-state.md — what the current SDK/broker do and don't expose (5 store types, document=repo, per-document ReadCap, inbox not exposed, iframe RPC proxy, mono-user/no-global-data, wallet import constraint). Keeps the nextgraph-rs source pointers. - simulation.md — how the lib emulates the mature behaviour on one shared wallet (shim, store!=document two axes, docCreate→private store, RepoNotFound scope rule, @ng-org double-proxy DataCloneError, emulated ReadCap/inbox/curator). - decisions/ — the current-SDK ADRs (private-store-nuri-scope, sparql-delete, shared-wallet-login, discovery mechanism). - fork-inbox-fallback.md — the Rust-patch/self-host route not taken. - migration-guide.md — the checklist for when real NextGraph matures. README: boundary framing from the lib's side + docs/ index; replaced the stale "scaffold/stubbed" status with the actually-implemented mechanisms per source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.2 KiB
ADR — Discovery mechanism (inbox-fed index, curator, fan-out)
Date: 2026-06-16 · Status: mechanism accepted; target owner undecided.
Ported here for the discovery MECHANISM it defines — the piece this lib
realizes (inbox.ts post/materialize/watch; store-registry.ts fan-out). The
product intent (what a consumer should surface) is the consumer's concern, not
this lib's; only the mechanism is recorded here.
Access ≠ discovery
- Access: may I read this document if I hold it? A public entity is world-readable with its NURI.
- Discovery: how do I learn it exists, in order to read it? ← this ADR.
The mechanism
- A single global index, fed via ITS inbox. The creator does not edit the index directly: it deposits a reference into the index's inbox. The index is an owned document (public read), materialized from its inbox (a watcher ingests deposits → adds entries).
- Primary discovery = that global index.
- Relational = secondary axis, overlaid: a connection's participations, markers on the global list. Rests on existing per-item data (protected scope) — no new primitive.
The 3-stage frame
discovery → synchronization → query
- Discovery: the index gives the NURIs of the entity documents.
- Synchronization: subscribe to those documents → they replicate locally
(verifier:
self.repos+ oxigraph dataset). - Query: query what is now local (sort, limit, reactivity). SPARQL/ORM
run on the local set only (
resolve_target_for_sparqlsearchesself.repos) — you cannot query what is not loaded.
Corollary: a reactive query does not replace the index — it runs at stage 3 on the local union that stages 1-2 built. You don't sync what you didn't discover.
Why one reused mechanism
- No Group store. The index is not open-write: it is an owned document (public read) + native inbox (a primitive present on every document). Nobody writes the index but its owner (by materializing inbox deposits). So the model stays "3 stores + Dialog + inboxes, no Group store."
- One mechanism, reused. The inbox + materialization watcher serve BOTH
submitting an entity to the index AND registering to a meeting-point — same
inbox.postAPI, same handling. This is exactlyinbox.tsin this lib (post/read/materialize/watch). - Natural dedup / moderation point: materialization (inbox → index) is where duplicates are detected / moderated before insertion.
Index owner — target model undecided
The "dedicated service with its own wallet sharing a freely-readable index" was
incorrect: NextGraph apps and services are mono-user with no global data
(see ../nextgraph-current-state.md § Apps &
services). The only path glimpsed for a global document is a singleton app
bound to the developer-user — not implemented, uncertain, to explore later.
This is why a global-index curator is a deferred separate package in this lib
(see the top-level README).
Polyfill reality (fan-out) vs target (global index)
What ships in the shared-wallet polyfill today is the cross-account fan-out over
every account's public documents (store-registry.ts listEntityDocs('public')
/ resolveReadGraphs) — one account sees another's public entity without a
connection. This ADR classified per-account fan-out as a drift to be
replaced by the single global index; the target (inbox-fed global index) remains
valid but the fan-out is the mechanism the shared-wallet staging actually runs on
until the global-index owner is decided. Recorded here as mechanism history — the
resolution belongs to ../migration-guide.md.
Alternatives rejected (mechanism)
- Open-write index (creator writes the index directly): required a collaborative document (Group store, SDK-blocked) and exposed the index to corruption. Replaced by inbox deposit + owner materialization.
- Purely relational discovery (
social_query): rejected as primary (a global list is wanted); kept as a secondary axis. - No index, direct reactive query: impossible — SPARQL is local-only (stage 3).