Commit Graph

5 Commits

Author SHA1 Message Date
Sylvain Duchesne 0b936d2119 fix: écrire est une PROPRIÉTÉ, et trois portes qui n'auraient pas dû être ouvertes
Suite de la revue adverse. Quatre trous de frontière, tous hors du champ « l'isolation
est fausse jusqu'à P1b » — P1b parle de matériau de clé, ceux-ci sont des défauts de
FORME et resteraient des trous avec une vraie clé.

**La garde d'écriture reposait sur la mauvaise question.** Elle demandait « ce cap m'a-t-il
été servi par un store public ? ». Ce prédicat était faux dans les deux sens à la fois :
trop laxiste — une clé reçue dans une inbox donnait l'écriture, alors qu'en amont un Link
est « external repos only » et qu'écrire est l'appartenance au repo ; trop strict — la
propriétaire de son propre document public était refusée dès qu'elle l'ouvrait depuis sa
référence avant que son store ne soit listé. Un prédicat poussé dans deux sens est le
signe que c'était le mauvais prédicat.

Écrire dépend désormais de la PROPRIÉTÉ, lue sur la branche Store (l'`AddRepo` émulé),
plus la paternité de session pour les documents créés par la primitive brute qui n'a
aucun store où s'inscrire. Conséquence assumée et documentée : seul le propriétaire écrit,
ce qui est l'état amont d'un repo tant qu'aucun membre n'a été ajouté — mécanisme qu'on
n'émule pas.

**`docs.depositInto` quittait la frontière en la publiant.** Sa doc disait « `inbox.post`
est le seul appelant » : vrai dans la bibliothèque, faux dès qu'on le publie. Démontré :
avec la seule référence nue d'un document public, on réécrit l'adresse d'inbox posée
dessus et on détourne les dépôts destinés à son propriétaire. Une porte qui saute une
garde ne doit pas être ouvrable par une application — elle rejoint la machinerie.

**Le filtre de lecture n'interceptait que trois membres** et transmettait tout le reste
lié à la CIBLE : `.values()`, `.map()`, `.getById()` rendaient le contenu d'un autre
utilisateur — précisément les membres qu'une API de set réactif met en avant. Les membres
qui rendent des éléments sont désormais filtrés, les mutations passent (elles ne rendent
rien), et **tout membre inconnu lève** au lieu de transmettre : une transmission est une
fuite silencieuse, une levée est bruyante et greppable.

**Le mémo du store public était par document.** Le premier demandeur déclenchait le
téléchargement, le cap était classé chez LUI, et tout demandeur suivant recevait « oui »
en ne détenant rien. En amont un broker qui sert un overlay externe répond à TOUS. Le
mémo garde la valeur, l'appelant la classe pour qui est connecté.

Aussi : l'exemption `declareInfrastructure` supprimée — zéro appelant, ensemble toujours
vide, et une doc décrivant deux documents exemptés qui ne l'ont jamais été. Et les caps
d'écriture décrits comme « partiels » sont dits **inertes**, ce qu'ils sont : `grantWrite`
n'a aucun appelant de production.

**Ce que l'e2e a rattrapé.** Ma première version de la garde refusait au créateur
l'écriture sur un document fait par `docs.docCreate` — 7 étapes rouges contre le broker,
après une suite unitaire restée verte. La primitive brute n'inscrit la paternité nulle
part ; c'est ce que `mintedHere` couvre désormais.

185 tests unitaires (dont quatre régressions : la propriétaire écrit, le destinataire non,
le store public sert tout demandeur, aucun membre non filtré ne transmet), e2e 40/40 et
applicatif 10/10.
2026-08-07 13:59:13 +02:00
Sylvain Duchesne c5878c6126 test: le contrôle de contrat disait oui à des noms absents
`isNamespaceMember` cherchait le nom dans **tout le texte** des modules de namespace,
commentaires compris. Un nom mentionné n'importe où comptait donc comme exporté.

Mesuré : `documentInboxAddress`, `escapeLiteral`, `getCaps`, `addLink`, `setCurrentUser`
passaient tous — et **`linkTo`** aussi, la fonction supprimée parce qu'elle brisait la
règle d'accès, dont l'absence est expliquée dans un commentaire de `surface/placement.ts`.
Le commentaire qui dit pourquoi elle n'est pas là suffisait à la faire passer pour
publiée.

C'est très probablement ainsi que la dérive du contrat a traversé cinq sections sans
rien faire rougir. Un contrôle qui dit oui à un nom absent est pire que pas de contrôle :
il se lit comme une vérification.

Les exports sont désormais extraits **après retrait des commentaires**, déclarations et
ré-exports nommés, par un seul `moduleExports()` que les trois contrôles partagent.
Vérifié : les six fantômes ci-dessus sont maintenant contrôlés, et les vrais membres
(`post`, `share`, `createEntityDoc`, `docCreate`) toujours reconnus.

Ajouté au passage un contrôle qui manquait dans l'autre sens : chaque groupe de namespace
de l'annexe doit correspondre **exactement** aux exports réels de son module. Un membre
retiré de `inbox:` ne faisait rougir personne.

Trouvé par une revue adverse, reproduit puis corrigé ici. 181 tests, 0 échec.
2026-08-07 13:32:49 +02:00
Sylvain Duchesne 0455a408b6 refactor(api): le bootstrap redescend de quatre appels à un
L'objectif acté était deux appels spécifiques au polyfill, voire un. Il en publiait
quatre. Chacun des trois de trop était une raison que la BIBLIOTHÈQUE a, pas un besoin
qu'une application a :

- **`configureStoreRegistry`** existait parce qu'il y a deux internes à câbler — le SDK
  injecté d'un côté, la session de l'autre. Vu de l'appelant, les deux disent « voici ce
  qu'il te faut pour tourner ». Replié dans `configure`, qui prend désormais
  `getSession` / `normalizeId` / `pointerGuard`.
- **`setCurrentUser`** n'a plus lieu d'être publié depuis que le portail d'accès est
  passé dans le polyfill : c'est lui qui pose l'identité. Et une application qui nomme
  sa propre identité est exactement le geste qui inverse le modèle — il ne doit pas
  exister d'appel publié vers lequel se tourner. Le harnais e2e, lui, joue plusieurs
  identités sur une même page ; il y accède par le chemin interne, ce qu'un harnais a
  le droit de faire et une application non.
- **`connectedUser`** est maintenant attendu DANS `ensureIdentity`. Ce n'était pas une
  commodité : la suite applicative avait montré qu'une app devait l'attendre elle-même,
  sinon une note qu'on venait de lui partager se lisait comme illisible. J'avais traité
  le symptôme dans l'app d'exemple ; le défaut était côté bibliothèque. En amont, ouvrir
  la session EST la connexion — aucune application n'attend un second appel.

Reste donc `configure({ … })`, plus `await ensureIdentity()` dont le site d'appel
survit à la migration : une application attendra toujours une session avant de rendre.

Le test étendu hier a fait son travail : les deux contrôles de contrat sont passés au
rouge sur `configureStoreRegistry`, `connectedUser` et `StoreRegistryDeps` dès que la
surface a bougé.

180 tests unitaires, e2e 40/40 (3,4 min) et applicatif 10/10 (0,8 min).
2026-08-07 12:06:15 +02:00
Sylvain Duchesne b98fcaa77d docs+test: le contrat avait dérivé — et le mécanisme ne voyait pas les règles
En vérifiant l'alignement de la surface, cinq sections du contrat s'étaient
désynchronisées du code sans que rien ne rougisse :

- § 1 montrait `getConfig`, `getStoreRegistryDeps`, `resetConfig` et
  `resetStoreRegistry` comme exportés — retirés à la fusion des portes ;
- § 11 documentait `escapeLiteral` / `escapeIri` / `assertNuri` comme publiés — ils ne
  le sont plus, et l'absence de garde de type est désormais expliquée par sa raison :
  les portes valident elles-mêmes (`NuriLike`), publier une garde inviterait le cast
  que les types servent à empêcher ;
- § 12 listait sept fonctions `storeRegistry` — il y en a cinq depuis que les deux
  fonctions d'ADRESSE d'inbox sont parties (une app nomme un document ou une personne,
  jamais une adresse) ;
- § 13 listait `IdentityStore`, `browserIdentityStore` et `getCurrentUser` comme
  publiés — retirés le 2026-08-05 ;
- `ensureIdentity` était publié **sans aucune règle**, et l'annexe renvoyait à un
  « § 2bis » qui n'existait pas.

**§ 2bis est écrit** : le portail d'accès n'a aucune contrepartie en substance — en
amont un utilisateur ouvre SON portefeuille et il n'y a rien à nommer — mais son SITE
D'APPEL survit, et c'est pourquoi sa signature ne prend pas d'identifiant : nommer son
identité est précisément la partie qui disparaît, donc elle ne doit pas figurer dans les
paramètres.

**Le mécanisme est étendu.** `test/vocabulary.test.ts` épinglait l'annexe — les NOMS —
et ne voyait pas les sections, là où vivent les règles. Une règle périmée est pire
qu'une règle absente : elle se lit comme vérifiée. Désormais tout `export` montré dans
un bloc « ### Today » doit être réellement exporté ; ce qu'on garde pour mémoire passe
en commentaire, que le contrôle ignore par construction. Les cinq dérives ci-dessus
auraient été rouges le jour même.

Nettoyé aussi : deux commentaires de doc orphelins dans `surface/placement.ts`,
restés au-dessus de l'accolade fermante après le retrait des fonctions qu'ils
décrivaient.

180 tests unitaires, typecheck bibliothèque / exemple / harnais.
2026-08-07 11:51:24 +02:00
Sylvain Duchesne 0eb25286c8 refactor: renommer client → sdk, et fusionner les deux portes en une
Deux mouvements de surface, aucun changement de comportement.

**`packages/client` → `packages/sdk`, `@ng-eventually/client` → `@ng-eventually/sdk`.**
« client » ne disait rien : ce paquet EST le SDK que l'application appelle, et c'est
tout ce qu'elle appelle. L'ancien nom reste comme mot-clé de recherche dans
`docs/source-layout-by-fate.md` et le tableau des paquets du README.

**Une seule entrée.** L'entrée `./polyfill` disparaît ; ses symboles applicatifs —
`configure`, `configureStoreRegistry`, `setCurrentUser`, `connectedUser` et leurs types
— vivent dans un bloc `POLYFILL-ERA` de `src/index.ts`.

Ce que la seconde porte portait mérite d'être nommé avant d'être retiré : *ce qu'on
importe de ce chemin est exactement ce qu'on supprimera à la migration*. Une seule
porte perd ce signal — rien à la ligne d'import ne distingue `configure`, qui part, de
`docs`, que le vrai SDK remplace sur place. Trois choses le portent désormais : le bloc
lui-même, l'inventaire d'exports de `docs/api-contract.md` (épinglé par
`test/vocabulary.test.ts`, donc il ne peut pas rancir en silence), et le contrôle de
vocabulaire sur les noms publiés.

**Six symboles quittent la surface au passage**, et la fusion est ce qui a rendu le
choix visible plutôt qu'hérité :

- `getConfig` / `getStoreRegistryDeps` — câblage interne, atteint par
  `shared-wallet/bootstrap` ;
- `resetConfig` / `resetStoreRegistry` / `resetCaps` — remises à zéro de test, atteintes
  par leur chemin interne, ce qui est leur raison d'être ;
- le `share` direct — `inbox.share` a toujours été la même fonction, et la publier deux
  fois brouillait la frontière qu'elle servait à marquer.

Corrections d'affirmations fausses trouvées en chemin : le contrat annonçait `isNuri` /
`hasReadCap` sur la porte SDK alors qu'ils ne sont plus exportés depuis le passage au
permissif en entrée (`NuriLike` validé à la porte) ; le README du paquet documentait
`capFor`, `shareCap`, `getCaps` et `publishRepoLink`, dont aucun n'existe ; et le README
de l'app d'exemple affirmait que la suite e2e la pilote, ce qui reste à faire.

179 tests unitaires, typecheck bibliothèque / exemple / harnais, e2e 42/42 contre le
broker en ligne — mesuré une fois après le renommage, une fois après la fusion.
2026-08-07 11:16:57 +02:00