fix: la barriere n'est retenue que tant que la tentative tient

Trouve en enumerant moi-meme les ecritures de `establishing` et de
`lastState` plutot qu'en croyant l'argument du commit precedent, qui
affirmait que le garde retire etait inatteignable. Il ne l'etait pas.

`isCurrentAttempt` dit que cette entree et cet appel sont encore les
bons ; il ne dit rien sur le fait que l'appel ait REUSSI. Le courtier
expedie avant de repondre, donc un `State` peut arriver apres que
l'appel a deja ete rapporte en echec - et ce module tient lui-meme pour
atteignable qu'une poussee survive a son demontage, c'est la raison
d'etre du garde sur ce callback. Mesure : la poussee tardive recreait la
barriere morte que `reportSetupFailure` venait d'oublier, sans canal
derriere, et `getSyncState` y repondait `"synced"` - donc autorite.

`lastState !== null` implique desormais qu'une tentative tient, a
l'ecriture comme a l'effacement, donc les deux ne peuvent plus diverger
et aucun lecteur n'a a le reimposer. La poussee reste diffusee aux
auditeurs : seule la BARRIERE est une affirmation sur le document.
This commit is contained in:
Sylvain Duchesne
2026-09-24 13:35:30 +02:00
parent 226db18054
commit 56072df2af
2 changed files with 45 additions and 2 deletions
+31
View File
@@ -788,3 +788,34 @@ test("a doc_subscribe that answers without a cancel function is still an OPEN ch
expect(docChannel(A).barrierCrossed).toBe(true);
unsub();
});
// A BARRIER BELONGS TO THE ATTEMPT THAT PRODUCED IT, and the attempt can fail after the push.
//
// The broker dispatches before it answers the call, so a `State` can land after `doc_subscribe`
// has already rejected - and this module's own doctrine is that a push outliving its teardown is
// reachable, which is why the callback carries a guard at all. Recorded as a barrier, it
// re-creates the dead one `reportSetupFailure` has just forgotten, with no channel behind it,
// and `emulated-verifier/open-repo.ts` answers `"synced"` to that - which is AUTHORITY, so a
// zero-row read over the document becomes a definitive absence and a deposit is destroyed.
test("a State that lands AFTER the call was reported failed is not a barrier", async () => {
let dispatch: ((r: unknown) => void) | null = null;
const doc_subscribe = mock(async (_n: string, _s: unknown, cb: (r: unknown) => void) => {
dispatch = cb; // the broker keeps the channel it was handed...
throw new Error("RepoNotFound: the call did not take"); // ...and then refuses the call
});
configure({ ng: { doc_subscribe } as any, useShape: (() => {}) as any });
configureStoreRegistry({ getSession: async () => SESSION });
const unsub = subscribeDoc(A, () => {});
await tick();
expect(doc_subscribe).toHaveBeenCalledTimes(1);
expect(docChannel(A).state).toBe("none");
expect(docChannel(A).barrierCrossed).toBe(false);
// A `State` the broker had already sent before it answered.
dispatch!({ V0: { State: {} } });
// `lastState !== null` must IMPLY an attempt stands - at the write as well as at the clear,
// or the two disagree and a reader is left to re-impose it.
expect(docChannel(A).barrierCrossed).toBe(false);
expect(docChannel(A).state).toBe("none");
unsub();
});