fix!: joindre un document n-est pas en avoir des nouvelles, et une reinit invalide ce qu-elle abandonne

Deux defauts, aucun des deux sur la reprise - elle a passe cette revue intacte et
n'a pas ete rouverte.

1. La barriere verifiait qu'un document nomme peut etre JOINT, jamais qu'il a
   REPONDU. Elle attendait `ensureRepoOpen` puis demandait `assertMayReach`, qui
   est la question de la capacite, et ne consultait pas l'etat de synchro que
   l'ouverture venait de produire. Un document dont l'abonnement a reussi mais dont
   le premier `State` n'est jamais venu publiait donc `isSuccess` avec `data: []` :
   le defaut d'origine, dans le cas precis pour lequel la barriere a ete ajoutee.

   La verification rejoint le reste de la decision, dans `publish`, donc rejouee a
   chaque publication au lieu d'etre memorisee. Les trois valeurs, enumerees avant
   d'ecrire la condition : `"synced"` fait autorite - zero ligne VEUT DIRE zero
   objet, la ligne tenue depuis cinq revues -, `"timed-out"` et `"syncing"` non, et
   `"unknown"` PASSE deliberement, parce qu'il veut dire « pas de semantique de
   `State` sur ce `ng` » et que le traiter autrement est exactement la regression
   du cas inerte livree aux tours 1 et 3.

   `isPending` plutot que `isError`, des deux que le contrat autorise : rien n'a
   echoue, il manque l'autorite de dire que le document est vide.

   Le test (t) EPINGLAIT le defaut - il attendait `isSuccess` apres trois
   barrieres expirees. Corrige. Ce n'est pas un test affaibli : l'ancienne
   assertion etait fausse.

   Et un trou dans ma propre suite, trouve par mutation : inverser la condition
   pour refuser `"unknown"` passait tous les tests, faute d'un `ng` SANS
   `doc_subscribe` nulle part. (cc) le couvre.

2. Regression de mon correctif du tour 4. Terminer une ouverture abandonnee
   faisait tourner sa continuation juste apres que la reinit avait vide les
   tables, donc elle ecrivait une entree `opened` perimee dans le registre neuf -
   et toute ouverture ulterieure de ce nuri court-circuitait dessus. Le depot
   n'etait jamais abonne dans la nouvelle session. Une epoque de registre corrige
   cela : une reinit l'incremente, une continuation dont l'epoque ne correspond
   plus n'ecrit rien. Une reinit INVALIDE ce qu'elle abandonne, en plus de le
   terminer.

`getSyncState` reste interne, et la doctrine cesse de l'offrir : elle pointait sur
un symbole qu'aucun appelant ne peut importer.
This commit is contained in:
Sylvain Duchesne
2026-09-24 11:03:07 +02:00
parent 8c890411bb
commit 4af0be433d
7 changed files with 224 additions and 15 deletions
+33
View File
@@ -204,6 +204,39 @@ describe("ensureRepoOpen", () => {
expect(getSyncState("did:ng:o:abandoned")).toBe("timed-out");
});
// A reset must INVALIDATE the opens it abandons, not merely end them.
//
// The continuation of an in-flight open records the repo as opened. Settling that open
// from the reset (2026-09-21) made the continuation run right after the maps were
// cleared, so it wrote a stale `opened` entry into the FRESH registry - and every later
// `ensureRepoOpen` for that nuri then short-circuited on it. The repo was never
// subscribed in the new session, and a read over it answered as though it had been.
// Before that change the same line ran only when the 8s fallback fired, so a re-open
// inside that window did re-subscribe; ending the open promptly is right, but it moved
// the write into the window where it does damage.
it("a reset INVALIDATES the opens it abandons, so the new session subscribes again", async () => {
const ng = makeFakeNgWithSubscribe({}, { pushState: false });
inject(ng);
const abandoned = ensureRepoOpen("did:ng:o:reopened");
await new Promise((r) => setTimeout(r, 5));
expect(ng.doc_subscribe).toHaveBeenCalledTimes(1);
resetOpenedRepos();
await abandoned; // its continuation runs here, on the cleared registry
await new Promise((r) => setTimeout(r, 5));
// The new session has subscribed NOTHING yet, so this must open the repo for real.
// NOT awaited: this broker never pushes a `State`, so the open runs to its bounded
// fallback - and what is under test is that the subscribe HAPPENS, not how it ends.
const reopened = ensureRepoOpen("did:ng:o:reopened");
await new Promise((r) => setTimeout(r, 5));
expect(ng.doc_subscribe).toHaveBeenCalledTimes(2);
// Released so the fallback's timer does not outlive the test with a live handle.
resetOpenedRepos();
await reopened;
});
it("the anchored document read triggers doc_subscribe then returns data (integration path)", async () => {
const ng = makeFakeNgWithSubscribe({
"did:ng:o:p": [[TYPE, `${FP}Participation`], [`${FP}event`, "did:ng:o:e"]],