diff --git a/.project/concepts/bdd-testing/knowledge_cucumber-setup.md b/.project/concepts/bdd-testing/knowledge_cucumber-setup.md index 691a1ae..16aef9f 100644 --- a/.project/concepts/bdd-testing/knowledge_cucumber-setup.md +++ b/.project/concepts/bdd-testing/knowledge_cucumber-setup.md @@ -23,7 +23,7 @@ Steps **partagés** (cross-domaine) dans `src/shared/steps/ui/` : Les noms français des écrans (`"accueil"`, `"détail événement"`, `"mon profil"`…) mappent vers les IDs d'écran via `screenNameMap`. -Tags de scénario : `@ui` / `@data` / `@e2e` (couche) + **`@wip`** pour un scénario dont les steps ne sont pas encore implémentés **ou dont le comportement applicatif n'est pas encore fiable** (ex. la désinscription qui ne se reflète pas dans l'UI — cf [[caveat_participation-deletion]]). **`@wip` est EXCLU du run par défaut** (`cucumber.json: "tags": "not @wip"`) : ces scénarios documentent un attendu sans casser la suite ; retirer le `@wip` quand c'est fiable. Un `Contexte` (Background) fréquent — « Étant donné que je suis connecté » — ne fait que poser un flag `isAuthenticated`, pas d'auth réelle en `@ui`. +Tags de scénario : `@ui` / `@data` / `@e2e` (couche) + **`@wip`** pour un scénario dont les steps ne sont pas encore implémentés **ou dont le comportement applicatif n'est pas encore fiable** (usage : marquer un attendu réel qui échoue à cause d'un bug produit, pas un test obsolète — ex. historique : la désinscription qui ne se reflétait pas dans l'UI, `@wip` **levé** depuis sa résolution T02.c, cf [[caveat_participation-deletion]]). **`@wip` est EXCLU du run par défaut** (`cucumber.json: "tags": "not @wip"`) : ces scénarios documentent un attendu sans casser la suite ; retirer le `@wip` quand c'est fiable. Un `Contexte` (Background) fréquent — « Étant donné que je suis connecté » — ne fait que poser un flag `isAuthenticated`, pas d'auth réelle en `@ui`. ## Config diff --git a/.project/concepts/bdd-testing/knowledge_data-layer-broker.md b/.project/concepts/bdd-testing/knowledge_data-layer-broker.md index ac07936..69728e3 100644 --- a/.project/concepts/bdd-testing/knowledge_data-layer-broker.md +++ b/.project/concepts/bdd-testing/knowledge_data-layer-broker.md @@ -1,6 +1,7 @@ --- type: knowledge summary: Couche @data — Playwright pilote Chromium (profil persistant) qui s'authentifie au broker NextGraph réel chargeant harness-ng.tsx en iframe ; cycle de vie wallet automatisé (création + login bootstrap), bridge window.__testData, fallback mock +last_checked: 2026-07-03 --- # Couche `@data` (broker réel) @@ -32,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 3 shapes avec scope `did:ng:${session.private_store_id}` (cf. concept `data-layer`). -- **Bridge `window.__testData`** : `events`/`users`/`participations` (sets live), `currentUserId`, lookups (`getEvent`, `getEventByTitle`), mutations (`joinEvent`, `leaveEvent`, `updateEvent`), requêtes (`isParticipating`, `getEventParticipants`). +- **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]]). +- **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/data-layer/_overview.md b/.project/concepts/data-layer/_overview.md index 350c831..88fce37 100644 --- a/.project/concepts/data-layer/_overview.md +++ b/.project/concepts/data-layer/_overview.md @@ -2,13 +2,13 @@ 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 triggers: - keywords: [nextgraph, useShape, ORM, SHEX, shape, store, private_store, "@graph", NURI, sparql, sparql_update, seed, wallet, RepoNotFound, FestipodData, ngGraph, bootstrap] + keywords: [nextgraph, useShape, ORM, SHEX, shape, store, private_store, "@graph", NURI, sparql, sparql_update, seed, wallet, RepoNotFound, FestipodData, ngGraph, bootstrap, multistore, document, isolation] 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-store** — tout atterrit dans le `private_store` de l'utilisateur connecté. +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`. > 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é. diff --git a/.project/concepts/data-layer/caveat_multistore-is-multi-document.md b/.project/concepts/data-layer/caveat_multistore-is-multi-document.md new file mode 100644 index 0000000..5d1838b --- /dev/null +++ b/.project/concepts/data-layer/caveat_multistore-is-multi-document.md @@ -0,0 +1,58 @@ +--- +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 c7cbe2b..cc3b5c6 100644 --- a/.project/concepts/data-layer/caveat_participation-deletion.md +++ b/.project/concepts/data-layer/caveat_participation-deletion.md @@ -1,12 +1,18 @@ --- type: caveat -summary: La suppression de Participation (leaveEvent) via ngSet.delete() NE se reflète PAS dans l'UI en mode broker (vérifié e2e 2026-06-30) — le bouton reste « ✓ Je participe » ; ngSet.delete() déclenche bien la réactivité mais la suppression ne se propage pas / l'item ressuscite via la sync. Scénario e2e « Se désinscrire » marqué @wip (exclu du run par défaut) -last_checked: 2026-06-30 +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. +last_checked: 2026-07-03 --- -# Caveat : suppression de Participation via `ngSet.delete()` +# Caveat : suppression de Participation (RÉSOLU en T02.c) -**État actuel du code** (`src/shared/context/FestipodDataContext.tsx`, `leaveEvent` en mode NG) : la suppression d'une `Participation` se fait via **`participationsShape.ngSet.delete(ngPart)`** — pas via `ng.sparql_update()` DELETE WHERE. +**É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). + +Preuve : `cycle-de-vie-evenement.feature` (@e2e, @wip levé) + `inscription-inbox.feature` (@data « désinscription persistante »). Validation multi-navigateur complète = T02.f. + +## Histoire du bug (avant T02.c) + +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 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 index 4e3e99b..4065b56 100644 --- 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 @@ -8,6 +8,8 @@ summary: Décision 2026-03-17 — utiliser private_store_id comme scope useShape **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. diff --git a/.project/concepts/data-layer/knowledge_entities.md b/.project/concepts/data-layer/knowledge_entities.md index 061e1a6..16a9c86 100644 --- a/.project/concepts/data-layer/knowledge_entities.md +++ b/.project/concepts/data-layer/knowledge_entities.md @@ -1,6 +1,7 @@ --- type: knowledge -summary: Types de données Fp* (Event, UserProfile, Participation persistés NextGraph ; MeetingPoint et Friendship encore local-only) +summary: Types de données Fp* — Event, UserProfile, Participation, MeetingPoint et Notification sont persistés NextGraph (shapes SHEX + ORM) ; seul Friendship reste local-only (app-TS) +last_checked: 2026-07-03 --- # Entités de données @@ -12,9 +13,12 @@ summary: Types de données Fp* (Event, UserProfile, Participation persistés Nex | `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` | **local-only** | eventId, location, time, host | +| `FpMeetingPointData` | NextGraph (shape MeetingPoint, T02.a) | eventId, location, time, host | +| `FpNotificationData` | NextGraph (shape Notification, T02.a) | kind, target, source | | `FpFriendshipData` | **local-only** | userId + friendId | -`MeetingPoint` et `Friendship` n'ont **pas encore de shape SHEX** ni de persistance NextGraph (cf. [[knowledge_nextgraph-stack]]). Les brancher au store est un prérequis du multi-user — voir les briefs du concept `nextgraph-platform`. +`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). + +`Friendship` n'a **pas** de shape SHEX ni de persistance NextGraph — 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/rule_private-store-scope.md b/.project/concepts/data-layer/rule_private-store-scope.md index 25096c5..51518e3 100644 --- a/.project/concepts/data-layer/rule_private-store-scope.md +++ b/.project/concepts/data-layer/rule_private-store-scope.md @@ -1,25 +1,34 @@ --- type: rule -summary: Utiliser did:ng:${private_store_id} comme scope useShape ET comme @graph d'écriture ; ne jamais utiliser did:ng:i comme scope (casse toutes les écritures par RepoNotFound) +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` = private_store_id +# Règle : scope = `@graph` = `protected_store_id` pour les entités partageables -Pour lire **et** écrire via l'ORM NextGraph : +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. -- **Scope** : `useShape(shapeType, \`did:ng:${session.private_store_id}\`)` -- **`@graph`** (cible des écritures) : `did:ng:${session.private_store_id}` +Pour lire **et** écrire ces entités via l'ORM NextGraph : -C'est critique : `orm_start_graph` avec le NURI du private_store **ouvre explicitement le repo** dans la HashMap `self.repos` du verifier. Sans ça, `orm_frontend_update` échoue en `RepoNotFound`. +- **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. +**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 `private_store`). +- `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* complet et les alternatives écartées : [[decision_2026-03-17_private-store-nuri-scope]]. **Ce scope mono-store est précisément ce que le chantier multi-store viendra remplacer** — voir [[brief_2026-05-17_multi-store-refactor]]. +> 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/knowledge_roadmap.md b/.project/concepts/functional-domain/knowledge_roadmap.md index 399a3aa..122a5b7 100644 --- a/.project/concepts/functional-domain/knowledge_roadmap.md +++ b/.project/concepts/functional-domain/knowledge_roadmap.md @@ -15,7 +15,7 @@ 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 -> Réserve : certaines actions de données restent des no-ops en l'état (ex. `joinEvent`/`leaveEvent` côté `FestipodDataContext` — détail dans [[brief_2026-05-21_fork-nextgraph-inbox]] §Couche 3). Le router et les écrans existent, mais le branchement données suit le chantier multi-store. +> 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. ## Évolutions identifiées (non implémentées) 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 index e371f4a..51df519 100644 --- 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 @@ -6,8 +6,10 @@ last_updated: 2026-05-21 # Forker NextGraph pour exposer l'inbox au SDK JS -**Status:** Incubating — aucun travail démarré -**Last updated:** 2026-05-21 +**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 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 index a955a9f..875f1cd 100644 --- a/.project/concepts/nextgraph-platform/decision_2026-06-16_discovery-model.md +++ b/.project/concepts/nextgraph-platform/decision_2026-06-16_discovery-model.md @@ -8,6 +8,8 @@ last_updated: 2026-06-16 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). 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 index 9ee3d7f..e262112 100644 --- a/.project/concepts/nextgraph-platform/decision_2026-06-17_eventually-library.md +++ b/.project/concepts/nextgraph-platform/decision_2026-06-17_eventually-library.md @@ -1,7 +1,7 @@ --- type: decision -summary: Tout le polyfill multi-user (wallet partagé, caps émulées, inbox émulée) est encapsulé dans une LIBRAIRIE GÉNÉRIQUE externe « ng-eventually-js » (repo hors Festipod, à côté de nextgraph-rs/orm-tests), zéro Festipod dedans. UN package pour l'instant : @ng-eventually/client (entrée principale SDK-IDENTIQUE ; bootstrap polyfill isolé sous /polyfill ; l'app n'en dépend que de lui). Le curateur d'index global (ex-@ng-eventually/service) est RETIRÉ/différé : son modèle « backend à données globales » est incorrect — NextGraph est mono-utilisateur sans données globales (cf. knowledge_apps-and-services) ; un index global passerait par une app singleton (incertain, différé, à creuser). Migration = alias de build retiré + le client redevient le vrai SDK. Festipod ne dépend que de @ng-eventually/client. -last_updated: 2026-07-02 +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é) @@ -84,6 +84,16 @@ Le TODO ci-dessus est **fait** : **tout le shim est désormais DANS la lib**. La - **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**. diff --git a/.project/concepts/nextgraph-platform/knowledge_stores-permissions.md b/.project/concepts/nextgraph-platform/knowledge_stores-permissions.md index 9e67000..414a06b 100644 --- a/.project/concepts/nextgraph-platform/knowledge_stores-permissions.md +++ b/.project/concepts/nextgraph-platform/knowledge_stores-permissions.md @@ -1,7 +1,7 @@ --- 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-05-21 +last_checked: 2026-07-03 --- # Stores NextGraph et droits d'accès @@ -43,6 +43,8 @@ Tout wallet a d'office les **3 stores** private/protected/public (session : `pri **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