fix: mon correctif sur createEntityDoc était faux dans les deux sens

Second tour adverse sur le lot C. Six trouvailles, dont trois sur ce que je venais de
livrer.

**Le correctif de `createEntityDoc` reproduisait le défaut qu'il annonçait avoir fermé.**
Il levait sur la PREMIÈRE écriture de registre en échec. Or :

- lever sur le listing sautait l'écriture de la clé — détruisant le chemin de
  récupération que le commentaire d'à côté décrit explicitement (« la clé doit rester
  récupérable même si le listing a échoué »), et laissant le document orphelin ;
- lever sur la clé laissait le document LISTÉ sans clé — précisément l'état « se lit vide
  pour toujours » que je prétendais empêcher, en pire, puisque l'appelant n'a même plus
  sa référence.

Les deux écritures sont désormais tentées, ce qui atterrit reste, et l'échec est rapporté
après en nommant la moitié manquante.

**Le refus de `share` reposait sur une valeur qui confond absence et ignorance.**
`resolveAccount` avale toute erreur de lecture et rend `null`, si bien qu'un incident
réseau faisait répondre « personne ne s'est connecté sous ce nom » à propos de quelqu'un
qui existe. `lookupAccount` propage désormais l'erreur ; `resolveAccount` reste la forme
tolérante que tous les autres appelants veulent.

**J'avais livré ce comportement sans un seul test.** `test/app-surface.test.ts` en ajoute
huit, tous sur ce qu'un APPELANT voit : `ensureIdentity` rend l'identité, un appel de
placement avant connexion nomme l'erreur, le placement agit comme l'utilisateur connecté,
`share` refuse un nom inventé mais laisse remonter une panne, et une création à moitié
écrite échoue en disant quelle moitié — dont le cas « le listing a échoué, la clé est
quand même là ».

En écrivant ces tests j'ai refait dans leur faux la faute que cette revue a corrigée
ailleurs : ignorer le sujet dans la requête de compte, ce qui rendait le dossier d'un
autre utilisateur. Deux des huit échouaient pour cette raison, sans rapport avec le code.

**Et la documentation contredisait le code livré dans le même commit** : le README
enseignait encore `createEntityDoc(me, "protected")` — en JS la portée devient `"alice"` —
et le contrat déclarait `Nuri` là où le code et la feuille disent `NuriLike`.

197 tests unitaires, e2e 40/40 et applicatif 12/12.
This commit is contained in:
Sylvain Duchesne
2026-08-10 10:48:57 +02:00
parent cdc09a1a1d
commit 0d9e2bbe97
5 changed files with 266 additions and 51 deletions
+7 -2
View File
@@ -32,7 +32,7 @@ import { subscribeDoc } from "./subscribe";
import { ensureRepoOpen } from "../emulated-verifier/open-repo";
import { getCaps, getCurrentUser, getStoreRegistryDeps } from "../shared-wallet/bootstrap";
import { addLink, documentInboxAddress, isOwnInbox } from "../emulated-verifier/branch-registers";
import { userInbox, isKnownInbox, resolveAccount } from "../shared-wallet/account-registry";
import { userInbox, isKnownInbox, lookupAccount } from "../shared-wallet/account-registry";
import { escapeLiteral } from "./sparql";
import { hasReadCap, toNuri } from "../model/nuri";
import {
@@ -348,7 +348,12 @@ export async function share(doc: NuriLike, toUser: string): Promise<void> {
// PUBKEY (`InboxMsg::new`, `engine/net/src/types.rs:4299`) that reached you through an
// inbound `ContactDetails` — someone has to have reached you first. Refusing is the
// faithful behaviour; provisioning was the invention.
if ((await resolveAccount(toUser)) === null) {
//
// `lookupAccount`, not `resolveAccount`: the tolerant form answers `null` for a read
// that FAILED as well as for one that found nothing, so it would have told a user
// "nobody has signed in as bob" because a query timed out. A refusal must not be
// built on a value that conflates absence with ignorance.
if ((await lookupAccount(toUser)) === null) {
throw new Error(
`[ng-eventually] inbox.share: no such recipient — nobody has signed in as ` +
`${JSON.stringify(toUser)}. Sharing does not create the person you share with.`,