doctrine: reconcile store/document model + T02 features into concepts
- data-layer/caveat_multistore-is-multi-document (new): the recurring store vs document confusion. Two axes — (A) which native store, (B) documents within a store. FESTIPOD_MULTISTORE toggles axis B (multi-document), not multi-store. Isolation (ReadCap) is per-document. As of T02.h the default path writes shareable entities to the real protected store (axis A, step 1). - rule_private-store-scope: rewritten — shareable entities now scope/@graph the protected store; private anchors the shim/inbox + settings; "never did:ng:i" kept. decision_2026-03-17 marked partially superseded. - knowledge_stores-permissions: ⚠️ store↔document callout. - knowledge_entities: MeetingPoint/Notification now persisted (not local-only). - nextgraph-platform: decision_2026-06-17 records the emulated inbox; fork-inbox brief marked short-circuited; discovery-model divergence (shipped fan-out vs global-index target) flagged for confirmation. - functional-domain/knowledge_roadmap, bdd-testing leaves updated. All doc-debt settled; lint clean (60 leaves). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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é.
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]].
|
||||
|
||||
@@ -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]].
|
||||
|
||||
Reference in New Issue
Block a user