Commit Graph

16 Commits

Author SHA1 Message Date
Sylvain Duchesne 72bb72c0b7 docs: useShape ne démarre pas contre un broker antérieur au renommage 2026-08-17 11:05:38 +02:00
Sylvain Duchesne bf1e16e1f3 docs: un tag de dépôt ne dit pas quel paquet il a figé 2026-08-17 10:53:05 +02:00
Sylvain Duchesne 1957a8fe0c docs: des contrats à deux voix, et une politique de version qui dit la vérité
Le régime des contrats est devenu bidirectionnel : chaque partie rédige sa
position dans son dépôt et tire celle de l'autre. Le fournisseur écrit son
engagement, chaque consommateur écrit sa déclaration — ce qu'il appelle
vraiment, sous quelles contraintes, et avec quelles frictions.

Ce dernier point change nos usages : la section Frictions est le chemin par
lequel un consommateur expose un problème, le canal hors-bande ne portant que le
signal. Les huit points que Festipod nous a remontés auraient dû arriver par là,
et notre réponse appartient à l'engagement plutôt qu'à un échange qui s'évapore.

Trois interfaces existaient, une seule était documentée. Elles ont maintenant
leur dossier et leurs deux voix : la surface du polyfill, ng-e2e-helpers, et la
couche d'indexation. On a écrit les engagements dont nous sommes l'auteur et les
déclarations des consommateurs qui sont à nous — délibérément AUCUN
usage_festipod : ce n'est pas notre dépôt, et décider à leur place ce qu'ils
consomment viderait le mécanisme de son sens.

Et la Change policy s'inverse. Elle disait « ce paquet n'offre pas de stabilité
sémantique ». Refuser un schéma ne ralentit pas le mouvement — ça retire au
consommateur le seul outil pour le gérer, alors qu'on faisait déjà le travail
d'une publication versionnée sans lui en donner l'étiquette.

Semver, donc, et les majeurs sont le cas NORMAL : une surface qui converge vers
une cible qu'elle n'atteint pas encore casse souvent, et c'est le signal vrai.
Le texte dit les déclencheurs plutôt que le nom du schéma — retirer un symbole
publié ou resserrer un appel est un majeur, ajouter est un mineur. Version
pleine sur main, pré-version sur une branche : Festipod peut épingler
aujourd'hui sans qu'on lui promette du stable, et sans qu'on ait à lui retirer
son adresse à la fusion.

Au passage, la signature publiée de watchShape était fausse — elle annonçait un
argument unique là où l'appel en prend deux. Rapporté par Festipod, confirmé
indépendamment.
2026-08-17 10:10:12 +02:00
Sylvain Duchesne a33fb8a214 docs: se connecter restaure ce qu'on possède, et un dépôt fautif ne verrouille personne
Deux affirmations à corriger, toutes deux écrites par moi et démenties par le
bug d'aujourd'hui.

La feuille disait que se connecter « restaure ce qui vous a été partagé ». C'est
la moitié : le portefeuille garde vos propres clés ailleurs, et ne rejouer que
le registre des partages refusait à une application son PROPRE document après un
rechargement. La règle qui reste : rejouer TOUS les registres durables, pas
celui qui se trouve sur le chemin qu'on teste.

Et la règle « tout échec remonte » était trop grossière. Elle était juste pour
la restauration ; appliquée à chaque étape, elle verrouillait. Un dépôt
inapplicable n'est pas consommé par son échec, donc il refusait la session à
chaque tentative — une personne qui ne peut plus entrer, là où il n'y avait
qu'un partage manquant.

La distinction consignée : ATTEINDRE les files et restaurer sont de
l'infrastructure, et refuser la session y est juste ; APPLIQUER un élément est
de la donnée, et ça se signale sans priver personne de sa session.

Plus la forme générale, parce qu'elle se reproduira : devant un « tout échec
remonte », demander lesquels se rattrapent en réessayant plus tard et lesquels
sont définitifs pour l'acteur. Les définitifs ne doivent jamais garder une porte
qu'il n'a pas d'autre moyen de franchir.
2026-08-16 17:58:25 +02:00
Sylvain Duchesne b50591f5bd feat(e2e-helpers): fabriquer un portefeuille, pour un test comme pour un déploiement
Trois appelants veulent la même chose — un portefeuille neuf, ses octets et son
mot de passe : la suite du polyfill à son démarrage, la suite d'une application
au sien, et un humain une fois pour provisionner un déploiement. Rien ne
produisait ce fichier ; il fallait aller le chercher à la main sur nextgraph.eu.

Mêmes entrées, mêmes sorties, mais des appelants de nature différente : une
suite ne va pas lancer un sous-processus et analyser sa sortie, et un humain ne
va pas écrire un fichier jetable pour appeler une fonction. Donc une fonction,
et un script mince par-dessus.

mintWalletBytes(password, name) rend les OCTETS, pas un chemin. C'est une
correction de ce qui existait : exportWalletFile imposait le disque à tout le
monde, et la suite applicative écrivait un fichier temporaire pour le relire
aussitôt en mémoire, puis devait le nettoyer. Elle ne le fait plus — serveApp
prend les octets. Qui veut un fichier l'écrit ; personne n'y est forcé.

Le script exige --password et refuse d'en inventer un, et refuse d'écraser un
.ngw existant sans --force, vérifié AVANT de fabriquer quoi que ce soit.
Exécuté pour de vrai : 800 octets, et le mot de passe imprimé est celui passé en
entrée. C'est le seul des trois cas qu'aucune suite n'exerce, donc le seul qui
pouvait être livré cassé sans que rien ne le dise.

Ce qui ne change pas : les suites fabriquent un portefeuille par exécution et
n'en héritent jamais ; le provisionnement veut l'inverse, durable et conservé.
Ils partagent la fabrication et l'export, ils divergent sur la durée de vie.
Les identifiants en dur deviennent un paramètre — les suites passent toujours
les leurs, le script prend ceux qu'on lui donne, et il n'existe aucun mot de
passe par défaut.
2026-08-16 15:44:37 +02:00
Sylvain Duchesne 9e7aeef269 docs: deux exécutions peuvent maintenant se chevaucher
La feuille disait le contraire, et c'est moi qui l'ai écrite hier : « deux
exécutions ne doivent jamais se chevaucher », « c'est désormais tenu par un
verrou », « une suite d'un dépôt consommateur entre en collision ». Les trois
sont fausses depuis l'extraction.

Chaque exécution a son propre répertoire de profil et tue le navigateur qui le
détient en finissant. Les exécutions concurrentes — y compris celle d'une
application consommatrice, depuis son propre dépôt — sont indépendantes par
construction.

Et la leçon de forme est consignée, parce qu'elle se généralise : le verrou ne
traitait qu'un symptôme, supprimer le partage a supprimé le besoin. Quand deux
exécutions se disputent une chose, la rendre propre à chacune plutôt que de
sérialiser l'accès.
2026-08-16 14:17:38 +02:00
Sylvain Duchesne 1271d48e9f refactor(e2e): la mécanique de test devient un paquet à part, ng-e2e-helpers
Créer un portefeuille, en obtenir le .ngw, traverser le broker : ce n'est pas du
ressort du polyfill. C'est un besoin commun au polyfill et à toute application
NextGraph — et surtout, ça SURVIT à la migration, alors que le polyfill est fait
pour disparaître. L'y laisser, c'était le faire mourir avec lui ou rendre le
polyfill indéracinable.

Le paquet n'importe rien du polyfill — vérifié mécaniquement — et déclare
Playwright et @ng-org/web en pairs, le consommateur devant maîtriser les
versions. Sa surface : attentes bornées, mesure, navigateur, profils,
portefeuille, traversée du broker, rapport d'exécution, et reconnaissance des
modes de panne connus.

La preuve qu'il est utilisable de l'extérieur : le polyfill le CONSOMME, sans
garder de copie. Restent chez lui les parcours, la barrière et les identités
virtuelles, qui lui sont propres.

Le verrou entre exécutions disparaît, remplacé par un profil par exécution. Il
ne traitait qu'un symptôme — un répertoire partagé que la création de
portefeuille effaçait. Avec un profil par exécution il n'y a plus rien à
sérialiser, les exécutions concurrentes deviennent indépendantes, et la
collision entre deux dépôts s'évanouit au lieu d'être exportée. Six exécutions :
aucun répertoire ni Chromium orphelin.

Et la connaissance descriptive est séparée du pilotage : URL, sélecteurs et
inventaire ordonné des écrans sont des données, passées DANS la page pour la
reconnaissance — donc un échec nomme le même écran que celui sur lequel on
dispatchait.

Un échec de navigateur est désormais nommé comme tel — « the actors browser
STOPPED ANSWERING » — au lieu de sortir sous le nom de l'opération innocente qui
se trouvait en vol.
2026-08-16 14:16:11 +02:00
Sylvain Duchesne cf3c7c7d8b docs: une borne englobante plus courte que ses étapes rend tout échec muet
La leçon du tour, et elle valait des jours : SIGN_IN_MS valait 180 s sur des
étapes totalisant 270 s. La borne du dessus se déclenchait donc toujours la
première, et chaque échec rapportait son nom à elle — jamais celui de l'étape en
cause. On a cherché une cause que le harnais était structurellement incapable de
nommer.

D'où les deux règles consignées : calculer une borne englobante à partir de ses
parties au lieu de choisir un nombre, et dimensionner chaque borne terminale sur
une durée MESURÉE inscrite à côté d'elle. Un chiffre nu ne dit pas s'il est
généreux ou serré, et pourrit sans que personne le voie.

Plus une troisième : déclarer les parcours et leurs vérifications avant que quoi
que ce soit puisse échouer, pour qu'une exécution rende toujours le même nombre
de lignes. Quand le total bouge avec la panne, deux exécutions ne sont plus
comparables — et un total qui rétrécit se lit comme un problème plus petit alors
qu'il est plus gros.

Enfin, un fait observé : notre verrou ne garde que ce dépôt. Une suite
appartenant à une application consommatrice, lancée depuis son propre checkout
contre le même broker, entre en collision exactement comme deux des nôtres — et
c'est invisible des deux côtés.
2026-08-16 12:38:29 +02:00
Sylvain Duchesne ed0f872f5d test(e2e): un échec ne peut plus emporter les suivants, et les bornes sont mesurées
Trois choses, dont une qui explique pourquoi aucun diagnostic n'aboutissait.

SIGN_IN_MS valait 180 s alors que la somme de ses propres étapes en faisait 270.
La borne englobante se déclenchait donc TOUJOURS avant celle de l'étape en
cause, et ne pouvait dire qu'une chose : « bob-… to sign in ». Le message était
structurellement condamné à ne rien apprendre — on a cherché des jours une cause
que le harnais s'interdisait de nommer. Les bornes englobantes sont maintenant
des sommes calculées de leurs étapes.

Les parcours sont isolés. La suite déclare ses 7 parcours et leurs 27
vérifications AVANT tout lancement de navigateur, et rend donc toujours 34
lignes — y compris quand le montage meurt, où les parcours non exécutés sont
rapportés comme tels. Auparavant le total valait 24, 26 ou 27 selon ce qui
mourait : deux exécutions ne mesuraient même pas la même chose. Un échec est
contenu, pas absorbé — il reste compté.

Et chaque borne est dimensionnée sur une durée MESURÉE, inscrite à côté d'elle
dans le code. Le premier rendu d'un acteur prend 4,9 à 7,4 s et vaut 45 s ; la
traversée du broker 1,3 à 2,8 s et vaut 75 s. Un nombre nu n'apprend rien et
pourrit en silence.

Au passage : un walletPage.close() n'avait aucune borne du tout.
2026-08-16 12:28:44 +02:00
Sylvain Duchesne f30685bdb9 docs: la doctrine enseignait le réflexe qui a coûté la journée
La feuille disait : « si la suite rapporte un délai nommé plutôt qu'une
assertion échouée, soupçonner le transport avant le code ». C'est faux, et je
l'ai écrite hier.

Un délai dit seulement qu'une chose n'est pas arrivée à temps. Il ne dit jamais
pourquoi — et se tourner vers l'environnement est la réponse confortable,
puisqu'elle exonère le code.

Le cas est maintenant raconté dans la feuille : tous les sign-in expiraient, on
a accusé le broker et le réseau de l'hôte pendant des heures, et la cause était
une correspondance de sous-chaîne sur une URL, lisible depuis le début. Le
propriétaire du dépôt a tranché contre cette attribution — « je n'ai jamais eu
de problème avec le broker, les tests si » — et il avait raison.

La discipline devient : lire son propre harnais d'abord, et ne parler de
transport qu'une fois le mécanisme nommé.
2026-08-14 10:57:21 +02:00
Sylvain Duchesne 7062364569 fix(e2e): le harnais reconnaissait la page du broker comme l'application
Le prédicat qui cherchait l'iframe applicative était une correspondance de
sous-chaîne : f.url().includes("127.0.0.1"). Or la page d'authentification du
broker porte l'adresse de l'application DANS SA PROPRE requête —
nextgraph.eu/auth/#/?o=http%3A%2F%2F127.0.0.1%3A39975. Elle correspondait donc
dès le premier instant.

Conséquence en chaîne : la boucle d'attente sortait immédiatement, le clic sur
le portefeuille et la saisie du mot de passe étaient sautés comme « déjà
connecté », et la frame principale du broker était rendue à l'appelant comme si
c'était l'application. Celui-ci attendait alors une minute un élément qui
n'existe pas sur cette page.

Ce qui sauvait une exécution était le clic sur « Login », qui change l'URL et
lui retire le paramètre — gardé par une sonde de 2 s sur un bouton mesuré à
1,0–1,6 s d'affichage. Ce tirage au sort était toute l'intermittence, et il
expliquait l'asymétrie : le premier acteur fait la cérémonie du mot de passe,
les suivants non, le portefeuille étant déjà ouvert et diffusé entre les onglets
du broker par BroadcastChannel — vérifié, 3,4 s contre 1,4 s.

Le harnais attend désormais des ÉTATS, plus des durées : il énumère les écrans
possibles, attend celui qui se présente, et aiguille — sous une seule échéance.
Plus aucun waitForTimeout dans ce chemin. Le chemin sans mot de passe est une
branche attendue de plein droit.

Et un échec nomme maintenant le dernier écran reconnu, l'origine attendue, la
trace horodatée des écrans traversés, les URL de toutes les frames et le texte
visible. Les échecs d'aujourd'hui ne disaient qu'une chose : qu'une chose
n'était pas apparue. C'est ce silence qui a coûté la journée en conjectures.

J'avais attribué tout ça au broker. C'était faux, et c'était lisible dans le
code.
2026-08-14 10:56:49 +02:00
Sylvain Duchesne dbd99738f0 docs: deux affirmations que les mesures ont démenties
La doctrine disait que la suite applicative ne peut pas être mesurée sur une
machine dont le réseau bouge. Elle vient de passer 27/27 quatre fois d'affilée
sous exactement ce bruit. Ce qui tranche n'est donc pas l'état de la machine
mais la FORME de l'échec — un délai nommé sur une opération broker désigne le
transport, une assertion qui rend une valeur inattendue désigne le code — et la
répétition.

Et la feuille sur le règlement de l'identité décrivait encore la session comme
un thunk fourni par l'application. Elle appartient désormais au paquet, dont le
wrapper init() capture l'événement : plus aucune application ne peut la câbler
de travers, ce qui était pourtant la cause exacte de la régression racontée
juste au-dessus.

Dette de doc soldée.
2026-08-12 18:32:23 +02:00
Sylvain Duchesne cc8a95d303 feat: le polyfill possède la session et la normalisation des identités
Pour démarrer, une application devait écrire une promesse autour du callback
d'init(), attraper l'événement loggedin, puis fournir un thunk getSession qui
dépiaute session_id et les trois identifiants de store dans notre forme. Plus un
normalizeId. C'est précisément la plomberie que ce paquet existe pour absorber :
chaque application la réécrirait à l'identique, et c'est elle qui a produit deux
défauts aujourd'hui — un blocage et un partage cassé en silence.

En amont, une session est RENDUE ; une application n'en assemble jamais une à
partir de champs bruts. Et les identités virtuelles sont une invention du
polyfill, donc leur normalisation lui appartient.

Le wrapper init() enveloppe désormais le callback de l'appelant : il capture
l'événement, en dérive la session, puis appelle le callback avec le même
événement. Le paquet n'appelle jamais init de sa propre initiative — il
l'enveloppe. Sans callback, il capture quand même.

getSession et normalizeId quittent la surface publiée. Le chemin d'injection
reste pour les harnais, mais inatteignable depuis l'entrée : vérifié par un
import à l'exécution et par un configure() refusé à la compilation.

Défaut trouvé et corrigé en route : le broker envoie session_id en NOMBRE, et le
convertir en chaîne faisait refuser tous les appels par le binding wasm. La
valeur ne fait que transiter, elle est relayée telle quelle. Reste que toute la
chaîne la type string — inexactitude antérieure à ce commit, à traiter à part.

Une application écrit maintenant : configure({ ng, useShape, init, sharedWallet }).
2026-08-12 17:39:12 +02:00
Sylvain Duchesne 218c9ab6b3 docs: la doctrine dit quand la barrière s'affiche, pas seulement ce qu'elle fait
La feuille décrivait l'ordre de résolution de l'identifiant mais taisait la
règle qui décide de l'affichage — page de tête toujours, iframe jamais — et
c'est exactement ce qui vient d'être corrigé dans le code.

Elle porte désormais le raisonnement : le discriminant est le cadre parce que
l'identifiant est un état observable alors que la présence d'un portefeuille ne
l'est pas, et qu'un écran conditionnel doit donc deviner l'état qui compte. Plus
les deux faits observés sur les sites réels qui ferment les alternatives —
l'import ouvre un sélecteur sur place, et notre onglet ne reçoit aucun signal
quand il réussit.
2026-08-12 14:59:37 +02:00
Sylvain Duchesne f5a3adc385 fix: la barrière s'affiche à chaque chargement en page de tête, comme chez Festipod
Notre version décidait d'afficher la barrière sur la présence de l'identifiant.
Festipod décidait sur la session — jamais établie en page de tête, donc l'écran
s'affichait toujours, l'identifiant servant seulement à pré-remplir le champ.

La différence n'est pas ergonomique. L'identifiant est un état qu'on observe ;
le portefeuille, lui, vit dans le stockage d'une autre origine et nous est
illisible. Un écran conditionnel doit donc DEVINER cet état invisible — et
quand il devine « déjà installé » alors que le portefeuille a disparu du
navigateur, il cache les seuls contrôles qui répareraient la situation et
précipite la personne dans une impasse.

Impasse observée sur le site réel : sans portefeuille, la page du broker affiche
un texte statique, zéro bouton, un seul lien vers nextgraph.eu qui NE TRANSPORTE
AUCUN retour vers l'application. Le retour arrière du navigateur est la seule
issue — et il ne sert à rien si la barrière ne reprend pas la personne à
l'arrivée.

Le discriminant devient le cadre, pas l'identifiant : page de tête → toujours,
iframe → on s'efface. C'est le signal que @ng-org/web utilise lui-même et que
Festipod utilisait un étage plus bas.

On ne détecte rien et on ne demande rien. Les trois étapes s'affichent toujours ;
qui possède déjà son portefeuille ignore les deux premières. Aucune case « je
l'ai déjà » : savoir si l'on a importé un portefeuille dans ce navigateur est
une question trop technique pour être posée.

Garde-fou repris de Festipod, qu'on n'avait pas : après un aller-retour vers
l'onglet NextGraph et un retour arrière, la barrière restait figée sur un bouton
mort. Elle recharge désormais sur pageshow persisted — seul moyen de rejouer le
init() qui porte la redirection.

Vérifié sur navigateur : cliquer « Import a Wallet File » ouvre un sélecteur sur
place, sans navigation ni changement d'onglet, et notre onglet ne reçoit AUCUN
signal quand l'import réussit. Détecter le retour est donc impossible, pas
seulement fragile.
2026-08-12 14:59:10 +02:00
Sylvain Duchesne 7c2e8d8f1f docs: deux concepts pour ce que la journée a appris
sign-in — comment un utilisateur passe de rien à une identité qui agit. La
barrière, l'identifiant qui franchit une frontière de partition par l'URL, et
surtout : la redirection vers le broker appartient à @ng-org/web, vérifié dans
son bundle. Le polyfill ne la réimplémente pas ; ce qui lui revient est la seule
chose qu'init() ne peut pas faire, écrire l'identifiant dans l'URL avant qu'il
ne la lise.

La feuille centrale dit pourquoi régler l'identité et se connecter sont deux
actes séparés : l'un ne demande aucune session, l'autre en exige une, et les
confondre bloque dans un sens et casse le partage en silence dans l'autre. Le
piège encore vivant — une connexion abandonnée qui reste joignable — est
consigné comme tel, non corrigé.

e2e-harness — ce que chaque suite juge, et deux choses qu'un agent doit savoir
avant de diagnostiquer : le raccourci qui pré-injectait l'identifiant dans l'URL
a tenu deux défauts invisibles pendant des mois (un lien de téléchargement vers
un 404 que rien ne servait, et une barrière inatteignable pour tout nouvel
arrivant) ; et un tuyau devtools qui lâche tue une exécution sans que Playwright
n'émette d'événement, ce qui ressemble à un défaut produit et n'en est pas.

Le vocabulaire gagne settle, barrier, journey et batch. J'avais aussi introduit
« hand-over » pour la redirection : retiré, c'est le mot de NextGraph qui
l'emporte.
2026-08-11 19:24:00 +02:00