fix: un dépôt est délivré une fois et retiré, comme en amont

This commit is contained in:
Sylvain Duchesne
2026-08-26 14:11:56 +02:00
parent abfb619c76
commit f6a48a4507
24 changed files with 1486 additions and 176 deletions
@@ -97,6 +97,19 @@ function makeFakeNg(faults: Faults) {
}
return undefined;
}
// Retiring a DEPOSIT: every triple of one subject, whatever predicates it carries
// (`emulated-verifier/inbox-consume.removeDeposit`). Modelled rather than ignored — a
// fake that let this delete fall through would leave every deposit in place, and a
// suite asserting convergence would pass on a queue that never shrinks.
const delDeposit = query.match(/^\s*DELETE\s+WHERE\s*\{\s*<([^>]+)>\s+\?\w+\s+\?\w+\s*\}/);
if (delDeposit) {
const subject = delDeposit[1]!;
for (let i = quads.length - 1; i >= 0; i--) {
const q = quads[i]!;
if (q.s === subject) quads.splice(i, 1);
}
return undefined;
}
const gm = query.match(/GRAPH\s+<([^>]+)>\s*\{([\s\S]*)\}/);
const body = gm ? gm[2]! : query.replace(/^\s*INSERT DATA\s*\{/, "").replace(/\}\s*$/, "");
const sm = body.match(/<([^>]+)>/);
@@ -160,6 +173,8 @@ function makeFakeNg(faults: Faults) {
for (const q of quads) {
if (q.g !== anchor) continue;
const rec = bySubject.get(q.s) ?? {};
// The deposit's own subject — the key a drain names to retire it.
rec.d = q.s;
if (q.p === `${INBOX}:payload`) rec.payload = q.o;
if (q.p === `${INBOX}:ts`) rec.ts = q.o;
if (q.p === `${INBOX}:from`) rec.from = q.o;
@@ -171,6 +186,7 @@ function makeFakeNg(faults: Faults) {
.filter((r) => r.payload !== undefined && r.ts !== undefined)
.map((r) => {
const row: Record<string, { value: string }> = {
d: { value: r.d! },
payload: { value: r.payload! },
ts: { value: r.ts! },
};
@@ -338,14 +354,15 @@ test("what the drain filed for the owner is DURABLE: he restores it with his inb
await share(aliceDoc, "bob");
await runScheduledInboxProcessingNow();
// EMPTY the inbox, as a consumed queue would be, and drop every in-memory cap — so what
// Bob recovers below can only have come from his own User branch, where the drain filed
// it. Without that, he would simply be draining the queue himself on connection, which
// is the behaviour this service exists to get ahead of.
// The inbox is ALREADY empty — the drain consumed the deposit as it applied it. Until
// 2026-08-26 this test had to empty the queue by hand to reach that state, which is
// exactly the shortcut that fabricates a state the system never produced; now it is
// asserted instead. It matters here because what Bob recovers below must come from his
// own User branch, where the drain filed it: with the deposit still in the queue he
// would simply be draining it himself on connection, which is the behaviour this
// service exists to get ahead of.
const bobInbox = await userInbox("bob", "protected");
for (let k = fake.quads.length - 1; k >= 0; k--) {
if (fake.quads[k]!.g === bobInbox) fake.quads.splice(k, 1);
}
expect(fake.quads.filter((q) => q.g === bobInbox && q.p === `${INBOX}:payload`)).toEqual([]);
resetCaps();
setCurrentUser("alice");
await createEntityDoc("alice", "private"); // re-arms the emulation: a cap exists again
@@ -436,8 +453,10 @@ test("a drain that fails says so in the package's log, and rejects into nobody",
// The wording is the SHARED reporter's since 2026-08-17 (`emulated-verifier/inbox-drain.ts`):
// the timer is no longer the only thing that drains an inbox — the continuous observation
// does too, through the same queue — so the line names the act and not the schedule.
const reported = errors.filter((line) => /could not apply what is in this inbox/.test(line));
// does too, through the same queue — so the line names the act and not the schedule. Since
// 2026-08-26 it also stops promising a retry it cannot make: a deposit already taken is
// consumed, and only what is STILL QUEUED waits for the next attempt.
const reported = errors.filter((line) => /could not finish draining this inbox/.test(line));
expect(reported.length).toBe(1);
// Prefixed by the CONNECTED identity, like every other polyfill-layer line — which is
// what makes a drain running under someone else's session legible in a live trace.