From db9eb1cf4760c84f8b5d479ca084372df9d92f70 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Fri, 3 Jul 2026 23:23:23 +0200 Subject: [PATCH] doctrine: Festipod treats @ng-eventually/client as a finished NextGraph SDK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enforce the project boundary: Festipod is written as if NextGraph were a mature, finished SDK; @ng-eventually/client IS that SDK. NO current-NextGraph-state, simulation, polyfill, shim, mono-store, store-id or broker-internal knowledge remains in this repo — it now lives in the @ng-eventually/client repo. - Dissolved the `nextgraph-platform` concept entirely (12 leaves — all current-state/simulation, now in the lib's docs/). Rescued the genuine domain parts into functional-domain/knowledge_data-scopes-and-discovery.md (which entity → which scope; product-level discovery/notification intent), framed as SDK usage with no mechanism. - data-layer re-anchored to "how Festipod persists via the SDK": stripped mono-store/private_store_id/RepoNotFound/DataCloneError/FESTIPOD_MULTISTORE. Deleted the current-SDK compensation leaves (private-store-scope, multistore, the 2026-03-17 ADRs, conditional-ng-init). Kept/reworded the domain + app leaves; caveat_participation-deletion reduced to the domain contract. - app-security reworded (isolation delegated to the SDK; app trusts it). - AGENTS.md: dropped the nextgraph-platform row, reworded data-layer/ functional-domain/app-security, added the "Frontière SDK NextGraph" note. - Fixed dangling [[links]]; concept lint clean (43 leaves). Co-Authored-By: Claude Opus 4.8 (1M context) --- .project/concepts/app-security/_overview.md | 20 +- .../brief_2026-05-18_authorization-matrix.md | 55 ++---- .../app-security/knowledge_authentication.md | 13 +- .../app-security/knowledge_trust-model.md | 21 +- .../knowledge_data-layer-broker.md | 4 +- .../knowledge_multibrowser-harness.md | 9 +- .project/concepts/data-layer/_overview.md | 35 ++-- .../caveat_event-fields-not-persisted.md | 4 +- .../caveat_multistore-is-multi-document.md | 58 ------ .../caveat_participation-deletion.md | 37 +--- ...13_conditional-ng-init-broker-detection.md | 35 ---- ...ion_2026-03-17_private-store-nuri-scope.md | 41 ---- ...026-03-17_sparql-delete-for-orm-objects.md | 51 ----- .../data-layer/knowledge_data-modes.md | 13 +- .../concepts/data-layer/knowledge_entities.md | 14 +- .../data-layer/knowledge_nextgraph-stack.md | 28 +-- .../data-layer/knowledge_seed-data.md | 2 +- .../data-layer/rule_conditional-ng-init.md | 14 -- .../data-layer/rule_private-store-scope.md | 34 ---- .../concepts/functional-domain/_overview.md | 11 +- .../knowledge_data-scopes-and-discovery.md | 44 +++++ .../functional-domain/knowledge_roadmap.md | 4 +- .../concepts/nextgraph-platform/_overview.md | 36 ---- .../brief_2026-05-17_multi-store-refactor.md | 84 -------- .../brief_2026-05-21_fork-nextgraph-inbox.md | 98 ---------- .../brief_2026-06-15_shared-wallet-shim.md | 185 ------------------ ...ion_2026-06-15_shared-wallet-login-flow.md | 51 ----- .../decision_2026-06-16_discovery-model.md | 60 ------ ...ision_2026-06-17_assisted-wallet-import.md | 53 ----- .../decision_2026-06-17_eventually-library.md | 112 ----------- .../knowledge_apps-and-services.md | 44 ----- .../knowledge_broker-import-constraint.md | 69 ------- .../knowledge_integration-model.md | 51 ----- .../knowledge_stores-permissions.md | 66 ------- .../tech-stack/knowledge_deployment.md | 2 +- .../knowledge_stack-and-commands.md | 2 +- AGENTS.md | 11 +- 37 files changed, 162 insertions(+), 1309 deletions(-) delete mode 100644 .project/concepts/data-layer/caveat_multistore-is-multi-document.md delete mode 100644 .project/concepts/data-layer/decision_2026-03-13_conditional-ng-init-broker-detection.md delete mode 100644 .project/concepts/data-layer/decision_2026-03-17_private-store-nuri-scope.md delete mode 100644 .project/concepts/data-layer/decision_2026-03-17_sparql-delete-for-orm-objects.md delete mode 100644 .project/concepts/data-layer/rule_conditional-ng-init.md delete mode 100644 .project/concepts/data-layer/rule_private-store-scope.md create mode 100644 .project/concepts/functional-domain/knowledge_data-scopes-and-discovery.md delete mode 100644 .project/concepts/nextgraph-platform/_overview.md delete mode 100644 .project/concepts/nextgraph-platform/brief_2026-05-17_multi-store-refactor.md delete mode 100644 .project/concepts/nextgraph-platform/brief_2026-05-21_fork-nextgraph-inbox.md delete mode 100644 .project/concepts/nextgraph-platform/brief_2026-06-15_shared-wallet-shim.md delete mode 100644 .project/concepts/nextgraph-platform/decision_2026-06-15_shared-wallet-login-flow.md delete mode 100644 .project/concepts/nextgraph-platform/decision_2026-06-16_discovery-model.md delete mode 100644 .project/concepts/nextgraph-platform/decision_2026-06-17_assisted-wallet-import.md delete mode 100644 .project/concepts/nextgraph-platform/decision_2026-06-17_eventually-library.md delete mode 100644 .project/concepts/nextgraph-platform/knowledge_apps-and-services.md delete mode 100644 .project/concepts/nextgraph-platform/knowledge_broker-import-constraint.md delete mode 100644 .project/concepts/nextgraph-platform/knowledge_integration-model.md delete mode 100644 .project/concepts/nextgraph-platform/knowledge_stores-permissions.md diff --git a/.project/concepts/app-security/_overview.md b/.project/concepts/app-security/_overview.md index dbd33d3..c8a58aa 100644 --- a/.project/concepts/app-security/_overview.md +++ b/.project/concepts/app-security/_overview.md @@ -1,23 +1,21 @@ --- type: _overview -summary: Sécurité & confidentialité de Festipod — posture ACTUELLE (mono-store, confiance broker, aucun contrôle d'accès côté app) et modèle d'autorisations CIBLE (incubation) ; authentification par wallet NextGraph +summary: Sécurité & confidentialité de Festipod — l'isolation entre périmètres est assurée par le SDK de données, l'app lui fait confiance et ne porte aucune logique d'autorisation dans les écrans ; authentification par wallet ; matrice d'autorisations cible en incubation triggers: - keywords: [sécurité, security, confidentialité, privacy, accès, "access control", contrôle d'accès, trust, confiance, authz, autorisation, permission, wallet, auth, authentification, anonyme, identité, login] + keywords: [sécurité, security, confidentialité, privacy, accès, "access control", contrôle d'accès, trust, confiance, authz, autorisation, permission, wallet, auth, authentification, anonyme, identité, login, scope, isolation] paths: ["src/modules/auth/**", "src/shared/context/NextGraphContext.tsx"] --- # App security -Le modèle de **sécurité, confidentialité et autorisations** de Festipod. Le pilier se lit en deux temps : +Le modèle de **sécurité, confidentialité et autorisations** de Festipod. -- **Actuel** — ce que le code applique aujourd'hui : voir [[knowledge_trust-model]]. Résumé brutal : **aucun contrôle d'accès côté app**, l'app affiche le `private_store` de l'utilisateur connecté et fait confiance au broker. Mono-user de fait. -- **Cible** — le modèle d'autorisations dérivé (qui peut faire quoi, données personnelles = réseau, anonymat via inbox) : [[brief_2026-05-18_authorization-matrix]]. **Incubation, non implémenté.** Il graduera en `rule_`/`behavior_` quand le multi-user atterrira (chantiers data dans le concept `nextgraph-platform`). - -L'écart entre les deux est volontaire : tant que l'app est mono-store (cf. concept `data-layer`), il n'y a rien à autoriser côté app. +- **Modèle appliqué** — l'**isolation entre périmètres** (public / protected / private) est **assurée par le SDK de données** (`@ng-eventually/client`), qui n'expose à chaque utilisateur que ce à quoi il a droit. L'app **fait confiance** au SDK : aucun écran ne porte de logique d'autorisation. Voir [[knowledge_trust-model]]. +- **Matrice d'autorisations cible** — le détail *qui peut faire quoi* par acteur × verbe (données personnelles = réseau, anonymat via inbox de notification) : [[brief_2026-05-18_authorization-matrix]]. **Incubation.** Graduera en `rule_`/`behavior_` à mesure que le produit se cale. ## Liens -- [[knowledge_trust-model]] — posture de sécurité actuelle (mono-store, confiance broker, pas d'enforcement app) -- [[knowledge_authentication]] — auth par wallet NextGraph, tous authentifiés, pas d'accès anonyme -- [[brief_2026-05-18_authorization-matrix]] — modèle d'autorisations cible (incubation) -- `nextgraph-platform` — les primitives (stores, capabilities, inbox) et les chantiers data qui porteront la cible +- [[knowledge_trust-model]] — l'app délègue l'isolation au SDK, pas de contrôle d'accès dans les écrans +- [[knowledge_authentication]] — auth par wallet, tous authentifiés, pas d'accès anonyme +- [[brief_2026-05-18_authorization-matrix]] — matrice d'autorisations cible (incubation) +- Concept `functional-domain` → [[knowledge_data-scopes-and-discovery]] — quel scope pour quelle entité (fait produit) 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 12fb9d2..b0f084b 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 @@ -1,19 +1,16 @@ --- type: brief -summary: Matrice d'autorisations par type de donnée (PdR, inscription, événement, profil, connexion) ; dérive que 3 stores natifs par utilisateur + Dialog stores suffisent, aucun Group store sur le périmètre validé ; questions ouvertes sur modèle d'écriture événement et identité de l'hôte +summary: Matrice d'autorisations cible par type de donnée (PdR, inscription, événement, profil, connexion) exprimée en scopes public/protected/private + dialog ; décisions cadre acquises (tous authentifiés, PdR publics, données personnelles = réseau, notification par inbox identifiée-ou-anonyme) ; questions ouvertes sur modèle d'écriture événement et identité de l'hôte last_updated: 2026-05-18 --- # Matrice d'autorisations et inventaire des requêtes -**Status:** Incubating — analyse en cours -**Last updated:** 2026-05-18 +**Status:** Incubating — modèle cible, non figé en règles. ## Context -Préalable au refactor multi-store ([[brief_2026-05-17_multi-store-refactor]]) et à toute évolution multi-user. La structure de stores NextGraph cible doit être *dérivée* de : (1) une matrice d'autorisations ; (2) un inventaire des requêtes par écran ; (3) les partitions naturelles qui en découlent (données partageant autorisations *et* schéma d'accès). - -C'est aussi le **modèle de confidentialité/sécurité** de Festipod (pilier sécurité), non encore implémenté. +Le modèle **cible** de qui-peut-quoi. La confidentialité de Festipod se dérive de : (1) une matrice d'autorisations par acteur × verbe ; (2) l'inventaire des requêtes par écran ; (3) les **périmètres** (scopes) qui en découlent — données partageant à la fois autorisation *et* schéma d'accès. Le placement concret entité → scope est un fait produit : concept `functional-domain` → [[knowledge_data-scopes-and-discovery]]. L'isolation est **assurée par le SDK de données** ([[knowledge_trust-model]]). ## Cadre @@ -33,7 +30,7 @@ C'est aussi le **modèle de confidentialité/sécurité** de Festipod (pilier s - **Hôte = détenteur des droits d'écriture** sur un PdR (1 hôte, le créateur ; le fait d'être hôte est public). - **Informations personnelles = réservées au réseau.** Visibles seulement au titulaire et à ses connexions : participations, intégralité du profil, liste de connexions, et tout état déclaratif dont la divulgation serait une fuite. Statut « public » (PdR, événement) et « personnel » (profil, participations, connexions) coexistent dans le même utilisateur. - **Connexion bilatérale.** Existe après acceptation des deux côtés. Deux objets : `DemandeDeConnexion` (unilatérale, transitoire) et `Connexion` (bilatérale, persistante). -- **Notification d'inscription via l'inbox NextGraph du PdR.** L'acte « s'inscrire » est composite : (a) écriture d'un objet `Inscription` dans le `protected_store` de l'inscrit, (b) dépôt d'un lien (DID cap) dans l'**inbox** du document PdR. Identification du sender par résolution du DID contre le graphe de connexions de l'hôte : connexion → inscription complète visible ; sinon → lien opaque (« quelqu'un (DID…) s'est inscrit »). Anonymat partiel **natif aux capabilities** (cf. [[knowledge_stores-permissions]] §Inbox). +- **Notification d'inscription via l'inbox du PdR.** L'acte « s'inscrire » est composite : (a) écriture d'un objet `Inscription` dans le périmètre *protected* de l'inscrit, (b) dépôt d'un lien dans l'**inbox** du document PdR. L'expéditeur est **identifié si connexion de l'hôte, anonyme sinon** — propriété du modèle de données. - **Adhésion à une communauté / suivi : hors périmètre actuel.** ## Matrice par type de donnée @@ -63,7 +60,7 @@ Notes : pas de différenciation `C` (les connexions sont un filtre d'affichage U | modifier | ? **à trancher** (selon champs) | ✗ | ✗ | ✗ | ✗ | | supprimer | ✓ (se désinscrire ; retirer le lien de l'inbox si possible) | ✗ | cond : modération inbox seule (ne supprime pas l'objet) | ✗ | ✗ | -**Visibilité hôte : résolue** (identifiée si connecté, anonyme sinon — natif). **Questions ouvertes :** champs modifiables d'une inscription (booléen seul ou +commentaire/statut/accompagnants ?) ; **suppression côté inbox** — un déposant peut-il retirer son lien d'un doc qu'il ne contrôle pas ? (à vérifier au protocole). +**Visibilité hôte : résolue** (identifiée si connecté, anonyme sinon). **Questions ouvertes :** champs modifiables d'une inscription (booléen seul ou +commentaire/statut/accompagnants ?) ; **suppression côté inbox** — un déposant peut-il retirer son lien d'un doc qu'il ne contrôle pas ? ### Événement @@ -74,7 +71,7 @@ Notes : pas de différenciation `C` (les connexions sont un filtre d'affichage U | modifier | ? **à trancher** | ? **à trancher** | ? **à trancher** | | supprimer | ? **à trancher** | ✗ | ✗ | -**Questions ouvertes :** qui peut **modifier** un événement déclaré — déclarant seul (propriétaire) ? tout utilisateur (wiki) ? personne (immuable) ? Central pour la déduplication (cf. concept `functional-domain`, [[brief_2026-06-15_event-deduplication]] côté functional-domain). Qui peut **supprimer**, et que deviennent les PdR greffés (orphelins/cascade/marqué supprimé) ? +**Questions ouvertes :** qui peut **modifier** un événement déclaré — déclarant seul (propriétaire) ? tout utilisateur (wiki) ? personne (immuable) ? Central pour la déduplication (concept `functional-domain`, [[brief_2026-06-15_event-deduplication]]). Qui peut **supprimer**, et que deviennent les PdR greffés (orphelins/cascade/marqué supprimé) ? ### Profil utilisateur @@ -89,7 +86,7 @@ Notes : pas de différenciation `C` (les connexions sont un filtre d'affichage U | modifier | ✓ | ✗ | ✗ | | supprimer (compte) | ✓ | ✗ | ✗ | -**Tension à résoudre :** un PdR est lisible par tous, mais son hôte ne devrait pas être identifiable par un lambda. Trois positions : (i) **pseudonyme par DID seul** (nom/avatar résolus seulement aux connexions) ; (ii) **identité dénormalisée dans l'offre** (l'hôte choisit une « carte de visite » par PdR, vivant dans l'objet PdR, profil fermé) ; (iii) **anonymat de l'hôte** (identité révélée seulement aux connexions). À trancher. Autres : composition champ-par-champ de chaque périmètre ; statut du `username` (public/réseau/supprimé ?). +**Tension à résoudre :** un PdR est lisible par tous, mais son hôte ne devrait pas être identifiable par un lambda. Trois positions : (i) **pseudonyme par identité seule** (nom/avatar résolus seulement aux connexions) ; (ii) **identité dénormalisée dans l'offre** (l'hôte choisit une « carte de visite » par PdR, vivant dans l'objet PdR, profil fermé) ; (iii) **anonymat de l'hôte** (identité révélée seulement aux connexions). À trancher. Autres : composition champ-par-champ de chaque périmètre ; statut du `username` (public/réseau/supprimé ?). ### Connexion (lien d'amitié) @@ -105,33 +102,18 @@ Bilatérale. `DemandeDeConnexion` (unilatérale, en attente) → `Connexion` (bi **Questions ouvertes :** granularité côté Bob (voit-il toute la liste d'Alice ou juste A↔B ? — conséquence du principe : toute la liste) ; découvrabilité « amis d'amis » (Alice voit-elle Bob↔Carole ? — non, sauf si Carole ∈ connexions(Alice)). -## Partitions naturelles dérivées +## Périmètres dérivés -Heuristique : même store si (a) même cellule d'autorisation en écriture *et* (b) accédées ensemble. À partir des seuls points validés, **trois périmètres** émergent — qui correspondent **presque parfaitement aux 3 stores natifs**. +Heuristique : même périmètre si (a) même cellule d'autorisation en écriture *et* (b) accédées ensemble. Trois **scopes** émergent, plus le cas bilatéral : -| Périmètre | Écriture | Lecture | Données validées | +| Périmètre | Écriture | Lecture | Données | |---|---|---|---| -| **Public** ↔ `public_store` | Alice seule | Tous | PdR hébergés par Alice ; événements déclarés *(sous réserve du modèle d'écriture)* | -| **Réseau** ↔ `protected_store` | Alice seule | Alice + connexions | Profil réseau ; participations ; index des connexions | -| **Privé** ↔ `private_store` | Alice seule | Alice seule | Profil privé (settings, email, préférences) | +| **public** | Alice seule | Tous | PdR hébergés par Alice ; événements déclarés *(sous réserve du modèle d'écriture)* | +| **protected** (réseau) | Alice seule | Alice + connexions | Profil réseau ; participations ; index des connexions | +| **private** | Alice seule | Alice seule | Profil privé (settings, email, préférences) | +| **dialog** (A↔B) | Alice et Bob | Alice et Bob | La `Connexion` bilatérale (+ matière à messagerie future) | -### Cas particulier : la Connexion bilatérale - -Donnée à *deux* écrivains → ne tient dans aucun store individuel. Primitive native : le **Dialog store**. Modèle : **une `Connexion` A↔B = un Dialog store** (contient l'objet + matière à messagerie future) ; l'**index « toutes les connexions d'Alice »** vit dans le `protected_store` d'Alice (liste les NURIs des Dialog stores). La `DemandeDeConnexion` : soit dans un Dialog store provisoire, soit dans le `public_store` du destinataire (à trancher selon le SDK). - -### Inbox du document PdR - -Le doc PdR (dans le `public_store` de l'hôte) a une **inbox** native : reçoit les dépôts d'inscription (liens DID cap), plus tard commentaires/signaux. **Pas un store séparé**, attribut du document. Pas d'impact sur les partitions. - -### Ce qui ne demande aucun Group store - -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. +La **`Connexion` bilatérale** a *deux* écrivains → périmètre **dialog** dédié à la paire ; l'**index « toutes les connexions d'Alice »** vit en *protected* (liste les références des connexions). L'**inbox du PdR** est un attribut du document public, pas un périmètre séparé. ## Inventaire des requêtes par écran @@ -139,7 +121,6 @@ Ce brief y propose une structure à 4 niveaux de Group stores. **Cette analyse d ## See Also -- [[brief_2026-05-17_multi-store-refactor]] — consommateur principal -- [[brief_2026-06-15_shared-wallet-shim]] — stopgap reprenant ces périmètres -- `README.md §Modèle fonctionnel` / concept `functional-domain` — source des acteurs -- Concept `data-layer` — état actuel mono-store +- Concept `functional-domain` → [[knowledge_data-scopes-and-discovery]] — placement entité → scope + découverte +- [[knowledge_trust-model]] — l'isolation est assurée par le SDK +- `README.md §Modèle fonctionnel` — source des acteurs diff --git a/.project/concepts/app-security/knowledge_authentication.md b/.project/concepts/app-security/knowledge_authentication.md index f762aaa..afb6ceb 100644 --- a/.project/concepts/app-security/knowledge_authentication.md +++ b/.project/concepts/app-security/knowledge_authentication.md @@ -1,20 +1,19 @@ --- type: knowledge -summary: Authentification = possession d'un wallet NextGraph ; tous les utilisateurs sont authentifiés (pas d'accès anonyme) ; l'auth passe par le redirect/iframe broker, et l'app n'auto-connecte que dans l'iframe +summary: L'identité d'un utilisateur = son wallet NextGraph ; tous les utilisateurs sont authentifiés (pas d'accès anonyme) ; l'auth est déléguée au SDK, l'app n'a pas de comptes/mots de passe applicatifs --- # Authentification -**L'identité d'un utilisateur = son wallet NextGraph.** Il n'y a **pas d'accès anonyme** à l'app : tout utilisateur est authentifié (cf. concept `functional-domain`). Il n'y a pas de système de comptes/mots de passe applicatif — l'auth est déléguée à NextGraph. +**L'identité d'un utilisateur = son wallet NextGraph.** Il n'y a **pas d'accès anonyme** à l'app : tout utilisateur est authentifié (cf. concept `functional-domain`). Il n'y a **pas de système de comptes/mots de passe applicatif** — l'authentification est **déléguée au SDK de données** (`@ng-eventually/client`) : ouvrir sa session, c'est ouvrir son wallet. ## Flux -- `LoginScreen` (`src/modules/auth/screens/`) déclenche la connexion via `useNextGraph()` (ne consomme pas `useFestipodData`). -- Le flux standard `@ng-org/web` est un **redirect vers le broker** (`nextgraph.net/redir/`) qui recharge l'app dans une **iframe** après authentification (détail dans concept `nextgraph-platform`, [[knowledge_integration-model]] côté nextgraph-platform). -- **L'app n'auto-connecte que dans l'iframe broker** (`window.self !== window.top`) — sinon `initNgWeb()` redirigerait toute la page. Cette règle vit côté data-layer ([[rule_conditional-ng-init]]) car elle concerne le cycle `NextGraphContext`, mais elle a une conséquence sécurité directe : **hors iframe, aucune session n'est ouverte sans action explicite** de l'utilisateur. +- L'écran d'auth (`src/modules/auth/`) déclenche la connexion via `useNextGraph()` (ne consomme pas `useFestipodData`). +- Une fois la session ouverte, l'utilisateur courant et son accès aux stores par scope sont fournis par `NextGraphContext`. ## Le wallet de test -Les tests `@data`/`@e2e` créent/ouvrent un wallet réel (`festipod-tests`/`festipod-tests`, profil persistant) — voir concept `bdd-testing`. Ce sont des **credentials de test en clair**, sans enjeu de sécurité, dédiés au staging (cohérent avec la posture « utilisateurs amicaux » du stopgap, concept `nextgraph-platform`). +Les tests `@data`/`@e2e` ouvrent un wallet réel (`festipod-tests`, profil persistant) — voir concept `bdd-testing`. Ce sont des **credentials de test en clair**, sans enjeu de sécurité, dédiés au staging. -> Le modèle d'autorisations qui s'appuiera sur cette identité (connexions bilatérales, données personnelles = réseau, anonymat hôte) est en incubation : [[brief_2026-05-18_authorization-matrix]]. +> Le modèle d'autorisations qui s'appuiera sur cette identité (connexions bilatérales, données personnelles = réseau, anonymat de l'hôte) est en incubation : [[brief_2026-05-18_authorization-matrix]]. diff --git a/.project/concepts/app-security/knowledge_trust-model.md b/.project/concepts/app-security/knowledge_trust-model.md index a339cc5..2a5706b 100644 --- a/.project/concepts/app-security/knowledge_trust-model.md +++ b/.project/concepts/app-security/knowledge_trust-model.md @@ -1,21 +1,20 @@ --- type: knowledge -summary: Posture de sécurité actuelle — aucun contrôle d'accès côté app, l'app lit/affiche le private_store de l'utilisateur connecté et fait confiance au broker NextGraph pour ne retourner que des données autorisées ; mono-user de fait -last_checked: 2026-06-15 +summary: L'isolation entre périmètres (public/protected/private) est assurée par le SDK de données ; l'app lui fait confiance et n'affiche que ce qu'il retourne — aucun contrôle d'accès dans les écrans, toute la confidentialité repose sur le SDK +last_checked: 2026-07-03 --- -# Modèle de confiance actuel +# Modèle de confiance -**Posture observée dans `src/shared/context/FestipodDataContext.tsx` (`useNgData`) :** l'app lit tout ce que les subscriptions ORM retournent depuis le `private_store` de l'utilisateur connecté et l'affiche **sans aucun filtre d'autorisation côté app**. +**Posture :** l'app lit les données via les subscriptions ORM du SDK `@ng-eventually/client` et les affiche **sans logique d'autorisation côté app** (`src/shared/context/FestipodDataContext.tsx`, `useNgData`). -Conséquences (à connaître avant de raisonner sécurité) : +Principes : -1. **Aucun contrôle d'accès applicatif.** Pas de vérification « l'utilisateur a-t-il le droit de voir cette donnée ». L'app suppose que **le broker/NextGraph ne retourne que ce que l'utilisateur peut voir**. Toute la confidentialité repose sur cette confiance dans la couche NextGraph, pas sur du code Festipod. -2. **Mono-store, donc mono-user de fait.** Tout (events, profils, participations) vit dans le `private_store` de l'utilisateur connecté (cf. concept `data-layer`, [[decision_2026-03-17_private-store-nuri-scope]] côté data-layer). Un autre utilisateur ne voit rien — par construction, le `private_store` n'est pas partageable. Il n'y a donc rien à « autoriser » : chacun ne voit que ses propres données. -3. **Pas de séparation de périmètres.** Le découpage public / réseau / privé du modèle cible ([[brief_2026-05-18_authorization-matrix]]) **n'existe pas encore** dans le code : aucun `protected_store`/`public_store` n'est utilisé pour le métier. +1. **L'isolation est déléguée au SDK.** Chaque entité vit dans le store de son **scope** (public / protected / private, cf. concept `functional-domain` → [[knowledge_data-scopes-and-discovery]]) ; le SDK **n'expose à l'utilisateur courant que ce à quoi il a droit**. L'app suppose que ce qu'elle reçoit est déjà autorisé — la confidentialité repose sur le SDK, pas sur du code Festipod. +2. **Les écrans ne portent aucune règle d'accès.** Pas de vérification « cet utilisateur a-t-il le droit de voir cette donnée » dans les composants ni dans le contexte de données. La séparation public / réseau / privé est une propriété du **placement par scope**, pas d'un filtre applicatif. -## Le piège pour la suite +## Le point de vigilance -Le jour où le multi-user arrive (lecture cross-wallet, voir les briefs de `nextgraph-platform`), cette **absence d'enforcement applicatif devient un risque** : si la séparation reste portée seulement par la crypto/capabilities NextGraph et que l'app continue d'afficher « tout ce qu'elle reçoit », une fuite de capability = une fuite de données. Le stopgap `shared-wallet-shim` (concept `nextgraph-platform`) prévoit d'ailleurs un **filtre d'isolation applicatif** explicite parce que, dans ce mode, un seul wallet rend tout physiquement lisible. +Parce que l'app **affiche tout ce qu'elle reçoit**, la confidentialité tient entièrement à ce que le SDK n'expose que le légitime. C'est un choix assumé (l'app reste mince), mais il implique de **ne jamais réintroduire côté écran une donnée que le scope n'aurait pas dû laisser passer**. -> À vérifier si on doute : `useNgData` dans `FestipodDataContext.tsx` ne contient aucune branche de filtrage par identité ; les seuls IDs manipulés sont ceux du wallet courant. +> À vérifier si on doute : `useNgData` dans `FestipodDataContext.tsx` ne contient aucune branche de filtrage par identité — c'est intentionnel, l'isolation vient d'en dessous. diff --git a/.project/concepts/bdd-testing/knowledge_data-layer-broker.md b/.project/concepts/bdd-testing/knowledge_data-layer-broker.md index 69728e3..d7fd0c2 100644 --- a/.project/concepts/bdd-testing/knowledge_data-layer-broker.md +++ b/.project/concepts/bdd-testing/knowledge_data-layer-broker.md @@ -22,7 +22,7 @@ Cucumber → Playwright (Chromium, profil persistant) ## Cycle de vie du wallet (automatisé, CI-ready) -- **Premier run** : pas de marker `.wallet-ready` → Chromium headless crée le wallet (`nextgraph.eu` → Create Wallet → ToS sur `account.nextgraph.eu` → username/password → submit), **puis se logge** — ce login déclenche le bootstrap du verifier depuis le broker distant (peuple `self.repos`, sauvé en localStorage). **Sans ce login initial, toutes les écritures échoueraient en `RepoNotFound`.** Marker écrit. +- **Premier run** : pas de marker `.wallet-ready` → Chromium headless crée le wallet (`nextgraph.eu` → Create Wallet → ToS sur `account.nextgraph.eu` → username/password → submit), **puis se logge** — ce login initial est requis pour amorcer la session (sauvé en localStorage) ; sans lui, les écritures ne passeraient pas. Marker écrit. - **Runs suivants** : marker trouvé → login automatisé (click Login → wallet → password → submit) → harness en iframe → `window.__testData.ready`. - Credentials wallet : `festipod-tests` / `festipod-tests`. @@ -33,5 +33,5 @@ Cucumber → Playwright (Chromium, profil persistant) - **Flags Chromium** (`--disable-web-security`, `--allow-insecure-localhost`, désactivation de Private Network Access) : nécessaires car le broker public charge un harness `http://127.0.0.1` en iframe. - **Profil persistant** `.playwright-profile/` (gitignored, wallet en localStorage) — exige le vrai binaire Chrome, pas `chrome-headless-shell`. - **Serveur HTTP** lancé en `BeforeAll` (port auto), sert le HTML + `/harness.js` (fichiers séparés — le script inline casse à cause de caractères spéciaux du bundle). -- **Subscriptions ORM** : les shapes des entités partageables avec scope `did:ng:${session.protected_store_id}` (le **protected** store depuis T02.h — `harness-ng.tsx` utilise `protectedNuri` ; le private store n'est plus le scope des entités domaine, cf. concept `data-layer` [[rule_private-store-scope]]). +- **Subscriptions ORM** : les shapes des entités partageables sont souscrites sur le scope **protected** (`harness-ng.tsx` utilise `protectedNuri`), cohérent avec le placement des entités domaine côté app (concept `data-layer`). - **Bridge `window.__testData`** : `events`/`users`/`participations` (sets live), `currentUserId`, lookups (`getEvent`, `getEventByTitle`), mutations (`joinEvent`, `leaveEvent`, `updateEvent` — `joinEvent`/`leaveEvent` réels depuis T02.b/c : persistance Participation + inbox + Notification / DELETE-WHERE), requêtes (`isParticipating`, `getEventParticipants`). diff --git a/.project/concepts/bdd-testing/knowledge_multibrowser-harness.md b/.project/concepts/bdd-testing/knowledge_multibrowser-harness.md index 53d2323..0068703 100644 --- a/.project/concepts/bdd-testing/knowledge_multibrowser-harness.md +++ b/.project/concepts/bdd-testing/knowledge_multibrowser-harness.md @@ -6,7 +6,7 @@ last_checked: 2026-06-16 # Harness multi-navigateur (private-wallet vs shared-wallet) -Capacité du harness `@data`/`@e2e` à piloter **plusieurs navigateurs isolés** dans un même scénario, sous **deux axes orthogonaux**. Sert à tester le stopgap wallet partagé (cf. concept `nextgraph-platform` → `brief_2026-06-15_shared-wallet-shim`) **et** le modèle cible (chacun son wallet). +Capacité du harness `@data`/`@e2e` à piloter **plusieurs navigateurs isolés** dans un même scénario, sous **deux axes orthogonaux**. Permet de tester à la fois le modèle « chacun son wallet » (`@private-wallet`) et le modèle « wallet partagé entre navigateurs » (`@shared-wallet`). ## Les deux axes (orthogonaux) @@ -15,7 +15,7 @@ Capacité du harness `@data`/`@e2e` à piloter **plusieurs navigateurs isolés** | **Nombre de navigateurs** (machinerie) | 1..N contextes nommés isolés | `openBrowser(name, …)` + steps `… dans le navigateur "X"` | | **Modèle de wallet** | identité NG distincte vs partagée | **phrasing du step + tag** (voir ci-dessous) | -Ne **pas** confondre `@multibrowser` (plusieurs navigateurs) avec `@shared-wallet` (même wallet) : on fait du multibrowser **en private** (utile dès que NextGraph livrera la lecture cross-wallet — le modèle cible) **et en shared** (stopgap), et on compare les deux setups avec les **mêmes** steps de comportement. +Ne **pas** confondre `@multibrowser` (plusieurs navigateurs) avec `@shared-wallet` (même wallet) : on fait du multibrowser **en private** (chacun son wallet) **et en shared** (wallet partagé), et on compare les deux setups avec les **mêmes** steps de comportement. ## Modèle de wallet : phrasing + tags @@ -38,11 +38,11 @@ Ne **pas** confondre `@multibrowser` (plusieurs navigateurs) avec `@shared-walle ## Parcours humain — e2e du mécanisme produit (vert) -Scénario `@humain` : valide le flux RÉEL de distribution du wallet **de bout en bout, via la vraie app**, pas l'injection de test (cf. concept `nextgraph-platform` → `decision_2026-06-17_assisted-wallet-import`). Un navigateur vierge ouvre l'app staging → l'`AccessGateScreen` propose le **fichier** + le **mot de passe** → on télécharge le fichier **depuis l'écran**, on vérifie que le mot de passe affiché **égale** celui du wallet → import sur `nextgraph.eu` « Import a Wallet File » → retour → clic « Entrer » → app connectée (`ConnexionScreen`). +Scénario `@humain` : valide le flux RÉEL de distribution du wallet **de bout en bout, via la vraie app**, pas l'injection de test. Un navigateur vierge ouvre l'app staging → l'`AccessGateScreen` propose le **fichier** + le **mot de passe** → on télécharge le fichier **depuis l'écran**, on vérifie que le mot de passe affiché **égale** celui du wallet → import sur `nextgraph.eu` « Import a Wallet File » → retour → clic « Entrer » → app connectée (`ConnexionScreen`). - **Wallet e2e** : un fichier `.ngw` (`festipod-e2e-tests`, mot de passe = identifiant) placé **à la racine du worktree** ; `findE2eWalletFile()` le localise (`*.ngw`). Gitignoré → chaque environnement doit l'ajouter (sinon erreur claire). - `pool.ensureStagingApp()` (`hooks.ts`) — build **isolé** `bun run build.ts --outdir=dist-staging` (barrière d'accès **ON par défaut** ; mot de passe gravé + **fichier copié** en `/shared-wallet.ngw`, cf. `build.ts`), servi statiquement. Mémoïsé, lazy (seul `@humain` le paie). -- **Bypass de la barrière pour `@e2e`** : le harness fait `browserContext.addInitScript` sur le **contexte persistant** pour poser `globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ = true` (s'applique à l'iframe app avant ses scripts) → `@e2e` voit l'app directement, pas la barrière. Les contextes frais (`@humain`) n'y touchent pas → barrière ON (cf. `decision_2026-06-17`). L'ancien `LoginScreen` `/login` a été retiré. +- **Bypass de la barrière pour `@e2e`** : le harness fait `browserContext.addInitScript` sur le **contexte persistant** pour poser `globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ = true` (s'applique à l'iframe app avant ses scripts) → `@e2e` voit l'app directement, pas la barrière. Les contextes frais (`@humain`) n'y touchent pas → barrière ON. L'ancien `LoginScreen` `/login` a été retiré. - `pool.importWalletViaFile(page, filePath, password)` — `nextgraph.eu/#/wallet/login` → `setInputFiles('input[type=file]')` (attendre que la SPA rende, sinon `EncryptionError`) → champ password → unlock. - `pool.completeBrokerLogin(page, appUrl, walletPassword?)` — moitié « login broker » extraite de `setupBrokerPage`. **Attente robuste** : après le redirect (multi-hop), attend l'iframe app OU le lien « Click here to login with your wallet », puis déverrouille avec le mot de passe. La session broker n'étant **pas** persistée entre lancements, ce login wallet est requis à chaque run (warm-up + `@e2e` + `@humain`). @@ -68,4 +68,3 @@ Scénario `@humain` : valide le flux RÉEL de distribution du wallet **de bout e - [[knowledge_data-layer-broker]] — la couche `@data` mono-navigateur (profil persistant) que cette capability étend. - [[cookbook_add-scenario]] — convention `@wip`, pièges de steps. -- Concept `nextgraph-platform` → `brief_2026-06-15_shared-wallet-shim` — le stopgap wallet partagé que ce harness sert à tester. diff --git a/.project/concepts/data-layer/_overview.md b/.project/concepts/data-layer/_overview.md index 88fce37..688229f 100644 --- a/.project/concepts/data-layer/_overview.md +++ b/.project/concepts/data-layer/_overview.md @@ -1,35 +1,28 @@ --- type: _overview -summary: Couche données NextGraph telle qu'utilisée AUJOURD'HUI (mono-store) — stack ORM/SHEX, modes connected/demo, entités, seed, et 3 règles d'écriture critiques +summary: Comment Festipod persiste ses données via le SDK @ng-eventually/client — entités stockées comme documents par scope, stack ORM/SHEX, modes connected/demo, seed triggers: - keywords: [nextgraph, useShape, ORM, SHEX, shape, store, private_store, "@graph", NURI, sparql, sparql_update, seed, wallet, RepoNotFound, FestipodData, ngGraph, bootstrap, multistore, document, isolation] + keywords: [nextgraph, "@ng-eventually", useShape, ORM, SHEX, shape, scope, "@graph", NURI, sparql, seed, wallet, FestipodData, ngSession, ngGraph, bootstrap, document, entité] paths: ["src/shared/shapes/**", "src/shared/hooks/useShape*", "src/shared/context/NextGraphContext.tsx", "src/shared/context/FestipodDataContext.tsx", "src/shared/utils/ng*", "src/shared/data/seedData.ts"] --- # Data layer -Comment Festipod **persiste ses données aujourd'hui** via NextGraph (P2P, local-first, chiffré). État actuel : **mono-document** — par défaut tout atterrit dans **un seul document**, le repo racine du `private_store` partagé (`@graph = did:ng:${private_store_id}`). ⚠️ « mono-store » est un raccourci trompeur : l'axe qui compte est le **document (repo/`@graph`)**, pas le store — voir `caveat_multistore-is-multi-document`. +Comment Festipod **persiste ses données** via NextGraph (P2P, local-first, chiffré de bout en bout). Le SDK de données est **`@ng-eventually/client`** : on le traite comme un SDK NextGraph fini — chaque entité est un **document** placé dans le store de son **scope** (public / protected / private), lu et écrit via l'ORM réactif. Le mapping *quelle entité → quel scope* est un fait **produit** (concept `functional-domain`, [[knowledge_data-scopes-and-discovery]]) ; ce concept décrit la **mécanique de persistance**. -> Distinction importante : ce concept décrit le **code actuel**. Le modèle *cible* (multi-store, multi-user, autorisations) est de la doctrine **prospective** qui vit dans le concept `nextgraph-platform` (briefs). NextGraph comme **système externe** (stores, permissions, inbox, SDK) y est aussi documenté. - -**À lire avant de toucher aux écritures :** les 3 règles ci-dessous — chacune corrige un bug réel (`RepoNotFound`, suppression non persistée, redirect intempestif). - -## Règles d'écriture (chacune adossée à une décision) - -- [[rule_private-store-scope]] ← [[decision_2026-03-17_private-store-nuri-scope]] -- [[rule_conditional-ng-init]] ← [[decision_2026-03-13_conditional-ng-init-broker-detection]] - -## Pièges (lire avant de toucher au contexte / aux suppressions / aux champs d'event) - -- [[knowledge_context-internals]] — currentUser `@mariedupont`, auto-seed dev, `participantCount` cache, IRI vide, no-op local -- [[caveat_participation-deletion]] — `leaveEvent` via `ngSet.delete()` (décision SPARQL annulée), persistance possiblement partielle -- [[caveat_event-fields-not-persisted]] — `startTime`/`themes`… perdus en connecté (SHEX incomplet) +> **Frontière SDK.** Le SDK de données de Festipod est `@ng-eventually/client` — initialisé/injecté **une seule fois** via `ngSession.configure(...)`. On l'écrit comme un SDK NextGraph **fini** : ne jamais documenter ici l'état courant de NextGraph (contraintes, contournements, internes broker) — cela vit dans le repo `@ng-eventually/client`. Voir [[knowledge_nextgraph-stack]]. ## Modèle & données -- [[knowledge_nextgraph-stack]] — paquets `@ng-org/*`, SHEX, ORM, `build:orm` -- [[knowledge_data-modes]] — connected vs disconnected/demo, providers selon le statut NG -- [[knowledge_entities]] — types `Fp*` et shapes +- [[knowledge_nextgraph-stack]] — SDK `@ng-eventually/client`, shapes SHEX, ORM réactif, `build:orm`, injection via `ngSession` +- [[knowledge_data-modes]] — connected (SDK) vs disconnected/demo (état local seedé), choix du provider +- [[knowledge_entities]] — types `Fp*` et leurs shapes SHEX - [[knowledge_seed-data]] — données de seed, `CURRENT_USER_ID` +- [[knowledge_context-internals]] — pièges de `FestipodDataContext` (currentUser, auto-seed dev, `participantCount` cache, no-op local) -> Sécurité/confidentialité (mono-store, confiance broker) : concept `app-security`. +## Pièges (lire avant de toucher aux suppressions / aux champs d'event) + +- [[caveat_participation-deletion]] — la désinscription doit être **autoritative** et ne pas réapparaître +- [[caveat_event-fields-not-persisted]] — `startTime`/`themes`… non couverts par la shape Event → perdus en connecté + +> Confidentialité (isolation par scope, confiance dans le SDK) : concept `app-security`. Périmètres produit par entité + découverte : concept `functional-domain`. diff --git a/.project/concepts/data-layer/caveat_event-fields-not-persisted.md b/.project/concepts/data-layer/caveat_event-fields-not-persisted.md index 2a8a92d..29350bd 100644 --- a/.project/concepts/data-layer/caveat_event-fields-not-persisted.md +++ b/.project/concepts/data-layer/caveat_event-fields-not-persisted.md @@ -10,8 +10,8 @@ Le type app `FpEventData` (`src/shared/data/types.ts`) et le seed (`seedData.ts` ## Conséquence -En **mode connected** (NextGraph), le mapping (`mapEvent` dans `FestipodDataContext.tsx`) ne lit/écrit que les champs de la shape. Les champs hors-shape sont **silencieusement perdus** : remplis par des defaults ou vides. Or des écrans **les affichent** (ex. `startTime`/`endTime` dans `EventDetailScreen`) — donc en mode démo (seed local) ils apparaissent, mais en connecté ils disparaissent. Décalage observable seulement à l'usage. +En **mode connected** (SDK), le mapping (`mapEvent` dans `FestipodDataContext.tsx`) ne lit/écrit que les champs de la shape. Les champs hors-shape sont **silencieusement perdus** : remplis par des defaults ou vides. Or des écrans **les affichent** (ex. `startTime`/`endTime` dans `EventDetailScreen`) — donc en mode démo (seed local) ils apparaissent, mais en connecté ils disparaissent. Décalage observable seulement à l'usage. ## Pour corriger (si on veut les persister) -Ajouter les champs à `festipodShapes.shex` puis `bun run build:orm`, et étendre `mapEvent`. C'est aussi un prérequis de la modélisation complète du point de rencontre (cf. concept `nextgraph-platform`, [[brief_2026-05-21_fork-nextgraph-inbox]] §Couche 3). Tant que ce n'est pas fait, **ne pas se fier aux champs date/heure/thèmes en mode connecté**. +Ajouter les champs à `festipodShapes.shex` puis `bun run build:orm`, et étendre `mapEvent`. Tant que ce n'est pas fait, **ne pas se fier aux champs date/heure/thèmes en mode connecté**. diff --git a/.project/concepts/data-layer/caveat_multistore-is-multi-document.md b/.project/concepts/data-layer/caveat_multistore-is-multi-document.md deleted file mode 100644 index 5d1838b..0000000 --- a/.project/concepts/data-layer/caveat_multistore-is-multi-document.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -type: caveat -summary: DEUX AXES à ne pas confondre — (A) quel STORE natif (private/protected/public) ; (B) combien de DOCUMENTS dans un store. Depuis T02.h : le chemin par défaut écrit les entités PARTAGEABLES dans le vrai store PROTECTED (axe A étape 1 faite) ; le private n'ancre plus que le shim/inbox + settings. Le flag FESTIPOD_MULTISTORE ne bascule QUE l'axe B (multi-document), et ces documents multi-scope vivent dans le store du wallet partagé — public/protected/private y sont des ÉTIQUETTES LOGIQUES du shim, pas des stores. L'isolation (ReadCap) est PAR-DOCUMENT. Cible (brief_2026-05-17) : vraiment utiliser les 3 stores natifs par périmètre. -last_checked: 2026-07-03 ---- - -# Caveat : store ≠ document — et « MULTISTORE » n'est PAS multi-store - -Confusion récurrente. Deux axes **orthogonaux** que la terminologie a fusionnés : - -- **Axe A — quel STORE natif ?** Un wallet a d'office 3 stores : `private_store_id`, - `protected_store_id`, `public_store_id` (cf. [[knowledge_stores-permissions]]). C'est - l'origine historique de « mono-store / multi-store » (utiliser 1 store vs les 3). -- **Axe B — combien de DOCUMENTS dans un store ?** Un store contient des documents ; - **le document (= repo = `@graph`) est la frontière de partage et de droits** ; on y stocke - des objets (dans le graphe). La ReadCap — donc l'**isolation** — est **PAR-DOCUMENT**. - -## État réel du code (vérifié 2026-07-03) - -1. **Depuis T02.h, le chemin par défaut écrit les entités partageables dans le vrai store - `protected`** (`@graph = did:ng:${protected_store_id}`, cf. [[rule_private-store-scope]]) — - **axe A étape 1 faite** : le protected s'ouvre pour ORM+SPARQL sans `RepoNotFound` - (vérifié). Les trois `*_store_id` sont résolus en session (`NextGraphContext`) ; le - **private** n'est plus la cible des entités domaine — il n'ancre que le shim/inbox - (cf. `nextgraph-platform`) et les settings privés. `public_store_id` reste non écrit en - tant que store natif (le scope « public » des entités reste une étiquette logique, cf. - point 2). Chemin par défaut mono-document → ReadCap tout-ou-rien sur ce document. - -2. **`FESTIPOD_MULTISTORE` ne bascule QUE l'axe B**, et son nom est trompeur. ON : - - événements → **un `doc_create` par entité** (`createEntityDoc`), NURI indexé dans le - document-index « public » du compte ; - - participations/profils → **groupés** dans le document-index « protected » du compte ; - - lecture → **fan-out** sur les documents de tous les comptes par scope. - MAIS dans la lib `store-registry`, chaque `doc_create` passe `store=undefined` → - **tous ces documents vivent physiquement dans le store `private`** du wallet partagé. - Le triplet `public|protected|private` y est une **ÉTIQUETTE LOGIQUE** trackée en RDF par - le shim, **pas** un store NextGraph. Donc « MULTISTORE » = en réalité **multi-DOCUMENT à - étiquettes de scope logiques**, jamais multi-store. - -## Conséquences - -- « Plus d'isolation » = **plus de documents** (axe B), pas plus de stores. -- Rendre l'isolation ReadCap **active** exige : chemin multi-document **+** câbler - `setCurrentUser` au login (aujourd'hui appelé seulement dans le harness → filtre dormant). -- **L'axe A (3 stores natifs) est désormais AMORCÉ mais pas complet.** Cible retenue - (2026-07-03, cf. [[brief_2026-05-17_multi-store-refactor]]) : utiliser les **3 stores par - périmètre** (public→événements/PdR, protected→profil réseau/participations, - private→settings). **Étape immédiate faite (T02.h)** : les entités partageables sont écrites - dans le **vrai store `protected`** (`did:ng:${protected_store_id}`) — représentatif du futur - wallet per-user — après vérification qu'il s'ouvre sans `RepoNotFound` (le private avait été - choisi précisément parce qu'il s'ouvrait, cf. [[decision_2026-03-17_private-store-nuri-scope]], - insight toujours valide pour les deux stores). Restent non exercés : `public_store_id` comme - store natif, et l'usage des 3 stores par périmètre distinct. - -**Vérifier** : `ensureGraphNuri`/`resolveWriteGraph` (choix du `@graph` = protected), -`grep FESTIPOD_MULTISTORE` (le flag axe B), `createEntityDoc`, lib `store-registry.ts` -(`docCreate(..., undefined)` = store du wallet partagé), `protected_store_id` (écrit), -`public_store_id` (résolu mais non écrit comme store natif). diff --git a/.project/concepts/data-layer/caveat_participation-deletion.md b/.project/concepts/data-layer/caveat_participation-deletion.md index cc3b5c6..455fe27 100644 --- a/.project/concepts/data-layer/caveat_participation-deletion.md +++ b/.project/concepts/data-layer/caveat_participation-deletion.md @@ -1,40 +1,15 @@ --- type: caveat -summary: RÉSOLU (T02.c, 2026-07-03) — leaveEvent supprime désormais la Participation via SPARQL DELETE-WHERE (docs.sparqlUpdate, le ng injecté), puis reflète en réactif. L'item ne ressuscite plus via la sync broker ; le scénario e2e « Se désinscrire » et un @data « désinscription persistante » passent, @wip levé. Historique du bug ngSet.delete() conservé ci-dessous. +summary: La désinscription à un point de rencontre doit être AUTORITATIVE — une fois la Participation supprimée, elle ne doit plus réapparaître ; vérifier après un vrai rafraîchissement que l'inscription a bien disparu côté données last_checked: 2026-07-03 --- -# Caveat : suppression de Participation (RÉSOLU en T02.c) +# Caveat : la désinscription doit être autoritative -**État actuel du code** (`src/shared/context/FestipodDataContext.tsx`, `leaveEvent` en mode NG, depuis T02.c 2026-07-03) : la suppression d'une `Participation` se fait via **SPARQL DELETE-WHERE** (`docs.sparqlUpdate` = le `ng` injecté réel, helper `deleteParticipation` dans `src/shared/data/registration.ts`) qui supprime le sujet Participation côté données ; on reflète ensuite le résultat dans l'état réactif (`participationsShape.ngSet.delete`) pour le rendu immédiat. Le DELETE-WHERE est **autoritatif** : l'item ne ressuscite plus après re-sync. **Ne pas** revenir à `ngSet.delete()` seul comme mécanisme de persistance (l'ancien bug ci-dessous). +Contrat métier : quand un utilisateur **se désinscrit** d'un point de rencontre (`leaveEvent` dans `src/shared/context/FestipodDataContext.tsx`), la `Participation` doit être **supprimée durablement**. Elle ne doit **pas ressusciter** après une resynchronisation. -Preuve : `cycle-de-vie-evenement.feature` (@e2e, @wip levé) + `inscription-inbox.feature` (@data « désinscription persistante »). Validation multi-navigateur complète = T02.f. +## Le piège -## Histoire du bug (avant T02.c) +Refléter la suppression uniquement dans l'état réactif de l'UI ne suffit pas : l'inscription peut réapparaître si la suppression n'est pas **persistée** côté données. La désinscription doit donc être **autoritative** au niveau du document, pas seulement au niveau de l'affichage. -Auparavant la suppression se faisait via **`participationsShape.ngSet.delete(ngPart)`** seul, ce qui NE se reflétait PAS durablement — l'item ressuscitait via la sync broker. - -## Constat e2e (2026-06-30) — la désinscription ne se reflète PAS dans l'UI - -Vérifié en `@e2e` contre le vrai broker (scénario auto-suffisant : s'inscrire puis se désinscrire dans la même session) : - -- **L'inscription se reflète** (clic « J'y serai » → bouton « ✓ Je participe »). -- **La désinscription NON** : après le clic « Je participe », le bouton **reste** « ✓ Je participe » même après >10 s d'attente — `isParticipating` reste vrai. - -Ce **n'est pas** un défaut de réactivité du set : `DeepSignalSet.delete()` appelle bien `touchIterable(meta, target)` quand l'item existait (`@ng-org/alien-deepsignals/dist/deepSignal.js`, bras `delete`), donc le composant **re-render**. Le problème est en aval : la suppression **ne se propage pas durablement** / **l'item ressuscite via la sync broker** (le bug CRDT historique ci-dessous). En `@data` la mutation peut sembler passer, mais le parcours `@e2e` réel montre que l'utilisateur reste inscrit. - -→ Le scénario `@e2e` « Se désinscrire d'un événement » (`src/modules/event/features/cycle-de-vie-evenement.feature`) est **`@wip`**, et le profil cucumber par défaut **exclut `@wip`** (`cucumber.json: "tags": "not @wip"`) — la suite reste verte sans masquer un faux succès. Le retirer du `@wip` quand la désinscription sera fiable. - -## Histoire (important) - -Une décision antérieure ([[decision_2026-03-17_sparql-delete-for-orm-objects]], **annulée le 2026-06-15**) imposait SPARQL DELETE car `ngSet.delete()` ne persistait pas (l'objet réapparaissait au refresh). Ce **bug du `@ng-org/orm` a depuis été en grande partie corrigé** : `ngSet.delete()` est redevenu le chemin utilisé. - -## Le piège (pourquoi un caveat et pas une règle) - -La correction **semble partielle** : selon les cas, la suppression via `ngSet.delete()` peut ne **pas se propager complètement** au broker. Donc : - -- **Ne pas tenir pour acquis** que `leaveEvent` persiste à coup sûr — **vérifier après un vrai refresh** que la participation a bien disparu côté wallet. -- Si une suppression se révèle non persistée, le repli connu reste `ng.sparql_update()` avec `DELETE WHERE { GRAPH <…> { <…> ?p ?o } }` (le mécanisme décrit dans la décision annulée). **Ne pas combiner** les deux (conflit CRDT — c'était l'autre enseignement de la décision). -- Re-tester ce point à chaque montée de version de `@ng-org/orm`. - -> À valider : ouvrir `FestipodDataContext.tsx` → `leaveEvent` (mode NG, `console.log('Deleting participation via ngSet.delete()')`). Si le code est repassé à `sparql_update`, mettre ce caveat à jour ou le promouvoir en règle. +**À vérifier après toute évolution de `leaveEvent`** : s'inscrire puis se désinscrire, faire un **vrai rafraîchissement**, et confirmer que la participation a bien disparu (le bouton ne doit pas rester « ✓ Je participe »). Couvert par le scénario `@e2e` « Se désinscrire d'un événement » (`src/modules/event/features/cycle-de-vie-evenement.feature`) et un `@data` « désinscription persistante » (`inscription-inbox.feature`). diff --git a/.project/concepts/data-layer/decision_2026-03-13_conditional-ng-init-broker-detection.md b/.project/concepts/data-layer/decision_2026-03-13_conditional-ng-init-broker-detection.md deleted file mode 100644 index a3d7b8c..0000000 --- a/.project/concepts/data-layer/decision_2026-03-13_conditional-ng-init-broker-detection.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -type: decision -summary: Décision 2026-03-13 — auto-init NextGraph seulement quand dans l'iframe broker (window.self !== window.top), sinon initNgWeb() redirige la page et casse le dev/démo standalone ---- - -# Conditional NextGraph Init Based on Broker Iframe Detection - -**Date:** 2026-03-13 14:00 -**Status:** Accepted - -## Context - -`initNgWeb()` de `@ng-org/web` teste `window.self === window.top`. En standalone (hors iframe), il redirige toute la page vers `nextgraph.net/redir/` pour déclencher l'auth broker. Résultat : l'app redirigeait à chaque chargement — même en dev ou quand l'utilisateur n'avait pas cliqué « Se connecter ». - -## Options Considered - -### Option A: toujours auto-init NG au mount -- Plus simple (pas de branchement). -- **Contre** : redirect immédiat vers le broker en standalone ; casse le workflow de dev ; l'utilisateur voit la page de login broker au lieu de l'app. - -### Option B: auto-init conditionnel selon détection iframe -- En iframe, le broker a déjà authentifié → auto-init sûr ; en standalone, l'utilisateur doit cliquer « Se connecter » ; préserve l'expérience démo/dev ; calque la propre logique de détection de `@ng-org/web`. -- **Contre** : repose sur l'heuristique `window.self !== window.top` (théoriquement faillible si embarqué dans une iframe non-broker). - -## Decision - -**Option B.** `NextGraphContext` calcule `isInsideBroker = typeof window !== 'undefined' && window.self !== window.top` au niveau module. `useEffect` n'auto-appelle `initNg()` que si `isInsideBroker`. Le callback `connect()` reste disponible pour la connexion explicite. De plus, `FestipodDataContext` rend des données vides (pas le seed) pendant `connecting` pour éviter de flasher le contenu démo. - -## Consequences - -**Positif :** l'app charge sans rediriger (standalone dev/démo) ; en iframe broker, connexion fluide et automatique ; pas de flash de seed pendant la connexion. -**Négatif :** aucun significatif. -**Risque :** si `@ng-org/web` change sa logique de détection, notre garde peut diverger — les garder alignés. - -> Règle dérivée : [[rule_conditional-ng-init]]. diff --git a/.project/concepts/data-layer/decision_2026-03-17_private-store-nuri-scope.md b/.project/concepts/data-layer/decision_2026-03-17_private-store-nuri-scope.md deleted file mode 100644 index 4065b56..0000000 --- a/.project/concepts/data-layer/decision_2026-03-17_private-store-nuri-scope.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -type: decision -summary: Décision 2026-03-17 — utiliser private_store_id comme scope useShape ET @graph (calqué sur expense-tracker-rdf) pour que orm_start_graph ouvre le repo et que les écritures ne lèvent plus RepoNotFound ---- - -# Use private_store_id as useShape scope and @graph - -**Date:** 2026-03-17 16:00 -**Status:** Accepted - -> **Superseded (partiel, 2026-07-03, T02.h).** Le scope private-store-only est **remplacé pour les entités domaine partageables** (events/profils/participations) : elles sont désormais scopées ET écrites sur le **protected store** (`did:ng:${protected_store_id}`), vérifié ouvrable sans `RepoNotFound` — cf. [[rule_private-store-scope]] et [[caveat_multistore-is-multi-document]]. **L'insight central de cet ADR reste vrai** : il faut ouvrir le repo via le NURI du store (`orm_start_graph`) sinon `RepoNotFound` — ceci s'applique désormais aux **DEUX** stores. Le corps ci-dessous est conservé tel quel (mémoire d'arbitrage). - -## Context - -Cliquer « Charger données de test » chargeait les données en mémoire (signaux ORM) mais produisait des `RepoNotFound` sur `doc_create` et `orm_frontend_update`. Les données disparaissaient au reload car les écritures SPARQL n'atteignaient jamais le broker. La HashMap `self.repos` du verifier ne contenait pas le repo du private store → `resolve_target()` échouait. - -## Options Considered - -### Option A: `did:ng:i` scope + `doc_create` pour @graph -- `did:ng:i` bien documenté comme scope d'abonnement, `doc_create` renvoie un vrai NURI. -- **Contre** : `did:ng:i` passe par `NuriTargetV0::UserSite` qui n'ouvre pas les repos individuels ; `doc_create` appelle `resolve_target(PrivateStore)` qui exige le repo dans `self.repos` → échoue ; exige une logique de retry/timing complexe. - -### Option B: `private_store_id` comme scope ET @graph -- Calque exact de l'exemple `expense-tracker-rdf` qui fonctionne ; `orm_start_graph` avec le NURI du private store ouvre le repo dans `self.repos` ; les écritures `orm_frontend_update` trouvent ensuite le repo. Simple, sans retry. -- **Contre** : un peu moins flexible que `did:ng:i` (scopé à un store) ; exige de passer la session à `useShapeWithDefaults`. - -### Option C: `did:ng:i` scope + réutiliser le @graph d'une entité existante -- Marche pour les users qui ont déjà des données. -- **Contre** : échoue pour les wallets vides (aucune entité à réutiliser) ; retombe sur `doc_create` et le même `RepoNotFound`. - -## Decision - -**Option B** : `did:ng:${session.private_store_id}` comme scope `useShape` ET `@graph` d'écriture, exactement comme `expense-tracker-rdf`. `useShapeWithDefaults` accepte un `storeNuri` ; `FestipodDataContext.useNgData()` récupère la session via `useNextGraph()` et passe le NURI du private store. `ensureGraphNuri()` simplifié : entités existantes d'abord (optimisation), sinon fallback `private_store`. - -## Consequences - -**Positif :** écritures immédiates après connexion (sans retry) ; persistance au reload ; aligné sur les exemples officiels ; les 7 scénarios e2e passent (dont la persistance). -**Négatif :** signature de `useShapeWithDefaults` modifiée (param `storeNuri`). -**Risque :** si NextGraph change le comportement du private store, ça casse. - -> Règle dérivée : [[rule_private-store-scope]]. Décision *remise en cause* par le futur multi-store : [[brief_2026-05-17_multi-store-refactor]]. diff --git a/.project/concepts/data-layer/decision_2026-03-17_sparql-delete-for-orm-objects.md b/.project/concepts/data-layer/decision_2026-03-17_sparql-delete-for-orm-objects.md deleted file mode 100644 index ab9ab0a..0000000 --- a/.project/concepts/data-layer/decision_2026-03-17_sparql-delete-for-orm-objects.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -type: decision -summary: Décision 2026-03-17 — supprimer les objets ORM via ng.sparql_update (DELETE WHERE) seul, car ngSet.delete() ne persiste pas et les combiner crée un conflit CRDT ---- - -# Use SPARQL DELETE instead of ORM ngSet.delete() for object removal - -**Date:** 2026-03-17 18:00 -**Status:** ~~Accepted~~ → **Superseded (2026-06-15)** - -> **Annulée le 2026-06-15.** Le bug de non-persistance de `ngSet.delete()` qui motivait cette décision a depuis été en grande partie corrigé côté `@ng-org/orm` : le code (`leaveEvent`) est repassé à `ngSet.delete()`. La persistance reste toutefois possiblement partielle — l'état courant et le repli SPARQL sont décrits dans [[caveat_participation-deletion]]. Décision conservée comme mémoire d'arbitrage (le conflit CRDT « ne pas combiner les deux » reste vrai). - -## Context - -Quitter un event exige de supprimer l'objet `Participation` du store NextGraph. `DeepSignalSet.delete()` met à jour l'état réactif local (UI immédiate) mais **ne persiste pas** au broker — après refresh, la participation réapparaît. - -## Options Considered - -### Option A: ORM `ngSet.delete(item)` -- API officielle (README ORM), update réactif local instantané. -- **Contre** : ne persiste pas en pratique (`delete()` renvoie `true`, set local à jour, mais objet de retour après refresh) ; `graph_orm_update` semble mal gérer les patches "remove" pour objets de set top-level (bug moteur probable) ; échoue silencieusement. - -### Option B: `ng.sparql_update()` avec SPARQL DELETE -- `DELETE WHERE { GRAPH { ?p ?o } }` retire tous les triples RDF. -- **Pour** : persiste (survit au refresh) ; le broker confirme via `GraphOrmUpdate` remove qui retire réactivement l'item du set ORM ; contrôle direct. -- **Contre** : pas instantané (round-trip SPARQL + callback broker, ~50ms) ; ne doit pas être combiné avec `ngSet.delete()`. - -### Option C: les deux ensemble -- **Ne marche pas** : le patch ORM `.delete()` et le DELETE SPARQL entrent en conflit CRDT → ni UI ni persistance. - -## Decision - -**Option B : SPARQL DELETE seul.** Le broker renvoie un `GraphOrmUpdate` `op: "remove"` qui retire réactivement l'item du set ORM (UI à jour, juste pas synchrone). **Ne pas** appeler `ngSet.delete()` à côté. - -```typescript -// FestipodDataContext.tsx leaveEvent(): -const session = await sessionPromise; -await ng.sparql_update( - session.session_id, - `DELETE WHERE { GRAPH <${partGraph}> { <${partId}> ?p ?o } }`, - partGraph, -); -``` - -## Consequences - -**Positif :** suppression persistée ; source de vérité unique (broker → ORM → UI). -**Négatif :** léger délai UI (~50ms) ; diverge des exemples README ORM. -**Risque :** si `ng.sparql_update` change, ça casse ; toute future suppression doit suivre le même pattern ; revisiter si `ngSet.delete()` est corrigé en montée de version. - -> État courant (la règle a été retirée) : [[caveat_participation-deletion]]. diff --git a/.project/concepts/data-layer/knowledge_data-modes.md b/.project/concepts/data-layer/knowledge_data-modes.md index feefd32..98a5b06 100644 --- a/.project/concepts/data-layer/knowledge_data-modes.md +++ b/.project/concepts/data-layer/knowledge_data-modes.md @@ -1,29 +1,28 @@ --- type: knowledge -summary: Deux modes (connected = NextGraph ORM, disconnected/demo = état local seedé) ; FestipodDataContext choisit le provider selon le statut NextGraphContext, tous les écrans passent par useFestipodData() +summary: Deux modes (connected = SDK @ng-eventually/client, disconnected/demo = état local seedé) ; FestipodDataContext choisit le provider selon le statut de connexion, tous les écrans passent par useFestipodData() --- # Modes de données & contextes L'app a **deux modes**, tous deux consommés via le hook `useFestipodData()` : -1. **Connected** — shapes ORM NextGraph (P2P, chiffré, local-first) +1. **Connected** — shapes ORM du SDK `@ng-eventually/client` (P2P, chiffré, local-first) 2. **Disconnected / Demo** — état React local seedé depuis `seedData.ts` (voir [[knowledge_seed-data]]) ## NextGraphContext (`src/shared/context/NextGraphContext.tsx`) - Cycle de connexion : `disconnected` → `connecting` → `connected` | `error`. -- Fournit la session avec les IDs de stores (private, protected, public). -- **Auto-init conditionnel** : voir [[rule_conditional-ng-init]] (n'auto-initialise que dans l'iframe broker). +- Fournit la session (l'utilisateur courant et son accès aux stores par scope). ## FestipodDataContext (`src/shared/context/FestipodDataContext.tsx`) - Enveloppe les shapes via `useShapeWithDefaults()`. -- Expose `useFestipodData()` (consommé par tous les écrans) + CRUD (`createEvent`, `updateEvent`, etc.). -- **Provider selon le statut NG** : +- Expose `useFestipodData()` (consommé par tous les écrans) + CRUD (`createEvent`, `updateEvent`, `joinEvent`, `leaveEvent`, etc.). +- **Provider selon le statut de connexion** : - `disconnected` → `LocalDataProvider` avec seed (démo) - `connecting` → `LocalDataProvider` **vide** (évite de flasher le seed avant le chargement du wallet) - `connected` → `NgDataProvider` (données réelles du wallet) - `error` → `LocalDataProvider` avec seed (fallback gracieux) -> Réserve : certaines mutations (`joinEvent`/`leaveEvent`) sont encore des **no-ops** (`console.log`) en attendant le chantier données — cf. [[brief_2026-05-21_fork-nextgraph-inbox]] §Couche 3. +> Les mutations sont **réellement persistées** en mode connected (`joinEvent` écrit une Participation et notifie l'hôte du PdR, `leaveEvent` supprime de façon autoritative — cf. [[caveat_participation-deletion]]). En mode local/demo elles sont des no-ops (cf. [[knowledge_context-internals]]). diff --git a/.project/concepts/data-layer/knowledge_entities.md b/.project/concepts/data-layer/knowledge_entities.md index 16a9c86..840571d 100644 --- a/.project/concepts/data-layer/knowledge_entities.md +++ b/.project/concepts/data-layer/knowledge_entities.md @@ -10,15 +10,15 @@ last_checked: 2026-07-03 | Type | Persistance | Champs clés | |---|---|---| -| `FpEventData` | NextGraph (shape Event) | id, title, date, location, distance, themes | -| `FpUserData` | NextGraph (shape UserProfile) | id, name, username, bio, city, counts | -| `FpParticipationData` | NextGraph (shape Participation) | eventId + userId + confirmed | -| `FpMeetingPointData` | NextGraph (shape MeetingPoint, T02.a) | eventId, location, time, host | -| `FpNotificationData` | NextGraph (shape Notification, T02.a) | kind, target, source | +| `FpEventData` | SDK (shape Event) | id, title, date, location, distance, themes | +| `FpUserData` | SDK (shape UserProfile) | id, name, username, bio, city, counts | +| `FpParticipationData` | SDK (shape Participation) | eventId + userId + confirmed | +| `FpMeetingPointData` | SDK (shape MeetingPoint) | eventId, location, time, host | +| `FpNotificationData` | SDK (shape Notification) | kind, target, source | | `FpFriendshipData` | **local-only** | userId + friendId | -`MeetingPoint` et `Notification` ont désormais de vraies **shapes SHEX** (`src/shared/shapes/shex/festipodShapes.shex`) avec bindings ORM générés (`festipodShapes.shapeTypes.ts` : `FpMeetingPointShapeType`, `FpNotificationShapeType`) et **sont persistés** (T02.a). `Notification` est notamment créée lors de l'inscription à un PdR (`joinEvent`, cf. `nextgraph-platform` inbox). +`MeetingPoint` et `Notification` ont de vraies **shapes SHEX** (`src/shared/shapes/shex/festipodShapes.shex`) avec bindings ORM générés (`festipodShapes.shapeTypes.ts` : `FpMeetingPointShapeType`, `FpNotificationShapeType`) et **sont persistés**. `Notification` est notamment créée lors de l'inscription à un point de rencontre (`joinEvent`). -`Friendship` n'a **pas** de shape SHEX ni de persistance NextGraph — il reste app-TS-only (cf. [[knowledge_nextgraph-stack]]). +`Friendship` n'a **pas** de shape SHEX ni de persistance — il reste app-TS-only (cf. [[knowledge_nextgraph-stack]]). > Piège : même pour `FpEvent` (persisté), plusieurs champs du type app ne sont **pas** dans la shape et sont perdus en connecté — voir [[caveat_event-fields-not-persisted]]. diff --git a/.project/concepts/data-layer/knowledge_nextgraph-stack.md b/.project/concepts/data-layer/knowledge_nextgraph-stack.md index 9a70f0f..46f4930 100644 --- a/.project/concepts/data-layer/knowledge_nextgraph-stack.md +++ b/.project/concepts/data-layer/knowledge_nextgraph-stack.md @@ -1,28 +1,32 @@ --- type: knowledge -summary: Paquets @ng-org/* (web, orm, shex-orm, alien-deepsignals), shapes SHEX festipodShapes, bindings ORM générés, régénérés via build:orm +summary: Le SDK de données est @ng-eventually/client (traité comme un SDK NextGraph fini) — injecté une seule fois via ngSession.configure ; ORM réactif useShape sur shapes SHEX festipodShapes, bindings régénérés via build:orm ; ne jamais documenter l'état courant de NextGraph ici --- -# Stack NextGraph (côté app) +# Stack de données (SDK `@ng-eventually/client`) + +Festipod persiste via **`@ng-eventually/client`** — le SDK NextGraph que l'app consomme. On le traite comme un **SDK fini et mature** : documents par entité placés par scope, capabilities, inboxes, ORM réactif. ``` -@ng-org/web # Runtime navigateur (proxy postMessage vers l'iframe) -@ng-org/orm # ORM réactif basé sur les shapes RDF (useShape…) -@ng-org/shex-orm # Génération SHEX → TypeScript -@ng-org/alien-deepsignals # Pont de signaux réactifs +@ng-eventually/client # LE SDK de données de l'app (ORM réactif useShape, docs, scopes, inbox) ``` -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). +## Frontière SDK (règle d'or) -> **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/*`. +- L'app **ne dépend que de `@ng-eventually/client`** pour la donnée. +- Le SDK est **initialisé/injecté une seule fois** via `ngSession.configure(...)` (`src/shared/utils/ngSession.ts`) — point d'injection unique. Le reste de l'app (data-plane, lifecycle, login, types) passe par la lib. +- **Ne jamais documenter dans ce repo l'état courant de NextGraph** (contraintes du SDK sous-jacent, contournements, internes broker/verifier, mécanique d'émulation) : cela vit dans le repo `@ng-eventually/client`. Ici on décrit seulement **comment Festipod utilise ce SDK**. -## Shapes SHEX +## ORM & shapes SHEX + +L'ORM réactif (`useShape`) s'appuie sur des **shapes SHEX** : `src/shared/shapes/shex/festipodShapes.shex` définit : -`src/shared/shapes/shex/festipodShapes.shex` définit : - **Event** — titre, description, dates, lieu, thèmes, participants - **UserProfile** — nom, username, bio, ville, visibilité - **Participation** — lie event + user, statut de confirmation +- **MeetingPoint** — point de rencontre (lieu, horaire, hôte) +- **Notification** — notification (créée notamment à l'inscription à un PdR) -Bindings ORM dans `src/shared/shapes/orm/` (`*.schema.ts`, `*.shapeTypes.ts`, `*.typings.ts`). **Régénérer** avec `bun run build:orm` après toute modif `.shex`. +Bindings ORM générés dans `src/shared/shapes/orm/` (`*.schema.ts`, `*.shapeTypes.ts`, `*.typings.ts`). **Régénérer** avec `bun run build:orm` après toute modif `.shex`. -> Manque côté shapes : **pas de `MeetingPoint`** ni d'entité notification — le point de rencontre est aujourd'hui local-only côté types (voir [[knowledge_entities]]). Leur modélisation est un chantier de [[brief_2026-05-21_fork-nextgraph-inbox]]. +> `Friendship` n'a **pas** de shape SHEX ni de persistance — il reste app-TS-only (cf. [[knowledge_entities]]). diff --git a/.project/concepts/data-layer/knowledge_seed-data.md b/.project/concepts/data-layer/knowledge_seed-data.md index 1056465..fa3abe4 100644 --- a/.project/concepts/data-layer/knowledge_seed-data.md +++ b/.project/concepts/data-layer/knowledge_seed-data.md @@ -14,4 +14,4 @@ summary: seedData.ts fournit des fixtures déterministes (10 users, events, part Ces fixtures servent (a) le **mode démo** (`LocalDataProvider`, cf. [[knowledge_data-modes]]) et (b) les tests **`@ui`** qui rendent les écrans avec ces données prévisibles (`Marie Dupont`/`@mariedupont` = currentUser, `Jean Durand`/`@jeandurand` existe, etc. — voir concept `bdd-testing`). -> `bootstrapWallet()` (`src/shared/utils/ngBootstrap.ts`) seede ces données dans le wallet NG en mode connected — déclenché uniquement par action explicite de l'utilisateur (« Charger données de test »). Sa refonte par documents/périmètres est un point des briefs `nextgraph-platform`. +> `bootstrapWallet()` (`src/shared/utils/ngBootstrap.ts`) seede ces données dans le wallet en mode connected — déclenché uniquement par action explicite de l'utilisateur (« Charger données de test »). diff --git a/.project/concepts/data-layer/rule_conditional-ng-init.md b/.project/concepts/data-layer/rule_conditional-ng-init.md deleted file mode 100644 index 6925d3c..0000000 --- a/.project/concepts/data-layer/rule_conditional-ng-init.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -type: rule -summary: N'auto-initialiser NextGraph que dans l'iframe broker (window.self !== window.top) ; en standalone, initNgWeb() redirige toute la page — attendre un connect() explicite ---- - -# Règle : auto-init NextGraph seulement dans l'iframe broker - -`initNgWeb()` de `@ng-org/web` teste `window.self === window.top`. **Hors iframe** (app standalone), il **redirige toute la page** vers `nextgraph.net/redir/` pour déclencher l'auth broker. - -Donc `NextGraphContext` calcule `isInsideBroker = window.self !== window.top` et **n'auto-appelle `initNg()` que si `isInsideBroker`**. En standalone, la connexion attend un `connect()` explicite (clic « Se connecter ») — sinon l'app redirige à chaque chargement et casse le dev/démo. - -De plus, `FestipodDataContext` rend des données **vides** (pas le seed) pendant la phase `connecting`, pour éviter de flasher du contenu démo avant le chargement du wallet (voir [[knowledge_data-modes]]). - -> Garder ce garde **aligné** sur la détection interne de `@ng-org/web` : si leur heuristique change, le nôtre doit suivre. Pourquoi + alternatives : [[decision_2026-03-13_conditional-ng-init-broker-detection]]. diff --git a/.project/concepts/data-layer/rule_private-store-scope.md b/.project/concepts/data-layer/rule_private-store-scope.md deleted file mode 100644 index 51518e3..0000000 --- a/.project/concepts/data-layer/rule_private-store-scope.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -type: rule -summary: Les entités domaine PARTAGEABLES (events/profils/participations) se lisent ET s'écrivent via did:ng:${protected_store_id} (scope useShape ET @graph) depuis T02.h ; le private store reste l'ancre shim/inbox + settings privés ; ne JAMAIS utiliser did:ng:i comme scope (RepoNotFound) — les DEUX stores doivent être ouverts via orm_start_graph ---- - -# Règle : scope = `@graph` = `protected_store_id` pour les entités partageables - -Depuis **T02.h** (axe A, cf. [[caveat_multistore-is-multi-document]]), le chemin par défaut (mono-document) lit **et** écrit les **entités domaine partageables** (events, profils, participations) dans le **store protected natif** — plus dans le private. - -Pour lire **et** écrire ces entités via l'ORM NextGraph : - -- **Scope** : `useShape(shapeType, \`did:ng:${session.protected_store_id}\`)` -- **`@graph`** (cible des écritures) : `did:ng:${session.protected_store_id}` - -C'est critique : `orm_start_graph` avec le NURI d'un store **ouvre explicitement le repo** dans la HashMap `self.repos` du verifier. Sans ça, `orm_frontend_update` échoue en `RepoNotFound`. Vérifié empiriquement que le **protected** s'ouvre pour ORM+SPARQL de la même façon que le private (round-trip probe, pas de `RepoNotFound`). Les **DEUX** stores utilisés doivent donc être ouverts via `orm_start_graph`. - -## Rôle résiduel du private store - -Le **private store** reste l'ancre pour : -- le shim shared-wallet et les dépôts d'inbox (cf. `nextgraph-platform`) ; -- les **settings privés** (cible future). - -## Interdit - -**Ne pas utiliser `did:ng:i` comme scope.** Il s'abonne au site entier de l'utilisateur via un chemin de code spécial (`NuriTargetV0::UserSite`) qui **n'ouvre pas les repos individuels** → casse toutes les écritures par `RepoNotFound`. - -## Fichiers porteurs - -- `src/shared/hooks/useShapeWithDefaults.ts` — accepte un `storeNuri`, le passe à `useShape`. -- `src/shared/utils/ngGraph.ts` — `ensureGraphNuri()` retourne le `@graph` (entités existantes d'abord, sinon fallback `protected_store`). -- `src/shared/context/FestipodDataContext.tsx` — récupère la session et passe le NURI du protected store (`protectedNuri`). -- `src/shared/utils/ngBootstrap.ts` — seede en utilisant `ensureGraphNuri()`. - -> Le *pourquoi* du choix historique (private, avant T02.h) et les alternatives écartées : [[decision_2026-03-17_private-store-nuri-scope]] (dont l'insight « ouvrir le repo via le NURI du store sinon RepoNotFound » reste vrai pour les DEUX stores). Les deux axes store/document et la cible : [[caveat_multistore-is-multi-document]] et [[brief_2026-05-17_multi-store-refactor]]. diff --git a/.project/concepts/functional-domain/_overview.md b/.project/concepts/functional-domain/_overview.md index d663fb4..45f9fef 100644 --- a/.project/concepts/functional-domain/_overview.md +++ b/.project/concepts/functional-domain/_overview.md @@ -1,8 +1,8 @@ --- type: _overview -summary: Modèle produit Festipod — le point de rencontre greffé sur un événement public comme unité de valeur, ses acteurs et ses concepts métier +summary: Modèle produit Festipod — le point de rencontre greffé sur un événement public comme unité de valeur, ses acteurs, ses concepts métier, et les périmètres de confidentialité (public/protected/private) par entité triggers: - keywords: [point de rencontre, rencontre, greffe, greffer, événement, déclarant, hôte, inscrit, inscription, communauté, connexion, festival, déduplication] + keywords: [point de rencontre, rencontre, greffe, greffer, événement, déclarant, hôte, inscrit, inscription, communauté, connexion, festival, déduplication, découverte, périmètre, scope, public, protected, privé] paths: ["src/modules/*/features/**"] --- @@ -16,13 +16,14 @@ Le **domaine fonctionnel** de Festipod : ce que le produit promet et le vocabula Festipod laisse les utilisateurs créer des **points de rencontre** qui se *greffent* sur des **événements publics** existants. L'événement (festival, conférence…) n'est qu'un *prétexte* et un point d'ancrage spatio-temporel ; la valeur produite, c'est le point de rencontre. **On s'inscrit à un point de rencontre, jamais à un événement.** -## Périmètre & sécurité +## Périmètre & confidentialité -Le modèle d'**autorisations / confidentialité** (qui voit quoi : « données personnelles = réseau seulement », anonymat via inbox, capabilities) n'est pas encore implémenté — il vit aujourd'hui comme incubation dans [[brief_2026-05-18_authorization-matrix]] (concept `nextgraph-platform`). Il graduera en règles/`behavior_` quand le multi-user atterrira. C'est la raison pour laquelle il n'y a pas encore de concept `app-security` distinct. +Le modèle produit de **qui voit quoi** — données personnelles réservées au réseau, événements/PdR publics, notification d'inscription identifiée-ou-anonyme — est un fait métier : voir [[knowledge_data-scopes-and-discovery]]. La matrice d'autorisations détaillée (acteur × verbe) et son incubation vivent dans le concept `app-security` ([[brief_2026-05-18_authorization-matrix]]). ## Liens +- [[knowledge_business-model]] — l'inversion événement / point de rencontre - [[knowledge_actors-and-concepts]] — référence des acteurs et concepts métier +- [[knowledge_data-scopes-and-discovery]] — périmètres public/protected/private par entité + découverte - [[knowledge_roadmap]] — fonctionnalités actuelles vs évolutions à venir - [[brief_2026-06-15_event-deduplication]] — défi ouvert de déduplication des événements en P2P -- `nextgraph-platform` — où vit la dérivation de la structure de données cible (authz matrix, multi-store) diff --git a/.project/concepts/functional-domain/knowledge_data-scopes-and-discovery.md b/.project/concepts/functional-domain/knowledge_data-scopes-and-discovery.md new file mode 100644 index 0000000..e3d9f30 --- /dev/null +++ b/.project/concepts/functional-domain/knowledge_data-scopes-and-discovery.md @@ -0,0 +1,44 @@ +--- +type: knowledge +summary: Modèle produit de confidentialité et de découverte — chaque entité vit dans un SCOPE (public / protected / private) selon qui doit la voir ; événements & points de rencontre = public, profil réseau & participations = protected (réseau), settings = private ; connexions bilatérales = scope dialog ; la découverte lit un index global d'événements +--- + +# Périmètres de données et découverte + +Le modèle **produit** de qui voit quoi, et comment on trouve les événements. C'est du **domaine** : le *comment* technique (documents, capabilities, index) est assuré par le SDK de données `@ng-eventually/client` — l'app décrit seulement **l'intention métier**. + +## Trois périmètres (scopes) par donnée + +Chaque entité est stockée dans le **scope** correspondant à qui doit pouvoir la lire : + +| Entité | Scope | Qui lit | +|---|---|---| +| Événement (l'ancrage) | **public** | tout le monde | +| Point de rencontre (PdR) | **public** | tout le monde | +| Profil réseau (nom, avatar, bio, ville, intérêts) | **protected** | le titulaire + ses connexions | +| Participation / inscription à un PdR | **protected** | l'inscrit + ses connexions | +| Index des connexions | **protected** | le titulaire + ses connexions | +| Profil privé (settings, email, préférences) | **private** | le titulaire seul | +| Connexion A↔B (lien bilatéral, + messagerie future) | **dialog** | les deux utilisateurs | + +Principe directeur : **le statut « public » (PdR, événement) et « personnel » (profil, participations, connexions) coexistent dans un même utilisateur.** Les informations personnelles sont réservées au **réseau** (connexions bilatérales), jamais visibles d'un utilisateur lambda. + +- **PdR / événement = publics universels.** Tout utilisateur peut lire et s'abonner ; créer un PdR rend hôte, créer un événement rend déclarant (aucun prérequis). +- **Hôte = seul détenteur des droits d'écriture** sur son PdR ; le déclarant n'a aucun droit particulier sur les PdR greffés sur son événement. +- **Connexion bilatérale** : `DemandeDeConnexion` (unilatérale, transitoire) → `Connexion` (bilatérale, persistante) — cette dernière ouvre l'accès aux données *protected* de l'autre. + +Festipod **place chaque entité dans le store de son scope** ; l'isolation entre scopes est **assurée par le SDK de données**, pas par du code applicatif (cf. concept `app-security`). + +## Découverte des événements + +Un utilisateur découvre les événements qu'il n'a pas créés via un **index global** : le SDK lit cet index, qui donne les références (NURIs) des documents-événements, puis synchronise et interroge en local. La découverte **primaire** passe par cet index ; un **axe secondaire** relationnel s'y superpose (les participations *protected* des connexions : « mes amis participent à… »). + +> **Notification d'inscription (intention produit).** S'inscrire à un PdR notifie son hôte : identifié si l'inscrit fait partie des connexions de l'hôte, **anonyme sinon**. Ce « identifié si connu, anonyme sinon » est une propriété du modèle de données — l'app y compte, le mécanisme est fourni par le SDK. + +## Questions ouvertes (métier) + +- **Modèle d'écriture de l'événement** : propriétaire (déclarant seul) / wiki (tous) / immuable ? Central pour la déduplication ([[brief_2026-06-15_event-deduplication]]). +- **Identité de l'hôte vis-à-vis d'un lambda** : un PdR est lisible par tous, mais faut-il que son hôte soit identifiable ? (pseudonyme par défaut, carte de visite par PdR, ou anonymat révélé aux seules connexions.) +- **Champs modifiables d'une inscription** ; **découvrabilité « amis d'amis »**. + +> La matrice d'autorisations détaillée par acteur × verbe vit dans le concept `app-security` ([[brief_2026-05-18_authorization-matrix]]). diff --git a/.project/concepts/functional-domain/knowledge_roadmap.md b/.project/concepts/functional-domain/knowledge_roadmap.md index 122a5b7..56da854 100644 --- a/.project/concepts/functional-domain/knowledge_roadmap.md +++ b/.project/concepts/functional-domain/knowledge_roadmap.md @@ -15,11 +15,11 @@ summary: Ce qui est implémenté aujourd'hui (cycle événement + point de renco - Profil utilisateur, mise à jour, partage de profil - Liste d'amis (connexions), profil d'un autre utilisateur -> MAJ T02.b/c (2026-07-03) : l'inscription/désinscription au PdR est **réellement branchée** côté données. `joinEvent` **persiste une Participation** + **dépose dans l'inbox de l'hôte** + **crée une Notification** (shape SHEX réelle) ; `leaveEvent` **supprime autoritativement** via `SPARQL DELETE-WHERE` (le bug CRDT de désinscription est résolu). Ce ne sont plus des no-ops. La **découverte publique cross-compte** fonctionne aussi (fan-out, T02.e — un utilisateur voit un événement public d'un autre sans connexion). Détail lib : [[decision_2026-06-17_eventually-library]] §Inbox émulée. +> L'inscription/désinscription au point de rencontre est **réellement branchée** côté données : `joinEvent` persiste une Participation, notifie l'hôte du PdR et crée une Notification ; `leaveEvent` supprime la Participation de façon autoritative (cf. concept `data-layer`, [[caveat_participation-deletion]] côté data-layer). La découverte publique — un utilisateur voit un événement public d'un autre — fonctionne aussi. ## Évolutions identifiées (non implémentées) - **Abonnement à une communauté d'intérêt** pour découvrir ses événements (discovery distribué). - **Abonnement à un utilisateur** pour suivre ses déclarations sans être ami. - **Listes curated** — créer/partager des sélections éditorialisées. -- **Multi-utilisateurs collaboratif** : aujourd'hui chaque utilisateur a ses données isolées dans son wallet. Le passage collaboratif (un point de rencontre vu par plusieurs) suppose un refactor de la couche données — voir [[brief_2026-05-17_multi-store-refactor]]. +- **Multi-utilisateurs collaboratif** : le partage effectif d'un point de rencontre vu par plusieurs utilisateurs, appuyé sur les périmètres public/protected/private (cf. [[knowledge_data-scopes-and-discovery]]). diff --git a/.project/concepts/nextgraph-platform/_overview.md b/.project/concepts/nextgraph-platform/_overview.md deleted file mode 100644 index c4f7acb..0000000 --- a/.project/concepts/nextgraph-platform/_overview.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -type: _overview -summary: NextGraph comme système EXTERNE (stores, permissions, inbox, modèle d'intégration iframe, limites SDK) + les 4 briefs prospectifs qui dérivent la structure de données cible et le chemin multi-user de Festipod -triggers: - keywords: [nextgraph-rs, store, group store, dialog store, protected_store, public_store, inbox, capability, nuri, fork, ngd, broker, verifier, multi-store, multi-user, sharedWalletShim, storeRegistry, permission, social_query, OpenRepo, wallet, auto-import, textcode, wallet import, AccessGateScreen] - paths: ["src/shared/utils/ngGraph.ts", "src/shared/hooks/useShapeWithDefaults.ts", "scripts/build-ng-packages.sh", "src/modules/auth/sharedWallet.ts", "src/modules/auth/screens/AccessGateScreen.tsx"] ---- - -# NextGraph platform - -Deux choses ici, distinctes du concept `data-layer` (qui décrit l'**usage actuel** de NextGraph par l'app) : - -1. **Référence du système externe NextGraph** — ses primitives de stockage et de permission, son inbox, son modèle d'intégration/déploiement, et ce que son SDK JS expose (ou pas). -2. **Briefs prospectifs** — la dérivation de la structure de données *cible* de Festipod et les chemins pour y arriver (stopgap wallet partagé, refactor multi-store, fork moteur pour l'inbox). - -> Le code de l'app touché par ces chantiers : `src/shared/utils/ngGraph.ts`, `useShapeWithDefaults.ts`, `FestipodDataContext.tsx`, `ngBootstrap.ts` — les seams du futur multi-store. Le modèle de **confidentialité/autorisations** (qui peut faire quoi) vit dans le concept `app-security` ([[brief_2026-05-18_authorization-matrix]]) ; ces chantiers data en sont l'infrastructure. - -## Source locale - -Le repo `nextgraph-rs` est cloné en `/home/sylvain/projects/nextgraph/nextgraph-rs` (soit `../../nextgraph/nextgraph-rs` depuis la racine projet). À consulter pour vérifier ce qui est réellement exposé au protocole/SDK plutôt que la doc. - -## Référence (système externe) - -- [[knowledge_stores-permissions]] — 5 types de stores, document/repo, capabilities/Nuri, inbox, exposition SDK JS -- [[knowledge_integration-model]] — paquets JS, modèle iframe, où tourne le verifier, broker `ngd`, déploiement, reciblage build-time -- [[knowledge_broker-import-constraint]] — le broker hébergé n'autorise pas l'auto-import d'un wallet par une web-app tierce (vérifié 2026-06-17) - -## Briefs (chantiers prospectifs) - -- [[brief_2026-05-17_multi-store-refactor]] — passer du mono-store actuel à une structure par entité -- [[brief_2026-06-15_shared-wallet-shim]] — stopgap staging : wallet partagé unique + `storeRegistry` -- [[brief_2026-05-21_fork-nextgraph-inbox]] — forker `nextgraph-rs` pour exposer l'inbox au SDK JS - -## Décisions - -- [[decision_2026-06-17_assisted-wallet-import]] — distribution du wallet partagé par import assisté (l'auto-import zéro-touche étant impossible) diff --git a/.project/concepts/nextgraph-platform/brief_2026-05-17_multi-store-refactor.md b/.project/concepts/nextgraph-platform/brief_2026-05-17_multi-store-refactor.md deleted file mode 100644 index ffb19b2..0000000 --- a/.project/concepts/nextgraph-platform/brief_2026-05-17_multi-store-refactor.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -type: brief -summary: Passer du mono-store actuel (tout dans private_store) à une structure de stores par entité ; hardcoding dans ngGraph.ts + useShapeWithDefaults ; contrainte SDK bloquante (Group stores/inbox non exposés) ; refactor structurel possible avec placeholders en attendant l'API -last_updated: 2026-05-17 ---- - -# Refactor multi-store NextGraph - -**Status:** Incubating — aucun travail démarré -**Last updated:** 2026-05-17 - -## Context - -L'app est aujourd'hui *mono-store* : tout (events, profils, participations, friendships) atterrit dans le `private_store` de l'utilisateur connecté. Héritage de l'exemple expense-tracker-rdf, formalisé dans la décision du 2026-03-17 (concept `data-layer`, [[decision_2026-03-17_private-store-nuri-scope]]). - -Ce choix bloque le multi-utilisateurs : le `private_store` est non partageable (*« not possible to share the documents of your private store »*, cf. [[knowledge_stores-permissions]]). Tant que tout y est, Bob ne verra jamais l'event d'Alice. Le modèle natif NextGraph est *multi-store par utilisateur* — Festipod doit s'y aligner avant de devenir collaboratif. - -**Déclencheur :** discussion du 2026-05-17 — *poser le cap, exécuter plus tard*. - -## What We Know - -### État actuel du code - -Deux fichiers concentrent le hardcoding du store unique : -- `src/shared/utils/ngGraph.ts` — `ensureGraphNuri()` retourne `did:ng:${session.private_store_id}` pour TOUTES les entités. -- `src/shared/hooks/useShapeWithDefaults.ts` — accepte un `storeNuri` mais l'appelant unique (`FestipodDataContext`) lui passe toujours le NURI du private_store. - -Entités impactées (toutes mélangées) : `FpEvent` (→ store partagé), `FpUserProfile` (→ partie privée/publique), `FpParticipation` (→ avec son event), `FpMeetingPoint` (local-only aujourd'hui), `FpFriendship` (local-only, privée). Cf. concept `data-layer` §entités. - -### Modèle cible proposé - -> **Note (2026-05-19)** : [[brief_2026-05-18_authorization-matrix]] a depuis dérivé, à partir des seuls points validés, une structure différente — 3 stores natifs par utilisateur + Dialog stores, **sans Group store** dans le périmètre actuel. La structure à 4 niveaux ci-dessous reste pertinente pour le périmètre élargi (communautés, collaboration multi-hôte), aujourd'hui hors périmètre. À reconcilier à l'exécution. - -Structure hiérarchique en **4 niveaux de Group stores** : index communautaire ⊃ communauté ⊃ event ⊃ meeting point. - -| Entité | Store cible | Justification | -|---|---|---| -| Event (métadonnées) | Group « communauté » | La communauté possède l'event → contrôle qui le modifie | -| Référence d'event (pointeur) | Group « index communautaire » | Discovery | -| Participation | Group « event » | N'a de sens que dans son event | -| MeetingPoint (métadonnées) | Group « event » | Le RDV appartient à l'event | -| Participation à un MeetingPoint | Group « meeting point » | RSVP scopé au RDV | -| UserProfile (partie publique) | public_store de l'utilisateur | Modèle natif | -| Friendship | private_store de l'utilisateur | Purement personnelle | - -### Contrainte SDK bloquante - -Primitives présentes au protocole mais **non exposées dans `@ng-org/web`** (vérifié `0.1.2-alpha.13`) : création de Group stores + invitations/permissions ; **dépôt/lecture d'inbox** (mécanisme retenu pour la notif d'inscription, cf. [[brief_2026-05-18_authorization-matrix]]). `app_request_stream` est la méthode générique la plus susceptible de porter ce mécanisme une fois exposée (à confirmer côté Rust). Cf. [[knowledge_stores-permissions]] §Limites SDK. - -**Implication :** le refactor *structurel* peut commencer sans attendre l'API, avec des placeholders (continuer à pointer `private_store_id` pour les Group stores impossibles). L'**aboutissement complet** (vrai multi-user) dépend de l'arrivée de l'API ou d'un contournement (voir [[brief_2026-05-21_fork-nextgraph-inbox]], [[brief_2026-06-15_shared-wallet-shim]]). - -### Implications côté code - -1. **Disparition de `ensureGraphNuri()`** comme helper unique → helpers par entité ou couche `storeRegistry` résolvant le NURI selon `(entité, contexte)`. -2. **`useShapeWithDefaults` reste un wrapper** mais l'appelant choisit explicitement le store (N appelants demain). -3. **Chaque entité déclare son store cible** (mapping centralisé ou convention shape→store). -4. **`bootstrapWallet()`** (`src/shared/utils/ngBootstrap.ts`) revu : seed réparti, ou seed = données de l'utilisateur courant seulement. -5. **`FestipodDataContext`** : hooks par entité, chacun avec son store résolu. - -## Open Questions - -1. Quand crée-t-on un Group store de communauté (API absente) ? Acte explicite vs communauté par défaut ? -2. Comment Bob connaît-il l'index communautaire d'Alice ? (possiblement via le public_store d'Alice) -3. Faut-il vraiment 4 niveaux ? Le « meeting point = group store » mérite validation. -4. Que devient le seed de démo quand les Group stores n'existent pas encore ? -5. Migration des wallets de test existants (script / wipe-reseed / ignore) ? -6. Bootstrap d'un user vierge : auto-créer un Group store « par défaut » ou attendre ? - -## Possible Approaches - -- **Refactor structurel d'abord, partage ensuite** (placeholders `private_store_id`). -- **Registry centralisé** vs **résolution par convention**. -- **Big-bang** vs **par entité** (commencer par Event). -- **Maintenir un mode mono-store** parallèle pour dev/demo. - -## Out of Scope - -Invitation effective (capability sharing), permissions par rôle, discovery cross-wallet, contournement de l'UI wallet, mode P2P direct sans broker. → second chantier multi-user dont ce refactor est le prérequis structurel. - -## Starting Points - -- Concept `data-layer` → [[decision_2026-03-17_private-store-nuri-scope]] (la décision qu'on viendra modifier), état du pattern d'écriture -- `src/shared/utils/ngGraph.ts`, `src/shared/hooks/useShapeWithDefaults.ts`, `src/shared/context/FestipodDataContext.tsx`, `src/shared/utils/ngBootstrap.ts` -- NextGraph docs : [Documents et Stores](https://docs.nextgraph.org/en/documents/), [Getting started](https://docs.nextgraph.org/en/getting-started/) diff --git a/.project/concepts/nextgraph-platform/brief_2026-05-21_fork-nextgraph-inbox.md b/.project/concepts/nextgraph-platform/brief_2026-05-21_fork-nextgraph-inbox.md deleted file mode 100644 index 51df519..0000000 --- a/.project/concepts/nextgraph-platform/brief_2026-05-21_fork-nextgraph-inbox.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -type: brief -summary: Forker temporairement nextgraph-rs pour exposer l'inbox au SDK JS (notif d'inscription, anonymat via from optionnel) — 3 couches : patch Rust (4 fichiers), auto-hébergement ngd+ng-app sur Coolify, intégration Festipod ; fork jetable abandonné quand l'upstream livrera sa solution -last_updated: 2026-05-21 ---- - -# Forker NextGraph pour exposer l'inbox au SDK JS - -**Status:** Court-circuité (2026-07-03, T02.b/c) — approche non retenue pour l'instant -**Last updated:** 2026-07-03 - -> **Court-circuité par l'inbox émulée en lib (T02.b/c).** Plutôt que de forker le broker pour exposer `inbox_post`, le namespace `inbox` de `@ng-eventually/client` **émule** l'inbox : `post`/`read`/`materialize`/`watch`, curateur **émulé inline**, dépôts via **SPARQL dans un document du `private_store`** — aucun patch Rust ni auto-hébergement `ngd` requis. L'inscription PdR est déjà câblée dessus (`joinEvent`/`leaveEvent` réels, Notification persistée en shape SHEX, cf. [[decision_2026-06-17_eventually-library]] §Inbox émulée). Ce brief reste conservé comme **plan de repli** si l'inbox broker native devenait nécessaire (anonymat crypto natif via `from = None`, que l'émulation ne fournit pas), et comme mémoire des chantiers Couche 3 (dont plusieurs — shapes MeetingPoint/Notification, joinEvent réel — sont **désormais faits**, T02.a). - -## Context - -Festipod doit notifier l'hôte d'un PdR quand quelqu'un s'inscrit, avec **identification si connexion / anonyme sinon** (cf. décision cadre inbox dans [[brief_2026-05-18_authorization-matrix]]). L'**inbox** NextGraph est idéale (le `from` optionnel donne l'anonymat) **mais n'est pas exposée au SDK JS** (cf. [[knowledge_stores-permissions]] §Inbox). Ce brief évalue **forker/patcher `nextgraph-rs`** pour l'exposer. - -### Posture stratégique (cadrée par l'utilisateur) - -Le fork est **explicitement temporaire, non destiné à l'upstream**. Hypothèse : NextGraph finira par exposer sa **propre** solution d'inbox au SDK JS, **possiblement différente**. Quand elle arrivera, on **abandonne le fork et on adapte Festipod**. Tant que leur solution n'est pas là : maintenir le fork à jour (rebase sur `upstream/main`, qui bouge vite en `0.1.2-alpha`) ; **déployer broker + ng-app depuis le fork** ; surveiller l'upstream pour basculer dès que possible. On ne vise **pas** une PR. - -## What We Know - -Trois couches. - -### Couche 1 — Le patch Rust : 4 fichiers (broker vanilla) - -1. **`engine/net/src/types.rs`** — `InboxMsgContent::Link` est une variante **unit** (stub) ; lui donner un payload (ou variante `Notification`) portant le NURI du PdR + lien vers l'`Inscription`. Ajouter un builder `InboxPost::new_link(...)` calqué sur `new_contact_details`. `from = None` → anonymat. -2. **`engine/verifier/src/request_processor.rs`** — ajouter le bras de commande manquant (pas de bras `InboxPost`). Idéalement une commande haut-niveau (`NotifyInbox`) construisant le post côté Rust (garde le scellement crypto en Rust). Calquer sur `SocialQueryStart`. -3. **`sdk/js/lib-wasm/src/lib.rs`** — exposer `pub async fn inbox_post_link(session_id, to_inbox_nuri, to_profile_nuri, link, anonymous)`, calqué sur `social_query_start`. -4. **`engine/verifier/src/inbox_processor.rs`** (`process_inbox`) — bras de réception qui **matérialise** le message en document dans le store de l'hôte (calquer sur le handler `ContactDetails`). L'app lit ensuite via ORM/SPARQL — pas de nouvelle API de lecture d'inbox. - -**Résolution d'identité** (connu/anonyme) : gratuite via SPARQL côté app (JOIN du NURI d'inbox émetteur contre les docs `social:contact`). **Découverte de l'inbox de l'hôte** : embarquer le NURI d'inbox du `public_store` de l'hôte dans le doc PdR ou le profil public (le flux QR-code de partage de profil le porte déjà). - -### Couche 2 — Déploiement (depuis le fork) - -Détail dans [[knowledge_integration-model]]. Le verifier patché tourne **dans l'iframe ng-app** → **construire et auto-héberger le `ngd` + le ng-app** depuis le fork, puis rebuilder le `@ng-org/web` de Festipod avec `NG_REDIR_SERVER`/`NG_DEV*` pointant sur ce ng-app. **Aucune réécriture de l'intégration Festipod** (reste iframe). Le routage inbox du broker est déjà natif, mais comme on auto-héberge le ng-app patché, **on déploie toute la stack depuis le fork** (un seul arbre source). - -- **Local** : `ngd` + ng-app du fork ; Festipod buildé avec `NG_DEV`/`NG_DEV_LOCAL_BROKER`. -- **Serveur de test** : `ngd` + ng-app du fork sur notre domaine ; Festipod buildé avec `NG_REDIR_SERVER=notre-domaine`. - -#### Hébergement sur Coolify — 3 pièces web - -1. **`ngd`** — démon WebSocket **stateful** : conteneur avec **volume persistant** pour `--base-path` (RocksDB + clés + PeerId, jamais wipé), mode `--domain` derrière le Traefik de Coolify. Build : Dockerfiles officiels cassés → **écrire notre Dockerfile multi-stage Rust** (RocksDB exige llvm/clang). Premier démarrage **interactif** (lien d'invitation wallet admin) → scripter via `ngcli` ou faire une fois à la main puis persister dans le volume. -2. **ng-app** (frontend iframe, wasm patché) — **build statique** (`pnpm webfilebuild`). Servi en statique (buildpack ou nginx). -3. **Routage** : un même domaine sert le statique du ng-app ET proxifie le WebSocket vers ngd. - -Plus **Festipod** lui-même (app Bun → skill `coolify-hosting` pour CELLE-CI, pas pour le `ngd` Rust). Drivers de complexité : build Rust+RocksDB sans Dockerfile prêt, conteneur stateful à volume critique, premier-run interactif, double-service (statique + WS). - -### Couche 1 (libs JS) — paquets npm clients patchés - -**On maintient des versions patchées des paquets clients, pas seulement le wasm.** Le forwarding générique permet *techniquement* d'atteindre une méthode wasm sans toucher le JS, mais c'est un **hack** (non typé, fragile) — test rapide seulement. À modifier réellement : - -- **`@ng-org/web`** — modifié de toute façon (URL broker) → y ajouter `inbox_post_link` dans la **surface d'API typée + `.d.ts`**. -- **Méthodes streamées** (si lecture inbox en *flux* un jour) — entrée des deux côtés (`E` + `streamed_api`). Pour la seule **écriture** (requête/réponse), inutile. -- **`@ng-org/orm`** — à modifier **si** on intègre l'écriture inbox au flux ORM. Sinon (appel `ng.inbox_post_link` à côté), inutile. -- **`@ng-org/alien-deepsignals`, `@ng-org/shex-orm`** — a priori inchangés. - -#### Outillage existant : `scripts/build-ng-packages.sh` - -`bun run build:ng` build les 4 paquets depuis `$NEXTGRAPH_RS/sdk/js/*` (défaut `../../nextgraph/nextgraph-rs`) → `pnpm pack` → `.tgz` dans `.ng-tarballs/` → `bun add` réécrit `package.json` vers les tarballs locaux. **Pattern d'origine du projet** : le commit `fd6d408` l'a abandonné quand les alphas ont été publiées sur npm. Pour repasser au custom : **réactiver `bun run build:ng`**. Nuances : `@ng-org/web` est TS pur (le script crée un *stub* `lib-wasm` ; le tarball porte l'API inbox typée + l'URL broker bakée, **pas** le wasm) ; pointer le script sur la **branche patchée** (retirer le `git pull --ff-only`) ; option recommandée : patcher `@ng-org/web` pour lire l'URL broker au **runtime** (évite de rebuilder par domaine). - -### Couche 3 — Intégration dans Festipod - -Exposer la méthode ne suffit pas. Chantiers (certains préexistent à l'inbox) : - -- **Modéliser le PdR.** Les SHEX (`src/shared/shapes/shex/festipodShapes.shex`) ne définissent qu'`Event`/`UserProfile`/`Participation` — **pas de `MeetingPoint`** (local-only), ni d'entité notification. Ajouter les shapes + `bun run build:orm`. -- **Implémenter l'inscription (aujourd'hui no-op).** Dans `FestipodDataContext.tsx`, `joinEvent`/`leaveEvent` sont des `console.log`. Le vrai flux : (a) écrire l'`Inscription` dans le `protected_store` de l'inscrit (via multi-store, [[brief_2026-05-17_multi-store-refactor]]), (b) appeler `ng.inbox_post_link(...)` pour notifier l'inbox du PdR. -- **Porter le NURI d'inbox de l'hôte** sur le doc PdR (ou lookup profil). -- **Lire et résoudre les notifications côté hôte** : lire les docs notification matérialisés (ORM/SPARQL), JOIN identité contre `social:contact`. UI : « N inscrits dont X identifiés ». -- **Câblage session** via `src/shared/utils/ngSession.ts`. - -**Dépendances** : présuppose (1) le fork SDK livré, (2) le refactor multi-store. **Surface jetable** : à l'arrivée de l'API officielle, migrer aussi ces points d'appel Festipod. - -## Open Questions - -- `NotifyInbox` haut-niveau vs `InboxPost` brut ? (haut-niveau préféré, garde la crypto en Rust) -- Où sourcer le NURI d'inbox de l'hôte (doc PdR vs lookup profil) ? -- Forme de la matérialisation côté réception (quels triples) ? -- Suppression côté inbox : un déposant peut-il retirer son dépôt ? (résiduelle, cf. [[brief_2026-05-18_authorization-matrix]]) -- Cadence de rebase du fork ? Critère de bascule vers la solution upstream ? -- `@ng-org/web` : patch runtime vs tarball par domaine ? -- `ngd` Coolify : automatiser le premier-run vs one-shot manuel persisté ? Un service (reverse-proxy maison) ou deux ? - -## Possible Approaches - -- **A. Fork temporaire + auto-hébergement (retenu comme stopgap)** — patch des 4 fichiers, déploiement depuis le fork. Vrai inbox, anonymat natif. Coût : maintenir le fork + héberger. Jetable. -- **B. Contribution upstream — écartée** comme objectif. -- **C. Pas de patch, détourner `social_query_start`** — repli, livrable tout de suite mais limité aux **contacts** (pas d'anonyme vers un hôte non-connecté). - -> Voir aussi [[brief_2026-06-15_shared-wallet-shim]] : le vrai multi-user (lecture cross-wallet) suppose en plus un patch `OpenRepo` + capabilities, au-delà de l'inbox. - -## Starting Points - -- [[knowledge_integration-model]], [[knowledge_stores-permissions]] -- [[brief_2026-05-18_authorization-matrix]] — la décision cadre inbox que ce patch sert -- Repo local `nextgraph-rs` : `sdk/js/lib-wasm/src/lib.rs`, `engine/verifier/src/{request_processor,inbox_processor}.rs`, `engine/net/src/types.rs` -- Remotes : `origin` = `git.nextgraph.org/slaivyn/nextgraph-rs` (fork perso), `upstream` = `git.nextgraph.org/NextGraph/nextgraph-rs` 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 deleted file mode 100644 index 640cf8b..0000000 --- a/.project/concepts/nextgraph-platform/brief_2026-06-15_shared-wallet-shim.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -type: brief -summary: Stopgap staging multi-user — deux visions cadrées. Lointaine (cible) — multi-wallet, 3 stores natifs par utilisateur + Dialog, 1 document par entité (événement/PdR), partage par capabilities, inbox native du PdR. Adaptée (stopgap) — UN wallet partagé, 1 document par entité dans son private_store, périmètre = métadonnée logique + index, filtre d'isolation applicatif, login simulé. Obstacles NextGraph — pas de lecture cross-wallet (OpenRepo TODO, ReadCap ignoré), capabilities/inbox non exposées au SDK, login non programmable. Shim désormais ENTIÈREMENT dans la lib ng-eventually (2026-07-02) : namespaces docs/storeRegistry/isolation/accounts ; l'app ne touche @ng-org au runtime que via ngSession (cf. decision_2026-06-17). sharedWalletShim + filtre = jetables à la migration. -last_updated: 2026-07-02 ---- - -# Stopgap multi-user : wallet partagé unique (`sharedWalletShim`) - -**Status:** **Shim entièrement migré dans la lib `ng-eventually` (2026-07-02)** — `storeRegistry`, couche comptes, filtre d'isolation **et** primitive `doc_create`/SPARQL vivent maintenant dans `@ng-eventually/client` (namespaces `docs`/`storeRegistry`/`isolation`/`accounts`), en plus du filtre de lecture ReadCap déjà porté. L'app ne consomme plus que la lib ; le domaine Festipod (mapping entité→scope, connexions, wrapper React des comptes) reste **injecté** côté app. Seul `ngSession.configure` touche encore `@ng-org` au runtime (+ 2 exceptions test-harness). Validé : lib 36/36 + `tsc` rc=0 ; suite BDD **78 passed / 0 failed / 71 skipped**. Détails dans [[decision_2026-06-17_eventually-library]] (§ « Shim migré dans la lib — 2026-07-02 »). Reste fonctionnel (indépendant de la migration) : reactivity in-app de la création (best-effort) + seeding multi-doc. - -## Objectif & posture - -Mettre Festipod en **staging** avec des **utilisateurs amicaux**, **sans enjeu de sécurité**, branché sur le vrai NextGraph, et **structuré au plus près de l'infra cible** pour qu'une migration soit un simple changement de résolveur, pas une réécriture. - -Trois choses doivent rester nettes pour ne pas dériver, et structurent ce brief : - -1. la **vision lointaine** — ce qu'on aura quand NextGraph offrira lecture cross-wallet, capabilities et inbox ; -2. les **obstacles NextGraph** — ce qui, aujourd'hui, empêche cette vision ; -3. la **vision adaptée** (stopgap) — au plus proche de la lointaine, compte tenu des obstacles. - -> **Invariant directeur** : tout ce que fait la vision adaptée doit avoir une **correspondance 1:1** explicite avec la vision lointaine (table en fin de section adaptée). Si un choix du stopgap n'a pas d'image claire dans la cible, c'est un signal de dérive. - ---- - -> **Direction (2026-06-17 → ATTEINTE 2026-07-02)** : ce polyfill devait être **encapsulé dans une librairie générique externe** (`ng-eventually-js`, hors repo) plutôt que dispersé dans l'app — voir [[decision_2026-06-17_eventually-library]]. **C'est fait, en totalité** : le routage du SDK (`useShape`/`init`/`ng`), le filtre de lecture ReadCap, **et** désormais `storeRegistry`, la couche comptes, le filtre d'isolation et la primitive `doc_create`/SPARQL vivent tous dans `@ng-eventually/client` (namespaces `docs`/`storeRegistry`/`isolation`/`accounts`, zéro Festipod — le domaine est injecté). L'app ne touche `@ng-org` au runtime que par le point d'injection unique `ngSession.configure` (+ 2 exceptions test-harness documentées). La description « encore in-app » du stopgap ci-dessous est donc **historique** : lire les fichiers cités comme des wrappers minces au-dessus de la lib. - -## 1. Vision lointaine (cible finale) - -Dérivée de [[brief_2026-05-18_authorization-matrix]] et [[knowledge_stores-permissions]]. Périmètre **validé** (hors communautés / listes curées / suivi, encore hors périmètre). - -### Identité & login -- **1 utilisateur = 1 wallet NextGraph.** Le wallet **est** l'identité ; pas de compte applicatif séparé. -- **Login = ouvrir son propre wallet** (redirect broker). C'est un vrai login par-utilisateur. - -### Stores & granularité documents -Modèle natif : `1 document = 1 repo = 1 frontière de permission = 1 inbox`. Un **store** est un document-conteneur qui regroupe et permissionne d'autres documents. Chaque utilisateur a **3 stores natifs** ; les entités sont des **documents individuels** dedans (pas un gros graphe par store). - -| Entité | Store (propriétaire) | Granularité | Notes | -|---|---|---|---| -| Événement | `public_store` du déclarant | **1 document / événement** | adressable par NURI (utile pour la déduplication) | -| Point de rencontre (PdR) | `public_store` de l'hôte | **1 document / PdR** | **possède son inbox native** (reçoit les inscriptions) | -| Profil réseau | `protected_store` | 1 document | nom, avatar, bio, ville, intérêts | -| Participation / Inscription | `protected_store` de l'inscrit | 1 document / inscription | + dépôt d'un lien dans l'**inbox du PdR** | -| Profil privé (settings, email) | `private_store` | 1 document | soi seul | -| Connexion A↔B | **Dialog store** A↔B | doc connexion (+ messagerie) | deux écrivains | -| Index des connexions | `protected_store` | 1 document | liste les NURIs des Dialog stores | - -**Aucun Group store** sur le périmètre validé (les 3 stores + Dialog + inboxes suffisent). - -### Partage & visibilité -- Par **capabilities** : on transmet un Nuri portant un read/write cap. **Public** = lisible par tous sans cap. **Protected** = cap obtenue via la connexion. **Privé** = soi. -- Ajout de permission asynchrone ; retrait synchrone (SyncSignature). - -### Inbox & notifications -- L'**inbox native du document PdR** reçoit les dépôts d'inscription (lien DID cap). `from` optionnel ⇒ **identifié si connexion de l'hôte, anonyme sinon**, gratuitement. - -### Découverte -- **Pas d'annuaire central.** On découvre via les `public_store` et le graphe de connexions (et plus tard `social_query`). - ---- - -## 2. Obstacles côté NextGraph (ce qui empêche la vision lointaine aujourd'hui) - -Vérifiés dans `nextgraph-rs` (2026-06-15). - -| Élément de la cible | Obstacle actuel | Preuve | -|---|---|---| -| Lire le store d'un **autre** utilisateur | `OpenRepo` **non implémenté** ; un NURI étranger lève `RepoNotFound` ; une session ne contient que ses 3 stores dans `self.repos` | `engine/verifier/src/verifier.rs:1423`, `request_processor.rs` `resolve_target` | -| Partager une **capability** (Nuri + droits) | non exposé au SDK ; le champ `access`/`ReadCap` du NURI **n'est jamais inspecté** | [[knowledge_stores-permissions]] | -| **Inbox** d'un document (notif d'inscription) | pas exposée au SDK JS (nécessite un fork moteur) | [[brief_2026-05-21_fork-nextgraph-inbox]] | -| **Login per-utilisateur** fluide | login **non programmable** (redirect web vers le broker) | [[decision_2026-06-15_shared-wallet-login-flow]] | - -**Conséquence centrale** : tant que la lecture cross-wallet n'existe pas, **aucune donnée ne franchit la frontière entre deux wallets**. Bob ne peut pas lire le `public_store` d'Alice. Toute approche « chacun son wallet » est donc bloquée à la racine. - ---- - -## 3. Vision adaptée (stopgap) — au plus proche de la cible - -### Principe : UN wallet partagé -Tous les utilisateurs amicaux ouvrent **le même** wallet. NextGraph ne voit qu'une identité → **tout est techniquement lisible** (on contourne l'absence de lecture cross-wallet en supprimant la frontière). Le « multi-utilisateur » devient une **fiction applicative**. - -### Granularité documents — **identique à la cible** : 1 document par entité -Pour rester fidèle, on reproduit les **deux niveaux** de la cible (conteneur → documents) : - -- chaque **événement** et chaque **PdR** = **son propre document** (`doc_create`), tous physiquement dans le `private_store` de l'unique wallet partagé ; -- le **périmètre** (public/protected/private) est une **métadonnée logique** portée par le document, **pas** un store physique ; -- un **document-index par (utilisateur × périmètre)** liste les NURIs des entités de ce périmètre — il **joue le rôle du futur store-conteneur** (`docPublic` ≈ futur `public_store`, etc.). - -Garder la granularité « 1 doc par entité » est ce qui rend la migration 1:1 **et** ce qui rendra l'**inbox du PdR** possible plus tard sans refonte (l'inbox est un attribut de document). - -| Entité | Périmètre logique | Document stopgap | Indexé dans | -|---|---|---|---| -| Événement | public | 1 doc / événement | `docPublic` du déclarant | -| PdR | public | 1 doc / PdR | `docPublic` de l'hôte | -| Profil réseau | protected | doc profil réseau | `docProtected` | -| Participation | protected | 1 doc / participation (ou groupé) | `docProtected` de l'inscrit | -| Profil privé | private | doc settings | `docPrivate` | -| Connexion A↔B | dialog | doc connexion | index de connexions | - -### `sharedWalletShim` (échafaudage, sans équivalent cible) -Index des **comptes** simulés → leurs documents-index : `username → profileId → { docPublic, docProtected, docPrivate }`. Ancré dans le `private_store` du wallet partagé (`session.private_store_id`, toujours connu → ancre de bootstrap). Rend possibles le **login cross-device** et le **picker d'utilisateurs**. **N'a aucun équivalent cible** (la cible n'a pas d'annuaire central) → **jetable**. - -### Partage simulé : filtre d'isolation applicatif -Un seul wallet ⇒ tout lisible. Pour **se comporter** comme la cible, la couche données filtre les lectures par `currentAccountId` + connexions : `private` → propriétaire ; `protected` → propriétaire + connexions ; `public` → tous. Remplace les **capabilities** (pas appliqué par la crypto, mais honoré par l'app) → **jetable**. - -### Identité & login simulés -- **Couche réelle (technique, invisible)** : le redirect broker du wallet partagé, présenté comme **barrière d'accès à l'environnement** (pas un login). Cf. [[decision_2026-06-15_shared-wallet-login-flow]]. -- **Couche applicative (le login perçu)** : écran « Connexion » = **username seul** (déclaratif, sans mot de passe) → `localStorage`. « Déconnexion » = efface le username, sans toucher NG. Vrai logout planqué. - -### Inbox / notification d'inscription -**Hors périmètre du stopgap** (nécessite le fork). Mais la granularité « 1 doc/PdR » est le **pré-requis** qui la rendra branchable plus tard sans refonte. - -### Correspondance stopgap → cible (l'invariant 1:1) - -| Stopgap | Vision lointaine | Migration | -|---|---|---| -| doc événement (dans le wallet partagé) | doc événement dans le `public_store` du déclarant | déplacer le doc + appliquer cap publique | -| doc PdR | doc PdR dans le `public_store` de l'hôte **+ inbox** | déplacer + brancher l'inbox | -| `docPublic`/`docProtected`/`docPrivate` (index) | `public_store`/`protected_store`/`private_store` | l'index devient le store natif | -| filtre d'isolation applicatif | capabilities (read caps via connexion) | retirer le filtre, poser les caps | -| login applicatif (username) | login = ouvrir son wallet | retirer la couche compte | -| `sharedWalletShim` | — (rien) | supprimer | -| wallet partagé unique | un wallet par utilisateur | éclater par propriétaire | - -**Migration = swap du résolveur `storeRegistry`** (de « doc dans le wallet partagé » vers « doc dans le vrai store du propriétaire ») + déplacement des documents + pose des capabilities + suppression de l'échafaudage. **Les écrans ne changent pas.** - ---- - -## Familles de contournement (pourquoi A) - -| Famille | Idée | Verdict | -|---|---|---| -| **A — wallet partagé** | un seul wallet, multi-user simulé côté app | **retenue** : livrable vite, zéro travail moteur, local-first préservé | -| B — NG comme backend | un backend Bun détient un wallet, clients HTTP | écartée : abandonne le local-first, plus lourd | -| C — lecture cross-wallet | chacun son wallet, lecture du public des autres | **infaisable** (obstacle §2) sans fork moteur | -| D — fork moteur (`OpenRepo` + capabilities) | rendre la cible réelle | hors stopgap : c'est le chemin cible, lourd (cf. [[brief_2026-05-21_fork-nextgraph-inbox]]) | - ---- - -## État d'implémentation (2026-06-16) - -Deux drapeaux de build, **OFF par défaut** (le mono-store validé reste le défaut ; dev/`@ui`/`@e2e` inchangés) : -- **`FESTIPOD_STAGING=1`** — flux login option 2 (découplé de `NODE_ENV` pour ne pas bloquer `@e2e`). -- **`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` | ✅ livré, pur, vérifié | -| storeRegistry + sharedWalletShim | `src/shared/utils/storeRegistry.ts` | ✅ **1 doc/entité** (`createEntityDoc`/`listEntityDocs` + index par périmètre) ; primitives **validées broker** | -| Câblage multi-document (reads fan-out `{graphs}` + write per-entité `createEntityDoc`) | `FestipodDataContext` (useNgData) derrière `MULTISTORE` | ✅ lecture fan-out validée ; ⚠️ reactivity de la création in-app = **best-effort** (le nouveau doc est ajouté au fan-out, l'`@id` peut être en attente jusqu'au re-subscribe) | -| Validation broker (`@data`) | `src/modules/workshop/{features/multistore-stopgap.feature, steps/data/multistore.steps.ts}` | ✅ **3 scénarios verts** (ORM-sur-doc-créé, shim r/w, **fan-out par entité**) | - -**Granularité = 1 document par entité (fait)** : public (événements/PdR) → un `doc_create` **par entité**, NURI ajouté à l'**index** du périmètre (le futur store-conteneur) ; protected (profil, participations) → **groupé** dans l'index protected. Lecture publique = `listEntityDocs('public')` → `useShape({graphs:[…]})`. Mono-store (défaut) inchangé. - -**Validation broker (2026-06-16, `@data` contre `nextgraph.net`, 3 scénarios verts)** : -1. ✅ `doc_create("Graph","data:graph","store",undefined)` → NURI utilisable comme `@graph` ORM (write+read d'une `Participation` 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` (`readBindings` tolérant, OK en pratique). -3. ✅ **Fan-out par entité** : 2 comptes × 1 doc-événement (`createEntityDoc` + indexé), un `useShape({graphs:[docA,docB]})` lit **les deux** événements, l'index public liste les deux docs. -4. ⏳ **Reste** : reactivity de la création in-app (best-effort, à itérer sur broker) + seeding multi-document (auto-seed neutralisé en `MULTISTORE`). - -## Open Questions - -- **Modèle d'écriture de l'événement** (propriétaire / wiki / immuable) — *ouvert dans la matrice*. Propriétaire/immuable → événement = doc dans le `public_store` du déclarant (granularité par entité exacte). **Wiki** → exigerait un Group store (hors périmètre) et **changerait la cible**. -- **Création des documents** : `doc_create` à la création de l'entité (retenu par la granularité par entité) ; création paresseuse des index de périmètre. -- **Picker d'utilisateurs** : saisie libre vs liste des comptes du `sharedWalletShim`. - -## Out of Scope - -- Le **vrai multi-user** (lecture cross-wallet) — fork moteur (`OpenRepo` + capabilities), voir [[brief_2026-05-21_fork-nextgraph-inbox]]. -- L'**inbox du PdR** (notif d'inscription) — même fork. -- L'**auto-hébergement** du broker/ng-app (staging sur `nextgraph.net`). -- Toute **sécurité réelle** (credential partagé, pas de mot de passe, pas de chiffrement par utilisateur). - -## Starting Points - -- [[brief_2026-05-18_authorization-matrix]] — périmètres et partition de la vision lointaine -- [[brief_2026-05-17_multi-store-refactor]] — l'indirection `storeRegistry` -- [[brief_2026-05-21_fork-nextgraph-inbox]] — le chemin cible réel (cross-wallet + inbox) -- [[decision_2026-06-15_shared-wallet-login-flow]] — flux login/logout -- [[knowledge_stores-permissions]] — stores, capabilities, inbox, limites SDK -- `src/shared/utils/storeRegistry.ts`, `src/shared/context/FestipodDataContext.tsx`, `src/app/AuthGate.tsx`, `src/shared/utils/isolation.ts` -- Source `nextgraph-rs` : `sdk/rust/src/tests/sparql_regressions.rs:136-200` (multi-document), `engine/verifier/src/verifier.rs:1423` (TODO `OpenRepo`) diff --git a/.project/concepts/nextgraph-platform/decision_2026-06-15_shared-wallet-login-flow.md b/.project/concepts/nextgraph-platform/decision_2026-06-15_shared-wallet-login-flow.md deleted file mode 100644 index 23c10a9..0000000 --- a/.project/concepts/nextgraph-platform/decision_2026-06-15_shared-wallet-login-flow.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -type: decision -summary: Flux login/logout du stopgap wallet partagé — le vrai login NextGraph (redirect broker) apparaît en premier, perçu comme une barrière technique d'accès à l'environnement ; l'écran applicatif « Connexion » (username seul → localStorage) EST le login perçu ; « Déconnexion » efface juste le username sans toucher NG ; vrai logout planqué -last_updated: 2026-06-15 ---- - -# Décision 2026-06-15 — Flux de login/logout du stopgap wallet partagé - -Arbitrage du flux d'authentification perçu pour le stopgap [[brief_2026-06-15_shared-wallet-shim]]. Frozen. - -## Contrainte de départ - -Le login NextGraph **n'est pas programmable** : c'est une **redirection web** vers la page du broker (`nextgraph.net`). Impossible d'ouvrir le wallet partagé en silence — il faut au minimum un passage par le redirect broker, au moins une fois par device. La question n'est donc pas *« comment éviter le redirect »* mais *« comment l'ordonner et le présenter »* pour que l'UX reste cohérente. - -## Décision : option 2 — gate technique d'abord, « Connexion » applicative ensuite - -Deux couches d'auth distinctes, présentées dans cet ordre : - -1. **Couche réelle (technique, non perçue comme login)** — le redirect broker apparaît **immédiatement, avant tout rendu de l'app**. Comme il précède l'app, l'utilisateur le lit comme une **barrière technique d'accès à l'environnement de test** (type mur de beta), **pas** comme un login applicatif. Mêmes credentials partagés pour tous (donnés dans l'invitation, façon « code d'accès »). Une fois par device, puis persistant. **Jamais étiqueté « login ».** Un splash Festipod minimal précède le redirect pour donner du contexte. -2. **Couche applicative (perçue comme LE login)** — écran **« Connexion »** = saisie du **username** (→ `localStorage`, `currentAccountId`). C'est le login *dans la perception* de l'utilisateur. **Sans mot de passe** (décision username-seul) → connexion **déclarative** : n'importe qui prend n'importe quel username (cohérent zéro-sécurité / amis). **« Déconnexion »** = efface **seulement** le username et revient à l'écran « Connexion » ; **n'appelle aucune fonction NG**. - -Le **vrai logout** (`ng.session_stop` / `user_disconnect` / `wallet_close`) reste **planqué** (réglages/debug), car il force un nouveau redirect. - -Le label **« Connexion »/« Déconnexion »** (et non « Changer de profil ») est un choix explicite : on assume de faire passer le username pour le login applicatif, puisque la barrière technique n'est pas perçue comme tel. - -## Pourquoi (vs option 1 écartée) - -**Option 1 écartée** — faux login d'abord (username), puis page d'avertissement « saisissez tel username/password », puis bouton *Continuer* déclenchant le redirect. Rejetée : workflow étrange, **double-login dissonant** (« je me suis déjà connecté, pourquoi je recommence ailleurs ? »), page d'avertissement qui **ressemble à une arnaque**, et le redirect **ressurgit en plein usage** à chaque expiration de session. - -**Option 2 retenue** parce que : -- **Cohérence du modèle mental** : la barrière technique n'étant pas perçue comme un login, la paire **Connexion/Déconnexion** applicative est complète et auto-cohérente — plus aucun mismatch sur le logout (se déconnecter ramène à l'écran de connexion, les deux dans la même couche). -- **Dégradation gracieuse** : un re-gate après redémarrage navigateur (perte de `sessionStorage`) se lit comme « reconnexion à l'environnement », pas comme un bug. -- **Implémentation plus simple** : `NextGraphContext` fait déjà le flux `connect`/redirect ; l'écran « Connexion » est un écran in-app normal ; pas de page d'avertissement bespoke. -- **Similarité avec l'infra cible** (objectif directeur du stopgap) : la forme **« redirect broker → app »** est exactement le flux du vrai multi-wallet. À la migration, on **supprime l'écran « Connexion » username** et la **barrière technique devient le vrai login per-user** — la forme du flux ne change pas. - -## Faits techniques vérifiés (`nextgraph-rs`, 2026-06-15) - -- **Persistance de session : OUI.** Wallet mémorisé côté iframe broker (`localStorage` long-terme + `sessionStorage` pour la session active) ; au rechargement, `init()` retrouve la session **sans re-déclencher le redirect** tant que la session broker existe (`sdk/js/web/src/index.ts`, `sdk/js/api-web/main.ts`). Un **redémarrage complet du navigateur** (perte de `sessionStorage`) peut re-déclencher le gate. -- **Logout réel exposé : OUI.** `ng.session_stop()`, `ng.user_disconnect()`, `ng.wallet_close()` (`sdk/js/lib-wasm/src/lib.rs`) ; arrêtent la session / effacent le wallet ; **forcent un nouveau redirect** ensuite → d'où le choix de **ne pas** les appeler dans la « Déconnexion » applicative et de planquer le vrai logout. - -## Conséquences côté code (Festipod) - -- `NextGraphContext` — déclencher le `connect`/redirect **au boot**, avant le rendu de l'app (+ splash pré-redirect). -- Un écran applicatif **« Connexion »** (username → `localStorage` / `currentAccountId`), username résolu contre les comptes du `sharedWalletShim`. -- Une **« Déconnexion »** qui efface seulement le username (aucun appel NG). -- Vrai logout exposé seulement en réglages/debug. - -## See Also - -- [[brief_2026-06-15_shared-wallet-shim]] — le stopgap que cette décision complète -- Concept `data-layer` — `NextGraphContext`, auto-init conditionnel, flux redirect broker 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 deleted file mode 100644 index 875f1cd..0000000 --- a/.project/concepts/nextgraph-platform/decision_2026-06-16_discovery-model.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -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). - -> **Réalité d'implémentation (T02.e, 2026-07-03) — divergence assumée avec le stopgap décrit ici.** Ce qui **ship aujourd'hui** est le **fan-out cross-compte sur les docs publics de tous les comptes** (`FestipodDataContext` : « Public discovery (T02.e): cross-account fan-out, ALWAYS on » ; `listEntityDocs('public')` sur tous les comptes) — Alice voit l'événement public de Bob **sans connexion**. C'est **précisément la voie que cette décision qualifiait de « dérive »** à remplacer par un **index global unique** dans le wallet partagé. L'index global (cible) **n'est pas** implémenté ; le fan-out est le mécanisme de découverte réel du wallet-partagé staging. La **cible** (index global alimenté par inbox, propriétaire à trancher) reste valable ; le corps ci-dessous la décrit et n'est pas réécrit. Vérifier : `grep -n "cross-account fan-out" src/shared/context/FestipodDataContext.tsx`, `resolveReadGraphs`/`listEntityDocs` dans `storeRegistry`. - -## 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_assisted-wallet-import.md b/.project/concepts/nextgraph-platform/decision_2026-06-17_assisted-wallet-import.md deleted file mode 100644 index 203ad56..0000000 --- a/.project/concepts/nextgraph-platform/decision_2026-06-17_assisted-wallet-import.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -type: decision -summary: Distribution du wallet partagé par IMPORT ASSISTÉ PAR FICHIER (.ngw) — l'auto-import zéro-touche étant impossible (broker hébergé), Festipod sert le FICHIER du wallet (téléchargement) + le mot de passe et guide un import unique sur nextgraph.eu « Import a Wallet File » depuis l'AccessGateScreen. Le TextCode a d'abord été retenu puis CORRIGÉ (transfert temporaire 5 min, inutilisable à embarquer). Barrière d'accès ON par défaut (ACCESS_GATE_DISABLED=1 pour bypass tests/dev), ancien LoginScreen retiré. Alternatives écartées (auto-import app, lien magique, self-host) ; provisioning de test (storageState) distinct -last_updated: 2026-06-29 ---- - -# Décision 2026-06-17 — Distribution du wallet partagé par import assisté - -Comment un utilisateur récupère le wallet partagé sur un nouveau navigateur, dans le stopgap [[brief_2026-06-15_shared-wallet-shim]]. Frozen. - -## Contrainte de départ - -L'auto-import zéro-touche par l'app est **impossible** avec le broker hébergé (fait vérifié : [[knowledge_broker-import-constraint]]). Le wallet doit préexister dans le navigateur, importé sur `nextgraph.eu` (cross-origin, non pilotable par Festipod). La question n'est donc pas « comment auto-importer » mais « comment **minimiser la friction de récupération** » — le problème initial étant que l'utilisateur devait d'abord *se procurer* le wallet. - -## Décision : import assisté par FICHIER - -Festipod **sert le FICHIER `.ngw`** du wallet partagé (téléchargement) et **affiche le mot de passe** dans l'`AccessGateScreen` (la barrière d'accès, cf. [[decision_2026-06-15_shared-wallet-login-flow]]), avec un guide en 3 étapes : - -1. Télécharger le fichier du wallet partagé (bouton de téléchargement). -2. Ouvrir `https://nextgraph.eu/#/wallet/login` (nouvel onglet) → « Import a Wallet File » → choisir le fichier → saisir le mot de passe affiché. -3. Revenir et cliquer « Entrer » (redirect broker → demande de déverrouiller le wallet → mot de passe → app). - -Festipod **fournit** ainsi le wallet (fin de la friction de récupération) ; l'**import lui-même reste un geste manuel unique par device**, incompressible avec le broker hébergé. Posture **zéro-sécurité, credential partagé** assumée (cf. brief) → embarquer le fichier + le mot de passe est cohérent. - -> **Correction 2026-06-25 (le TextCode était une fausse piste)** : la 1ʳᵉ version embarquait le **TextCode**. Or le TextCode est un **transfert temporaire** (5 min, deux devices en ligne, usage unique — cf. [[knowledge_broker-import-constraint]]), donc **inutilisable embarqué** (un testeur arrivant plus tard aurait un code mort). Le test e2e passait quand même car il génère+importe le code dans la foulée. La primitive correcte est le **FICHIER statique**. - -## Pourquoi (alternatives écartées) - -- **(a) Auto-import embarqué par l'app** — *impossible* : le broker ne laisse aucune fenêtre d'exécution avant son gate wallet ([[knowledge_broker-import-constraint]]). -- **(b) TextCode embarqué** — *cassé* : transfert temporaire 5 min, non réutilisable (cf. correction ci-dessus). -- **(c) Lien magique pré-rempli** vers le broker — pas de route d'import par URL côté broker hébergé. -- **(d) Self-host / fork du ng-app** — seule voie vers le **vrai zéro-touche**, mais lourde ; track séparé ([[brief_2026-05-21_fork-nextgraph-inbox]]). Non retenu pour le stopgap. - -## Conséquences côté code (Festipod) - -- `src/modules/auth/sharedWallet.ts` — `SHARED_WALLET_PASSWORD` lu depuis un **global gravé au build** `globalThis.__FESTIPOD_SHARED_WALLET_PASSWORD__` (`define` dans `build.ts`, depuis `FESTIPOD_SHARED_WALLET_PASSWORD`) ; `SHARED_WALLET_FILE_URL = /shared-wallet.ngw` ; `hasSharedWallet()` (mot de passe non vide) pilote l'affichage. Vide par défaut → la barrière retombe sur le flux simple. -- `build.ts` — copie le fichier (`FESTIPOD_SHARED_WALLET_FILE`) dans le bundle en `/shared-wallet.ngw` + grave le mot de passe. -- `src/modules/auth/screens/AccessGateScreen.tsx` — section assistée (téléchargement du fichier + mot de passe + guide) affichée si `hasSharedWallet()`. -- `src/app/AuthGate.tsx` — la barrière est **ON PAR DÉFAUT** (« Festipod ne fonctionne jamais sans NextGraph »). **Révision 2026-06-29** : drapeau **inversé** — la barrière n'est désactivée que si `globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ === true` (gravé par `build.ts` depuis `ACCESS_GATE_DISABLED=1`, ou injecté par le harness via `context.addInitScript` pour `@e2e`). Absent → barrière ON. (Remplace l'ancien `FESTIPOD_STAGING`/`__FESTIPOD_REQUIRE_NG__`, qui était OFF par défaut.) -- **Ancien `LoginScreen` retiré** (`/login`, bouton « Se connecter avec NextGraph » + login démo email/mdp) : obsolète puisque l'`AccessGateScreen` précède le routeur. Évite le dead-end « connecter sans wallet ». Route `/login` supprimée. -- **Atterrissage post-login** : après le choix du pseudo, `ConnexionScreen` navigue vers `/home` (la redirection vers l'accueil que faisait l'ancien `LoginScreen` avait disparu avec lui → on retombait sur l'onboarding `WelcomeScreen` à `/`). Filet pour les retours : `WelcomeScreen` redirige vers `/home` si déjà connecté. L'e2e `@humain` va désormais jusqu'à l'accueil pour couvrir ça. -- L'admin exporte le fichier une fois (nextgraph.eu : menu wallet → Download/Export Wallet File) et l'injecte au build (`FESTIPOD_SHARED_WALLET_FILE= FESTIPOD_SHARED_WALLET_PASSWORD= bun run build` ; barrière ON par défaut, pas de drapeau à poser). - -## À distinguer du provisioning de test - -Le harness multi-navigateur provisionne le wallet partagé par **injection storageState** (niveau navigateur, sans la contrainte broker) — concept `bdd-testing` → `knowledge_multibrowser-harness`. Cela **prouve** « wallet partagé → app connectée » mais **court-circuite l'import**. Le mécanisme RÉEL est validé **de bout en bout par la vraie app** (e2e `@humain`) : un navigateur vierge ouvre l'app staging, **télécharge le fichier proposé par l'`AccessGateScreen`** (et vérifie que le mot de passe affiché est celui du wallet), l'importe via le vrai flux `nextgraph.eu` « Import a Wallet File », revient, clique « Entrer » et atteint l'app connectée. Les deux ne se confondent pas. - -## See Also - -- [[knowledge_broker-import-constraint]] — le fait technique qui force cette décision -- [[brief_2026-06-15_shared-wallet-shim]] — le stopgap -- [[decision_2026-06-15_shared-wallet-login-flow]] — le flux d'accès dont l'AccessGateScreen est la barrière -- [[knowledge_stores-permissions]] — wallet, capabilities, limites SDK 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 deleted file mode 100644 index e262112..0000000 --- a/.project/concepts/nextgraph-platform/decision_2026-06-17_eventually-library.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -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. MAJ T02.b/c (2026-07-03) : le namespace inbox (post/read/materialize/watch, curateur ÉMULÉ inline, dépôts SPARQL dans un doc du private store) est IMPLÉMENTÉ et l'inscription PdR est réellement câblée (joinEvent persiste Participation + dépôt inbox hôte + Notification ; leaveEvent DELETE-WHERE autoritatif) — court-circuite l'approche fork broker. -last_updated: 2026-07-03 ---- - -# 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** : **ReadCap émulées** dans un registre **par DOCUMENT** (`CapRegistry` : qui détient la read/write-cap de chaque NURI ; docs publics lisibles sans cap), enforcées **génériquement** par le client. L'unité d'accès est le **document = le `@graph`** de l'item, **jamais l'item** — fidèle au modèle vérifié ([[knowledge_stores-permissions]] : un store est un repo conteneur ; détenir la cap du store ne donne PAS celles des repos qu'il référence ; pas d'héritage de lecture). En mono-store (tout dans un repo) le filtre est donc **tout-ou-rien** sur ce document → la granularité fine **exige 1 document par entité**. L'app **ouvre/accorde les caps** via des opérations anticipées (`open(doc, scope, owner)`, `grantRead`, `makePublic`) — **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 ReadCap émulées (registre par document) en vraies caps NextGraph** (é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-25) - -**Tout le runtime NextGraph de l'app passe par la lib** (en passthrough — la lib forwarde au vrai SDK, mécanismes du polyfill encore stubés) : - -- `@ng-eventually/client` en **dépendance locale** (`file:../../nextgraph/ng-eventually-js/packages/client`). -- Surface routée via `@ng-eventually/client` : **`useShape`** (`useShapeWithDefaults`, `harness-ng`), **`init`** et **`initNg`** (signals), **`ng`** (login) dans `ngSession`. -- **Point d'injection unique** : `ngSession` importe le vrai SDK **uniquement** pour `configure({ ng, useShape, init, initNg })`, puis utilise les exports de la lib. L'engine ORM reçoit le vrai `ng` (passé à `initNg`) — plomberie interne, pas un appel applicatif. -- **Exception assumée** : `src/shared/test-harness/auth-setup.tsx` (bootstrap du wallet de test, antérieur à `configure`) reste sur `@ng-org/web`. Les imports **de types** restent aussi sur `@ng-org/*`. -- La lib expose `init`/`initNg` (forwarders, `src/lifecycle.ts`) ; `EventuallyConfig` accepte `init`/`initNg` ; `NgLike`/`UseShapeLike` assouplis pour le vrai SDK. -- **Types via la lib (2026-06-25)** : la lib **ré-exporte** `ShapeType`/`BaseType`/`Schema`/`DeepSignalSet`/`NG` ; l'app importe ses types depuis `@ng-eventually/client`. `export type` est **effacé au build** → **aucun import runtime `@ng-org`** ajouté dans la lib (pas de double copie). `@ng-org` en **devDependencies** de la lib (typecheck seulement). -- **Point d'injection unique (option 1)** : dans l'app, **seul `ngSession`** importe le vrai SDK au runtime — uniquement pour `configure(...)`. Tout le reste de l'app (data, lifecycle, login, types) passe par la lib. - - **Pourquoi pas « lib importe le SDK elle-même »** : la lib étant dans un **repo séparé** (arbre `node_modules` distinct), si elle importait `@ng-org` au runtime, le bundle aurait **deux copies** d'`@ng-org` → l'ORM (signaux mono-instance) casserait. L'injection garantit **un seul exemplaire** (celui de Festipod). *(Le « zéro accès direct » exigerait la lib en workspace dans le repo — écarté pour la garder externe ; cf. options 2/3 discutées.)* - - **Exceptions assumées** (hors « app ») : `src/shared/test-harness/auth-setup.tsx` (bootstrap wallet de test) et `src/shared/test-harness/harness.tsx` (harness **mock**, `deepSignal`) gardent un import direct `@ng-org`. Les **bindings ORM générés** (`festipodShapes.*`) aussi (types générés). -- **Filtre ReadCap — IMPLÉMENTÉ & validé (2026-06-29, refactor du modèle grant→ReadCap)** : `caps.ts` — `CapRegistry` (read/write-cap **par document NURI** + docs publics ; `open/grantRead/grantWrite/makePublic/canRead/canWrite/governsRead/hasReadPolicy`). `read-filter.ts` — `makeReadFilteredView` (un **Proxy** sur le set réactif : itération/`size`/`forEach` gardés par `caps.canRead(item['@graph'], utilisateur)` ; un item sans `@graph` ou dans un document non gouverné est conservé ; mutations forwardées) + `filterReadable` (pur). `useShape` l'applique **uniquement si `caps.hasReadPolicy()`** (sinon passthrough → pas de régression). **Plus de `grantOf` injecté** : le filtre lit l'`@graph` et consulte le registre — automatique et domaine-agnostique. Validé : **6 tests `caps` + 4 tests `read-filter`** (logique + Proxy + utilisateur dynamique + non-héritage entre documents) **et un scénario `@data`** sur le **vrai `DeepSignalSet`** contre le broker : on gouverne le document du wallet par une ReadCap accordée à un autre utilisateur → l'utilisateur courant voit **0** ; il obtient la cap → il voit **toutes** les participations (tout-ou-rien en mono-store, fidèle). -- **Validé (global, 2026-06-29)** : `@data` ReadCap 5/5 steps contre le broker · lib (typecheck `rc=0` + **10 tests**). *(2 échecs e2e préexistants « J'y serai » = libellé obsolète depuis le portage redesign 5a29938, hors périmètre — l'app ne déclare aucune cap, `useShape` reste en passthrough.)* - -Reste à implémenter dans la lib (stubs `TODO`, nécessitent la couche comptes/caps pour être *actifs* dans l'app) : **garde d'écriture** (`caps.canWrite` est prêt côté registre), **`inbox.post`** + matérialisation, **login wallet partagé**. - -### Intégration du shim mono-wallet (merge 2026-06-30) - -Le merge de `main` (shim staging wallet partagé : `storeRegistry`, comptes, isolation, e2e multi-navigateur) a ramené du code écrit contre le SDK brut. - -**Limite découverte (validée en suite complète, 2026-06-30)** : `doc_create` (et les appels SPARQL du shim) **ne peuvent PAS passer par le proxy `ng` de la lib**. Le `ng` de `@ng-org/web` est déjà un **proxy iframe (RPC postMessage)** ; l'envelopper dans le `Proxy` JS de `makeNg` (double proxy) casse le marshaling de `doc_create` → `DataCloneError: function ... could not be cloned`. Tenté (`storeRegistry`+`harness` routés via la lib) → **4 scénarios multistore rouges** ; **annulé**. - -**Frontière d'intégration retenue** : -- **Passent par la lib** (validés) : `useShape` (ORM + filtre ReadCap), `init`/`initNg`, `login`. -- **Restent sur le vrai `ng`** (`@ng-org/web`) : `doc_create` + SPARQL du shim — dans `storeRegistry.ts` (app) et `harness-ng.tsx` (`createSmokeDoc`). C'est cohérent avec « shim **encore in-app** » : quand `storeRegistry` **migrera dans la lib**, il utilisera le `ng` **réel injecté** (`getConfig().ng`) en interne — **pas** le proxy public → plus de double-proxy. - -Imports `@ng-org` runtime de l'app après merge : point d'injection (`ngSession`) + `storeRegistry`/`harness-ng` (doc_create, le temps que le shim rejoigne la lib) + exceptions documentées (`auth-setup`, `harness` mock) + bindings ORM `import type`. - -**Encore in-app** (à migrer dans la lib ensuite) : `storeRegistry`, `AccountContext`, filtre d'**isolation** (`isolation.ts`) — distinct du filtre **ReadCap** de la lib ([[brief_2026-06-15_shared-wallet-shim]]). **TODO lib** : exposer une primitive `doc_create`/SPARQL côté lib qui utilise le `ng` injecté (évite le double-proxy) pour que l'app n'ait plus jamais besoin du `ng` direct. - -### Shim migré dans la lib — TERMINÉ & validé (2026-07-02) - -Le TODO ci-dessus est **fait** : **tout le shim est désormais DANS la lib**. La frontière d'intégration a bougé de « `doc_create` reste sur le vrai `ng` / shim encore in-app » à **« tout est dans la lib ; l'app ne touche `@ng-org` au runtime que via `ngSession` »**. - -- **Primitive `doc_create`/SPARQL — FAITE.** Namespace **`docs`** de la lib : `docCreate(sessionId, crdt, cls, dest, store?)`, `sparqlUpdate(sessionId, query, anchor?)`, `sparqlQuery(sessionId, query, base?, anchor?)`. En interne appelle le **`ng` RÉEL injecté** (`getConfig().ng`), **JAMAIS** le proxy public `makeNg` → pas de double-proxy, pas de `DataCloneError`. C'est la résolution de la limite du 2026-06-30. -- **Nouvelles surfaces lib** (exposées en **namespaces** dans `src/index.ts`, calquées sur `docs`/`inbox`) : - - **`storeRegistry`** — mécanique générique (résolveur `(account, scope)→NURI`, `createEntityDoc`/`listEntityDocs` + index par périmètre, `sharedWalletShim` ancré dans le `private_store`, cache, `ensureAccount`/`allAccounts`). **Zéro Festipod** : le mapping entité→scope (`EntityKind`/`entityScope`) reste **injecté par l'app** via `configureStoreRegistry({ getSession, normalizeUser })`. - - **`isolation`** — `applyIsolation` **pur** (matrice public=tous / protected=owner+connexions / private=owner) ; accessors (`ownerOf`/`scopeOf`) **et** le graphe de connexions **injectés par le consommateur** — la lib n'invente pas les connexions. - - **`accounts`** — `AccountStore` (faux login localStorage, storage **injecté**) + `normalizeUsername`. Le wrapper **React** (`Context`/`Provider`) **n'est PAS porté** : il reste dans l'app (couche mince), la lib n'impose pas React. -- **Décision isolation↔ReadCap = COEXISTENT** (ne pas fusionner) : axes distincts — **ReadCap** = capacité **par-document** broker-native ; **isolation** = visibilité **sociale par-item** (owner + scope + graphe de connexions). Le `protected` dérivé des connexions n'a pas d'équivalent dans le modèle doc-cap. -- **App recâblée** : `storeRegistry.ts` = `EntityKind`/`entityScope` + `configureStoreRegistry` + ré-export de la lib ; `AccountContext` = wrapper mince (clé historique `festipod.account.username` épinglée → zéro changement de comportement) ; `isolation.ts` = wrapper Festipod sur la lib ; `harness-ng.tsx` `createSmokeDoc` = `docs.docCreate`. -- **Invariant atteint** : `grep -rn "from '@ng-org" src/ | grep -v "import type"` ne liste plus que **`ngSession`** (injection `configure`) + les 2 exceptions test-harness documentées (`auth-setup.tsx`, `harness.tsx`/`deepSignal`). Plus aucun `doc_create` via le proxy public. -- **Validation** : lib **36/36 `bun test` + `tsc --noEmit` rc=0** ; app `bun run build` + bundle `harness-ng` OK ; **suite BDD complète 78 passed / 0 failed / 71 skipped** (baseline 2026-06-30 respectée, dont les 3 `@data` multistore, `@humain`, ReadCap `@data`). - -### Inbox émulée dans la lib — FAIT & câblée dans l'app (2026-07-03, T02.b/c) - -Le « Reste à implémenter » ci-dessus (inbox `post` + matérialisation) et le stub `inbox.post` **sont faits** ; l'inscription PdR est réellement branchée. La matérialisation ne passe **pas** par un curateur/package séparé différé mais par un **curateur ÉMULÉ inline** dans la lib. - -- **Namespace `inbox` de la lib** — implémente désormais `post` / `read` / `materialize` / `watch`. Les dépôts sont écrits **via SPARQL dans un document du `private_store`** (le private reste l'ancre shim/inbox ; les entités partageables domaine sont, elles, sur le `protected_store` — cf. [[rule_private-store-scope]], T02.h). Curateur **émulé** (pas d'`inbox_post` broker natif exposé). -- **App câblée (réelle inscription PdR)** : dans `FestipodDataContext`, `joinEvent` **persiste une Participation** + **dépose dans l'inbox de l'hôte** + **crée une Notification** (shape SHEX réelle, T02.a) ; `leaveEvent` **supprime autoritativement** via `SPARQL DELETE-WHERE` (le bug CRDT de désinscription est **RÉSOLU** — cf. [[caveat_participation-deletion]]). -- **Découverte publique cross-compte** fonctionne (fan-out sur les docs publics de tous les comptes ; Alice voit l'événement public de Bob sans connexion) — T02.e, réalise [[decision_2026-06-16_discovery-model]] côté découverte primaire. - -L'approche **fork broker** pour exposer l'inbox ([[brief_2026-05-21_fork-nextgraph-inbox]]) est **court-circuitée** par cette émulation en lib (voir le statut superséédé de ce brief). - -## 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 deleted file mode 100644 index bd79923..0000000 --- a/.project/concepts/nextgraph-platform/knowledge_apps-and-services.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -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 diff --git a/.project/concepts/nextgraph-platform/knowledge_broker-import-constraint.md b/.project/concepts/nextgraph-platform/knowledge_broker-import-constraint.md deleted file mode 100644 index 46e04c2..0000000 --- a/.project/concepts/nextgraph-platform/knowledge_broker-import-constraint.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -type: knowledge -summary: Le broker NextGraph hébergé n'autorise pas l'auto-import d'un wallet par une web-app tierce — init() top-level redirige, toute méthode ng.* exige d'être déjà loggé dans l'iframe, et le broker renvoie un device sans wallet vers nextgraph.eu (cross-origin, non pilotable). Un wallet doit préexister dans le navigateur. Des 4 méthodes d'import nextgraph.eu, seul le FICHIER .ngw est statique/réutilisable ; le TextCode/QR sont des transferts temporaires (5 min, deux devices en ligne) inutilisables à embarquer. -last_checked: 2026-06-25 ---- - -# Contrainte : pas d'auto-import de wallet par une web-app tierce - -Fait technique **vérifié empiriquement (2026-06-17)** : avec le broker NextGraph **hébergé** (`nextgraph.net`), une web-app tierce (Festipod) **ne peut pas** provisionner/importer un wallet par programme. Le wallet doit **préexister** dans le navigateur avant que le redirect d'authentification puisse réussir. - -## Pourquoi (mécanisme du proxy `@ng-org/web`) - -Lecture de `ngweb.js` (dist de `@ng-org/web`) : - -- **`init()` top-level REDIRIGE** : si `window.self === window.top`, il fait `window.location.href = https://nextgraph.net/redir/#/?o=`. Le code de l'app ne tourne plus. -- **Toute méthode `ng.*` est relayée** par `parent.postMessage` vers `nextgraph.net`, et le handler **lève `"you must call init() first"` tant que la session n'est pas établie** (garde interne `d !== false`). Cela inclut `wallet_import_from_code`, `add_in_memory_wallet`, `session_in_memory_start`. -- L'app tierce ne s'exécute **dans l'iframe qu'APRÈS** que le broker a déjà ouvert un wallet et établi la session. **Il n'existe aucune fenêtre** où notre code tourne *avant* le gate wallet du broker → **rien à quoi accrocher un auto-import**. - -> Vérifier : `node_modules/@ng-org/web/dist/ngweb.js` — fonction `init` (redirect / postMessage selon top-vs-iframe) et le handler `apply` du Proxy `ng`. Cohérent avec [[knowledge_integration-model]] (le verifier tourne dans l'iframe du ng-app, le proxy ne fait que relayer). - -## Ce que montre le broker (probe Playwright, navigateur frais sans wallet) - -Sur `https://nextgraph.net/redir/#/?o=...`, le broker affiche **littéralement** : - -> « We could not find a wallet in your browser. For now, creating a new wallet while a Web App is authenticating, is not implemented. Please create or import your wallet in a new tab. » - -…et renvoie vers `https://nextgraph.eu/` (app wallet **standalone**). L'import standalone (`/#/wallet/login`) propose **4 méthodes**, mais elles ne sont **PAS équivalentes** : - -| Méthode | Nature | Embarquable / réutilisable ? | -|---|---|---| -| **Import a Wallet File** (`.ngw`) | **fichier statique** (export portable, hors-ligne) | ✅ **oui** — statique, sans expiration, sans device source | -| Import with TextCode | **transfert temporaire** device↔device via leurs serveurs : **5 min**, **les deux appareils en ligne**, usage unique | ❌ non | -| Import with QR-Code | transfert live (même famille que TextCode) | ❌ non | -| Import via Username | récupération liée à un compte | à étudier | - -> **Piège vérifié (2026-06-25)** : le **TextCode n'est PAS un export statique** — l'écran nextgraph.eu le dit (« temporarily stored on our servers for up to 5 minutes », « both devices need to be online »). L'embarquer dans l'app est **inutilisable** : il expire / est à usage unique. Un test automatisé qui génère ET importe le code dans la foulée **passe** (live), masquant le problème — d'où une fausse piste initiale. **La primitive correcte pour un wallet partagé embarqué = le FICHIER `.ngw`.** - -## Logique du redirect nextgraph.net (broker discovery) + ORDRE critique - -`init()` redirige vers `nextgraph.net/redir`. Là, nextgraph.net regarde dans le **localStorage** s'il connaît un **broker** (le domaine du broker, posé lors d'un import/login wallet antérieur) : - -- **trouvé** → redirige vers le broker (`nextgraph.eu/auth/#/wallet/login` → « Click here to login with your wallet » → mot de passe) → app. **Cas qui marche.** -- **absent** → message « We could not find a wallet in your browser… Please create or import your wallet in a new tab by clicking here ». Le lien ouvre `nextgraph.eu` (page **Welcome**) → **Login** → **Import a Wallet File** → on rejoint l'import. - -> **Ordre critique** : il faut **importer le wallet AVANT** d'atteindre nextgraph.net. Le guide de l'`AccessGateScreen` impose cet ordre (télécharger + importer, *puis* « Entrer »). - -**Deux routes vers l'import** : **A** = lien direct `nextgraph.eu/#/wallet/login` (celui de Festipod + des tests — saute la page Welcome/Login) ; **B** = fallback nextgraph.net « no wallet → clicking here » → Welcome → **Login** → Import (si on atteint nextgraph.net sans wallet). - -**Pourquoi l'e2e `@humain` ne bute pas sur le « no wallet »** : il importe le fichier (route A) **avant** le « Entrer », donc nextgraph.net trouve déjà le broker. Le test **ne couvre pas** la route B (message « no wallet » + page Welcome/Login de nextgraph.eu) — ce sont des comportements nextgraph, pas Festipod, mais un humain cliquant « Entrer » en premier y tombe. - -> **Atténuation côté Festipod (2026-06-29)** : la barrière `AccessGateScreen` (qui **fournit** le fichier wallet + le mot de passe + le guide) est désormais l'écran d'entrée **par défaut** (cf. [[decision_2026-06-17_assisted-wallet-import]]) — l'ancien `LoginScreen` « Se connecter avec NextGraph » (qui menait directement au redirect sans fournir le wallet) a été retiré. Un utilisateur ne peut donc plus atteindre nextgraph.net **sans** que Festipod lui ait d'abord proposé le wallet. La route B reste possible s'il clique « Entrer » avant d'importer, mais il a le wallet sous les yeux pour le faire. - -> **Contrainte UX irréductible + dé-piégeage** : « Entrer » fait une **redirection pleine-page**. Si on clique AVANT d'importer → message « no wallet » → l'import se fait dans un **autre onglet** sans retour auto vers Festipod (le broker hébergé ne sait pas revenir). Il faut donc **importer d'abord, PUIS Entrer** (guide de l'`AccessGateScreen`, ordre du `@humain`). Piège corrigé (2026-06-29) : au retour (back) après un « Entrer » prématuré, la page standalone était restaurée du bfcache avec l'état figé sur `connecting` → bouton « Accès en cours » bloqué ; `NextGraphContext` écoute `pageshow.persisted` (sans session) et réinitialise sur `disconnected` pour permettre de réessayer. - -## Pistes d'élimination du va-et-vient — testées, ÉCARTÉES (2026-06-30) - -Deux idées pour éviter le 2ᵉ onglet ; les deux **infaisables** sans fork : - -1. **Embarquer `nextgraph.eu` en iframe** dans Festipod pour guider l'import sur le même écran. nextgraph.eu **n'a pas** d'en-tête anti-framing (embed possible, import OK dans l'iframe), **MAIS** le wallet importé atterrit dans le stockage **partitionné** `(top: festipod, frame: nextgraph.eu)` — invisible du login top-level → « no wallet ». **Confirmé en vrai navigateur (Chrome).** ⚠️ **Le Chromium de Playwright N'applique PAS ce partitioning → faux positif** : un probe Playwright montrait le wallet « transmis », alors que le vrai Chrome bloque. Ne jamais valider une question d'**isolation de stockage** via Playwright ; tester en vrai navigateur. -2. **Déclencher l'écriture cross-origin sur nextgraph.net** : le mécanisme existe (pont `/auth` iframe+postMessage qui écrit `ng_bootstrap` sur nextgraph.net pendant le login nextgraph.eu) mais ce sont **les pages de NextGraph** qui l'orchestrent ; un tiers ne peut pas écrire le localStorage d'une autre origine (same-origin policy), et ça ne couvrirait que la découverte du broker, pas le wallet. - -→ Seule élimination réelle = self-host/fork du ng-app ([[brief_2026-05-21_fork-nextgraph-inbox]]). Le flow stopgap reste l'import **en onglet séparé** (top-level `nextgraph.eu`, première-partie → pas de partitioning → fonctionne). - -## Conséquences - -- Le wallet doit être importé **sur `nextgraph.eu` (cross-origin)** — Festipod ne peut **pas** piloter ce flux ni pré-remplir l'import (pas de route d'import par URL côté broker hébergé). -- Le **vrai zéro-touche** exigerait de **self-host/forker le ng-app** (territoire de [[brief_2026-05-21_fork-nextgraph-inbox]]). -- Distribution produit retenue, vu cette contrainte : **import assisté par FICHIER** — Festipod fournit le `.ngw` (téléchargement) + le mot de passe et guide l'import — voir [[decision_2026-06-17_assisted-wallet-import]]. -- À ne pas confondre avec le **provisioning de TEST** (injection storageState dans le harness), qui n'a pas cette contrainte car il agit au niveau navigateur — concept `bdd-testing` → `knowledge_multibrowser-harness`. diff --git a/.project/concepts/nextgraph-platform/knowledge_integration-model.md b/.project/concepts/nextgraph-platform/knowledge_integration-model.md deleted file mode 100644 index 3fe25ed..0000000 --- a/.project/concepts/nextgraph-platform/knowledge_integration-model.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -type: knowledge -summary: Modèle d'intégration NextGraph — @ng-org/web est un proxy iframe (verifier tourne dans l'iframe ng-app, pas dans le broker), reciblable au build via NG_REDIR_SERVER/NG_DEV*, broker ngd stateful WebSocket ; modifier le verifier = rebuilder le ng-app, pas le broker -last_checked: 2026-05-21 ---- - -# 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)**. Vérifié dans `nextgraph-rs` le 2026-05-21. - -NextGraph s'utilise via un **proxy iframe** (`@ng-org/web`) : l'app tierce ne contient pas le moteur, elle délègue à un ng-app hébergé (défaut `nextgraph.net`) qui exécute le moteur dans une iframe. - -## Les paquets JS - -- **`@ng-org/web`** — paquet **publié**. Proxy postMessage léger (aucun wasm embarqué). **Le** chemin d'intégration tierce ; `@ng-org/orm` et tous les exemples en dépendent. **Festipod l'utilise.** -- **`@ng-org/api-web`** — **privé** (non publié). Moteur navigateur complet (charge `@ng-org/lib-wasm` dans un Web Worker). Consommé uniquement par `app/nextgraph` (frontend ng-app) — **pas** une cible d'intégration tierce. -- **`@ng-org/lib-wasm`** — moteur compilé wasm (contient le verifier). Source `sdk/js/lib-wasm/`. -- **`nextgraph`** (npm) — API NodeJS (build `pkg-node`). -- **`@ng-org/orm`** — 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 **transport et stockage**. - -**Conséquence** : modifier la logique du verifier (`request_processor`, `inbox_processor`) = reconstruire le **ng-app**, pas le broker. - -## Le modèle iframe & reciblage build-time - -`@ng-org/web` redirige vers le ng-app hébergé, qui recharge l'app tierce en iframe après auth, puis relaie par `postMessage`. **Reciblable au build** (`sdk/js/web/src/index.ts`, `import.meta.env`) : - -| Variable | Cible | -|---|---| -| `NG_REDIR_SERVER` | défaut `nextgraph.net` | -| `NG_DEV3` | `127.0.0.1:3033` | -| `NG_DEV` | `localhost:14402`/`14404` | -| `NG_DEV_LOCAL_BROKER` | `localhost:1421` | - -**Pas d'override runtime** — `init()` ne prend pas d'URL broker. Pour pointer vers un ng-app auto-hébergé : **rebuilder `@ng-org/web`** (TS pur, sans wasm → build trivial). - -## Plomberie proxy ↔ iframe ↔ worker (générique) - -Le chemin d'appel d'une méthode est **entièrement générique** (aucune allowlist) : `@ng-org/web` est un `Proxy` JS qui relaie *n'importe quel* nom de méthode par `postMessage` ; `app/nextgraph` dispatch via `Reflect.apply(ng[method], …)`. **Conséquence** : une nouvelle fonction wasm en requête/réponse simple est *atteignable* sans toucher le JS — mais c'est un **hack** non typé (test rapide, pas un plan ; cf. [[brief_2026-05-21_fork-nextgraph-inbox]]). Cas **streamé** : exige une entrée des deux côtés (`E` dans `@ng-org/web` + `streamed_api` dans api-web ; méthodes streamées actuelles : `doc_subscribe`, `orm_start_graph`, `orm_start_discrete`, `file_get`, `app_request_stream`). - -## 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**. -- Démon **WebSocket** (`async-tungstenite`), **stateful** : RocksDB sous `--base-path`, PeerId persisté (volume critique). -- CLI : `--local PORT`, `--domain DOMAIN:PORT,LOCAL_PORT` (mode derrière reverse-proxy TLS-terminé type Traefik/Coolify). -- **Ne sert pas de statique** : le ng-app frontend est un déploiement statique séparé (`pnpm webfilebuild`). Premier démarrage **interactif** (lien d'invitation wallet admin). Dockerfiles officiels **cassés**. - -> Détail du déploiement depuis un fork : [[brief_2026-05-21_fork-nextgraph-inbox]] §Couche 2. diff --git a/.project/concepts/nextgraph-platform/knowledge_stores-permissions.md b/.project/concepts/nextgraph-platform/knowledge_stores-permissions.md deleted file mode 100644 index 414a06b..0000000 --- a/.project/concepts/nextgraph-platform/knowledge_stores-permissions.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -type: knowledge -summary: Référence des 5 types de stores NextGraph et leurs droits, document=repo, granularité des permissions, capability/Nuri, inbox native (anonymat via from optionnel), et ce que le SDK @ng-org/web n'expose PAS -last_checked: 2026-07-03 ---- - -# Stores NextGraph et droits d'accès - -Référence des primitives de stockage et permission de NextGraph (**système externe**, pas le code de Festipod). Socle des briefs [[brief_2026-05-17_multi-store-refactor]] et [[brief_2026-05-18_authorization-matrix]]. - -Source : doc NextGraph officielle ([Documents & Stores](https://docs.nextgraph.org/en/documents/), [Getting started](https://docs.nextgraph.org/en/getting-started/)) vérifiée le 2026-05-21. - -## Points d'entrée du code source local - -Repo cloné en `../../nextgraph/nextgraph-rs` (cf. `_overview`) : -- `sdk/js/lib-wasm/src/lib.rs` — API wasm effectivement exposée au JS. -- `engine/net/src/app_protocol.rs` — enum `AppRequestCommandV0`, formats `NuriV0`. -- `engine/verifier/src/request_processor.rs` — dispatch effectif des `app_request` (la vérité sur ce qui est *traité*). -- `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 + détenteurs d'un lien + permission | 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 — *« only you have access to … not possible to share »* ; Protected — *« share … but they will need a special link and permission »*, *« protected social profile »* ; Public — *« equivalent to your website … without the need for special permissions »* ; Group — *« each Group is a separate Store … documents 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 »*. - -Tout wallet a d'office les **3 stores** private/protected/public (session : `private_store_id`, `protected_store_id`, `public_store_id`). Group et Dialog se créent à la demande. - -## Concepts transverses - -**Document vs Repo.** *« A Repo is the equivalent of an E2EE group for one and only one Document. »* **1 document = 1 repo** (commits + permissions). Identifiant : `did:ng:o:`. **Il n'existe pas de type `Document`** dans le code (`nextgraph-rs`, vérifié 2026-06-29) : « document » = **un repo quelconque**. Un **store est un repo spécial** (`is_store=true`, avec branches `Store`/`Overlay`/`User`) — donc *un store est un document, mais un document n'est pas forcément un store*. - -**Containment (store → repos) par RÉFÉRENCE, pas par liste.** Un store **ne contient pas** un `Vec` : il référence ses repos via un **graphe RDF** dans sa branche Overlay/User. À l'inverse, chaque repo déclare son store parent via `RootBranchV0.store: StoreOverlay` (`engine/repo/src/types.rs`) → **un repo appartient à exactement un store**. C'est la « structure de graphe » : un store **peut contenir d'autres documents**. - -**Granularité des caps.** `ReadCap = ObjectRef`. Granularité au niveau **repo ET branche** (chaque branche a son `read_cap`), jusqu'au **bloc** (clé `ObjectKey`/ChaCha20). Écriture gérée au niveau **Document (repo)**. - -**Pas d'héritage de lecture automatique.** Détenir la ReadCap d'un **store** ne donne **pas** accès aux repos qu'il contient — **il faut la ReadCap de chaque repo**. L'héritage optionnel `inherit_perms_users_and_quorum_from_store: Option` ne partage que les **users/quorum** (écriture/permissions), **pas** la possession de read-cap. (Repos d'un private_store : héritage implicite.) **Conséquence pour l'émulation** : l'unité d'accès en lecture est le **repo = le `@graph`** de chaque item — un filtre par document, pas par store ni par item (cf. [[decision_2026-06-17_eventually-library]]). - -> ⚠️ **Confusion récurrente store ↔ document.** L'axe de l'isolation est le **document (repo/`@graph`)**, jamais le **store** : un store *contient* plusieurs documents et n'en partage pas la lecture. Piège concret côté Festipod : le flag `FESTIPOD_MULTISTORE` crée en réalité **plusieurs DOCUMENTS** (1 par entité) dans **un seul store partagé**, pas plusieurs stores — voir data-layer `caveat_multistore-is-multi-document`. « Plus d'isolation » = **plus de documents**, pas plus de stores. - -**Capability / Nuri.** Le partage transmet un **Nuri** embarquant la capability crypto (lecture et/ou écriture). Pas d'ACL centralisée : posséder le Nuri = le droit. *« adding permissions can be done offline »* ; *« removing permissions … requires a SyncSignature »* (synchrone). - -## Inbox - -**Chaque document a une inbox native.** Un non-éditeur peut y **déposer un lien (DID cap)** sans être invité éditeur ; le propriétaire **modère**. NURI : `did:ng:d:`. Contenu : enum `InboxMsgContent` (`ContactDetails`, `DialogRequest`, **`Link`**, `Patch`, `ServiceRequest`, `ExtRequest`, `RemoteQuery`, `SocialQuery`…). Message **scellé** (`crypto_box::seal`) vers la pubkey de l'inbox → seul le titulaire déchiffre. Champ `from` **optionnel** → expéditeur **anonyme** possible. C'est le « identifié si connu, anonyme sinon » voulu par Festipod, **natif au protocole** (mécanisme retenu pour la notification d'inscription, cf. [[brief_2026-05-18_authorization-matrix]]). - -### L'inbox n'est PAS utilisable directement depuis le SDK JS - -- `app_request(request)` est exposé, et `AppRequestCommandV0::InboxPost` + `AppRequest::inbox_post()` existent. **MAIS** le `request_processor` du verifier **n'a aucun bras `InboxPost`** (commandes traitées : `OrmStart(Discrete)`, `Fetch`, `FileGet`, `OrmUpdate`, `OrmDiscreteUpdate`, `SocialQueryStart`, `QrCodeProfile(Import)`, `Header`, `Create`, `FilePut`). Envoyer un `InboxPost` ne déclenche rien. -- Construire un `InboxPost` exige le scellement crypto côté Rust ; **aucun helper wasm** ne l'expose. -- Le dépôt en inbox n'est déclenché qu'**en interne** par `QrCodeProfileImport` (`post_to_inbox(new_contact_details)`) et `social_query_start` (propagation via inbox des **contacts**). - -**Conséquence** : pas de moyen propre de « drop a Link » arbitraire dans l'inbox d'un PdR depuis le SDK JS aujourd'hui. → chantier [[brief_2026-05-21_fork-nextgraph-inbox]]. Piste connexe : `social_query_start` EST exposé (requête fédérée via inbox jusqu'à `degree` sauts) mais limité aux **contacts** (ne couvre pas la notif anonyme vers un hôte non-connecté). - -## Limites du SDK JS - -`@ng-org/web` (vérifié `0.1.2-alpha.13` = `upstream/main` au 2026-05-21, version installée) **n'expose pas** : création de Group/Dialog store ; partage de capability (Nuri avec droits) ; manipulation de permissions ; dépôt/lecture d'inbox. - -Méthodes JS 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 *« An API will be provided for permission manipulation »* (sans date). diff --git a/.project/concepts/tech-stack/knowledge_deployment.md b/.project/concepts/tech-stack/knowledge_deployment.md index 966a9c8..67714a6 100644 --- a/.project/concepts/tech-stack/knowledge_deployment.md +++ b/.project/concepts/tech-stack/knowledge_deployment.md @@ -16,7 +16,7 @@ Un `Dockerfile` existe (multi-stage Bun Alpine) : ## CI/CD -**Aucun** pipeline committé (`.github/workflows/` absent, pas de config Coolify dans le repo). Angle mort assumé. Pour héberger l'app Bun, le skill `coolify-hosting` s'applique (mentionné aussi dans concept `nextgraph-platform` pour distinguer Festipod du `ngd` Rust). +**Aucun** pipeline committé (`.github/workflows/` absent, pas de config Coolify dans le repo). Angle mort assumé. Pour héberger l'app Bun, le skill `coolify-hosting` s'applique. ## Variables d'environnement diff --git a/.project/concepts/tech-stack/knowledge_stack-and-commands.md b/.project/concepts/tech-stack/knowledge_stack-and-commands.md index 9c945c8..f92917f 100644 --- a/.project/concepts/tech-stack/knowledge_stack-and-commands.md +++ b/.project/concepts/tech-stack/knowledge_stack-and-commands.md @@ -31,7 +31,7 @@ summary: Composants de la stack (Bun, React, NextGraph, Storybook, Cucumber, Tai | `features:parse` | `bun scripts/parse-features.ts` → `features.ts` | | `steps:extract` | `bun scripts/extract-step-definitions.ts` | | `build:orm` | `rdf-orm build --input ./src/shapes/shex --output ./src/shapes/orm` | -| `build:ng` | `bash scripts/build-ng-packages.sh` — rebuild des `@ng-org/*` depuis le fork local (concept `nextgraph-platform`) | +| `build:ng` | `bash scripts/build-ng-packages.sh` — (re)build des paquets NextGraph depuis une source locale (outil optionnel) | | `storybook` / `build-storybook` | Storybook dev (6006) / build statique | ## Pièges diff --git a/AGENTS.md b/AGENTS.md index d4b6a48..5642c46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,18 +14,21 @@ Web app mobile-first où les utilisateurs créent des **points de rencontre** qu - **Un module n'importe QUE depuis `shared/` — jamais d'un autre module.** C'est l'invariant qui rend l'archi réelle. - **Bun-first** : `bun` / `bun install` / `bun test` / `bun build`, jamais node/npm/vite/jest. `bun run dev` (port 3000). +## Frontière SDK NextGraph + +Le SDK de données de Festipod est **`@ng-eventually/client`** — traité comme un **SDK NextGraph fini et mature** (documents par entité placés par scope public/protected/private, capabilities, inboxes). Il est injecté une seule fois via `ngSession.configure(...)`. **Ne jamais documenter dans ce repo l'état courant de NextGraph** (contraintes du SDK sous-jacent, contournements, internes broker/verifier) : cela vit dans le repo `@ng-eventually/client`. La doctrine Festipod décrit uniquement *comment Festipod utilise ce SDK* + le domaine + l'architecture + le contrat BDD. + ## Doctrine du projet — concepts (livrée automatiquement) La connaissance détaillée vit dans `.project/concepts/` (système *concept*) : fiches courtes, typées, **livrées par un hook quand tu touches leur territoire** — tu n'as pas à les charger d'avance. Les 6 concepts : | Concept | Couvre | |---|---| -| `functional-domain` | Modèle produit : point de rencontre, acteurs, concepts métier, défi déduplication | +| `functional-domain` | Modèle produit : point de rencontre, acteurs, concepts métier, périmètres public/protected/private par entité, découverte, défi déduplication | | `app-architecture` | Modules, invariant d'imports, app shell, routing path-based, écrans | | `tech-stack` | Bun-first, APIs Bun, build pipeline, commandes | -| `data-layer` | NextGraph actuel (mono-store), shapes, modes connected/demo, règles + pièges (suppression, champs perdus, internals) | +| `data-layer` | Persistance via le SDK `@ng-eventually/client` : entités-documents par scope, shapes SHEX/ORM, modes connected/demo, pièges | | `bdd-testing` | Cucumber multi-couches FR, contrat `@ui`/`@data`/`@e2e`, harness broker, cookbook | -| `app-security` | Posture de sécurité actuelle (mono-store, confiance broker), auth wallet, modèle d'autorisations cible | -| `nextgraph-platform` | NextGraph système externe (stores, inbox, SDK) + briefs prospectifs (multi-store, fork, shim) | +| `app-security` | Isolation déléguée au SDK (pas de contrôle d'accès dans les écrans), auth wallet, matrice d'autorisations cible | Pour **documenter** un fait projet : `/concept document ` (ne pas écrire en libre dans `.project/`).