Files
ng-eventually/docs/decisions/shared-wallet-login-flow.md
T
Sylvain Duchesne 107f9d1633 refactor(vocabulary): les noms publiés parlent la langue de la cible, et un test le tient
La correction de nomenclature du 2026-07-30 — en amont un *wallet* n'est qu'un
trousseau, ce qui possède des stores est un **user** (un *site*) — s'était faite
à la main. `walletInbox` y a échappé et a vécu des semaines, en faisant des
dégâts : le nom rendait « une inbox par wallet » évident, masquant qu'un user en
a **deux** en amont (repos de store public et protected, les deux seuls
`AddInboxCap` du moteur). Une discipline appliquée à la main en oublie un ; un
test non.

D'où `test/vocabulary.test.ts` : tout nom publié est bâti sur des mots que la
CIBLE emploie — vérifiés dans `nextgraph-rs` — ou porte un marqueur disant
POURQUOI il n'existe qu'ici (`virtual`, `physical`, `shim`, `emulated`,
`polyfill`), ce qui dit aussi quand il disparaît. Un échec n'est pas « renommer
pour faire passer le test », c'est une question : la cible a-t-elle un mot pour
ça ? la chose n'existe-t-elle qu'ici ? le mot est-il vraiment de la glue ?

Ce que le test a trouvé, et les réponses :

- `walletInbox` → `userInbox`, avec l'écart de cardinalité écrit noir sur blanc
  plutôt que caché par le nom.
- `accounts` / `AccountRecord` / `AccountStorage` → `virtualUsers` /
  `VirtualUserRecord` / `VirtualUserStorage`, module `accounts.ts` →
  `virtual-users.ts`. « account » n'est pas de la cible : c'est notre mot pour
  l'utilisateur virtuel, et le marqueur le dit désormais.
- `readModel` → la fonction `readUnion`, exposée directement. « model » n'était
  ni de la cible ni de la glue, et le namespace ne tenait qu'une fonction.
- Le reste était du vocabulaire légitime à déclarer (`subject`, `base`,
  `schema`, `connected`, le modèle réactif de l'ORM).

Corrigé au passage, sur signalement du contrat interne : l'en-tête d'`open-repo`
justifiait son correctif par un mécanisme que le source contredit. Un repo absent
de `self.repos` lève bien `RepoNotFound`
(`engine/verifier/src/request_processor.rs:264,269`). Les 0 lignes observées
viennent d'ailleurs — `Verifier::load` repeuple `self.repos` depuis le stockage
sur un profil persistant (`verifier.rs:535-560`), et notre propre `readDoc`
attrape toute erreur et rend `[]`. Le correctif est bon, le diagnostic écrit à
côté ne l'était pas.

159 tests unitaires, typecheck src/test/e2e vert, e2e 40/40 contre le broker.
2026-08-04 14:35:01 +02:00

71 lines
4.0 KiB
Markdown

# ADR — Shared-wallet identity flow (perceived login)
**Date:** 2026-06-15 · **Status:** Accepted (frozen). The rationale behind how
the consumer application presents identity selection as a perceived login, and why
the lib's identity store (`shared-wallet/virtualUsers.ts`) must never touch NextGraph. The lib itself
no longer frames this as a login: it receives an identity id, set at wallet-import
time; the perceived-login UX lives entirely in the consumer application.
## Starting constraint
NextGraph login is not programmable: it is a web redirect to the broker
page (`nextgraph.net`). The shared wallet cannot be opened silently — at least one
broker-redirect pass is required per device. The question is therefore not "how to
avoid the redirect" but "how to order and present it" so the UX stays coherent.
## Decision — technical gate first, application "Connexion" second
Two distinct layers, presented in this order:
1. **Real layer (technical, not perceived as login).** The broker redirect appears
immediately, before any app render. Because it precedes the app, the user
reads it as a technical access barrier to the test environment (a beta
wall), not an application login. Same shared credentials for everyone
(given in the invitation, "access code" style). Once per device, then
persistent. It is not labelled "login."
2. **Application layer (perceived as the login).** A "Connexion" screen where the
user picks an identity id (relayed to the lib's identity store, persisted in
`localStorage`, the current principal). This is the login *in the user's
perception*, presented by the consumer application. No password — declarative
selection (anyone takes any id — coherent with zero-security / friends). In
practice the id is often a human-friendly handle. "Déconnexion" clears only
the stored id and returns to "Connexion"; it calls no NG function.
The real logout (`ng.session_stop` / `user_disconnect` / `wallet_close`) stays
hidden (settings/debug), because it forces a new redirect.
## Why (vs the rejected option)
**Rejected** — a perceived login first, then a warning page "enter this
id/password", then a *Continue* button triggering the redirect. Rejected: strange
workflow, dissonant double-login, a warning page that looks like a scam, and the
redirect resurfacing mid-use on every session expiry.
**Chosen** because: the mental model stays coherent (the technical barrier not
being perceived as login, the app-level Connexion/Déconnexion pair is complete and
self-consistent); graceful degradation (a re-gate after a browser restart reads as
"reconnecting to the environment", not a bug); and similarity to the target
infra — the "broker redirect → app" shape is exactly the real multi-wallet flow.
At migration you remove the id "Connexion" screen and the technical
barrier becomes the real per-user login — the flow shape does not change.
## Verified technical facts (`nextgraph-rs`, 2026-06-15)
- **Session persistence: yes.** Wallet remembered iframe-side (`localStorage`
long-term + `sessionStorage` for the active session); on reload `init()`
recovers the session without re-triggering the redirect while the broker
session exists (`sdk/js/web/src/index.ts`, `sdk/js/api-web/main.ts`). A full
browser restart (losing `sessionStorage`) can re-trigger the gate.
- **Real logout exposed: yes.** `ng.session_stop()`, `ng.user_disconnect()`,
`ng.wallet_close()` (`sdk/js/lib-wasm/src/lib.rs`); they stop the session /
clear the wallet and force a new redirect afterwards — hence the app-level
"Déconnexion" does not call them, and the real logout stays hidden.
## How this lib realizes it
`shared-wallet/virtualUsers.ts` is an `IdentityStore`: `set(id)` / `clear()` / `get()` only read/write
the identity id in an injected `VirtualUserStorage`; they never call NG. The id is set at
wallet-import time and relayed via the lib's current-identity call; the perceived
login is the consumer application's. See the identity store in
[`../simulation.md`](../simulation.md).