Quatrieme tour. Ce n'est pas un drapeau de plus : c'est la forme qui change.
Les tours 2 et 3 construisaient tous deux la reprise ainsi - noter qu'on a
refuse, puis reagir a un evenement ulterieur - et c'est la note qui etait
fautive les deux fois : une fois conditionnee a quelque chose qui ne pouvait pas
devenir vrai, une fois ecrite apres la fenetre ou arrive l'evenement decisif. Un
front manque n'est pas rattrapable par construction. Donc :
- LA DECISION EST UN NIVEAU. Les documents nommes sont reinterroges a chaque
publication, jamais reportes depuis la barriere. Une capacite qui arrive change
`isError` en document par le canal qui existait deja, et rien n'etant memorise,
il n'y a aucun evenement a manquer. C'est aussi ce qui rend sans objet la
question du porteur : une requete ouverte pendant qu'un autre etait connecte ne
peut pas revenir a cet abonne en succes auquel il n'a pas droit.
- UNE TENTATIVE QUI A COURU CONTRE UN CHANGEMENT S'EN APERCOIT au lieu
d'attendre. `open()` echantillonne `CapRegistry.version()` avant la barriere et
compare apres son echec ; un ecart signifie qu'elle a decide sur un terrain
perime, et elle retente d'elle-meme. Cela termine : une tentative ne se repete
que parce que le trousseau a bouge, les mouvements sont finis, et ceux qu'une
tentative provoque elle-meme sont les capacites qu'elle depose - au plus une par
document nomme.
La comparaison de porteur disparait : `holderKey()` vaut "" tant que personne
n'est connecte, donc un refus pris avant que l'identite se fixe - la ou les
composants se montent - ne correspondait plus jamais ensuite.
Un instantane d'echec inchange n'est plus republie : la surface documente sa
stabilite, et huit arrivees sans rapport produisaient huit objets porteurs de huit
`Error` distinctes pour un etat identique.
Enfin, un defaut a moi, trouve en mesurant : le changement rendait la suite
intermittente. `resetOpenedRepos()` abandonnait les ouvertures en vol sans les
terminer, donc un appelant attendait les 8s du repli sur un abonnement demonte,
puis repartait avec son etat de synchro efface - et « unknown » est la lecture
LAXE, celle qui accepte qu'une lecture a zero ligne soit une absence averee. Une
absence-qui-cache-un-echec latente, de la famille meme de cette tache. Une
reinitialisation termine desormais ce qu'elle abandonne, en « timed-out ».
Deux fixtures qui se couraient apres sont corrigees, raison ecrite a cote.
Troisieme tour de revue adversariale, trois constats, tous reproduits avant
correction.
1. La garde etait CIRCULAIRE, et c'etait structurel. `namedDocumentsReachable`
demandait si la capacite est DETENUE. Pour un document en store public, le seul
canal qui puisse jamais la deposer est `fetchReadCap` - l'appel meme que la
garde retenait. Elle attendait une reponse que seule l'action qu'elle bloquait
pouvait produire, et toute cette classe restait irrecuperable.
La bonne question n'est pas une meilleure garde, c'est de RETENTER. Les deux
facons d'etre refuse se reparent par des choses opposees, et le memo les
distingue deja : un echec n'est deliberement pas memoise, donc une nouvelle
tentative redemande vraiment au reseau - c'est la reparation de cette classe,
pas un cout a eviter ; une absence est memoise, donc une nouvelle tentative ne
demande a personne et coute un instantane, jusqu'au jour ou la capacite arrive
d'ailleurs, seule chose qui repare celle-la. La garde est supprimee : le module
est plus petit qu'au tour precedent.
2. La reouverture faisait un ACCES sous la mauvaise identite. Republier REFILTRE
une requete existante ; rouvrir EMET un abonnement et une requete au store
public pour un document que cet abonne a nomme. C'est un acte neuf, pas un
ancien qui ressort, et l'etat final etait `isSuccess` avec `data: []` pour un
document illisible - le mensonge que cette tache supprime, par la porte du
correctif. `refusedFor` retient QUI a ete refuse ; on ne retente que tant que
c'est encore lui le connecte.
Etabli plutot qu'affirme : une application ne peut pas changer d'identite en
cours de session. `ensureIdentity` est le seul appel publie qui en etablit une,
et `resolveIdentity` commence par `getCurrentUser()`. Corrige quand meme.
3. `Promise.all` court-circuite a la premiere rejection ARRIVEE, et un fetch qui
rejette n'attend pas la barriere de synchronisation : il double un refus portant
sur un document nomme avant lui. Le comportement est corrige plutot que le
commentaire - `allSettled` puis parcours dans l'ordre du scope.
Et le quatrieme controle e2e est retire, pas repare : il passait avec toute la
machinerie de reprise supprimee. Un controle qui ne peut echouer pour aucune
regression de ce qu'il garde se lit comme de la couverture et vaut moins que rien.
Le controle « une cle qui tombe pour un AUTRE document laisse le scope refuse »
mesure l'ISSUE, pas le ciblage : il ne separe pas « aucune reouverture n'a ete
tentee » de « une a ete tentee et refusee a nouveau », les deux finissant au meme
endroit. Le ciblage se mesure au compte de notifications, dans la suite unitaire.
Nommer ce qu'un controle vert prouve est toute la lecon du tour qui a livre cette
barriere sans aucune couverture de scope nomme.
Revue adversariale : la barriere livree au tour precedent rendait le refus
TERMINAL. Le signal du trousseau s'ecrit `if (query !== null) publish(...)`, parce
qu'avant la barriere il n'y avait qu'une seule raison de n'avoir pas de requete -
rien n'avait encore ete tente - et qu'une cle qui arrive ne pouvait changer que ce
qu'une requete OUVERTE avait le droit de montrer. La barriere en a ajoute une
seconde : elle abandonne AVANT que la requete existe, donc le seul canal qui fait
converger une cle tardive passait a cote de l'etat qui en avait le plus besoin.
Une page qui s'abonne pendant que son trousseau se restaure, ou un document
partage sous les yeux de son destinataire, publiait `isError` et y restait. Ce
n'est pas une surface plus stricte, c'est une pire : echanger un vide mensonger
contre une erreur bloquee ne repare rien, et cela casse une garantie que ce paquet
publie.
Le signal rouvre donc un scope refuse, et seulement quand le refus est leve
(`namedDocumentsReachable`). Le garde-fou porte autant que la reouverture :
rouvrir a chaque changement de trousseau rejouerait la barriere pour chaque cle
qu'une connexion depose, redemanderait au reseau tout document dont le
telechargement avait ECHOUE - un echec n'est deliberement pas memoise - et
republierait la meme erreur a chaque abonne. La variante indiscriminee casse deux
tests, dont celui de demontage qui preexistait.
La barriere passe en parallele. Elle etait serie, donc N documents qui ne poussent
jamais de `State` coutaient N replis bornes, 8s chacun en production. Les refus
restent lus dans l'ordre du scope apres coup.
Et ce qui a laisse passer les deux : l'e2e ne parcourait pas ce chemin. Les deux
journeys `useShapeQuery` lisent `{graphs: "did:ng:i"}`, donc 43/43 n'a jamais rien
prouve sur la barriere. Quatre controles ajoutes sur un scope NOMME, contre le
vrai broker : la proprietaire lit son document, un porteur de rien recoit
`isError` et jamais un succes vide, un document PUBLIC s'ouvre pour lui depuis la
seule reference via la requete de la barriere, et une cle qui tombe pour un AUTRE
document laisse le scope refuse en place.
L'enumeration a ete ecrite avant le code, comme demande : les quatre sites
d'appel, ce que chacun fait aujourd'hui quand la capacite ne peut pas etre
obtenue, le cas inerte, et la liste de ce qui doit rester un succes. Le resultat
consigne le choix, les quatre options ecartees avec leur raison, ce qui a casse
et ce que ces tests affirmaient vraiment, et les deux bug_ qui ne se ferment pas.