feat!: une seule reponse a « ce document est-il ouvert dans cette session »
Trois reponses coexistaient et pouvaient se contredire : un ensemble de souscriptions bootstrap et une carte d'etat de barriere dans `open-repo`, et les auditeurs du fan-out dans `surface/subscribe`. Les deux premieres survivaient a la disparition de la troisieme, parce que `resetDocSubscriptions()` - ce que fait le `configure()` publie - largue ses auditeurs sans rien dire a personne. Il n'en reste qu'une, l'etat de l'unique souscription reelle du document, exposee par `docChannel(nuri)`. `open-repo` n'enregistre plus que l'auditeur qu'il a pose - « est-ce que j'ecoute encore », rien de plus - et derive `getSyncState` a chaque lecture, si bien qu'aucune reponse ne peut survivre au canal qu'elle decrit. Disparaissent : `held`, `syncState`, `inFlight`, `settleInFlight` et le compteur de generation, remplace par l'identite de l'enregistrement. Les deux echecs prouves deviennent des tests qui echouent sans le changement : un second `configure()` apres une synchro reussie (`"synced"` au lieu de `"timed-out"`, zero re-souscription, et `isSuccess` cote application), et un etablissement encore en cours quand la couche de souscription est demontee - cas que le canal `onSetupFailed` ne peut pas rattraper, puisqu'il ne rapporte que la tentative encore attendue. La ligne qui ne casse pas : un document synchronise qui ne contient rien repond toujours un succes a zero objet. `1.0.0-dev.10`, les deux non-garanties nommees sont retirees et la garantie livree porte le test qui l'epingle.
This commit is contained in:
@@ -95,17 +95,17 @@ export function subscribePhysicalDoc(nuri: Nuri, onChange: (r: DocChange, type:
|
||||
## 6. Bootstrap repo opening — `emulated-verifier/open-repo.ts`
|
||||
|
||||
```ts
|
||||
// open-repo.ts:90
|
||||
// open-repo.ts:98
|
||||
export type SyncState = "syncing" | "synced" | "timed-out";
|
||||
// open-repo.ts:120 - TEST-ONLY
|
||||
// open-repo.ts:197 - TEST-ONLY
|
||||
export function setOpenTimeoutForTests(ms: number): void;
|
||||
// open-repo.ts:127
|
||||
// open-repo.ts:204
|
||||
export function resetOpenedRepos(): void;
|
||||
// open-repo.ts:162
|
||||
// open-repo.ts:252 - DERIVED, nothing stores it
|
||||
export function getSyncState(nuri: Nuri): SyncState | "unknown";
|
||||
// open-repo.ts:210
|
||||
// open-repo.ts:315
|
||||
export async function ensureRepoOpen(nuri: Nuri, holderKey?: string): Promise<void>;
|
||||
// open-repo.ts:234 - the unguarded core, for ONE importer (`shared-wallet/physical.ts`)
|
||||
// open-repo.ts:339 - the unguarded core, for ONE importer (`shared-wallet/physical.ts`)
|
||||
export async function openRepoUnguarded(nuri: Nuri): Promise<void>;
|
||||
```
|
||||
|
||||
@@ -116,7 +116,9 @@ export async function openRepoUnguarded(nuri: Nuri): Promise<void>;
|
||||
> `ensureRepoOpen`'s second parameter. The rest of this document carries drift of the same
|
||||
> age and was deliberately left alone.
|
||||
|
||||
Heals the cold-start defect of the anchored read path: on a fresh session a not-yet-open repo reads empty, so before an anchored read the repo is opened by subscribing (`subscribePhysicalDoc`) and awaiting the first `State` push — the sync barrier — with a bounded timeout. The subscription is held for the session; per-nuri `SyncState` keeps `synced` and `timed-out` apart.
|
||||
Heals the cold-start defect of the anchored read path: on a fresh session a not-yet-open repo reads empty, so before an anchored read the repo is opened by subscribing (`subscribePhysicalDoc`) and awaiting the first `State` push — the sync barrier — with a bounded timeout. The subscription is held for the session; `SyncState` keeps `synced` and `timed-out` apart.
|
||||
|
||||
> Corrected 2026-09-24 (`1.0.0-dev.10`), for this module only: the line numbers above, and the fact that nothing here STORES openness or the barrier any more. This module kept a set of live bootstrap subscriptions and a per-nuri sync-state map, and both outlived the subscription they described - `surface/subscribe.ts` drops its listeners without a word, which `configure()` makes it do. There is one answer now, `surface/subscribe.ts`'s `docChannel(nuri)`, and `getSyncState` derives from it on every call; what this module records is the bootstrap listener it took out, which answers "am I still listening" and nothing more.
|
||||
|
||||
- The opening mechanism is **level 2, VERIFIED as a composition**: `doc_subscribe` exists (`sdk/js/lib-wasm/src/lib.rs:1908`), and the push variants `TabInfo`/`State`/`Patch` are the engine's `AppResponseV0` (`engine/net/src/app_protocol.rs:1354-1358`). The ORDER (TabInfo first, then the initial State) and "first State = presence guaranteed, absence definitive" are **empirical, pinned by the in-repo e2e CONTRACT-3 probe — an ASSUMPTION about ordering as far as upstream is concerned**: no upstream statement fixes the push order, so a future reordering upstream would silently break the barrier. Bound: the e2e probe fails loudly if the order changes.
|
||||
- "Hold a live subscription to keep the repo open" — **ASSUMPTION** (nothing upstream documents subscription lifetime as what retains a repo in `self.repos`); observed to work, bounded by the same probe.
|
||||
|
||||
Reference in New Issue
Block a user