Files
ng-eventually/docs/decisions/discovery-model.md
T
Sylvain Duchesne bea9f51d91 docs: own the current-NextGraph-state knowledge + boundary (lib side)
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>
2026-07-03 23:23:23 +02:00

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

  1. 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).
  2. Primary discovery = that global index.
  3. 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

  1. Discovery: the index gives the NURIs of the entity documents.
  2. Synchronization: subscribe to those documents → they replicate locally (verifier: self.repos + oxigraph dataset).
  3. Query: query what is now local (sort, limit, reactivity). SPARQL/ORM run on the local set only (resolve_target_for_sparql searches self.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.post API, same handling. This is exactly inbox.ts in 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).