From a21d9b07350e1f17c9f5b2cbcc398238a0847790 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Mon, 20 Jul 2026 13:16:04 +0200 Subject: [PATCH] =?UTF-8?q?docs(concept):=20durabilit=C3=A9=20=C3=A9critur?= =?UTF-8?q?e=E2=86=94d=C3=A9connexion,=20d=C3=A9cision=20wallet-partag?= =?UTF-8?q?=C3=A9-unique,=20rule=5Fbun-first=20(install=20pnpm)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit caveat_write-durability-across-disconnect + decision_2026-07-20 (wallet partagé = seul mode ; identifiant ≠ username profil) + amendement bun-first. Marqueurs _debt.md inclus (voyagent avec la branche, à réconcilier avant push). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg --- .project/concepts/app-architecture/_debt.md | 9 ++++++ .project/concepts/app-security/_debt.md | 9 ++++++ ...sion_2026-07-20_shared-wallet-only-mode.md | 32 +++++++++++++++++++ .project/concepts/bdd-testing/_debt.md | 20 ++++++++++++ .project/concepts/data-layer/_debt.md | 8 +++++ .project/concepts/data-layer/_overview.md | 3 +- ...veat_write-durability-across-disconnect.md | 17 ++++++++++ .project/concepts/functional-domain/_debt.md | 10 ++++++ .../concepts/tech-stack/rule_bun-first.md | 16 +++++++--- 9 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 .project/concepts/app-architecture/_debt.md create mode 100644 .project/concepts/app-security/_debt.md create mode 100644 .project/concepts/app-security/decision_2026-07-20_shared-wallet-only-mode.md create mode 100644 .project/concepts/bdd-testing/_debt.md create mode 100644 .project/concepts/data-layer/_debt.md create mode 100644 .project/concepts/data-layer/caveat_write-durability-across-disconnect.md create mode 100644 .project/concepts/functional-domain/_debt.md diff --git a/.project/concepts/app-architecture/_debt.md b/.project/concepts/app-architecture/_debt.md new file mode 100644 index 0000000..3e75c82 --- /dev/null +++ b/.project/concepts/app-architecture/_debt.md @@ -0,0 +1,9 @@ +# Doc-debt — app-architecture + +> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file. +> One block = one "big change": `why` + `files` + `verify` (leaves to review). + +## Raw markers (consolidate into blocks, then delete) +- TOUCHED src/shared/context/FestipodDataContext.tsx @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/auth/screens/AccessGateScreen.tsx @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/shared/context/AccountContext.tsx @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) diff --git a/.project/concepts/app-security/_debt.md b/.project/concepts/app-security/_debt.md new file mode 100644 index 0000000..3a464f9 --- /dev/null +++ b/.project/concepts/app-security/_debt.md @@ -0,0 +1,9 @@ +# Doc-debt — app-security + +> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file. +> One block = one "big change": `why` + `files` + `verify` (leaves to review). + +## Raw markers (consolidate into blocks, then delete) +- TOUCHED src/modules/auth/screens/AccessGateScreen.tsx @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/auth/steps/ui/barriere-acces.steps.ts @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/auth/steps/data/connexion.steps.ts @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) diff --git a/.project/concepts/app-security/decision_2026-07-20_shared-wallet-only-mode.md b/.project/concepts/app-security/decision_2026-07-20_shared-wallet-only-mode.md new file mode 100644 index 0000000..ff53fe3 --- /dev/null +++ b/.project/concepts/app-security/decision_2026-07-20_shared-wallet-only-mode.md @@ -0,0 +1,32 @@ +--- +type: decision +summary: Le wallet partagé est le SEUL mode de fonctionnement (le polyfill @ng-eventually/client en dépend comme backend de données) ; le repli « sans wallet partagé » est retiré — mauvaise config → écran d'erreur franc, plus de formulaire nu. Réaffirme que l'identifiant de la barrière = id du wallet/espace, distinct du username du profil. +--- + +# Décision (2026-07-20) — le wallet partagé est l'unique mode ; identifiant ≠ username du profil + +## Contexte + +Régression observée : à l'ouverture, l'app tombait sur un **formulaire nu demandant un identifiant**, sans l'assistance de chargement du portefeuille. Cause : `FESTIPOD_SHARED_WALLET_PASSWORD` non défini dans l'environnement du serveur → `hasSharedWallet()` faux → `AccessGateScreen` basculait sur son mode replié. Or ce mode est une **impasse** : un appareil sans wallet ne peut pas se connecter une fois l'assistance d'import masquée. En parallèle, l'ancienne notion de « username » traînait encore pour désigner l'**identité du wallet**, ce qui la confondait avec le vrai username du profil. + +## Décision + +1. **Le wallet partagé est le seul mode supporté.** Festipod ne fonctionne pas sans lui — le polyfill `@ng-eventually/client` s'en sert comme backend de données (voir [[knowledge_authentication]], `rule_app-uses-sdk-surface-only`). `hasSharedWallet() === false` n'est donc **pas un mode fonctionnel** : c'est une **mauvaise configuration** → `AccessGateScreen` affiche un **écran d'erreur franc** (« Portefeuille partagé non configuré, définir `FESTIPOD_SHARED_WALLET_PASSWORD` »), jamais le formulaire nu en impasse. + +2. **L'identifiant de la barrière ≠ le username du profil.** L'identifiant saisi à `AccessGateScreen` est l'**id technique du wallet/espace** (normalisé en minuscules, porté par le param d'URL `?id=`), pas un username. Le **username** est un concept distinct qui vit dans `UserProfile` (`@handle`, prédicat `http://festipod.org/username`). Le code et les tests ne doivent plus étiqueter l'identité du wallet « username/user » (renommé en `identifier`). Réaffirme et prolonge [[decision_2026-07-06_identifier-at-access-barrier]]. + +## Conséquences + +- `AccessGateScreen` : rendu 3-branches (erreur config / flux d'import assisté quand non connecté / champ identifiant seul quand déjà connecté). +- Renommage `username → identifier` de l'identité du wallet dans l'infra de test (`freshScenarioIdentifier`, `freshIdentifier`), `registration.ts`, `ngSession`, + commentaires ; **`UserProfile.username` intact** (profil, seed, affichage, SHEX). +- `.env.example` ajouté à la racine pour rendre la config explicite (dont `FESTIPOD_SHARED_WALLET_PASSWORD`, `FESTIPOD_SHARED_WALLET_FILE`). + +## Alternative écartée + +Garder le repli sans-wallet comme futur « flux wallet-propre » : écarté **pour l'instant** — aucun flux wallet-propre à court terme, et le repli silencieux créait une impasse trompeuse. À réintroduire **explicitement** le jour où un mode wallet-propre (chaque utilisateur avec son propre wallet NextGraph) existera, hors stopgap. + +## Liens + +- Stopgap wallet partagé : `decision_2026-06-15_shared-wallet-login-flow` (référencé par `AccessGateScreen`/`AccountContext`). +- [[decision_2026-07-06_identifier-at-access-barrier]] — l'identifiant à la barrière. +- [[knowledge_authentication]], [[knowledge_trust-model]]. diff --git a/.project/concepts/bdd-testing/_debt.md b/.project/concepts/bdd-testing/_debt.md new file mode 100644 index 0000000..50704b2 --- /dev/null +++ b/.project/concepts/bdd-testing/_debt.md @@ -0,0 +1,20 @@ +# Doc-debt — bdd-testing + +> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file. +> One block = one "big change": `why` + `files` + `verify` (leaves to review). + +## Raw markers (consolidate into blocks, then delete) +- TOUCHED src/modules/event/features/reconnexion-persistance-e2e.feature @2026-07-13 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/steps/e2e/reconnexion-persistance.steps.ts @2026-07-13 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/shared/test-harness/harness-ng.tsx @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/shared/test-harness/harness.tsx @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/features/reconnexion-socket-mort.feature @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/steps/data/reconnexion-socket-mort.steps.ts @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/features/reconnexion-meme-identite.feature @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/steps/data/reconnexion.steps.ts @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/features/reconnexion-froide-sans-local.feature @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/steps/data/reconnexion-froide-sans-local.steps.ts @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/auth/steps/ui/barriere-acces.steps.ts @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/shared/support/hooks.ts @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/steps/data/isolation.steps.ts @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/auth/steps/data/connexion.steps.ts @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) diff --git a/.project/concepts/data-layer/_debt.md b/.project/concepts/data-layer/_debt.md new file mode 100644 index 0000000..2a1fa73 --- /dev/null +++ b/.project/concepts/data-layer/_debt.md @@ -0,0 +1,8 @@ +# Doc-debt — data-layer + +> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file. +> One block = one "big change": `why` + `files` + `verify` (leaves to review). + +## Raw markers (consolidate into blocks, then delete) +- TOUCHED src/shared/context/FestipodDataContext.tsx @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/shared/utils/ngSession.ts @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) diff --git a/.project/concepts/data-layer/_overview.md b/.project/concepts/data-layer/_overview.md index 0e3e129..24751b0 100644 --- a/.project/concepts/data-layer/_overview.md +++ b/.project/concepts/data-layer/_overview.md @@ -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é] + 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] 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"] --- @@ -28,5 +28,6 @@ Comment Festipod **persiste ses données** via NextGraph (P2P, local-first, chif - [[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é +- [[caveat_write-durability-across-disconnect]] — une écriture juste avant une inactivité/chute de socket peut être **perdue** (non durable broker) ; compte survit. Incident ouvert → post-mortem dans le polyfill > 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_write-durability-across-disconnect.md b/.project/concepts/data-layer/caveat_write-durability-across-disconnect.md new file mode 100644 index 0000000..154fb8b --- /dev/null +++ b/.project/concepts/data-layer/caveat_write-durability-across-disconnect.md @@ -0,0 +1,17 @@ +--- +type: caveat +summary: Une entité écrite juste avant une inactivité/chute de socket peut être perdue silencieusement (jamais durable côté broker) ; le compte survit (pas de fork). Observé Firefox. Le SDK ne confirme pas la durabilité et ne se reconnecte pas seul. +last_checked: 2026-07-14 +--- + +# Piège : une écriture juste avant une déconnexion n'est pas garantie durable + +**Symptôme produit.** L'utilisateur crée une entité (un événement), ça semble réussir, puis une **période d'inactivité** survient ; au rechargement / à la reconnexion, l'entité a **disparu**. Le scope se relit **vide**. L'**identité/compte survit** — ce n'est PAS un fork, c'est une écriture non durable. + +**Mécanisme (résumé, non tranché).** Le socket broker peut mourir spontanément pendant l'idle (`SOCKET IS CLOSED … SerializationError`). L'écriture était dans l'outbox local ; au retour, le replay échoue (`Err(TopicNotFound)`) et l'entité est abandonnée. **Observé Firefox uniquement** à ce jour. Un test @data à froid (2026-07-14) a par ailleurs montré qu'une session **fraîche** (aucun état local, même compte A) ne récupère **pas** le scope propre de A depuis le broker : le test de reconnexion @data qui « passait » relisait en fait l'IndexedDB **locale**. Reste à trancher : **perte à l'écriture** vs **échec de réhydratation à froid** (mécanismes distincts) — voir le post-mortem dans le polyfill. + +**Pourquoi l'app ne le voit pas.** `NgStatus` est dérivé **une seule fois** de la session initiale → aveugle aux chutes en cours de session. Le canal `disconnections_subscribe` du SDK se déclenche sur la panne mais **n'est pas consommé** (ni polyfill ni app). Aucune API ne confirme qu'une écriture a atteint le broker. + +**Ne pas documenter ici les internes NextGraph.** Frontière SDK (voir [[knowledge_nextgraph-stack]]) : cause racine, chaîne causale (socket, reconnexion en TODO) et pistes de correction vivent dans le repo `@ng-eventually/client` → `docs/incidents/2026-07-14-write-loss-on-disconnect.md`. Cette fiche ne garde que l'**impact consommateur** + le pointeur. + +**Statut : ouvert, non traité (2026-07-14).** À revisiter quand le core/SDK adresse la reconnexion ou expose une confirmation de durabilité — ce caveat tombera alors. Voir aussi le débat lecture-à-froid vs perte réelle dans [[brief_2026-07-06_reactive-reads-and-attendance]] (le `BARRIER timed-out` de @data est une signature distincte, non confirmée comme ce bug). diff --git a/.project/concepts/functional-domain/_debt.md b/.project/concepts/functional-domain/_debt.md new file mode 100644 index 0000000..e516b15 --- /dev/null +++ b/.project/concepts/functional-domain/_debt.md @@ -0,0 +1,10 @@ +# Doc-debt — functional-domain + +> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file. +> One block = one "big change": `why` + `files` + `verify` (leaves to review). + +## Raw markers (consolidate into blocks, then delete) +- TOUCHED src/modules/event/features/reconnexion-persistance-e2e.feature @2026-07-13 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/features/reconnexion-socket-mort.feature @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/features/reconnexion-meme-identite.feature @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) +- TOUCHED src/modules/event/features/reconnexion-froide-sans-local.feature @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) diff --git a/.project/concepts/tech-stack/rule_bun-first.md b/.project/concepts/tech-stack/rule_bun-first.md index 0ab3152..72c783d 100644 --- a/.project/concepts/tech-stack/rule_bun-first.md +++ b/.project/concepts/tech-stack/rule_bun-first.md @@ -1,6 +1,6 @@ --- type: rule -summary: Par défaut utiliser Bun et ses APIs natives, jamais les équivalents Node — bun au lieu de node/ts-node, bun install/test/build, bunx, et pas d'express/ws/pg/dotenv +summary: Par défaut utiliser Bun et ses APIs natives, jamais les équivalents Node — bun au lieu de node/ts-node, bun test/build, bunx, et pas d'express/ws/pg/dotenv. EXCEPTION : l'installation des paquets passe par pnpm (les deux repos), pas bun install --- # Règle : Bun-first @@ -11,7 +11,7 @@ Par défaut, utiliser **Bun** et ses APIs natives plutôt que les équivalents N |---|---| | `node `, `ts-node` | `bun ` | | `jest`, `vitest` | `bun test` | -| `npm/yarn/pnpm install` | `bun install` | +| `npm/yarn install`, `bun install` | **`pnpm install`** (voir exception ci-dessous) | | `npm run