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:
@@ -38,6 +38,8 @@ Three rules follow, and all three are load-bearing:
|
||||
|
||||
1. **A breakdown rejects.** Anything that stops a read from happening - a repo that will not open, a broker that does not answer, an emulation step that could not be performed - reaches the caller as a failure. `A rejection means "unknown", never "absent"` is the contract's own wording.
|
||||
2. **An empty document is a SUCCESS holding zero objects.** The repair for (1) must never be bought by making a real emptiness into an error; that only moves the lie. An empty document, an empty scope, a document nobody opened an inbox on, a document in a public store that exposes no key - each is an answer, and each stays one.
|
||||
3. **The unsynced/empty collision is closed by the barrier, not by an error.** Where this package can cross the sync barrier before answering, it does (`emulated-verifier/open-repo.ts`), which is what makes a zero-row answer mean something. Where it cannot, the answer is still a success, and `getSyncState` is what says whether it was authoritative. Turning an unsynced read into a rejection would be inventing a third outcome the engine does not have.
|
||||
3. **The unsynced/empty collision is closed by the barrier, and where the barrier was not crossed the read says so rather than answering.** Crossing it is what makes a zero-row answer mean something (`emulated-verifier/open-repo.ts`, which keeps `synced` and `timed-out` deliberately apart). A reactive read over documents it NAMES consults that distinction and stays PENDING when the barrier was not reached - "I have no authoritative answer" rather than "there is nothing". Turning it into a rejection would be inventing a third outcome the engine does not have, and answering a success would be the collapse this whole leaf is about.
|
||||
|
||||
**What an application can see of this, exactly.** `useShapeQuery` folds it into its own states, and that is the only published channel: a named scope whose barrier was not crossed does not reach `isSuccess`. `inbox.readSyncedForDocument` is the same guarantee on the inbox path. `getSyncState` is INTERNAL and is deliberately not published - this leaf offered it until 2026-09-24, which was a pointer at something no caller can import. It stays internal because it has no counterpart upstream at any level: publishing it would hand an application a polyfill-era symbol to unlearn at migration, on a surface that is busy removing exactly those. The consequence to state plainly rather than paper over: a caller using the anchored `docs.sparqlQuery` directly gets zero rows for an unsynced document and has **no published way to tell that from an empty one** - that is the collapse upstream has too, and it is why the reactive read is the one that carries the distinction.
|
||||
|
||||
Related: `knowledge_what-a-read-scope-is-upstream` (what a read request can SAY, including the `[]` that means the empty set and the `[]` that means no filtering), and `polyfill-surface/contract_polyfill-surface` `## Guarantees` for the caller-facing form of all of this.
|
||||
|
||||
@@ -193,13 +193,13 @@ Where a call must first find out whether something already exists — a document
|
||||
|
||||
**A reactive read says "nothing" and "I could not find out" differently.** `useShapeQuery` answers in three states and only two of them are answers about your data: `isPending` while the question is still open, `isSuccess` once it has been answered, `isError` when it could not be. An empty `data` under `isSuccess` means this scope holds nothing of that shape — the distinction the surface exists for, and the one thing it will never do is publish that snapshot for a read that did not happen. And because an observable cannot take back a list a subscriber has already rendered, `data` under `isError` keeps the **last read that answered** rather than emptying — so an empty `data` is never handed to you as a failure's answer. Read the load state before `data`: **a rejection means "unknown", never "absent"** here too.
|
||||
|
||||
**A document you NAME in `graphs` is reached before the read opens, since `1.0.0-dev.9`.** Naming a document in a scope is the same act as anchoring `docs.sparqlQuery` on it, and it is now answered the same way: the key is asked of a public store if you do not already hold it, the document is refused out loud if you hold no key for it, and its repo is synced before the question is put. A named document you cannot read publishes `isError`, where until this version it reached the engine, came back, and was emptied by the per-document filter into `isSuccess` with `data: []` - the one snapshot this surface reserves for "this scope holds nothing". A named document that has never synced published that same snapshot for a document that is not empty; it too is now `isError` or a longer `isPending`, never a false emptiness. **Naming SEVERAL documents fails as a whole** when one of them cannot be reached: a short list is the same lie as an empty one, so nothing is answered rather than half of it. What is NOT touched, and each stays a success: `graphs: "did:ng:i"` or `""` (you named no document - the per-document filter is the whole of the answer there, and an empty result means you hold nothing of that shape), `graphs: []` (no objects, by NextGraph's own definition), and a named document you can read that holds nothing of the shape.
|
||||
**A document you NAME in `graphs` is reached before the read opens, since `1.0.0-dev.9`.** Naming a document in a scope is the same act as anchoring `docs.sparqlQuery` on it, and it is now answered the same way: the key is asked of a public store if you do not already hold it, the document is refused out loud if you hold no key for it, and its repo is synced before the question is put. A named document you cannot read publishes `isError`, where until this version it reached the engine, came back, and was emptied by the per-document filter into `isSuccess` with `data: []` - the one snapshot this surface reserves for "this scope holds nothing". A named document that has never synced published that same snapshot for a document that is not empty; it now stays **PENDING** instead - the sync barrier is consulted on every publication, and a named document whose barrier was not crossed is not an answer. Nothing failed there, so it is not `isError`: the subscribe succeeded and the read did not throw, what is missing is the authority to say the document is empty. If the document later syncs and brings data, the read answers by itself. If it never does, the read stays pending for that session - handle a `isPending` that does not end, as you already must for a shape query the engine refuses. **Naming SEVERAL documents fails as a whole** when one of them cannot be reached: a short list is the same lie as an empty one, so nothing is answered rather than half of it. What is NOT touched, and each stays a success: `graphs: "did:ng:i"` or `""` (you named no document - the per-document filter is the whole of the answer there, and an empty result means you hold nothing of that shape), `graphs: []` (no objects, by NextGraph's own definition), and a named document you can read that holds nothing of the shape.
|
||||
|
||||
**And it converges by itself.** A named scope refused this way is not a dead view. Whether you may reach the documents you named is re-read on every publication rather than decided once, so a key arriving turns the refusal into the document through the same channel that already re-published a filtered read - and a scope whose barrier failed outright is attempted again on that signal. The ordinary sequences all converge with no call from the application: a page whose components subscribe before the connection work has restored its keys, a document shared with you while you are looking at the screen, a broker that hiccupped once and healed, and a key arriving in the same instant the read was deciding. One consequence to design for: an interface that renders `isError` will briefly show it where it used to show an empty success, so branch on the load state rather than on `data` alone. Nothing here polls, so a scope refused in a session whose keyring never moves again stays `isError` until your own `refetch()` - which is what `isError` has always meant here, and what it means for every other failure this call reports.
|
||||
|
||||
**A standing failure is the SAME snapshot.** While a refusal persists unchanged, the snapshot object does not change either, so `useSyncExternalStore` and anything else comparing by identity see no update - even though the read is being attempted again underneath. A failure that says something different is published normally.
|
||||
|
||||
**What it costs.** One public-store ask and one sync barrier per document named, once per session. The opens for a scope naming several documents run together, so the wait is the slowest of them rather than their sum. A document that never answers the barrier is bounded by an internal fallback rather than waiting for ever, and until `1.0.0-dev.9` that same document left the read `isPending` with no bound at all.
|
||||
**What it costs.** One public-store ask and one sync barrier per document named, once per session. The opens for a scope naming several documents run together, so the wait is the slowest of them rather than their sum. The wait itself is bounded internally, so a document that never answers does not hold the open for ever - but the READ over it stays pending, because a barrier that was never crossed is not authority to call anything empty.
|
||||
|
||||
**Which failure lands in `isError` changed in `1.0.0-dev.6`, and one of them now stays PENDING.** The read goes through the SDK's own shape query, so what `isError` carries is the query failing to open: a shape the ORM cannot subscribe, or a `configure` that was never given `ormSubscription`. A start the underlying engine REFUSES does not reach it: NextGraph catches that failure and logs it, and its readiness signal is only ever resolved, never rejected, so such a read stays `isPending` indefinitely. The guarantee above is unaffected — you are still never told "there is nothing" for "nobody answered" — but an interface that handles only `isError` will show its loading state where it used to show an error. Handle a `isPending` that does not end.
|
||||
|
||||
@@ -278,6 +278,7 @@ What each level means here, in this package's own terms:
|
||||
`1.0.0-dev.9` is a **major**, and all of it is one rule: **a read that could not reach its document now FAILS, where it used to answer as though the document were empty.** Nothing is added, nothing is renamed, nothing is removed from the entry - three existing calls narrow.
|
||||
|
||||
- **`useShapeQuery` publishes `isError` for a document you NAME and cannot read.** Until this version a document named in `scope.graphs` was handed straight to the engine; whatever came back was filtered per document by what you hold, and a document you hold no key for was emptied out of the result. The snapshot was `isSuccess` with `data: []` - indistinguishable, byte for byte, from the scope genuinely holding nothing. It now asks the public store for the key, refuses out loud if there is none to be had, and crosses the sync barrier first, so the same read answers `isError`. **Handle `isError` on a named scope**; an interface that only branched on `isPending` / `isSuccess` will show its success state with nothing in it. Naming several documents fails as a whole when one of them fails.
|
||||
- **A named document that has not SYNCED keeps the read pending**, where it used to publish the same empty success. Reaching a document and hearing back from it are two questions, and only the first was being asked. An interface must therefore expect a named scope to sit in its loading state while a document it named is not answering, rather than reading an empty success as "nothing there".
|
||||
- **A named scope now costs round-trips it did not cost before**, once per named document per session: the key fetch, and the subscribe that syncs the repo. The opens run together, so several named documents cost the slowest wait rather than the sum. `graphs: "did:ng:i"` is unchanged and spends nothing.
|
||||
- **A refused named scope RECOVERS on its own.** Reachability is re-read on every publication rather than remembered, and a scope whose barrier failed is attempted again when the keyring moves - including when the move happened while the read was still deciding. Without that a refusal would be terminal, and trading a silent empty answer for a permanently stuck error would have been no improvement at all. Expect to see `isError` briefly on a page that subscribes before its keys are restored. There is no polling: in a session whose keyring never moves again, `refetch()` is the recovery, as it is for every other failure this call reports.
|
||||
- **`inbox.postToDocument`, `inbox.readForDocument` and `inbox.readSyncedForDocument` reject where they used to report "no inbox".** Resolving a document's inbox address reads that document, and when the read could not happen - the broker could not answer for it, or its public store could not be asked for the key - all three used to render the breakdown as the document's owner having opened nothing: a refusal on the deposit side, an empty list on the read side. They now reject. A document that genuinely has no inbox is unchanged, and still answers exactly as before.
|
||||
|
||||
Reference in New Issue
Block a user