feat(client): discovery via a global index (special @index account)

Add a generic discovery-index surface: submitToIndex(ref) deposits a reference
into the index document's inbox; readIndex() returns the materialized entries. A
reserved special account (@index) owns the index document; deposits flow through
the emulated inbox and are materialized by the emulated curator (the dedup/
moderation point). This replaces cross-account fan-out as the discovery path and
is more faithful to the target (a single owned index fed via its inbox). Generic
(the consumer supplies the reference to index). 79 tests pass; tsc rc=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-07-04 09:33:42 +02:00
parent 7db5eef33f
commit 9951cd5223
5 changed files with 461 additions and 8 deletions
+45
View File
@@ -274,6 +274,51 @@ the deferred global-index note in the top-level README and
watcher is the ONE deposit/materialization mechanism reused for BOTH meeting-point
registration AND submission to a discovery index — same `post` API, same watcher.
## Emulated discovery index + special account (`discovery.ts`)
Discovery is a **surface on top of the inbox**, not a new primitive. **Access ≠
discovery**: a public entity is world-readable *with its NURI*; the discovery
index is how a client learns that NURI **exists** without holding a connection
to its creator (see [`decisions/discovery-model.md`](./decisions/discovery-model.md)).
The model is: ONE global index = an **owned document** (public read), **fed via
ITS inbox**, **materialized by a curator**. Nobody writes the index directly — a
creator DEPOSITS a reference into the index's inbox; the curator ingests deposits
into entries. Materialization is the natural **dedup / moderation point**.
- **The special account (polyfill owner).** "Who owns the global index" is
undecided in the target (NextGraph is mono-user with no global data — a
singleton app is the only glimpsed path). So the polyfill parks ownership on a
**RESERVED SPECIAL ACCOUNT** in the shim — `INDEX_ACCOUNT = "@index"`. It is a
normal shim account (so its 3 scope documents are created on first sight like
any other), but never a real user; it only HOSTS the index document. Its
`public` scope document IS the index document, and its inbox receives the
deposits — a **stable NURI**: every client opening the same shared wallet
resolves the same account → same document, so all clients read/write ONE
shared index.
- **`submitToIndex(ref, opts?)`** — the SDK act "make this discoverable".
Deposits `ref` into the index document's inbox via `inbox.post`. `from` follows
the inbox convention (anonymous when `null`). `ref` is **opaque** here — the
consumer serializes whatever locates the entity (e.g. an entity document NURI +
discovery metadata).
- **`readIndex()`** — the EMULATED CURATOR. Reads every submission, **dedups by
serialized `ref`** (the moderation point: a duplicate submission surfaces
once), returns entries sorted by `ts`. `watchIndex(onEntries, opts?)` is the
emulated watcher (polls `readIndex`).
**This REPLACES the cross-account fan-out** (`store-registry.ts`
`listEntityDocs('public')` / `resolveReadGraphs`) as the app-facing discovery
path: the app submits public entities to the index and reads the index, instead
of fanning out over every account's public documents. The fan-out survives only
as an **internal lib fallback** — kept for the per-scope listing it also powers
(e.g. `resolveReadGraphs`), never the app's discovery route.
GENERIC: `discovery.ts` knows no application domain — the consumer defines the
`ref` shape and its meaning. At migration the special account disappears:
ownership moves to the decided global-index owner, `submitToIndex` becomes the
native `inbox_post_link` on the index's inbox, and `readIndex` queries the real
materialized index document. The consumer surface (`submitToIndex` / `readIndex`)
is designed to survive that swap unchanged.
## Emulated write guard (`ng-proxy.ts`)
The public `ng` proxy overrides `sparql_update` to enforce an emulated **write