fix: un abonnement en échec n'empoisonne plus le document, et un dépôt ne change plus de destinataire
This commit is contained in:
@@ -669,16 +669,47 @@ export async function readSyncedForDocument(docLike: NuriLike): Promise<Deposit[
|
||||
* Idempotent: `addLink` ignores a Link it already holds, so processing twice (a
|
||||
* second tab, a reconnect) costs nothing. Returns the consumer deposits, exactly as
|
||||
* {@link read} does — Links are never surfaced.
|
||||
*
|
||||
* ── The holder is captured, not re-resolved after the read ────────────────
|
||||
* The ownership guard runs at entry (inside {@link readSynced}) and the FILING happens a
|
||||
* broker round-trip later, so re-asking `getCurrentUser()` at that point asks a different
|
||||
* question: not "whose inbox is this" but "who is holding the page NOW". A page that
|
||||
* switched identity in that window filed CAROL's cap, deposited for ALICE, durably into
|
||||
* BOB's ring — `documentsGivenTo("bob")` returns Carol's document, Alice gets nothing, and
|
||||
* Bob's next sign-in restores it as a capability he holds. Not a near-miss: one user is
|
||||
* given another's document and the real recipient is left with none.
|
||||
*
|
||||
* So the holder the guard authorised is the one the filing is checked against, and a switch
|
||||
* ABANDONS — the same answer {@link read} gives for its own half of this window, and the
|
||||
* same one `connect.connectedUser` gives for a drain it started. Nothing is lost: an inbox
|
||||
* is not consumed by being read, so the deposit is still there for its owner's next
|
||||
* connection or its next push. Filing it FOR the absent owner instead would be a write on
|
||||
* somebody else's store from a published call — which is the deferred processor's
|
||||
* deliberate, unpublished privilege (`emulated-verifier/inbox-processor.ts`), not this one's.
|
||||
*/
|
||||
// @provenance inbox.processInbox kind=aligned level=1 ref=engine/verifier/src/inbox_processor.rs:process_inbox — unseal-and-apply: Links are filed durably and never surfaced. Returning the remaining deposits is the divergent half it shares with `read`
|
||||
export async function processInbox(targetInboxLike: NuriLike): Promise<Deposit[]> {
|
||||
const targetInbox = toNuri(targetInboxLike, "inbox.processInbox");
|
||||
// WHO this processing is for, captured before the read that authorises it — see above.
|
||||
const holder = getCurrentUser();
|
||||
const deposits = await readSynced(targetInbox);
|
||||
// `readSynced` already put every Link in memory for this session; now make
|
||||
// them durable. Reading the raw deposits again would mean re-parsing, so the caps
|
||||
// are taken from what the read just observed.
|
||||
for (const cap of capsSeenIn(targetInbox)) await addLink(cap);
|
||||
const seen = capsSeenIn(targetInbox);
|
||||
seenByInbox.delete(targetInbox);
|
||||
for (const cap of seen) {
|
||||
// Re-checked per cap, not once: `addLink` reads and writes, so the identity can move
|
||||
// between two of them just as easily as during the read.
|
||||
if (getCurrentUser() !== holder) {
|
||||
logStage(
|
||||
"ABANDONED " + shortNuri(targetInbox) + " — the identity changed while it was being " +
|
||||
"processed; its deposits stay for their owner",
|
||||
);
|
||||
return deposits;
|
||||
}
|
||||
await addLink(cap, holder ?? undefined);
|
||||
}
|
||||
return deposits;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user