docs(concept): modèle d'inscription recalé + l'inbox NextGraph suit aussi les manques

Le brief inscriptions est réécrit sur le modèle reposé par le PO : tout est
clés et URLs, sans notion d'appartenance. Le participant crée une Participation
chiffrée, dépose son did (URI sans ReadCap) dans l'inbox de l'événement ; le
créateur traite l'inbox automatiquement, déduplique sans pouvoir lire, et range
la référence dans un Set porté par l'événement ; compteur = Set.size ; seules
les connexions détiennent la clé et reconnaissent la personne.

La dédup s'appuie sur un fait vérifié dans nextgraph-rs : l'overlay (segment
`✌️` d'un NURI) est STORE-scopé, jamais document-scopé. Deux Participations
d'une même personne portent donc le même `✌️`. Contrepartie actée dans le
brief : ce `✌️` est un pseudonyme stable et permanent — c'est le MÊME bit
d'information qui permet de dédupliquer sans lire et de tracer d'un événement
à l'autre ; on ne peut pas garder l'un sans l'autre.

Retiré du brief : le trilemme et la piste de dédup par vérification de
signature. Ils reposaient sur une notion de membership importée de l'état
courant du source Rust, où elle est un échafaudage inerte — erreur de méthode
désormais consignée en règle.

Règles :
- rule_capture-nextgraph-findings (nouvelle) — toute connaissance établie sur
  le fonctionnement réel de NextGraph se consigne AU MOMENT de la découverte
  dans la doc du polyfill ; distinguer VÉRIFIÉ d'INFÉRÉ ; ne jamais déduire la
  forme cible de l'état courant du source.
- rule_file-nextgraph-bugs → rule_nextgraph-inbox — l'inbox reçoit désormais
  DEUX familles : les dysfonctionnements ET les manques dont on a besoin. Une
  fiche de manque dit ce que le polyfill émule en attendant et ce qu'il faudra
  en RETIRER quand ça atterrit en amont : l'inbox devient un suivi de
  l'avancement de NextGraph, pas un simple bug-tracker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
This commit is contained in:
Sylvain Duchesne
2026-07-27 12:07:06 +02:00
parent 5b536ff981
commit 42dbfd0c34
5 changed files with 137 additions and 63 deletions
+7 -1
View File
@@ -2,7 +2,7 @@
type: _overview
summary: Comment Festipod persiste ses données via le SDK @ng-eventually/client — entités stockées comme documents par scope, écriture SPARQL directe + lecture par modèle union, stack SHEX, modes connected/demo, seed
triggers:
keywords: [nextgraph, "@ng-eventually", union, readUnion, readEntities, SHEX, shape, scope, "@graph", NURI, sparql, seed, wallet, FestipodData, ngSession, ngGraph, bootstrap, document, entité, déconnexion, reconnexion, durabilité, outbox, SerializationError]
keywords: [nextgraph, "@ng-eventually", polyfill, union, readUnion, readEntities, SHEX, shape, scope, "@graph", NURI, overlay, ReadCap, WriteCap, cap-less, sparql, seed, wallet, FestipodData, ngSession, ngGraph, bootstrap, document, entité, déconnexion, reconnexion, durabilité, outbox, SerializationError]
paths: ["src/shared/shapes/**", "src/shared/data/readEntities.ts", "src/shared/data/entityWrites.ts", "src/shared/context/NextGraphContext.tsx", "src/shared/context/FestipodDataContext.tsx", "src/shared/utils/ng*", "src/shared/data/seedData.ts"]
---
@@ -23,6 +23,12 @@ Comment Festipod **persiste ses données** via NextGraph (P2P, local-first, chif
## Règles d'écriture
- [[rule_document-per-entity]] — chaque entité = **son propre document** (par scope), jamais au niveau du store ; c'est ce qui rend l'isolation par-document du SDK possible
- [[rule_app-uses-sdk-surface-only]] — l'app se comporte comme si NextGraph était fini ; tout contournement vit dans le polyfill
## Ce qui sort de ce repo (deux destinations, ne pas les confondre)
- [[rule_capture-nextgraph-findings]] — une **connaissance** établie sur le fonctionnement réel de NextGraph → doc de référence du **polyfill**, au moment de la découverte
- [[rule_nextgraph-inbox]] — un **dysfonctionnement** de NextGraph, ou un **manque** dont on a besoin et qu'on émule en attendant → fiche dans `orm-tests/INBOX/`, qui suit l'avancement amont et dit quoi retirer du polyfill
## Pièges (lire avant de toucher aux suppressions / aux champs d'event)
@@ -1,62 +1,78 @@
---
type: brief
summary: Réaligner les inscriptions sur la vision initiale — objet participation auto-possédé (vérité) + Set curé cap-less sur l'événement + cap scellé aux connexions → compteur = refs distinctes, anonyme par défaut, personne ne désinscrit autrui. Supersede en partie l'Option-B (compteur muté + userId dans l'inbox). Dépend de l'alignement caps du polyfill.
summary: Modèle cible des inscriptions — le participant crée une Participation chiffrée et dépose son did (URI sans ReadCap) dans l'inbox de l'événement ; le créateur traite l'inbox automatiquement, déduplique sur l'overlay (store-scopé) sans pouvoir lire, et range la référence dans un Set porté par l'événement ; compteur = Set.size ; seules les connexions détiennent la clé et reconnaissent la personne. Supersede l'Option-B (compteur muté + userId en clair dans l'inbox).
---
# Brief (2026-07-20) — inscriptions par Set (objet-vérité + Set curé cap-less)
# Brief (2026-07-20, révisé 2026-07-27) — inscriptions par Set
## Pourquoi
## Le modèle
L'implémentation actuelle (Option-B, [[brief_2026-07-06_reactive-reads-and-attendance]]) dérive un `participantCount` **muté-en-place** depuis des marqueurs d'inbox qui portent le **`userId` en clair** → (1) compteur **falsifiable** (marqueurs non authentifiés), (2) **fuite d'identité** au créateur, indépendamment de toute connexion. La **vision initiale**, retrouvée : **présence anonyme par défaut**, seul un **connecté (= ami)** voit l'identité. Faisabilité : **seules les références cap-less sont confirmées** (cf. polyfill `docs/readcap-and-nuri-model.md`) ; le **compteur** repose encore sur des inconnues (keyless-fetch, dédup-vérifiée — voir Tensions). Ne pas surestimer.
Posé par le PO le 2026-07-27. Tout est **clés et URLs** — pas de rôle, pas d'appartenance, pas de liste d'autorisation.
## Modèle cible
1. Le participant crée un objet **Participation****chiffré**, dans son store *protected* — décrivant son inscription.
2. Il dépose dans l'**inbox de l'événement** le **did** de cet objet : l'URI **sans ReadCap**.
3. Le **créateur** de l'événement traite son inbox **automatiquement**, dès qu'il est en ligne.
4. Il **ne peut pas lire** la Participation, donc il **ignore l'identité** du participant — mais il a de quoi **dédupliquer** (voir ci-dessous).
5. Il range une **référence** à la Participation dans un **Set** porté par le document de l'événement.
6. N'importe qui lit **`Set.size`** → le nombre de participants.
7. Une personne **connectée** au participant détient la clé, **déchiffre** la Participation et **reconnaît** la personne. *(Traité dans un 2e temps.)*
- **Objet participation = la vérité**, auto-possédé (protected), mis à jour/supprimé par le **seul inscrit****personne ne désinscrit autrui**.
- **Set sur l'événement** (doc du créateur — lui seul l'écrit) de **références cap-less** aux participations. Le créateur **ajoute** (sur nudge d'inbox) et **nettoie** les périmées.
- **Compteur = nombre de références distinctes** (`Set.size`) — plus de compteur muté séparé.
- **Identité** résolue **seulement** par les détenteurs du cap = les **connexions** (à qui l'inscrit a scellé le cap-porteur) → anonyme au public **et** au créateur non-ami.
- **Désinscription** = l'inscrit supprime son objet **ET** dépose un **nudge « retire la réf X »** chez le créateur — car une suppression n'est **PAS** détectable sans clé (**VÉRIFIÉ P0, Q2** : broker append-only, tombstone chiffré). Le créateur retire la réf du Set sur le nudge (forgeable = accepté, hors-scope sécurité). Les connexions (qui ont le cap) peuvent aussi filtrer en relisant l'objet.
- **Validation d'existence — CONFIRMÉ (P0, Q1)** : les lectures broker ne sont pas cap-gatées → le créateur peut vérifier que l'objet **existe** (fetch des blocs chiffrés) avant d'ajouter → un join forgé **sans objet réel** est **rejeté**. *Nuances* : (a) marche avec l'overlay **inner** (ou outer exposé) — une **sonde e2e** confirmera lequel la réf porte ; (b) existence ≠ **dédup** (#3) et ≠ inviolabilité totale (référencer un objet réel *quelconque* passe la vérif).
Trois propriés en découlent, et ce sont elles qu'on cherchait : **présence anonyme par défaut** (le créateur lui-même ne voit pas qui) ; **personne ne désinscrit autrui** (l'objet est auto-possédé) ; **aucun `userId` en clair** ne circule.
## Ce qui change vs l'actuel
## Sur quoi ça repose — faits établis dans NextGraph
- **Retirer le `userId`** des marqueurs d'inbox → ne garder qu'un **nudge + référence cap-less**.
- **Compter par références distinctes**, pas par `userId`.
- `event.participantCount` muté-en-place **disparaît** au profit de `Set.size`.
- La **résolution d'identité** passe par la **lecture de l'objet** (cap), pas par le marqueur.
Vérifiés par lecture de `nextgraph-rs`. Le détail et les pointeurs vivent côté polyfill (`docs/readcap-and-nuri-model.md`) — cf. [[rule_capture-nextgraph-findings]].
## Ce qui reste valable
| Fait | Statut | Ce qu'il permet ici |
|---|---|---|
| Un NURI cap-less (`did:ng:o:{repo}:v:{overlay}`, sans `:k:`) **nomme sans donner à lire** | VÉRIFIÉ | Le did déposé dans l'inbox référence sans divulguer |
| L'**overlay** (`:v:`) est **store-scopé**, jamais document-scopé | VÉRIFIÉ | **La clé de dédup** — voir section suivante |
| Sans la clé, les blocs restent **du ciphertext** | VÉRIFIÉ | Le créateur ne peut vraiment pas lire |
| L'existence d'un objet est **vérifiable sans clé** (protocole `Ext`, sans identité ni contrôle) | VÉRIFIÉ | *Optionnel* : valider qu'un did pointe sur un objet réel avant de l'ajouter |
| Une **suppression** n'est **PAS** détectable sans la clé (append-only, tombstone chiffré) | VÉRIFIÉ | La désinscription ne peut pas être *observée* — voir points ouverts |
- La **lecture réactive** + le **reconnect-lifecycle** (lire le Set réactivement, ré-armer à la reconnexion).
- Le **fix identité** déjà livré (résoudre participation→profil via la clé).
## La dédup : sur quoi exactement
## Tensions fondamentales (revue adverse — 2026-07-20)
**Hypothèse de travail — à confirmer par le PO.** Le mécanisme que j'identifie derrière « il a normalement assez d'informations pour dédupliquer » :
Un adversaire a réfuté les deux promesses phares. **Distinction clé** (le polyfill vise la *forme*, pas la *sécurité* — cf. son objectif) : les attaques par **forge** (compteur gonflé, `from:null`) relèvent de l'insécurité **acceptée** de l'émulation ; **mais** le trilemme ci-dessous est un problème de **MODÈLE** qui **survit au vrai crypto** — ce sont donc de vraies questions de conception, pas des nitpicks d'émulation. Sous les primitifs NextGraph, **anonyme + compté-juste (dédupliqué) + inviolable** ne tiennent PAS ensemble :
Le segment `:v:` d'un NURI ne vient **pas du document** mais de **son store** (`ProtectedStore(id) → outer(id)`). Or une personne a **un seul** store protected. Donc **toutes ses Participations portent le même `:v:`**, quel que soit le nombre d'objets qu'elle crée. Le créateur déduplique là-dessus : deux références de même `:v:` dans le Set d'un même événement = la même personne. **Sans jamais savoir qui.**
1. **« Inviolable » est faux.** Le fetch keyless (INFÉRÉ, absent de `caps.ts` : `canRead(doc,null)→false`) ne prouve que l'**existence** d'un NURI, jamais une participation **valide, distincte, de cet événement** — un attaquant référence n'importe quel NURI existant → compteur gonflé. Sans keyless-fetch, retour au nudge (`inbox.post` accepte `from:null`) → forge comme Option-B.
2. **Anonymat ⊥ inviolabilité (overlay).** Valider l'existence exige l'**overlay** ; overlay = `outer(store_id)`, **un seul store protected par identité** → stable sur TOUS les événements → un créateur voyant `:v:{overlay}` sur N événements **corrèle** « même participant partout » sans lire l'identité (**pseudonyme de traçage**). *Mitigation candidate : chaque participation dans son propre repo/store → overlay par-participation.*
3. **Dédup : base VÉRIFIÉE (`nextgraph-rs`), mais GATÉE sur la vérification.** Le fond était juste : les **commits SONT signés** par un `UserId` (clé technique **≠ le profil**), donc un id technique de dédup **existe** — pas besoin de pseudonyme applicatif. MAIS deux gaps : (a) **vérifier** la signature exige d'être **membre du repo** (`member_pubkey`) — le créateur n'est pas membre du store du participant, et le store-partagé-en-écriture n'existe pas ; (b) le **dépôt d'inbox n'est PAS authentifié** (sealed box anonyme ; un `from` nommé = clé de **profil** → fuite). → dédup-par-signature-vérifiée **pas directement atteignable** dans le flux actuel. **Piste** : participations dans un **store PAR-ÉVÉNEMENT** vérifiable par le créateur → le digest d'auteur (par-overlay, donc par-store) = un **pseudonyme par-événement** → dédup + **pas** de corrélation cross-événement + anonyme-profil. Reste à résoudre « le créateur peut vérifier » (membership). *(Le digest par-user redevient corrélable pour un membre disposant de la members map sur un store stable par-user → d'où l'intérêt du store par-événement.)*
4. **Leave-par-détection-de-suppression : IMPOSSIBLE keyless — VÉRIFIÉ (P0, Q2).** Broker append-only, tombstone chiffré → sans clé on voit « une activité », pas « une suppression ». **Résolu** par un **nudge de leave** (voir Modèle cible), pas une détection automatique de réf pendante.
Conséquence de conception : le Set est **indexé par `:v:`** — au plus une référence par `:v:`. `Set.size` = nombre de `:v:` distincts = nombre de personnes distinctes.
De plus (revue caps polyfill) : dans l'**émulation actuelle sans crypto**, le contenu est en **clair** dans le wallet partagé (`sparqlQuery` bypass le filtre) → l'anonymat « cap-less ne peut pas lire » **n'est même pas applicable** tant que le polyfill ne fournit pas soit du vrai crypto, soit une projection read-model masquée.
### La contrepartie, à connaître avant de la découvrir plus tard
**Ce qui tient (validé par l'adversaire)** : l'objet auto-possédé **tue le griefing** de la désinscription forgée d'autrui, et **retire la fuite** du userId-dans-le-marqueur. Vrais gains — mais ils n'exigent pas tout le pivot.
Ce `:v:` est un **pseudonyme stable et permanent de la personne**, présent dans **toute** référence cap-less vers **n'importe lequel** de ses documents protected. Il ne dit pas *qui* (`BLAKE3(store_id)` n'est pas inversible), mais c'est un **handle constant**, le même partout et pour toujours.
- Un créateur organisant plusieurs événements voit « le même `:v:` revient » → **corrélation de présence** d'un événement à l'autre.
- Si quelqu'un apprend un jour, par un seul recoupement, que `:v:X` = Marie, **toutes** les participations de Marie deviennent liables **rétroactivement**.
**Le point structurel** : c'est **le même bit d'information** qui permet de dédupliquer sans lire et qui permet de tracer. On ne peut pas garder l'un en supprimant l'autre — sauf à changer le **découpage en stores**, ce qui déplace le curseur sans faire disparaître l'arbitrage. Ce n'est pas un défaut de l'émulation : la propriété survit au vrai NextGraph.
## Ce qui change vs l'implémentation actuelle (Option-B)
L'existant ([[brief_2026-07-06_reactive-reads-and-attendance]]) dérive un `participantCount` **muté en place** depuis des marqueurs d'inbox portant le **`userId` en clair**.
- **Retirer le `userId`** des dépôts d'inbox → ne reste que le **did cap-less**.
- **Compter des références distinctes** (par `:v:`), plus des `userId`.
- **`event.participantCount` muté disparaît** au profit de `Set.size`.
- La **résolution d'identité** passe par la **lecture de l'objet** (donc par la clé), plus par le marqueur.
Ce qui reste valable tel quel : la **lecture réactive** et le **ré-armement à la reconnexion** ; le **fix d'espaces d'id** déjà livré.
## Points ouverts
- **Désinscription.** Supprimer son objet ne suffit pas : sans clé, le créateur ne peut pas *observer* la suppression (VÉRIFIÉ). Il faut donc un **acte explicite** — un second dépôt d'inbox « retire la référence X ». Forgeable (n'importe qui peut le déposer), ce qui est **hors périmètre sécurité** mais mérite d'être acté. Cf. [[caveat_participation-deletion]] : la désinscription doit rester **autoritative**.
- **Créateur hors-ligne.** Le Set ne bouge pas tant qu'il n'a pas traité son inbox. Accepté en V1 ; un service curateur est la sortie éventuelle.
- **Validation d'existence.** Le créateur *peut* vérifier qu'un did pointe sur un objet réel avant de l'ajouter (protocole `Ext`). **Pas requis** par le modèle — à ranger comme durcissement optionnel, pas comme prérequis.
- **Reconnaissance par les connexions** (étape 7) — explicitement remise à un 2e temps : comment la clé est scellée aux connexions, et ce qu'il advient d'une connexion rompue.
## Dépendances
- **Bloquant** : l'**émulation caps du polyfill** alignée — réfs cap-less + possession, **ET** (pour la dédup #3) **WriteCap=membership + stores par-événement à signature vérifiable**. Ces deux derniers **ne sont pas encore façonnés** par le brief polyfill → son **scope doit s'élargir**, sinon les deux briefs **ne composent pas** (constat adverse). Brief polyfill `2026-07-20-caps-emulation-alignment`.
- **Parké** : la **terminologie identité** (wallet/user/profil) — détermine « à quelle identité on scelle ».
- **INFÉRÉ** : le **fetch keyless** (existence sans lire) — à confirmer pour la version forte.
- **Bloquant** : l'**émulation caps du polyfill**. Aujourd'hui `caps.ts` modélise une **ACL** (set de principals par document) là où le réel est **possession de clé**, et le contenu reste lisible en clair (`sparqlQuery`, `inbox.read` contournent le filtre). Tant que ce n'est pas corrigé, coder l'anonymat côté Festipod produirait du code qui **prétend** isoler sans isoler. Brief polyfill `2026-07-20-caps-emulation-alignment`, lot P1.
- **Parké** : la **terminologie identité** (wallet / user / profil) — cf. `.project/to-discuss.md`.
## Statut : direction cible, PAS un pivot immédiat
## Statut : direction cible validée, mise en œuvre gatée
Vu les tensions ci-dessus, **ne pas retirer Option-B** tant que : (a) le **trilemme** n'est pas tranché (que sacrifie-t-on, ou quelle mitigation — pseudonyme par-événement signé ?) ; (b) l'**émulation caps du polyfill** est alignée (sinon l'anonymat n'est pas applicable) ; (c) la **terminologie identité** est clarifiée ; (d) le **fetch keyless** est **vérifié** (spike P0). Les fixes contenus déjà faits (id-space) restent ; le reconnect-lifecycle reste utile quel que soit le modèle.
Le modèle est **tranché** (PO, 2026-07-27) et ses fondations sont **vérifiées**. Ce qui reste gaté, c'est la **mise en œuvre** : elle attend le lot P1 du polyfill. **Ne pas retirer l'Option-B** d'ici là.
## Questions ouvertes
- **Owner hors-ligne** : le Set ne bouge pas tant que le créateur n'a pas matérialisé (accepté V1 ; futur service curateur).
- **Dédup** : « une participation = un objet par user par événement » à garantir (sinon un user avec 2 objets = compté 2×).
- **Anonymat-hôte** : par défaut le créateur ne détient pas le cap (sauf ami) → il ne voit pas l'identité. Cohérent avec l'arbitrage discuté.
Liens : [[brief_2026-07-06_reactive-reads-and-attendance]] (superseded en partie), [[caveat_participation-deletion]], app-security ([[brief_2026-05-18_authorization-matrix]], [[knowledge_trust-model]]), polyfill `readcap-and-nuri-model.md` + brief caps.
Liens : [[brief_2026-07-06_reactive-reads-and-attendance]] (superseded), [[caveat_participation-deletion]], [[rule_capture-nextgraph-findings]], [[rule_document-per-entity]], app-security ([[brief_2026-05-18_authorization-matrix]], [[knowledge_trust-model]]), polyfill `readcap-and-nuri-model.md` + `docs/vision.md`.
@@ -0,0 +1,33 @@
---
type: rule
summary: Toute connaissance importante établie sur le fonctionnement RÉEL de NextGraph (mécanisme du cœur/broker/verifier, sémantique d'un primitif, propriété de forme) → la consigner AU MOMENT de la découverte dans la doc de référence du polyfill `../../nextgraph/ng-eventually-js/docs/`, jamais dans le repo Festipod ; distinguer VÉRIFIÉ d'INFÉRÉ, et ne jamais déduire la forme CIBLE de l'état COURANT du source
---
# Règle : consigner toute connaissance NextGraph au moment où on l'établit
Quand une enquête établit un **fait important sur le fonctionnement réel de NextGraph** — le mécanisme d'un primitif, la sémantique d'une structure, une propriété de forme (« l'overlay est *store*-scopé, jamais document-scopé »), une garde d'accès, ce qu'une opération exige ou n'exige pas — **écris-le tout de suite** dans la documentation de référence du polyfill :
`../../nextgraph/ng-eventually-js/docs/` (depuis la racine de ce repo) — typiquement la fiche de référence du sujet (modèle de caps/NURI, état courant, référence SDK).
**Jamais dans le repo Festipod.** `AGENTS.md` l'interdit explicitement : la doctrine Festipod décrit *comment Festipod utilise le SDK*, pas l'état de NextGraph. Cf. [[rule_app-uses-sdk-surface-only]].
## Au moment de la découverte — pas à la fin
Le « je consignerai en fin de session » ne marche pas : le contexte est compacté avant, et le fait est perdu. Ces connaissances coûtent **très cher** à établir (plusieurs enquêtes d'agents dans le source Rust, souvent contradictoires avant convergence) et sont **invérifiables de mémoire** — une seconde session repaiera le prix fort pour la même réponse, ou pire, se contentera d'une intuition fausse.
## Le piège central : état courant ≠ forme cible
**Ne jamais lire l'état courant de `nextgraph-rs` pour en DÉDUIRE la forme cible.** Le source contient de l'**échafaudage inachevé** qui ressemble à du modèle : on peut y lire des types d'appartenance et de permissions qui sont **inertes au runtime** (jamais appelés hors tests unitaires, structures construites vides). En déduire un primitif « membership » et le façonner dans le polyfill, c'est y graver une forme qui n'existera pas — exactement le mode d'échec que le polyfill existe pour empêcher.
Le source sert à **vérifier un mécanisme existant**, jamais à **inférer une intention**. L'intention se demande au concepteur de NextGraph.
## Forme de la note
- **Distinguer VÉRIFIÉ** (chemin lu de bout en bout, ou mieux : observé à l'exécution) d'**INFÉRÉ** (déduit, non tracé). Un fait porteur non marqué se transforme silencieusement en certitude.
- **Pointer des symboles**, pas des numéros de ligne (volatils) — et dater la note.
- Écrire aussi la **conséquence** du fait, pas seulement le fait : c'est elle qu'on relira.
- Un fait qui **contredit** une note existante → corriger la note, ne pas empiler.
## Règle sœur
Celle-ci vise la **connaissance** — ce qui *est* ; [[rule_nextgraph-inbox]] vise ce qu'il faut **remonter ou attendre** — les dysfonctionnements et les manques (→ `../../nextgraph/orm-tests/INBOX/`). Une même enquête produit souvent les deux : ranger chaque moitié à sa place. Cf. [[knowledge_nextgraph-stack]].
@@ -1,23 +0,0 @@
---
type: rule
summary: Tout dysfonctionnement de NextGraph lui-même (cœur/broker/verifier ou primitif SDK qui se comporte mal — PAS un bug app ni un câblage polyfill) → créer un rapport .md dans le bug-inbox NextGraph partagé `../../nextgraph/orm-tests/INBOX/`
---
# Règle : consigner tout dysfonctionnement NextGraph dans le bug-inbox partagé
Quand tu identifies un **dysfonctionnement de NextGraph lui-même** — le cœur/broker/verifier, ou un primitif SDK (`doc_subscribe`, `sparql_update`, socket, reconnexion, ouverture de repo…) qui se comporte mal — **crée un rapport `.md`** dans le bug-inbox NextGraph partagé :
`../../nextgraph/orm-tests/INBOX/` (depuis la racine de ce repo) — le repo frère `nextgraph/orm-tests` (tests d'intégration ORM contre un vrai broker, avec `tests/standalone/` pour les repros).
## Ce qui QUALIFIE (et ce qui ne qualifie pas)
- **Oui** : un primitif NextGraph a le mauvais comportement — socket qui meurt (`SerializationError`), pas de reconnexion automatique, `doc_subscribe` qui ne délivre pas / est lent, cold-open de repo lent, écriture non durable côté broker.
- **Non** : un bug de l'**app** (effet React mal câblé, gating d'un effet) ou un **câblage du polyfill** (mauvais NURI, souscription non ré-armée). Ceux-là se corrigent **chez nous** — ils ne vont PAS dans l'inbox NextGraph. La distinction est cruciale : d'abord prouver que c'est le primitif qui faute (idéalement par un test), pas notre intégration. Cf. [[rule_app-uses-sdk-surface-only]].
## Format du rapport
Nom : `YYYY-MM-DD-<slug>.md`. Contenu : symptôme, **preuve verbatim** (logs / mesures), repro (idéalement un script standalone — `orm-tests/tests/standalone/`), attendu vs observé, pointeurs source (marqués « à re-vérifier » si non revérifiés), sévérité + statut. Un post-mortem plus complet peut vivre côté polyfill/Festipod ; l'inbox reçoit le **rapport actionnable pour les mainteneurs NextGraph**.
## Pourquoi
Séparer proprement « bug NextGraph » (remonte à l'upstream via ce bug-inbox) de « à corriger chez nous » (app/polyfill), et ne **rien perdre** : chaque dysfonctionnement identifié laisse une trace triable. Cf. [[knowledge_nextgraph-stack]].
@@ -0,0 +1,42 @@
---
type: rule
summary: L'inbox NextGraph partagée `../../nextgraph/orm-tests/INBOX/` reçoit DEUX familles de fiches — les dysfonctionnements (un primitif se comporte mal) ET les manques (un primitif dont on a besoin, pas encore implémenté, qu'on émule dans le polyfill en attendant). Elle sert de suivi de l'avancement de NextGraph : quand un manque est comblé en amont, sa fiche dit quoi RETIRER du polyfill.
---
# Règle : l'inbox NextGraph reçoit les dysfonctionnements ET les manques
L'inbox NextGraph partagée est `../../nextgraph/orm-tests/INBOX/` (depuis la racine de ce repo) — dans le repo frère `nextgraph/orm-tests`, qui héberge les tests d'intégration ORM contre un vrai broker (`tests/standalone/` pour les repros).
Elle n'est **pas** qu'un bug-tracker. Elle a **deux entrées** et **une boucle de sortie**.
## Entrée 1 — les dysfonctionnements
Un primitif NextGraph existe mais **se comporte mal** : socket qui meurt (`SerializationError`), pas de reconnexion automatique, `doc_subscribe` qui ne délivre pas ou tarde, cold-open de repo lent, écriture non durable côté broker, panique atteignable.
## Entrée 2 — les manques dont on a besoin
Un primitif **n'est pas encore implémenté** (ou n'est qu'un échafaudage inerte) alors que notre modèle en dépend. Le déposer aussi, avec les trois informations qui font sa valeur :
- **ce dont on a besoin** et pourquoi — le modèle qui en dépend ;
- **ce que le polyfill fait en attendant** — l'émulation qui bouche le trou ;
- **ce qu'il faudra retirer** du polyfill le jour où ça atterrit en amont.
C'est ce troisième point qui transforme la fiche en **ticket de nettoyage**. Sans lui, l'émulation survit à sa raison d'être et le polyfill se met à diverger de la cible — exactement ce qu'il existe pour éviter.
## Ce qui ne qualifie PAS
Un bug de l'**app** (effet React mal câblé, gating d'un effet) ou un **câblage du polyfill** (mauvais NURI, souscription non ré-armée). Ceux-là se corrigent **chez nous**. La distinction est cruciale : d'abord prouver que c'est le primitif qui faute — idéalement par un test — pas notre intégration. Cf. [[rule_app-uses-sdk-surface-only]].
## La boucle : l'inbox suit l'avancement de NextGraph
Les fiches ne partent pas seulement vers l'amont, elles se **relisent** : ensemble, elles disent où en est NextGraph par rapport à ce dont Festipod a besoin. Quand une fiche se résout en amont, la mise à jour du polyfill suit — souvent en **retirant** de l'émulation devenue inutile, pas en ajoutant du code.
## Format de la fiche
Nom : `YYYY-MM-DD-<slug>.md`. Contenu : nature (**dysfonctionnement** ou **manque**), symptôme ou besoin, **preuve verbatim** (logs, mesures, pointeurs source marqués « à re-vérifier »), repro quand c'est un dysfonctionnement (idéalement un standalone dans `orm-tests/tests/standalone/`), attendu vs observé, et — pour un manque — le **contournement polyfill** et **ce qu'il faudra retirer**. Sévérité + statut.
L'inbox reçoit le **rapport actionnable pour les mainteneurs NextGraph** ; un post-mortem plus long peut vivre côté polyfill.
## Règle sœur
Celle-ci vise ce qu'il faut **remonter ou attendre** ; [[rule_capture-nextgraph-findings]] vise la **connaissance** établie sur le fonctionnement réel (→ doc de référence du polyfill). Une même enquête produit souvent les deux : ranger chaque moitié à sa place. Cf. [[knowledge_nextgraph-stack]].