docs: NextGraph multi-user data model — stores, auth matrix, inbox fork plan
Capture the multi-user design exploration as project knowledge + briefs: - knowledge: NextGraph store types/permissions (+ inbox at protocol, SDK exposure, local repo path); integration model (iframe, where the verifier runs, generic JS plumbing, ngd stateful, build-time broker target) - briefs: multi-store refactor; authorization matrix + query inventory + derived store partitions; temporary fork to expose the inbox (3 layers: SDK fork, Coolify self-hosting, Festipod integration; libs via build:ng) - fix stale @ng-org versions (alpha.11 -> alpha.13) and a broken decision-record link in data-layer.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,7 @@ See [decision record](../decisions/2026-03-17-1800-sparql-delete-for-orm-objects
|
||||
- `src/shared/utils/ngGraph.ts` — `ensureGraphNuri()` returns `@graph` for entity creation
|
||||
- `src/shared/utils/ngBootstrap.ts` — Seeds test data using `ensureGraphNuri()` for `@graph`
|
||||
|
||||
See [decision record](.project/decisions/2026-03-17-1600-private-store-nuri-scope.md) for why.
|
||||
See [decision record](../decisions/2026-03-17-1600-private-store-nuri-scope.md) for why.
|
||||
|
||||
## Context Providers
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Modèle d'intégration et de déploiement NextGraph
|
||||
|
||||
Comment une app web tierce s'intègre à NextGraph, et où tourne le moteur (verifier).
|
||||
|
||||
## Overview
|
||||
|
||||
NextGraph s'utilise depuis une app web via un **proxy iframe** (`@ng-org/web`) : l'app tierce ne contient pas le moteur, elle délègue à un ng-app hébergé (par défaut `nextgraph.net`) qui exécute le moteur dans une iframe. Comprendre ce découpage est nécessaire pour savoir ce qu'on peut modifier sans auto-héberger. Vérifié dans `nextgraph-rs` le 2026-05-21 (voir [chemin du repo local](./nextgraph-stores-permissions.md#code-source-local)).
|
||||
|
||||
## Les paquets JS
|
||||
|
||||
- **`@ng-org/web`** — paquet **publié**. Proxy postMessage léger (aucun wasm embarqué). C'est **le** chemin d'intégration d'une app web tierce. `@ng-org/orm` et tous les exemples officiels (expense-tracker…) en dépendent. **Festipod l'utilise.**
|
||||
- **`@ng-org/api-web`** — paquet **privé** (`"private": true`, non publié). Moteur navigateur complet : charge `@ng-org/lib-wasm` dans un Web Worker (`?worker&inline`), utilise `sessionStorage`/`Worker`. Consommé uniquement par `app/nextgraph` (le frontend ng-app) et `engine/broker/auth`. C'est le moteur **interne** de l'app NextGraph, **pas** une cible d'intégration tierce.
|
||||
- **`@ng-org/lib-wasm`** — le moteur compilé en wasm (contient le verifier via la dépendance `nextgraph` / `local_broker`). Source : `sdk/js/lib-wasm/`.
|
||||
- **`nextgraph`** (npm) — l'API **NodeJS** (build `pkg-node` de lib-wasm).
|
||||
- **`@ng-org/orm`** — l'ORM réactif (`useShape`…), bâti sur `@ng-org/web`.
|
||||
|
||||
## Où tourne le verifier
|
||||
|
||||
Dans le modèle web standard (iframe), le verifier tourne **dans l'iframe** : `app/nextgraph` charge `api-web` → `lib-wasm` dans un Web Worker, côté navigateur. Le broker (`ngd`) ne fait que **le transport et le stockage**.
|
||||
|
||||
**Conséquence** : modifier la logique du verifier (ex. `request_processor`, `inbox_processor`) = reconstruire le **ng-app**, pas le broker.
|
||||
|
||||
## Le modèle iframe (intégration tierce)
|
||||
|
||||
- `@ng-org/web` redirige vers le ng-app hébergé, qui recharge l'app tierce dans une iframe après authentification, puis relaie les appels par `postMessage`.
|
||||
- **Reciblable au build** via variables d'env (fichier `sdk/js/web/src/index.ts`) :
|
||||
|
||||
| Variable | Cible |
|
||||
|---|---|
|
||||
| `NG_REDIR_SERVER` | défaut `nextgraph.net` |
|
||||
| `NG_DEV3` | `127.0.0.1:3033` |
|
||||
| `NG_DEV` | `localhost:14402` (redir) / `14404` (origin) |
|
||||
| `NG_DEV_LOCAL_BROKER` | `localhost:1421` |
|
||||
|
||||
Une app tierce peut donc pointer `@ng-org/web` vers un ng-app **auto-hébergé** sans changer son code, juste en rebuildant avec ces variables.
|
||||
|
||||
## Build pipeline lib-wasm
|
||||
|
||||
Scripts cargo dans `sdk/js/lib-wasm/Cargo.toml` (`[package.metadata.scripts]`) :
|
||||
|
||||
- `web` / `webdev` — `wasm-pack build --target web`
|
||||
- `node` / `nodedev` — `wasm-pack build -t nodejs`
|
||||
- `app` / `appdev` — `wasm-pack build --target bundler`
|
||||
|
||||
Post-traités par `prepare-web.js` / `prepare-node.js`.
|
||||
|
||||
## Plomberie proxy ↔ iframe ↔ worker (générique)
|
||||
|
||||
Le chemin d'appel d'une méthode du moteur est **entièrement générique** — aucune allowlist :
|
||||
|
||||
- `@ng-org/web` (proxy) : un `Proxy` JS qui relaie *n'importe quel* nom de méthode à l'iframe par `postMessage` (`apply` → `postMessage({method, args})`). Seules les méthodes *streamées* ont une entrée dans une table interne (positions d'arguments) ; les autres passent en simple requête/réponse.
|
||||
- `app/nextgraph` → `api-web/wasm-worker.js` : dispatch générique `Reflect.apply(ng[method], null, args)` (la table `mapping` est commentée/inutilisée).
|
||||
|
||||
**Conséquence** : une nouvelle fonction wasm en **requête/réponse simple** est *atteignable* de bout en bout via ce forwarding générique sans modifier le JS. Mais c'est un mécanisme de relais, **pas un substitut à une API typée** : l'appeler ainsi est un appel string non typé/non documenté (hack de test). Pour une intégration propre, on ajoute la méthode à la surface d'API du paquet (`@ng-org/web`) et à ses `.d.ts`, et éventuellement à `@ng-org/orm` (qui, lui, n'est **pas** un forwarder générique).
|
||||
|
||||
Cas **streamé** : une méthode en flux exige une entrée dans la table de streaming **des deux côtés** — `E` dans `@ng-org/web` (`ngweb.js`) **et** `streamed_api` dans `api-web/main.ts`. (Méthodes streamées actuelles : `doc_subscribe`, `orm_start_graph`, `orm_start_discrete`, `file_get`, `app_request_stream`.)
|
||||
|
||||
## Ciblage du broker : build-time uniquement
|
||||
|
||||
La cible (broker/ng-app) est figée **au build** de `@ng-org/web` via `import.meta.env` (`sdk/js/web/src/index.ts`) — **pas d'override runtime**, et `init()` ne prend pas d'URL de broker. Pour pointer une app vers un ng-app auto-hébergé, il faut donc **rebuilder `@ng-org/web`** avec `NG_REDIR_SERVER`/`NG_DEV*` (paquet en TypeScript pur, sans wasm → build trivial).
|
||||
|
||||
## Le broker (ngd)
|
||||
|
||||
- Supporte déjà nativement l'inbox (`inbox_post`, `inbox_register`, `inbox_pop_for_user` dans `engine/net/src/server_broker.rs`). Un `ngd` standard routerait l'inbox — aucun patch broker nécessaire.
|
||||
- C'est un démon **WebSocket** (`async-tungstenite`), **stateful** : stockage RocksDB sous `--base-path`, identité de pair (PeerId) persistée. Le volume est critique (clés + données chiffrées des users).
|
||||
- CLI (`bin/ngd/src/cli.rs`) : `--local PORT`, et surtout `--domain DOMAIN:PORT,LOCAL_PORT` = mode « derrière reverse-proxy TLS-terminé qui envoie X-Forwarded-For » (adapté à Traefik/Coolify).
|
||||
- **Ne sert pas de fichiers statiques** : pas de `ServeDir`/HTTP statique dans le crate. Le **ng-app frontend est un déploiement statique séparé** (`pnpm webfilebuild`). En prod, un reverse-proxy sert le statique du ng-app et proxy le WebSocket vers ngd sur un même domaine.
|
||||
- Premier démarrage **interactif** : ngd émet un lien d'invitation pour créer le wallet admin (cf. DEV.md « first run »). Wrinkle pour un déploiement conteneurisé headless.
|
||||
- Les Dockerfiles officiels (`bin/ngd/docker/Dockerfile.{alpine,fedora,ubuntu}`) sont **incomplets/cassés** (chemins obsolètes, échec de link llvm/clang documenté en commentaire) — pas de build conteneur turnkey.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Stores NextGraph et droits d'accès](./nextgraph-stores-permissions.md) — stores, permissions, inbox au protocole, chemin du repo local
|
||||
- [Data Layer](./data-layer.md) — usage actuel côté Festipod (auto-init iframe conditionnel)
|
||||
- [Brief : forker NextGraph pour l'inbox](../briefs/fork-nextgraph-inbox.md) — consommateur de cette fiche
|
||||
@@ -0,0 +1,108 @@
|
||||
# Stores NextGraph et droits d'accès
|
||||
|
||||
Fiche de référence des 5 types de stores NextGraph et de leurs droits de lecture/écriture.
|
||||
|
||||
## Overview
|
||||
|
||||
Décrit les primitives de stockage et de permission de NextGraph (système externe, pas le code de Festipod). Sert de socle aux briefs [multi-store-refactor](../briefs/multi-store-refactor.md) et [authorization-matrix](../briefs/authorization-matrix.md), qui dérivent la structure de données cible de Festipod à partir de ces primitives.
|
||||
|
||||
Source : doc NextGraph officielle — [Documents & Stores](https://docs.nextgraph.org/en/documents/) et [Getting started](https://docs.nextgraph.org/en/getting-started/), vérifiée le 2026-05-21.
|
||||
|
||||
## Code source local
|
||||
|
||||
Le repo `nextgraph-rs` est cloné localement à **`../../nextgraph/nextgraph-rs`** (relatif à la racine du projet, soit `/home/sylvain/projects/nextgraph/nextgraph-rs`). À consulter pour vérifier ce qui est réellement exposé au protocole/SDK plutôt que de se fier à la doc. Points d'entrée utiles :
|
||||
|
||||
- `sdk/js/lib-wasm/src/lib.rs` — l'API wasm effectivement exposée au JS (`@ng-org/web` n'est qu'un proxy postMessage vers ces fonctions).
|
||||
- `engine/net/src/app_protocol.rs` — l'enum `AppRequestCommandV0` (commandes de l'app protocol) et `NuriV0` (formats de NURI).
|
||||
- `engine/verifier/src/request_processor.rs` — le dispatch effectif des commandes `app_request` (la vérité sur ce qui est *traité*, pas seulement déclaré).
|
||||
- `engine/net/src/types.rs` — types inbox (`InboxPost`, `InboxMsg`, `InboxMsgContent`).
|
||||
- `engine/verifier/src/inbox_processor.rs` — traitement des messages d'inbox.
|
||||
|
||||
## Les 5 types de stores
|
||||
|
||||
| Store | Lecture | Écriture | Création |
|
||||
|---|---|---|---|
|
||||
| **Private** | Titulaire seul | Titulaire seul | Par défaut |
|
||||
| **Protected** | Titulaire + utilisateurs disposant d'un lien + permission (capability) | Titulaire + collaborateurs permissionnés | Par défaut |
|
||||
| **Public** | Tout le monde, sans capability | Titulaire seul | Par défaut |
|
||||
| **Group** | Membres du groupe | Membres du groupe (collaboratif) | À la demande |
|
||||
| **Dialog** | Les deux utilisateurs uniquement | Les deux utilisateurs uniquement | À la demande |
|
||||
|
||||
### Citations doc (verbatim)
|
||||
|
||||
- **Private** — *« this is a place where you put only private and personal information that only you have access to »*, *« It is not possible to share the documents of your private store with anybody else »*.
|
||||
- **Protected** — *« a space where you can share data, documents, and media with other users, but they will need a special link and permission in order to access them »* ; fait office de *« protected social profile »*.
|
||||
- **Public** — *« equivalent to your website, blog, or public profile on social networks … that you want everybody to have access to, without the need for special permissions »*.
|
||||
- **Group** — *« each Group is a separate Store … you can configure the store so that all the documents included in this store, inherit the permissions of the store »*.
|
||||
- **Dialog** — *« hold all the data you exchange with another user (and only with that other user) … You cannot add more users to this store »*.
|
||||
|
||||
### Stores par défaut vs à la demande
|
||||
|
||||
Tout wallet utilisateur dispose d'office des **3 stores** private / protected / public. Ils sont exposés dans la session du SDK sous `private_store_id`, `protected_store_id`, `public_store_id`. Les **Group** et **Dialog** stores se créent à la demande.
|
||||
|
||||
## Concepts transverses
|
||||
|
||||
### Document vs Repo
|
||||
|
||||
- *« A Repo is basically the equivalent of an E2EE group for one and only one Document. »*
|
||||
- **1 document = 1 repo.** Le repo détient les commits (changements) **et** les permissions du document.
|
||||
- Identifiant du repo : `did:ng:o:<RepoID>` (RepoID de 44 caractères).
|
||||
- Un **store** est lui-même un document spécial qui regroupe et permissionne d'autres documents.
|
||||
|
||||
### Granularité des permissions
|
||||
|
||||
- **Écriture** : gérée au niveau du **Document (repo)**, pas de la branche ni du bloc — *« Write permissions are managed at the level of the Document, not at the level of the branch or block »*.
|
||||
- **Lecture** : peut être plus fine, **par bloc ou par branche** — *« Read permissions can be by block or branch »*.
|
||||
- **Héritage** : un store (notamment Group) peut être configuré pour que tous les documents qu'il contient héritent des permissions du store.
|
||||
|
||||
### Capability / Nuri
|
||||
|
||||
- Le partage se fait en transmettant un **Nuri** qui embarque la capability cryptographique (lecture et/ou écriture). Pas d'ACL centralisée : la possession du Nuri = le droit.
|
||||
- *« adding permissions can be done offline »* — l'ajout de permission est asynchrone.
|
||||
- *« removing permissions is a synchronous operation that requires a SyncSignature »* — le retrait est synchrone et nécessite une SyncSignature.
|
||||
|
||||
### Inbox
|
||||
|
||||
- **Chaque document a une inbox native.** Un non-éditeur (sans capability d'écriture) peut y **déposer un lien (DID cap)** sans être invité comme éditeur.
|
||||
- Le propriétaire **modère** : accepter / rejeter / retirer.
|
||||
- Citation : *« each document has an inbox, which is used in this case to drop the link »*.
|
||||
- C'est le mécanisme retenu par Festipod pour la notification d'inscription à un point de rencontre (voir [authorization-matrix](../briefs/authorization-matrix.md)).
|
||||
|
||||
#### Modèle inbox au protocole (vérifié dans `nextgraph-rs`, 2026-05-21)
|
||||
|
||||
- NURI d'inbox : `did:ng:d:<inbox_id>`.
|
||||
- Contenu : enum `InboxMsgContent` avec les variantes `ContactDetails`, `DialogRequest`, **`Link`**, `Patch`, `ServiceRequest`, `ExtRequest`, `RemoteQuery`, `SocialQuery` (`Comment`, `Transaction`, `BackLink` encore en TODO).
|
||||
- Le message est **scellé** (`crypto_box::seal`) vers la pubkey de l'inbox destinataire → seul le titulaire de l'inbox déchiffre.
|
||||
- Le champ `from` est **optionnel** → l'expéditeur peut être **anonyme** (pas de signature, pas de `from_inbox`). C'est exactement le « identifié si connu, anonyme sinon » voulu par Festipod, **natif au protocole**.
|
||||
|
||||
#### Exposition côté SDK JS : l'inbox n'est PAS utilisable directement
|
||||
|
||||
Investigation dans `lib-wasm` + `request_processor.rs` :
|
||||
|
||||
- `app_request(request)` est exposé au JS, et l'enum `AppRequestCommandV0::InboxPost` + le constructeur `AppRequest::inbox_post()` existent.
|
||||
- **MAIS** le `request_processor` du verifier (qui traite les `app_request`) **n'a aucun bras `InboxPost`**. Commandes réellement traitées : `OrmStart`, `OrmStartDiscrete`, `Fetch`, `FileGet`, `OrmUpdate`, `OrmDiscreteUpdate`, `SocialQueryStart`, `QrCodeProfile`, `QrCodeProfileImport`, `Header`, `Create`, `FilePut`. Envoyer un `InboxPost` via `app_request` ne déclenche donc rien.
|
||||
- En plus, construire un `InboxPost` exige le scellement crypto côté Rust ; **aucun helper wasm** n'expose cette construction.
|
||||
- Le dépôt en inbox n'est déclenché qu'**en interne** par deux features, elles exposées au JS :
|
||||
- `QrCodeProfileImport` → `post_to_inbox(InboxPost::new_contact_details(...))` (échange de contact) ;
|
||||
- `social_query_start(...)` → propagation de requête sociale via les inbox des **contacts**.
|
||||
|
||||
**Conséquence** : pas de moyen propre, aujourd'hui, de faire un « drop a Link » arbitraire dans l'inbox d'un PdR depuis le SDK JS. Il faudrait, dans `nextgraph-rs`, soit exposer un helper `inbox_post_link(...)` dans `lib-wasm` **et** ajouter le bras `InboxPost` au `request_processor`, soit détourner `social_query`.
|
||||
|
||||
**Piste connexe — `social_query_start`** : EST exposé au JS. C'est une requête fédérée sur le graphe social (propagée via inbox jusqu'à `degree` sauts), pertinente pour « qui dans mon réseau participe à X » et pour la découverte. Limite : ne touche que les **contacts**, donc ne couvre pas la notification anonyme vers un hôte non-connecté.
|
||||
|
||||
## Limites du SDK JS
|
||||
|
||||
Le SDK `@ng-org/web` (vérifié en `0.1.2-alpha.13`, soit `upstream/main` au 2026-05-21 — la version installée dans Festipod) **n'expose pas** les primitives suivantes, pourtant présentes au niveau protocole :
|
||||
|
||||
- création de Group / Dialog store ;
|
||||
- partage de capability (transmission de Nuri avec droits) ;
|
||||
- manipulation de permissions (ajout / retrait) ;
|
||||
- dépôt et lecture d'inbox.
|
||||
|
||||
Méthodes JS effectivement disponibles : `doc_create`, `doc_subscribe`, `sparql_query`, `sparql_update`, `orm_start_graph`, `orm_start_discrete`, `graph_orm_update`, `discrete_orm_update`, `file_get`, `app_request_stream`. La doc annonce qu'*« An API will be provided for permission manipulation »* (sans date). Détail dans [multi-store-refactor §Contrainte SDK](../briefs/multi-store-refactor.md).
|
||||
|
||||
## See Also
|
||||
|
||||
- [Brief : refactor multi-store](../briefs/multi-store-refactor.md) — consommateur de cette fiche
|
||||
- [Brief : matrice d'autorisations](../briefs/authorization-matrix.md) — dérive la structure de stores Festipod
|
||||
- [Knowledge : data layer](./data-layer.md) — état actuel mono-store de l'app
|
||||
Reference in New Issue
Block a user