Ng eventually #1

Open
Sylvain wants to merge 110 commits from ng-eventually into main
9 changed files with 119 additions and 5 deletions
Showing only changes of commit a21d9b0735 - Show all commits
@@ -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)
+9
View File
@@ -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)
@@ -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]].
+20
View File
@@ -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)
+8
View File
@@ -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)
+2 -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é]
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`.
@@ -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).
@@ -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)
+12 -4
View File
@@ -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 <file>`, `ts-node` | `bun <file>` |
| `jest`, `vitest` | `bun test` |
| `npm/yarn/pnpm install` | `bun install` |
| `npm/yarn install`, `bun install` | **`pnpm install`** (voir exception ci-dessous) |
| `npm run <script>` | `bun run <script>` |
| `npx <pkg>` | `bunx <pkg>` |
| `webpack`, `esbuild`, `vite` | `bun build` / bundler Bun (HTML imports) |
@@ -26,6 +26,14 @@ Par défaut, utiliser **Bun** et ses APIs natives plutôt que les équivalents N
Détail des APIs : [[knowledge_bun-apis]].
## Pourquoi
## Exception : l'installation des paquets passe par pnpm
Le projet est tout-Bun (runtime, bundler, test, serveur). Réintroduire un outil Node redondant ajoute une dépendance, divergerait des conventions du repo, et casse l'intégration native (HMR, transpilation TS automatique, chargement `.env`). C'est un choix de cohérence, pas une préférence cosmétique.
**L'installation des dépendances se fait avec `pnpm install` — pas `bun install` — dans les DEUX repos** (Festipod *et* le polyfill `@ng-eventually/client`). Tout le reste reste Bun : **runtime, build, test, scripts** (`bun run dev`, `bun build`, `bun test`, `bunx`). Seule l'étape d'installation change de gestionnaire.
**Pourquoi.** Le polyfill est installé en prod depuis un dépôt Gitea comme dépendance git à **sous-répertoire** : `git+https://…/ng-eventually.git#main&path:/packages/client`. pnpm (≥ 10.26) résout ce format `#<ref>&path:/…` et garantit une **seule** instance de `@ng-org/*` (un seul verifier) ; `bun install` ne couvre pas ce workflow proprement. Le lockfile de référence est donc `pnpm-lock.yaml`, et le lien local réactif du polyfill passe par `pnpm run link:polyfill` (voir [[knowledge_deployment]]).
**Conséquence pratique.** Les scripts npm qui reposaient sur `node_modules/.bin/*` peuvent casser (pnpm y place des shims shell, pas des entrées JS) — appeler l'entrée JS réelle du paquet (ex. `node_modules/@cucumber/cucumber/bin/cucumber.js`) plutôt que le shim `.bin/`.
## Pourquoi (Bun pour tout le reste)
Le projet est tout-Bun (runtime, bundler, test, serveur). Réintroduire un outil Node redondant ajoute une dépendance, divergerait des conventions du repo, et casse l'intégration native (HMR, transpilation TS automatique, chargement `.env`). C'est un choix de cohérence, pas une préférence cosmétique. L'exception d'installation ci-dessus est le seul écart, et il est motivé par la dépendance git à sous-répertoire.