diff --git a/.project/concepts/app-security/brief_2026-05-18_authorization-matrix.md b/.project/concepts/app-security/brief_2026-05-18_authorization-matrix.md index 37c31de..12fb9d2 100644 --- a/.project/concepts/app-security/brief_2026-05-18_authorization-matrix.md +++ b/.project/concepts/app-security/brief_2026-05-18_authorization-matrix.md @@ -127,6 +127,8 @@ Le doc PdR (dans le `public_store` de l'hôte) a une **inbox** native : reçoit Sur le périmètre validé, **aucune donnée ne demande de Group store**. Tout tient dans : 3 stores natifs par utilisateur + Dialog stores + inboxes natives. Les Group stores ne deviennent nécessaires que si le modèle d'écriture événement est « wiki », ou si communautés/suivi/collaboration multi-hôte reviennent dans le périmètre. +> **Note (2026-06-17) — la découverte n'impose PAS de Group store.** On a un instant cru qu'un **index global des événements** exigerait un document à écriture ouverte (= Group store). La [[decision_2026-06-16_discovery-model]] a finalement retenu un index **possédé** (lecture publique) **alimenté via son inbox** (le créateur y *dépose* une référence ; le propriétaire matérialise). Comme l'**inbox est une primitive native de tout document**, l'index tient dans un `public_store` ordinaire → **« aucun Group store » reste vrai**. Les Group stores ne redeviennent nécessaires que pour communautés / collaboration multi-écrivains réels. + ### Implication pour [[brief_2026-05-17_multi-store-refactor]] Ce brief y propose une structure à 4 niveaux de Group stores. **Cette analyse dérive une structure différente** (3 stores natifs + Dialog, sans Group) parce que les concepts qui justifient les Group stores ont été mis hors périmètre. À reconcilier à l'exécution. diff --git a/.project/concepts/data-layer/knowledge_nextgraph-stack.md b/.project/concepts/data-layer/knowledge_nextgraph-stack.md index aec3f72..9a70f0f 100644 --- a/.project/concepts/data-layer/knowledge_nextgraph-stack.md +++ b/.project/concepts/data-layer/knowledge_nextgraph-stack.md @@ -14,6 +14,8 @@ summary: Paquets @ng-org/* (web, orm, shex-orm, alien-deepsignals), shapes SHEX Installés depuis npm (`@ng-org/*`, versions alpha). Pour développer contre un build local non publié de `nextgraph-rs`, `scripts/build-ng-packages.sh` pack le monorepo en tarballs et repointe `package.json` (cf. `nextgraph-platform` — le pattern d'origine du projet, réactivable pour un fork). +> **Indirection via `ng-eventually` (depuis 2026-06-22).** Le data-plane ne consomme plus le SDK directement : `useShape` est importé de **`@ng-eventually/client`** (wrapper SDK-identique), et `ngSession` injecte le vrai SDK dans la lib via `configure()` (`@ng-eventually/client/polyfill`). Aujourd'hui la lib **forwarde tout** (passthrough) — comportement identique, validé `@data`. Détails et raison d'être : [[decision_2026-06-17_eventually-library]]. Les imports **de types** (`ShapeType`, `DeepSignalSet`…) restent sur `@ng-org/*`. + ## Shapes SHEX `src/shared/shapes/shex/festipodShapes.shex` définit : diff --git a/.project/concepts/nextgraph-platform/brief_2026-06-15_shared-wallet-shim.md b/.project/concepts/nextgraph-platform/brief_2026-06-15_shared-wallet-shim.md index a93b720..39429bc 100644 --- a/.project/concepts/nextgraph-platform/brief_2026-06-15_shared-wallet-shim.md +++ b/.project/concepts/nextgraph-platform/brief_2026-06-15_shared-wallet-shim.md @@ -1,13 +1,13 @@ --- type: brief -summary: Stopgap staging multi-user — un wallet partagé unique + couche storeRegistry (Piste A), comptes/login Festipod simulés (username seul), 1 document par (utilisateur × périmètre) via doc_create, filtre d'isolation applicatif ; structure préfigurant l'infra cible, sharedWalletShim jetable à la migration -last_updated: 2026-06-15 +summary: Stopgap staging multi-user — un wallet partagé unique (Piste A) + couche storeRegistry, comptes/login Festipod simulés (username seul, cf. decision login-flow), 1 document PAR ENTITÉ (événement/PdR) + index global de découverte, participations groupées en protected, filtre d'isolation applicatif ; structure préfigurant l'infra cible (stores per-user, Group store pour l'index), sharedWalletShim jetable. Prototype implémenté puis réverti — à (ré)implémenter. +last_updated: 2026-06-16 --- # Stopgap multi-user : wallet partagé unique (`sharedWalletShim`) -**Status:** En cours — couche compte/login + isolation livrées et vérifiées ; couche multi-document livrée derrière flag, à valider sur broker -**Last updated:** 2026-06-15 +**Status:** Conçu — décisions prises ([[decision_2026-06-15_shared-wallet-login-flow]], [[decision_2026-06-16_discovery-model]], granularité par entité). Faits clés vérifiés sur broker via un **prototype**. ⚠️ **Le prototype a été réverti — aucun code dans le tree** ; à (ré)implémenter. +**Last updated:** 2026-06-16 ## Context @@ -24,6 +24,8 @@ Donc lire le store d'un autre utilisateur — **même son `public_store`** — e **Décision retenue :** Piste A (wallet partagé unique) + couche `storeRegistry`, broker **`nextgraph.net`**. +> **Évolution majeure (2026-06-17)** : tout ce polyfill est désormais **encapsulé dans une librairie générique externe** (`ng-eventually-js`, hors repo), pas dans l'app — voir [[decision_2026-06-17_eventually-library]]. L'app Festipod ne dépendra que de `@ng-eventually/client` (wrapper SDK-identique). Ce brief décrit donc la **conception du polyfill** ; son lieu d'implémentation est la lib, et les mécanismes ci-dessous (storeRegistry, caps émulées, inbox, index) y sont réalisés. Le `storeRegistry` et le filtre d'isolation décrits plus bas sont la **version « dans l'app »** désormais remplacée par la lib. + ## What We Know ### Les trois familles de contournement (et pourquoi A) @@ -41,34 +43,37 @@ Donc lire le store d'un autre utilisateur — **même son `public_store`** — e ┌─ Couche COMPTE (simulée — UX cible, jetable à la migration) ────────┐ │ signup / login Festipod · currentAccountId en localStorage │ ├─ Couche STORES VIRTUELS (fidèle — survit à la migration) ───────────┤ -│ storeRegistry : (appUser, scope) → NURI de document │ -│ 1 document par (utilisateur × périmètre), créé via doc_create │ +│ storeRegistry : 1 document PAR ENTITÉ (événement/PdR) via │ +│ doc_create ; index global de découverte ; protected groupé/compte │ ├─ Couche NEXTGRAPH (réelle mais invisible) ──────────────────────────┤ │ UN wallet partagé, mêmes credentials pour tous │ └─────────────────────────────────────────────────────────────────────┘ ``` 1. **NextGraph (réelle mais invisible)** — un wallet partagé, mêmes credentials. Le login NextGraph **n'est pas programmable** (redirect web vers `nextgraph.net/redir`, cf. `NextGraphContext`, concept `data-layer`) ; il est donc présenté comme une **barrière technique d'accès** avant l'app, pas comme un login (flux arrêté dans [[decision_2026-06-15_shared-wallet-login-flow]]). Session **persistante** côté iframe broker → ouverture **une fois par device** dans une même session navigateur. -2. **Stores virtuels (fidèle, survit à la migration)** — **1 document par (utilisateur × périmètre)** via `doc_create`. Vérifié : `doc_create` retourne un NURI `did:ng:o:…`, le repo est **inséré immédiatement** dans `self.repos` (`verifier.rs:2900`), et `orm_start_graph`/`sparql_update` l'acceptent **sans pin explicite** (`sdk/rust/src/tests/sparql_regressions.rs:136-200`). À la migration : **swap du résolveur** `storeRegistry` vers les vrais stores, sans réécrire les écrans. +2. **Stores virtuels (fidèle, survit à la migration)** — **1 document par entité** (événement/PdR) via `doc_create`, référencé par un **index global de découverte** (cf. [[decision_2026-06-16_discovery-model]]) ; les données *protected* (profil, participations) restent **groupées** par compte. Vérifié : `doc_create` retourne un NURI `did:ng:o:…`, le repo est **inséré immédiatement** dans `self.repos` (`verifier.rs:2900`), et `orm_start_graph`/`sparql_update` l'acceptent **sans pin explicite** (`sdk/rust/src/tests/sparql_regressions.rs:136-200`). À la migration : **swap du résolveur** `storeRegistry` vers les vrais stores, sans réécrire les écrans. 3. **Compte/login simulés (UX, jetable)** — signup/login Festipod, **username seul** (pas de mot de passe), `currentAccountId` en `localStorage`. ### `sharedWalletShim` -Nom **volontairement explicite** du mapping temporaire (hack) : comptes simulés → NURIs des stores virtuels. Ancré dans le **`private_store` du wallet partagé** (`session.private_store_id`, toujours présent → ancre de bootstrap). **Seul artefact sans équivalent cible** (l'infra cible n'a **pas** d'index central : la découverte y passe par les connexions et les `public_store`). Rend possibles le **login cross-device** et le **picker d'utilisateurs**. **À supprimer à la migration.** +Nom **volontairement explicite** du mapping temporaire (hack) : comptes simulés → NURIs des stores virtuels. Ancré dans le **`private_store` du wallet partagé** (`session.private_store_id`, toujours présent → ancre de bootstrap). **Sans équivalent cible** : la cible n'a pas d'**annuaire de comptes** (l'identité = le wallet). Rend possibles le **login cross-device** et le **picker d'utilisateurs**. **À supprimer à la migration.** + +> **Découverte des événements** (à ne pas confondre avec le shim) : modèle tranché dans [[decision_2026-06-16_discovery-model]] — un **index global unique** (pas une découverte par-compte), **alimenté via son inbox** : le créateur **dépose** une référence dans l'inbox de l'index ; l'index (document **possédé**, lecture publique) est matérialisé depuis son inbox. Architecture en 3 étapes (découverte → synchronisation → requête locale). **Pas de Group store** (index = doc possédé + inbox native) ; inbox + watcher réutilisés (même mécanisme que l'inscription au PdR). Contenu par compte : `username → profileId → { docPublic, docProtected, docPrivate }`. Chaîne de bootstrap d'un device : session → `private_store_id` → lire le `sharedWalletShim` → comptes + NURIs par périmètre. ### Placement des entités -Identique à la dérivation de [[brief_2026-05-18_authorization-matrix]], au mapping `document ↔ store` près : +Dérivée de [[brief_2026-05-18_authorization-matrix]] (périmètres) + [[decision_2026-06-16_discovery-model]] (granularité par entité + index global) : -| Entité | Périmètre | Doc aujourd'hui | Store cible | +| Entité | Périmètre | Doc stopgap (wallet partagé) | Cible | |---|---|---|---| -| Événement déclaré par U | public | `U/public` | `public_store` de U | -| PdR hébergé par U | public | `U/public` | `public_store` de U | -| Profil réseau de U | protected | `U/protected` | `protected_store` de U | -| Participation de U | protected | `U/protected` | `protected_store` de U | -| Index des connexions de U | protected | `U/protected` | `protected_store` de U | +| Événement (créé par U) | public | **1 doc / événement**, référencé dans l'index global | doc dans le `public_store` de U | +| PdR (hôte U) | public | **1 doc / PdR**, référencé dans l'index global | doc dans le `public_store` de U (+ inbox native) | +| **Index global des événements** | — (possédé + inbox) | **1 doc** alimenté via son **inbox** (dépôt + matérialisation) | doc **possédé** (lecture publique) **+ inbox** dans le `public_store` du propriétaire | +| Profil réseau de U | protected | groupé dans `U/protected` | `protected_store` de U | +| Participation de U | protected | groupé dans `U/protected` | `protected_store` de U | +| Index des connexions de U | protected | groupé dans `U/protected` | `protected_store` de U | | Profil privé de U (settings, email) | private | `U/private` | `private_store` de U | | Connexion A↔B | dialog | `dialog/A∙B` | Dialog store A↔B | @@ -89,35 +94,45 @@ Un seul wallet ⇒ tout lisible par tous. Pour que le staging se **comporte** co - `CURRENT_USER_ID` constant → `currentAccountId` sélectionnable (persisté `localStorage`). - `src/shared/utils/ngBootstrap.ts` — seed réparti **par documents**. -## État d'implémentation (2026-06-15) +## Plan d'implémentation & faits vérifiés -Deux drapeaux de build, tous deux **OFF par défaut** (le mono-store validé reste le défaut ; dev/`@ui`/`@e2e` inchangés) : +> ⚠️ **Un prototype a été développé puis réverti** : le code décrit ici **n'est pas dans le tree** (working tree propre). Cette section sert de **plan de (ré)implémentation** et consigne les **faits vérifiés sur broker** durant le prototype — ils restent vrais indépendamment du code. -- **`FESTIPOD_STAGING=1`** — active le flux login option 2 (gate technique → écran « Connexion »). Découplé de `NODE_ENV` exprès, pour qu'un `@e2e` en build production ne soit pas bloqué par le gate. -- **`FESTIPOD_MULTISTORE=1`** — active la couche multi-document (storeRegistry). +**Mécanisme prévu — deux drapeaux de build, OFF par défaut** (mono-store reste le défaut ; dev/`@ui`/`@e2e` inchangés) : +- `FESTIPOD_STAGING=1` — flux login option 2 (gate technique → écran « Connexion »). **À découpler de `NODE_ENV`** (sinon un `@e2e` en build prod est bloqué par le gate). +- `FESTIPOD_MULTISTORE=1` — couche multi-document (storeRegistry). -| Pièce | Fichier | État | -|---|---|---| -| Couche compte (faux login, localStorage) | `src/shared/context/AccountContext.tsx` | ✅ livré, vérifié (build + `@ui`) | -| Gate technique + écran « Connexion » + orchestrateur | `src/modules/auth/screens/{AccessGateScreen,ConnexionScreen}.tsx`, `src/app/AuthGate.tsx` | ✅ livré | -| Vrai logout planqué | `ngSession.ts:logoutNg`, `SettingsScreen.tsx` | ✅ livré | -| Filtre d'isolation (mode connecté) | `src/shared/utils/isolation.ts` + `FestipodDataContext` (useNgData) | ✅ livré, pur, vérifié | -| storeRegistry + sharedWalletShim (doc_create, SPARQL shim, entité→périmètre) | `src/shared/utils/storeRegistry.ts` | ⚠️ livré, **compile**, runtime NG **à valider sur broker** | -| Câblage multi-document (reads `{graphs}` + writes par périmètre) | `FestipodDataContext` (useNgData) derrière `MULTISTORE` | ⚠️ livré, à valider sur broker | +**Pièces à (ré)créer** : -**Pourquoi le flag** : le runtime NextGraph (doc_create, shim SPARQL, abonnement multi-graphes) ne peut pas être validé sans broker live. Conformément au « mode mono-store parallèle » endossé par [[brief_2026-05-17_multi-store-refactor]], il ship OFF — rien de fonctionnel n'est cassé. +| Pièce | Fichier prévu | +|---|---| +| Couche compte (faux login, localStorage) | `src/shared/context/AccountContext.tsx` | +| Gate technique + écran « Connexion » + orchestrateur | `src/modules/auth/screens/{AccessGateScreen,ConnexionScreen}.tsx`, `src/app/AuthGate.tsx` | +| Vrai logout planqué | `ngSession.ts:logoutNg`, `SettingsScreen` | +| Filtre d'isolation (mode connecté) | `src/shared/utils/isolation.ts` | +| storeRegistry (1 doc/entité + index global) + sharedWalletShim | `src/shared/utils/storeRegistry.ts` | +| Câblage multi-document (lecture via index global + fan-out ; écriture per-entité) | `FestipodDataContext` derrière `MULTISTORE` | +| Scénarios `@data` de validation | `src/modules/workshop/…` | -**Étapes de validation broker** (pour passer `MULTISTORE` ON) : -1. Sur `nextgraph.net`, vérifier que `doc_create(session, "Graph", "data:graph", "store", undefined)` retourne un NURI utilisable comme `@graph` ORM (le test rust `sparql_regressions.rs:136-200` le suggère, à confirmer côté SDK JS). -2. Vérifier l'écriture/lecture du shim via `sparql_update`/`sparql_query` sur le NURI du private store (et le format de retour de `sparql_query` — `readBindings()` est tolérant mais à confirmer). -3. Vérifier l'abonnement `useShape(shape, { graphs: [...] })` sur plusieurs documents et sa réactivité quand la liste grandit. -4. Décider du seeding multi-document (le dev auto-seed est neutralisé en `MULTISTORE`). +> **Piège vérifié sur le prototype** : un `process.env.FESTIPOD_*` lu au **top-level** plante dans le navigateur (« process is not defined ») car le bundler n'inline que `NODE_ENV` ; lire en `typeof process !== 'undefined' && process.env.X`. + +**Faits vérifiés sur broker** (prototype, `@data` contre `nextgraph.net`, 2026-06-16) — restent vrais : +1. ✅ `doc_create("Graph","data:graph","store",undefined)` → NURI utilisable comme `@graph` ORM (write+read via `useShape({graphs:[nuri]})`). On n'est **pas** limité au `private_store` comme scope (cf. [[rule_private-store-scope]]). +2. ✅ **Shim r/w** : 3 docs créés + `sparql_update`, rechargés via `sparql_query`. +3. ✅ **Fan-out par entité** : 2 comptes × 1 doc-événement → `useShape({graphs:[docA,docB]})` lit les deux ; un index liste les deux. + +**Restes à traiter** (à la réimplémentation) : +- **Découverte réactive via l'index global** (un doc partagé réactif) — cf. [[decision_2026-06-16_discovery-model]] ; remplace le fan-out par-compte du prototype. +- **Réactivité de la création in-app** (best-effort : le nouveau doc rejoint le fan-out ; `@id` éventuellement en attente → envisager de frapper l'`@id` soi-même). +- **Synchronisation** (étape 2 du modèle 3-étapes) et **seeding** multi-document. ## Open Questions -- ~~**Login NextGraph invisible**~~ → **tranché** : login non programmable, présenté comme barrière technique d'accès ; session persistante. Voir [[decision_2026-06-15_shared-wallet-login-flow]]. -- **Création des documents au signup** : `doc_create` ×3 synchrone, ou paresseux au premier write par périmètre ? -- **Picker d'utilisateurs** : UX pour l'écran « Connexion » (saisie libre vs liste des comptes du `sharedWalletShim`) ? +- ~~**Login NextGraph invisible**~~ → **tranché** ([[decision_2026-06-15_shared-wallet-login-flow]]). +- ~~**Modèle de découverte**~~ → **tranché** : index global à écriture ouverte ([[decision_2026-06-16_discovery-model]]). +- ~~**Granularité documentaire**~~ → **tranché** : 1 doc par entité (événement/PdR) ; protected groupé. +- **Synchronisation** : prochain sujet — cible (réplication des docs souscrits via le broker) vs simulation dans le wallet partagé. +- **Picker d'utilisateurs** : UX de l'écran « Connexion » (saisie libre vs liste des comptes du `sharedWalletShim`). ## Possible Approaches diff --git a/.project/concepts/nextgraph-platform/decision_2026-06-16_discovery-model.md b/.project/concepts/nextgraph-platform/decision_2026-06-16_discovery-model.md new file mode 100644 index 0000000..a955a9f --- /dev/null +++ b/.project/concepts/nextgraph-platform/decision_2026-06-16_discovery-model.md @@ -0,0 +1,58 @@ +--- +type: decision +summary: Modèle de découverte des événements — index GLOBAL unique, alimenté via SON INBOX (le créateur y dépose une référence ; l'index est un document possédé, lisible par tous, matérialisé depuis son inbox). Découverte primaire ; relationnel secondaire (participations des connexions). Architecture en 3 étapes : découverte (index) → synchronisation (réplication des docs souscrits) → requête (SPARQL/ORM, LOCAL uniquement). Pas de Group store (index = doc possédé + inbox native) → cohérent avec la matrice. Inbox + watcher de matérialisation réutilisés (même mécanisme que l'inscription au PdR) ; point de dédup/modération naturel. +last_updated: 2026-06-16 +--- + +# Décision 2026-06-16 — Modèle de découverte des événements + +Comment un utilisateur **découvre** les événements (qu'il n'a pas créés). En P2P local-first, pas de registre global natif ; la matrice ([[brief_2026-05-18_authorization-matrix]]) repoussait la question. Cette décision la tranche et **guide l'implémentation** (cible et stopgap). + +## Accès ≠ découverte + +- **Accès** : ai-je le droit de lire ce document si je le tiens ? PdR/événement = **public universel** (lisible par tous, avec le NURI). +- **Découverte** : comment j'apprends qu'il existe, pour le lire ? ← l'objet de cette décision. + +## Décision + +1. **Index global unique des événements**, **alimenté via son inbox**. Le créateur **ne modifie pas l'index directement** : il **dépose une référence de son événement dans l'inbox de l'index**. L'index est un **document possédé** (lecture publique), **matérialisé depuis son inbox** (un watcher ingère les dépôts → ajoute les entrées). Découpage en **index communautaires** = plus tard. +2. **Découverte primaire = cet index global.** +3. **Relationnel = axe secondaire**, en surimpression : (a) page d'un ami → ses participations (événements passés / à venir) ; (b) sur la liste globale, marquer si une de mes connexions participe. Repose sur les **participations** (périmètre *protected*, visibles des connexions) — **aucune brique nouvelle**. + +## Architecture en 3 étapes (cadre directeur) + +`découverte → synchronisation → requête` + +1. **Découverte** : l'**index** donne les NURIs des documents-événements. +2. **Synchronisation** : s'abonner à ces documents → ils se **répliquent en local** (verifier : `self.repos` + dataset oxigraph). +3. **Requête** : interroger ce qui est **désormais local** (tri par date, limite, réactivité). **SPARQL/ORM ne portent que sur le local** (`resolve_target_for_sparql` cherche dans `self.repos` ; on ne requête pas ce qui n'est pas chargé). + +**Corollaire** : une requête réactive **ne remplace pas l'index** — elle s'exécute à l'étape 3, sur l'union locale que 1-2 ont constituée. On ne synchronise pas ce qu'on n'a pas découvert. + +État de la couche requête : l'**ORM (`useShape`) est réactif mais scopé par graphes, sans `ORDER BY`/`LIMIT`** (tri/limite en JS). Une **souscription SPARQL réactive** (`SELECT … ORDER BY … LIMIT n` auto-réévaluée) serait l'idéal de l'étape 3 — **à vérifier dans le SDK** (non confirmée). Si absente : ORM + tri JS. + +## Granularité documentaire (rappel, cf. discussion) + +Chaque **événement / PdR = son propre document** (adressable, futur inbox du PdR). L'**index global liste des références** (NURIs) vers ces documents — pas une copie dénormalisée (la dénormalisation « résumé dans l'index » est une optimisation d'échelle ultérieure). + +## Conséquences + +- **Pas de Group store** (correction du 2026-06-17). L'index n'est **pas** à écriture ouverte : c'est un **document possédé** (lecture publique) **+ inbox native** (primitive présente sur tout document). Personne n'écrit l'index sauf son propriétaire (via la matérialisation des dépôts d'inbox). Donc on **reste dans le modèle « 3 stores + Dialog + inboxes, sans Group store »** de [[brief_2026-05-18_authorization-matrix]] — la matrice **reste cohérente**, contrairement à ce qu'on avait d'abord cru. +- **Un seul mécanisme réutilisé** : l'**inbox + le watcher de matérialisation** servent **à la fois** la soumission d'un événement à l'index **et** l'inscription à un PdR. Même API (`inbox.post`), même traitement. +- **Point de dédup / modération naturel** : la matérialisation (inbox → index) est l'endroit où détecter les doublons / modérer **avant** insertion. Donne une prise concrète à [[brief_2026-06-15_event-deduplication]] ; logique de dédup non spécifiée ici. +- **Propriétaire de l'index — modèle cible à revoir (corrigé 2026-06-19).** Le « service dédié avec son propre wallet qui partage l'index en lecture libre » était **incorrect** : dans NextGraph, **apps et services sont mono-utilisateur** et il n'y a **pas de données globales** ([[knowledge_apps-and-services]]). Le seul chemin entrevu pour un **document global** est une **app singleton** liée à l'utilisateur-**développeur**, qui administre ce document global — mais c'est **non implémenté et incertain**, et **d'autres voies plus simples** sont possibles. **À creuser plus tard.** La mécanique de soumission tient quand même : un document d'index **alimenté via son inbox** (dépôt par le créateur + matérialisation par l'administrateur). En **stopgap** : l'index est un document du **wallet partagé** (les clients ne peuvent pas lire un autre wallet) ; un **curateur émulé** matérialise les dépôts ; les lecteurs s'abonnent. Cela **remplace** le fan-out-sur-tous-les-comptes (une dérive). + +## Alternatives écartées + +- **Index à écriture ouverte** (le créateur écrit l'index directement) : écartée — imposait un document collaboratif (Group store), bloqué SDK, et exposait l'index à la corruption. Remplacée par **dépôt dans l'inbox de l'index** + matérialisation par le propriétaire. +- **Découverte purement relationnelle** (connexions + `social_query`) : écartée comme modèle **primaire** (on veut une liste globale) ; **gardée comme axe secondaire**. +- **Pas d'index, requête réactive directe** : impossible — SPARQL local seulement (cf. étape 3). +- **Index par-utilisateur + fan-out sur tous les comptes** (état antérieur du stopgap) : remplacé par l'index global unique. + +## See Also + +- [[brief_2026-06-15_shared-wallet-shim]] — le stopgap (index global + inbox ; remplace le fan-out par-compte) +- [[brief_2026-05-18_authorization-matrix]] — **reste cohérente** : pas de Group store (index = doc possédé + inbox) +- [[brief_2026-05-21_fork-nextgraph-inbox]] — l'inbox (mécanisme réutilisé pour l'index) +- [[brief_2026-06-15_event-deduplication]] — doublons : la matérialisation inbox→index est le point de dédup +- [[knowledge_stores-permissions]] — inbox native sur tout document ; SPARQL/local diff --git a/.project/concepts/nextgraph-platform/decision_2026-06-17_eventually-library.md b/.project/concepts/nextgraph-platform/decision_2026-06-17_eventually-library.md new file mode 100644 index 0000000..9c831c0 --- /dev/null +++ b/.project/concepts/nextgraph-platform/decision_2026-06-17_eventually-library.md @@ -0,0 +1,67 @@ +--- +type: decision +summary: Tout le polyfill multi-user (wallet partagé, caps émulées, inbox émulée) est encapsulé dans une LIBRAIRIE GÉNÉRIQUE externe « ng-eventually-js » (repo hors Festipod, à côté de nextgraph-rs/orm-tests), zéro Festipod dedans. UN package pour l'instant : @ng-eventually/client (entrée principale SDK-IDENTIQUE ; bootstrap polyfill isolé sous /polyfill ; l'app n'en dépend que de lui). Le curateur d'index global (ex-@ng-eventually/service) est RETIRÉ/différé : son modèle « backend à données globales » est incorrect — NextGraph est mono-utilisateur sans données globales (cf. knowledge_apps-and-services) ; un index global passerait par une app singleton (incertain, différé, à creuser). Migration = alias de build retiré + le client redevient le vrai SDK. Festipod ne dépend que de @ng-eventually/client. +last_updated: 2026-06-17 +--- + +# Décision 2026-06-17 — Librairie « ng-eventually-js » (polyfill encapsulé) + +Tout le polyfill qui compense l'immaturité de NextGraph (pas de lecture cross-wallet, pas de capabilities ni d'inbox exposées au SDK, pas de Group store) est **sorti de l'app Festipod** et encapsulé dans une **librairie générique externe**. But : l'app ne voit **aucune** de cette complexité, et **migrer = remplacer la dépendance par le vrai SDK**. + +## Principe directeur + +1. **Forme client = identique au SDK.** Ce que le code applicatif appelle a **exactement** les signatures de `@ng-org/web` / `@ng-org/orm`. Mécanisme : un **Proxy** qui forwarde tout vers le vrai SDK et **n'override que le nécessaire** ; l'ORM (`useShape`/set réactif) est enveloppé. Migration = **alias de build** retiré (l'app importe `@ng-org/*`, résolus vers le wrapper pendant le polyfill) → le code applicatif ne mentionne jamais le wrapper. +2. **Compensation « à côté », jamais dans le métier.** Le code applicatif est écrit *comme si* l'infra cible existait ; la compensation vit dans la librairie. +3. **Générique, zéro Festipod.** La lib ne connaît que des mécanismes et les scopes NextGraph natifs. Le domaine (shapes, actes d'attribution de droits, collections concrètes) est **fourni par le consommateur**. + +## Décision + +### Repo & packaging +- **Repo** : `/home/sylvain/projects/nextgraph/ng-eventually-js` — **hors du repo Festipod** (sibling de `nextgraph-rs`, `orm-tests`, `expense-tracker`), pour éviter toute confusion. +- **Un seul package pour l'instant** (préfixe commun `@ng-eventually` réservé) : + - **`@ng-eventually/client`** — le wrapper **SDK-identique** + les polyfills qui, en cible, sont assurés **par le broker/verifier** (donc *retirés* à la migration) : login du wallet partagé, **enforcement des capabilities** (filtre de lecture + garde d'écriture), méthodes **anticipées** (caps, inbox `post`). **L'app Festipod ne dépend QUE de ce package.** Entrée principale = surface **SDK-identique** ; le bootstrap polyfill (le seul non-SDK) est isolé sous `@ng-eventually/client/polyfill`. + - **Curateur d'index — retiré / différé (2026-06-21).** Le package `@ng-eventually/service` a été **supprimé du scaffold** : son modèle (« backend à données globales ») était **incorrect** — NextGraph est **mono-utilisateur sans données globales** ([[knowledge_apps-and-services]]) — et le mécanisme cible d'index global (**app singleton** ? voie plus simple ?) est **incertain et différé**. Le curateur (qui ne doit **jamais** être chargé côté client) sera réintroduit comme **package séparé** quand le mécanisme sera tranché. + +### Comment les mécanismes tranchés s'y logent +- **Identité / login** : le client fixe l'utilisateur courant (username en polyfill ; wallet en cible — [[decision_2026-06-15_shared-wallet-login-flow]]). +- **Droits d'accès** : **capabilities émulées** comme données (grants attachés aux documents), enforcées **génériquement** par le client. L'app **attache les grants** via des opérations de cap anticipées (créer public, accorder à une connexion…) — **comme en cible**. Aucune politique n'est injectée ; seuls les shapes et les *actes* d'attribution viennent du consommateur. +- **Inbox** : `inbox.post(...)` (signature anticipée) côté client ; **matérialisation** par un **curateur** (package séparé, **différé**). Mécanisme réutilisé pour l'inscription PdR **et** la soumission à l'index. +- **Découverte** : index **alimenté via son inbox** ([[decision_2026-06-16_discovery-model]]). Le client **dépose** (inbox) + **lit** (abonnement) ; un **curateur** matérialise. Le **propriétaire cible** de l'index reste à décider (app singleton ?, incertain — [[knowledge_apps-and-services]]). +- **Synchronisation** : `s'abonner à un document` (natif). En polyfill, wallet partagé ⇒ sync multi-device native entre sessions. + +### Tests +- Les tests du **polyfill contre le vrai broker** vivent **dans la lib** (sa propre suite). Festipod teste ses features contre l'**API propre de la lib, mockée** (rapide, sans broker). + +## Conséquences + +- **Festipod ne dépend que de `@ng-eventually/client`** ; la complexité du polyfill est invisible côté app ; rien de Festipod dans la lib. +- **Migration** : retirer l'alias de build + l'appel de bootstrap → le client redevient le vrai SDK ; **traduire les grants émulés en vraies caps** (étape de données). Le **mécanisme cible de l'index global** reste à décider (app singleton ?, [[knowledge_apps-and-services]]) — ce n'est **pas** un backend. Le code applicatif ne bouge pas. +- Le [[brief_2026-06-15_shared-wallet-shim]] décrit désormais **comment Festipod consomme `ng-eventually`** (les mécanismes y sont *réalisés par la lib*), plus une implémentation interne à l'app. + +## Statut d'intégration (2026-06-22) + +Premier branchement réalisé, **en passthrough** (la lib forwarde tout vers le vrai SDK ; mécanismes du polyfill encore stubés) : + +- `@ng-eventually/client` ajouté en **dépendance locale** de Festipod (`file:../../nextgraph/ng-eventually-js/packages/client`). +- **`useShape`** importé depuis `@ng-eventually/client` (`useShapeWithDefaults`, `harness-ng`) ; **vrai SDK injecté** via `configure({ ng, useShape })` dans `ngSession` (`@ng-eventually/client/polyfill`). +- Types `NgLike`/`UseShapeLike` de la lib **assouplis** pour accepter le vrai SDK. +- **Validé** : build Festipod · `@ui` 4/4 · **`@data` 8/8 contre le broker** · lib (typecheck + 4 tests). Comportement identique (passthrough) → la plomberie de remplacement est prouvée. + +Reste à implémenter dans la lib (les stubs `TODO`) : filtre de lecture sur l'ORM réactif, garde d'écriture, `inbox.post`, login wallet partagé. + +## Open Questions + +- **Curateur d'index / index global** : package `@ng-eventually/service` **retiré pour l'instant** (2026-06-21) — modèle « backend » incorrect ([[knowledge_apps-and-services]]). À **réintroduire** (et nommer : curateur/admin) quand le **mécanisme cible d'index global** sera tranché (app singleton ? voie plus simple ?) — incertain, **à creuser plus tard**. +- **Signatures anticipées** (caps, inbox) : à ajuster si l'API officielle NextGraph diffère (point unique dans la lib). +- **Scope npm** `@ng-eventually` vs préfixe non-scopé `ng-eventually-*` (à confirmer) ; publication éventuelle plus tard. +- **Exécution du curateur** (quand réintroduit) : processus dédié (Node, API `nextgraph`) vs watcher idempotent — à trancher à l'implémentation. +- **Enveloppe de l'ORM réactif** (filtrer un `DeepSignalSet` vivant, garder les écritures) = le morceau technique le plus délicat. + +## See Also + +- [[brief_2026-06-15_shared-wallet-shim]] — le polyfill Festipod, réalisé par cette lib +- [[decision_2026-06-16_discovery-model]] — index alimenté via son inbox (propriétaire cible à revoir) +- [[knowledge_apps-and-services]] — apps/services mono-utilisateur, pas de données globales (corrige le modèle « service ») +- [[decision_2026-06-15_shared-wallet-login-flow]] — utilisateur courant / login +- [[knowledge_integration-model]] — `@ng-org/web` est déjà un Proxy (d'où le wrapper) +- [[knowledge_stores-permissions]] — caps / inbox non exposées au SDK (d'où l'émulation) diff --git a/.project/concepts/nextgraph-platform/knowledge_apps-and-services.md b/.project/concepts/nextgraph-platform/knowledge_apps-and-services.md new file mode 100644 index 0000000..bd79923 --- /dev/null +++ b/.project/concepts/nextgraph-platform/knowledge_apps-and-services.md @@ -0,0 +1,44 @@ +--- +type: knowledge +summary: Apps ET services NextGraph sont mono-utilisateur — ils ne voient que ce que l'utilisateur leur met à disposition, PAS de données globales. Toute app/service a un document de settings local. Une app non-singleton est instanciée plusieurs fois (ex. 1 instance par fichier ouvert). Une app SINGLETON est mono-utilisateur mais liée à un utilisateur précis (le développeur) et peut détenir un document global administré par lui → seul chemin entrevu pour un index global, mais NON implémenté et incertain. +--- + +# Apps et services NextGraph : mono-utilisateur, pas de données globales + +Modèle d'exécution des applications et services dans NextGraph (système externe). +Important parce qu'il **invalide** l'idée d'un « service avec son propre wallet +qui partagerait des données globales ». + +## Règles + +- **Apps ET services sont mono-utilisateur.** Ils ne voient que **ce que + l'utilisateur leur met à disposition**. Il n'y a **pas de données globales** + nativement, ni de service central qui détiendrait des données partagées. +- **Document de settings local.** Toute app — même singleton — et tout service + dispose d'un **document de settings**, qui permet à l'utilisateur de la + paramétrer. +- **Apps multi-instances.** Une app **non-singleton** peut être **instanciée + plusieurs fois** par l'utilisateur. Exemple : un traitement de texte est + instancié autant de fois qu'il y a de fichiers ouverts avec lui. +- **Apps singleton.** Aussi **mono-utilisateur**, mais **liées à un utilisateur + particulier (le développeur)**. Une app singleton **peut détenir un document + global**, **administré par cet utilisateur**. + +## Conséquence : le « document global » (ex. index) + +- Le seul chemin entrevu pour un **document global** (un index global de + découverte, par exemple) est l'**app singleton** : le document global est + administré par l'utilisateur-développeur lié à cette app. +- **Mais : non implémenté aujourd'hui, et le choix n'est pas garanti.** D'autres + voies plus simples sont possibles. **À creuser plus tard.** +- **Ce qui était incorrect** : un « service dédié avec son propre wallet qui + partage l'index en lecture libre » — ça n'existe pas dans le modèle NextGraph + (un service est mono-utilisateur, sans données globales). Voir la correction + dans [[decision_2026-06-16_discovery-model]]. + +## See Also + +- [[decision_2026-06-16_discovery-model]] — l'index global : propriétaire cible à revoir (app singleton, incertain) +- [[decision_2026-06-17_eventually-library]] — le package `@ng-eventually/service` (fondé sur ce modèle incorrect) a été **retiré/différé** +- [[knowledge_stores-permissions]] — stores, caps, inbox +- [[knowledge_integration-model]] — modèle d'intégration iframe / verifier