bea9f51d91
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>
82 lines
4.2 KiB
Markdown
82 lines
4.2 KiB
Markdown
# 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`](../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`](../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).
|