e24a20cc46
Réécriture de « Apps & services » : la version précédente déduisait la forme cible de l'absence d'implémentation dans le moteur — exactement ce que le principe de conception du README interdit — et concluait l'inverse de ce que le développeur NextGraph énonce. Deux couches désormais séparées et étiquetées comme telles : - ce que le moteur CONTIENT (vérifié) : `AppManifestV0` avec `singleton: bool`, `access_requests`, `installs`, `dependencies` ; `init(callback, singleton, access_requests)` côté JS. Et personne ne le consomme — le module `permissions` n'est importé par aucune crate, `AppManifest` n'est construit nulle part. Du vocabulaire, pas du comportement. - ce que le modèle SERA (énoncé par le développeur, non implémenté) : une app singleton peut aussi gérer les documents par utilisateur ; les données communes prennent la forme d'un document ou d'un store partagé par tous les utilisateurs et codé en dur dans l'app ; le développeur détient les droits d'écriture et peut les déléguer, jamais à tous — les contributions arrivent par une inbox. Le commentaire du champ dit `/// cannot create Documents?`, avec le point d'interrogation. Une seconde glose publiée le contredit et rejoint ce que dit le développeur : `sdk/js/web/README.md:90,108` annote l'argument « will your app create many docs in the system, or should it be launched as a unique instance » — `singleton` porte sur la multiplicité d'instance, pas sur une interdiction de créer des documents. C'est cette lecture qu'il faut retenir. Conséquence sur l'ADR discovery : sa moitié « la voie app singleton est incertaine » est caduque et doit être re-posée, sans que cela rétablisse la découverte, qui tient sur son propre appui.
116 lines
8.3 KiB
Markdown
116 lines
8.3 KiB
Markdown
# ADR — Discovery mechanism (inbox-fed index, fan-out)
|
|
|
|
> ## SUPERSEDED — 2026-07-30. The premise does not hold.
|
|
>
|
|
> **There is no discovery in NextGraph. You cannot discover; you can only follow links** (PO, 2026-07-30 — the principle is documented in [`../readcap-and-nuri-model.md`](../readcap-and-nuri-model.md) §4ter-bis). Publishing is two acts: place the data in your public store, **and** circulate the link — into inboxes, or into somewhere already reachable by the people concerned. It is seen only by those who received the link. This is a foundation of local-first, not a gap to be filled.
|
|
>
|
|
> A global index therefore fails on **two independent counts**:
|
|
>
|
|
> 1. it emulates a capability the target will never have — teaching consumers a model that does not exist, which is the one failure mode this library exists to prevent;
|
|
> 2. it is **data common to several users/wallets**, and nothing may be common — only indexing mechanisms that make the virtual users work (the shim qualifies; a shared index of user announcements does not).
|
|
>
|
|
> This ADR already recorded the first half of that verdict — *"a dedicated service with its own wallet sharing a freely-readable index is not a NextGraph shape"*, resting on a singleton-app path *"not implemented, uncertain"*. That reservation is now the conclusion.
|
|
>
|
|
> **Amended 2026-08-03 — the singleton-app half must be re-put, not cited as closed.** The NextGraph developer has since named the singleton app as *the* way an application holds data common to all its users: a document or store shared by all users, hardcoded in the app, write-owned by the developer and delegable — never delegated to all users, so user contributions arrive **through an inbox**. Still unimplemented, but no longer uncertain in direction. This does **not** reinstate discovery — the "you cannot discover, you can only follow links" verdict stands on its own footing, and count 2 above is about pooling users' data across wallets, not about an app's own data. What is void is treating "the singleton path is uncertain" as a settled argument. See [`../nextgraph-current-state.md`](../nextgraph-current-state.md) § Apps & services.
|
|
>
|
|
> **`discovery.ts` and its tests were removed on 2026-07-30**, along with `watchShape`'s public-scope fold and `INDEX_ACCOUNT`. See [`../briefs/2026-07-30-virtual-wallet-boundary.md`](../briefs/2026-07-30-virtual-wallet-boundary.md).
|
|
>
|
|
> One factual error below is worth naming so it is not carried into a future design: *"a native inbox (a primitive present on every document)"* is **false**. No document has an inbox upstream — only the public and protected STORE repos do (`engine/verifier/src/site.rs:128,149`; `doc_create` leaves `inbox: None`, `engine/repo/src/repo.rs:574`). See [`../nextgraph-current-state.md`](../nextgraph-current-state.md) § Inbox.
|
|
>
|
|
> What survives, and is worth keeping from the text below: the **3-stage frame** (`discovery → synchronization → query`) is still exactly right, with stage 1 re-read as *"a link reached you"* rather than *"you consulted an index"*. You still cannot query what you have not synchronized, and you still do not synchronize what nobody gave you. The **inbox** is what feeds stage 1 — which makes it the bootstrap of the whole reachability graph, not a side feature.
|
|
>
|
|
> Kept in full below as a record of what was built and why, and of the reasoning that has to be re-read through the correction above.
|
|
|
|
**Date:** 2026-06-16 · **Status:** SUPERSEDED 2026-07-30 (see the block above). *Originally: 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 application *should* surface) is the consumer
|
|
application's concern, not this lib's; only the mechanism is recorded here.
|
|
|
|
## Access is not 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 is the ADR's topic.
|
|
|
|
## 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), built up from its inbox (a
|
|
watcher ingests deposits and adds entries).
|
|
2. Primary discovery is that global index.
|
|
3. Relational is a secondary axis, overlaid: a peer's participations,
|
|
markers on the global list. It rests on existing per-item data (protected scope),
|
|
with 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 so 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) plus a native inbox (a primitive present on every document). Nobody
|
|
writes the index but its owner (by ingesting inbox deposits). So the model
|
|
stays "3 stores + Dialog + inboxes, no Group store."
|
|
- **One mechanism, reused.** The inbox + ingest watcher serve both
|
|
submitting an entity to the index and a registration/deposit in one consumer
|
|
app — same `inbox.post` API, same handling. This is exactly `inbox.ts` in this
|
|
lib (`post` / `read` / `materialize` / `watch`).
|
|
- **Natural dedup / moderation point:** the inbox → index ingest is where
|
|
duplicates are detected / moderated before insertion.
|
|
|
|
## Index owner — target model undecided
|
|
|
|
NextGraph apps and services are mono-user with no global data
|
|
(see [`../nextgraph-current-state.md`](../nextgraph-current-state.md) § Apps &
|
|
services), so a dedicated service with its own wallet sharing a freely-readable
|
|
index is not a NextGraph shape. 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 package is a deferred separate package in this lib
|
|
(see the top-level README).
|
|
|
|
## Polyfill reality — the fan-out drift is now RESOLVED (special-account index)
|
|
|
|
The shared-wallet polyfill originally shipped a cross-account fan-out over
|
|
every account's public documents (`store-registry.ts` `listEntityDocs('public')`
|
|
/ `resolveReadGraphs`) — one account saw another's public entity without any
|
|
relationship to its creator. This ADR classified that per-account fan-out as a drift
|
|
to be replaced by the single global index.
|
|
|
|
That drift is now resolved in the polyfill. The inbox-fed global index of
|
|
this ADR is implemented on top of a reserved special account in the shim
|
|
(`discovery.ts`, `INDEX_ACCOUNT = reservedAccount("index")` — a sentinel-prefixed
|
|
key in the shim's reserved namespace that `normalizeId` can never produce, so it is
|
|
disjoint from any normalized user id, not the literal `"@index"`) that owns the
|
|
index document while
|
|
the target owner stays undecided: `submitToIndex(ref)` deposits into the index
|
|
document's inbox; `readIndex()` ingests (dedups) the entries. The app-facing
|
|
discovery path is now "read the index", exactly as this ADR prescribes — not
|
|
the fan-out. The cross-account fan-out survives only as an internal lib
|
|
fallback (it still powers per-scope listing like `resolveReadGraphs`), never
|
|
the discovery route. The special account is the provisional owner; at migration
|
|
it disappears and ownership moves to the decided global-index owner (see
|
|
[`../migration-guide.md`](../migration-guide.md)) with the consumer application's
|
|
surface (`submitToIndex` / `readIndex`) unchanged.
|
|
|
|
## 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-side ingest.
|
|
- **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).
|