@real-life/trust-protocol 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +137 -0
- package/README.md +91 -24
- package/dist/adapters/vti-mediator.d.ts +36 -8
- package/dist/adapters/vti-mediator.d.ts.map +1 -1
- package/dist/adapters/vti-mediator.js +193 -29
- package/dist/adapters/vti-mediator.js.map +1 -1
- package/dist/codec.d.ts +2 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +75 -26
- package/dist/codec.js.map +1 -1
- package/dist/engine.d.ts +77 -4
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +419 -31
- package/dist/engine.js.map +1 -1
- package/dist/entropy.d.ts +1 -1
- package/dist/entropy.d.ts.map +1 -1
- package/dist/entropy.js +1 -1
- package/dist/entropy.js.map +1 -1
- package/dist/events.d.ts +21 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/holder-api.d.ts +44 -2
- package/dist/holder-api.d.ts.map +1 -1
- package/dist/holder-api.js +40 -5
- package/dist/holder-api.js.map +1 -1
- package/dist/{holder-state-v1.d.ts → holder-state-v3.d.ts} +261 -201
- package/dist/holder-state-v3.d.ts.map +1 -0
- package/dist/holder-state-v3.js +2 -0
- package/dist/holder-state-v3.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/introduction/holder.d.ts.map +1 -1
- package/dist/introduction/holder.js +7 -0
- package/dist/introduction/holder.js.map +1 -1
- package/dist/invariants.d.ts.map +1 -1
- package/dist/invariants.js +70 -0
- package/dist/invariants.js.map +1 -1
- package/dist/membership/holder.d.ts +3 -5
- package/dist/membership/holder.d.ts.map +1 -1
- package/dist/membership/holder.js +165 -88
- package/dist/membership/holder.js.map +1 -1
- package/dist/membership.d.ts +31 -0
- package/dist/membership.d.ts.map +1 -1
- package/dist/membership.js +25 -2
- package/dist/membership.js.map +1 -1
- package/dist/message/holder.d.ts +49 -0
- package/dist/message/holder.d.ts.map +1 -0
- package/dist/message/holder.js +290 -0
- package/dist/message/holder.js.map +1 -0
- package/dist/message.d.ts +43 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +51 -0
- package/dist/message.js.map +1 -0
- package/dist/receive.d.ts +4 -3
- package/dist/receive.d.ts.map +1 -1
- package/dist/receive.js +23 -4
- package/dist/receive.js.map +1 -1
- package/dist/results.d.ts +22 -3
- package/dist/results.d.ts.map +1 -1
- package/dist/results.js.map +1 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +9 -6
- package/dist/schemas.js.map +1 -1
- package/dist/state.d.ts +88 -14
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +2 -1
- package/dist/state.js.map +1 -1
- package/dist/trust/continuity.d.ts.map +1 -1
- package/dist/trust/continuity.js +7 -1
- package/dist/trust/continuity.js.map +1 -1
- package/dist/trust/entries.d.ts.map +1 -1
- package/dist/trust/entries.js +103 -2
- package/dist/trust/entries.js.map +1 -1
- package/dist/views.d.ts +34 -2
- package/dist/views.d.ts.map +1 -1
- package/dist/views.js +19 -1
- package/dist/views.js.map +1 -1
- package/dist/wire.d.ts +47 -6
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js.map +1 -1
- package/package.json +6 -4
- package/schemas/{holder-state-v1.schema.json → holder-state-v3.schema.json} +300 -12
- package/dist/holder-state-v1.d.ts.map +0 -1
- package/dist/holder-state-v1.js +0 -2
- package/dist/holder-state-v1.js.map +0 -1
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
// RLTP transport adapter: VTI mediator — published as
|
|
2
2
|
// @real-life/trust-protocol/adapters/vti-mediator (lib Stage 2, note §4.6).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
4
|
+
// It takes the holder's documents from the holder's custody (Delivery 0.83
|
|
5
|
+
// RLTP-DEL-6120 to 6135, lib 0.6.0): reconcile(view, gate, now) files every
|
|
6
|
+
// entry of holder.pendingTransport(when) not yet filed, and withdraws every
|
|
7
|
+
// filed submission still before the trias whose digest the view no longer
|
|
8
|
+
// names — a missing entry means stop, never keep sending. Before every
|
|
9
|
+
// deposit it asks gate.mayHandOver(entry, now) (the holder): false
|
|
10
|
+
// withdraws the submission, an error skips the attempt. It keeps no
|
|
11
|
+
// custody of its own: its copies are volatile and bound to the holder's
|
|
12
|
+
// opening: bound to the holder of its first reconcile, it refuses another,
|
|
13
|
+
// so after holder.close() nothing it holds goes out, and the next opening
|
|
14
|
+
// gets a new adapter (restored from ackState like any other). Its give-up horizon counts
|
|
15
|
+
// from the entry's first submission (`since`), never from when it took the
|
|
16
|
+
// entry over (RLTP-DEL-6130). Its reports reach the holder as the ports'
|
|
17
|
+
// DeliveryReport: report(id) maps the adapter's SubmissionState onto
|
|
18
|
+
// DeliveryReport (deliveryReportOf), keyed by the document digest, which
|
|
19
|
+
// the app hands to holder.report(). An adapter's `delivered` is its claim
|
|
20
|
+
// about transport; the holder's receive core verifies the delivery-ack
|
|
21
|
+
// itself.
|
|
10
22
|
//
|
|
11
23
|
// Profile: spec/adapter-vti-mediator.md v0.23 (twenty-third casting;
|
|
12
24
|
// round journals under design/adapter-review*). A
|
|
13
25
|
// transport adapter contract — NOT a carrier at the port.
|
|
14
26
|
//
|
|
27
|
+
// Beyond the profile (lib 0.5.0): conclude(collection, now, {keep}) leaves
|
|
28
|
+
// the items whose disposition is transient unconcluded and offers them
|
|
29
|
+
// again (Delivery 0.82 RLTP-DEL-5810, 5820: failed(rate-limited) is never
|
|
30
|
+
// concluded); `concludable(result)` says which receive outcomes are
|
|
31
|
+
// terminal. The profile's §4 concludes a collection whole; its next
|
|
32
|
+
// casting is to take this up.
|
|
33
|
+
//
|
|
15
34
|
// Structural rules (reviews 3+4):
|
|
16
35
|
// · No port operation ever awaits the mediator — including
|
|
17
36
|
// register: keylist synchronization is a recorded intent with a
|
|
@@ -26,14 +45,15 @@ import { jcs } from '../core.js';
|
|
|
26
45
|
import { sha } from '../crypto.js';
|
|
27
46
|
import { carrierPrincipal, carrierScopedIdentity } from '../carrier-identity.js';
|
|
28
47
|
// ── Declared constants (profile §2) — promises of THIS casting ──────
|
|
29
|
-
|
|
48
|
+
// frozen: the declared promises cannot be changed at run time either
|
|
49
|
+
export const DECLARED = Object.freeze({
|
|
30
50
|
giveUpHorizonMs: 72 * 3_600_000,
|
|
31
51
|
statusHorizonMs: 60_000,
|
|
32
52
|
queueFloorCount: 64,
|
|
33
53
|
queueFloorBytes: 1_048_576,
|
|
34
54
|
maxQueueBytes: 16_777_216,
|
|
35
55
|
duplicateWindowMs: 30 * 86_400_000,
|
|
36
|
-
};
|
|
56
|
+
});
|
|
37
57
|
export class WireError extends Error {
|
|
38
58
|
kind;
|
|
39
59
|
constructor(kind, message) {
|
|
@@ -59,6 +79,16 @@ export function deliveryReportOf(digest, st) {
|
|
|
59
79
|
: { digest, status: 'failed', reason: st.reason };
|
|
60
80
|
}
|
|
61
81
|
}
|
|
82
|
+
/** Whether a receive outcome is terminal and its item may be concluded
|
|
83
|
+
* (Delivery 5a.8): every disposition but `failed(rate-limited)`, the one
|
|
84
|
+
* failure that is transient (RLTP-DEL-5820). An InfraError of the
|
|
85
|
+
* holder's store is transient as well (RLTP-DEL-5810): an item whose
|
|
86
|
+
* receive threw is kept, too. */
|
|
87
|
+
export function concludable(r) {
|
|
88
|
+
return !(r.disposition === 'failed' && r.reason === 'rate-limited');
|
|
89
|
+
}
|
|
90
|
+
/** The adapter's own copy of a wire's token set: the wire keeps no handle on it. */
|
|
91
|
+
const tokenCopy = (t) => ({ accessToken: t.accessToken, accessExpiresAt: t.accessExpiresAt, refreshToken: t.refreshToken, refreshExpiresAt: t.refreshExpiresAt });
|
|
62
92
|
const hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('');
|
|
63
93
|
// construction capability: only create() holds it
|
|
64
94
|
const KEY = Symbol('MediatorCarrierAdapter.create');
|
|
@@ -77,6 +107,8 @@ export class MediatorCarrierAdapter {
|
|
|
77
107
|
#tokens = null;
|
|
78
108
|
#clock = Number.NEGATIVE_INFINITY;
|
|
79
109
|
#closed = false;
|
|
110
|
+
/** the opening this adapter serves, bound at its first reconcile */
|
|
111
|
+
#gate = null;
|
|
80
112
|
#subs = new Map();
|
|
81
113
|
#inflight = new Set();
|
|
82
114
|
#nextId = 1;
|
|
@@ -161,7 +193,8 @@ export class MediatorCarrierAdapter {
|
|
|
161
193
|
this.#wire.onDeliver(async (raw) => {
|
|
162
194
|
if (this.#closed)
|
|
163
195
|
return;
|
|
164
|
-
|
|
196
|
+
// a plain copy of its own: a Node Buffer's slice() would share memory
|
|
197
|
+
const bytes = new Uint8Array(raw);
|
|
165
198
|
const queueId = hex(await sha(bytes));
|
|
166
199
|
if (this.#closed)
|
|
167
200
|
return;
|
|
@@ -229,7 +262,7 @@ export class MediatorCarrierAdapter {
|
|
|
229
262
|
const refreshed = await this.#wire.refresh(this.#tokens.refreshToken);
|
|
230
263
|
if (this.#closed)
|
|
231
264
|
throw new Error('retired'); // as on the handshake path (R6-B1)
|
|
232
|
-
this.#tokens = refreshed;
|
|
265
|
+
this.#tokens = tokenCopy(refreshed);
|
|
233
266
|
return this.#tokens.accessToken;
|
|
234
267
|
}
|
|
235
268
|
catch (e) {
|
|
@@ -247,7 +280,7 @@ export class MediatorCarrierAdapter {
|
|
|
247
280
|
const { challenge, sessionId } = await this.#wire.authChallenge(this.#connectionDid);
|
|
248
281
|
if (this.#closed)
|
|
249
282
|
throw new Error('retired'); // nothing new starts, not even the answer (R6-B1)
|
|
250
|
-
this.#tokens = await this.#wire.authenticate({ did: this.#connectionDid, challenge, sessionId });
|
|
283
|
+
this.#tokens = tokenCopy(await this.#wire.authenticate({ did: this.#connectionDid, challenge, sessionId }));
|
|
251
284
|
return this.#tokens.accessToken;
|
|
252
285
|
})();
|
|
253
286
|
this.#authing = run.finally(() => { this.#authing = null; });
|
|
@@ -278,7 +311,7 @@ export class MediatorCarrierAdapter {
|
|
|
278
311
|
const token = await this.#ensureAuth(now);
|
|
279
312
|
if (this.#closed)
|
|
280
313
|
return;
|
|
281
|
-
await this.#wire.keylistUpdate(token, wanted);
|
|
314
|
+
await this.#wire.keylistUpdate(token, [...wanted]); // the wire's own list: `wanted` is compared below
|
|
282
315
|
// land only if the desire has not moved meanwhile
|
|
283
316
|
if (!this.#closed && this.#desiredRkids !== null &&
|
|
284
317
|
wanted.join(' ') === this.#desiredRkids.join(' ')) {
|
|
@@ -303,7 +336,9 @@ export class MediatorCarrierAdapter {
|
|
|
303
336
|
const id = 's' + this.#nextId++;
|
|
304
337
|
const knownOffline = this.#wire.offline?.() === true;
|
|
305
338
|
const sub = {
|
|
306
|
-
|
|
339
|
+
id,
|
|
340
|
+
// a plain copy of its own (a Node Buffer's slice() shares memory)
|
|
341
|
+
bytes: new Uint8Array(envelopeBytes),
|
|
307
342
|
submittedAt: now,
|
|
308
343
|
attemptStartedAt: null,
|
|
309
344
|
lastKind: null,
|
|
@@ -314,16 +349,79 @@ export class MediatorCarrierAdapter {
|
|
|
314
349
|
this.#startAttempt(sub, now);
|
|
315
350
|
return id;
|
|
316
351
|
}
|
|
317
|
-
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
|
|
352
|
+
/** Takes the holder's custody set (holder.pendingTransport, after a
|
|
353
|
+
* resume of this opening) and the holder as the gate: files every entry
|
|
354
|
+
* not yet filed under its digest (its envelope as JCS bytes; the give-up
|
|
355
|
+
* horizon from entry.since) and returns their SubmissionRefs; withdraws
|
|
356
|
+
* every filed submission still before the trias (no report, or
|
|
357
|
+
* awaiting-transport) whose digest the view does not name. Accepted and
|
|
358
|
+
* terminal submissions stay for their reports. */
|
|
359
|
+
reconcile(view, gate, rawNow) {
|
|
321
360
|
this.#open();
|
|
322
|
-
if (
|
|
323
|
-
throw new Error('
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
361
|
+
if (view?.ok !== true || !Array.isArray(view.entries))
|
|
362
|
+
throw new Error('reconcile needs a view of holder.pendingTransport');
|
|
363
|
+
if (typeof gate?.handOver !== 'function')
|
|
364
|
+
throw new Error('reconcile needs the holder as its gate');
|
|
365
|
+
// one adapter per opening: its copies and the holder's grants belong to
|
|
366
|
+
// that opening, and the next opening gets an adapter of its own
|
|
367
|
+
if (this.#gate === null)
|
|
368
|
+
this.#gate = gate;
|
|
369
|
+
else if (this.#gate !== gate)
|
|
370
|
+
throw new Error('one adapter per opening: this adapter reconciles with another holder (create a new adapter for the next opening)');
|
|
371
|
+
const now = this.#now(rawNow);
|
|
372
|
+
// a submission's key: the digest, and for a duplicate's answer its
|
|
373
|
+
// grant too — the retries and each answer are hand-overs of their own
|
|
374
|
+
const keyOf = (digest, resend) => resend === undefined ? digest : `${digest}#${resend}`;
|
|
375
|
+
const named = new Set(view.entries.map((e) => keyOf(e.digest, e.resend)));
|
|
376
|
+
const filed = new Map();
|
|
377
|
+
for (const [id, sub] of this.#subs) {
|
|
378
|
+
if (sub.digest === undefined)
|
|
379
|
+
continue;
|
|
380
|
+
const key = keyOf(sub.digest, sub.entry?.resend);
|
|
381
|
+
const before = sub.st === null || sub.st.state === 'awaiting-transport';
|
|
382
|
+
if (before && !named.has(key)) {
|
|
383
|
+
this.#subs.delete(id);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
filed.set(key, sub);
|
|
387
|
+
}
|
|
388
|
+
const refs = [];
|
|
389
|
+
for (const entry of view.entries) {
|
|
390
|
+
const had = filed.get(keyOf(entry.digest, entry.resend));
|
|
391
|
+
if (had !== undefined) {
|
|
392
|
+
// the newest entry of a live submission is the one its gate checks
|
|
393
|
+
if (had.st === null || had.st.state === 'awaiting-transport') {
|
|
394
|
+
had.entry = entry;
|
|
395
|
+
had.gate = gate;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
// an answer the holder still offers is still owed — its hand-over
|
|
399
|
+
// never began, or the holder could not record that it did: a
|
|
400
|
+
// terminal submission of it is filed again (the holder ends it)
|
|
401
|
+
if (entry.resend === undefined)
|
|
402
|
+
continue;
|
|
403
|
+
this.#subs.delete(had.id);
|
|
404
|
+
}
|
|
405
|
+
const id = 's' + this.#nextId++;
|
|
406
|
+
const sub = {
|
|
407
|
+
id, digest: entry.digest, entry, gate,
|
|
408
|
+
bytes: new TextEncoder().encode(jcs(entry.envelope)),
|
|
409
|
+
// a duplicate's ack is a hand-over of its own, from now
|
|
410
|
+
submittedAt: entry.resend !== undefined ? now : Math.min(entry.since, now),
|
|
411
|
+
attemptStartedAt: null, lastKind: null, st: null,
|
|
412
|
+
};
|
|
413
|
+
this.#subs.set(id, sub);
|
|
414
|
+
refs.push({ submissionId: id, digest: entry.digest });
|
|
415
|
+
// taken over past its horizon (counted from the first submission,
|
|
416
|
+
// RLTP-DEL-6130): given up at once, never attempted
|
|
417
|
+
if (now - sub.submittedAt >= DECLARED.giveUpHorizonMs) {
|
|
418
|
+
sub.st = { state: 'failed', reason: 'expired-by-adapter-policy' };
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (this.#wire.offline?.() !== true)
|
|
422
|
+
this.#startAttempt(sub, now);
|
|
423
|
+
}
|
|
424
|
+
return refs;
|
|
327
425
|
}
|
|
328
426
|
/** The current state as the ports' DeliveryReport for holder.report(),
|
|
329
427
|
* or null while no honest report exists (and for an unknown id). A
|
|
@@ -334,7 +432,7 @@ export class MediatorCarrierAdapter {
|
|
|
334
432
|
return null;
|
|
335
433
|
const digest = this.#subs.get(id).digest;
|
|
336
434
|
if (digest === undefined)
|
|
337
|
-
throw new Error('submitted without a document digest:
|
|
435
|
+
throw new Error('submitted without a document digest: file holder documents through reconcile');
|
|
338
436
|
return deliveryReportOf(digest, st);
|
|
339
437
|
}
|
|
340
438
|
has(id) {
|
|
@@ -355,7 +453,56 @@ export class MediatorCarrierAdapter {
|
|
|
355
453
|
const token = await this.#ensureAuth(now);
|
|
356
454
|
if (this.#closed)
|
|
357
455
|
return; // retirement between auth and deposit: nothing new starts (R5-B2)
|
|
358
|
-
|
|
456
|
+
// each attempt hands the wire a copy: what it does with it never reaches the next
|
|
457
|
+
const deposit = () => {
|
|
458
|
+
try {
|
|
459
|
+
return this.#wire.deposit(token, new Uint8Array(sub.bytes), this.#egressDid);
|
|
460
|
+
}
|
|
461
|
+
catch (e) {
|
|
462
|
+
return Promise.reject(e);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
if (sub.gate !== undefined) {
|
|
466
|
+
// the holder decides, and the deposit starts in the same step of its
|
|
467
|
+
// queue (Delivery RLTP-DEL-6125 to 6135); the adapter re-checks its
|
|
468
|
+
// own state there: retired, withdrawn, past the trias, past the
|
|
469
|
+
// entry's deadline or its own horizon → no deposit
|
|
470
|
+
// the deposit's outcome, caught at once: the holder's step may still
|
|
471
|
+
// be committing when the wire answers
|
|
472
|
+
let started = null;
|
|
473
|
+
let declined = false;
|
|
474
|
+
const entry = sub.entry;
|
|
475
|
+
const start = () => {
|
|
476
|
+
if (this.#closed || this.#subs.get(sub.id) !== sub || sub.gate.closed === true ||
|
|
477
|
+
(sub.st !== null && sub.st.state !== 'awaiting-transport') || this.#clock >= entry.notAfter ||
|
|
478
|
+
this.#clock - sub.submittedAt >= DECLARED.giveUpHorizonMs) {
|
|
479
|
+
declined = true;
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
started = deposit().then(() => null, (e) => ({ failed: e }));
|
|
483
|
+
return true;
|
|
484
|
+
};
|
|
485
|
+
let ok;
|
|
486
|
+
try {
|
|
487
|
+
ok = await sub.gate.handOver(entry, this.#clock, start);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
ok = null;
|
|
491
|
+
}
|
|
492
|
+
if (started === null) {
|
|
493
|
+
// the holder said no: withdrawn; the adapter declined or the
|
|
494
|
+
// holder was unreachable: not this attempt
|
|
495
|
+
if (ok === false && !declined && this.#subs.get(sub.id) === sub)
|
|
496
|
+
this.#subs.delete(sub.id);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const outcome = await started;
|
|
500
|
+
if (outcome !== null)
|
|
501
|
+
throw outcome.failed;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
await deposit();
|
|
505
|
+
}
|
|
359
506
|
sub.lastKind = null;
|
|
360
507
|
next = { state: 'accepted' };
|
|
361
508
|
}
|
|
@@ -406,7 +553,7 @@ export class MediatorCarrierAdapter {
|
|
|
406
553
|
if (!sub)
|
|
407
554
|
return null;
|
|
408
555
|
if (sub.st !== null && sub.st.state !== 'awaiting-transport')
|
|
409
|
-
return sub.st;
|
|
556
|
+
return { ...sub.st };
|
|
410
557
|
if (this.#wire.offline?.() === true) {
|
|
411
558
|
return { state: 'awaiting-transport', reason: 'offline' };
|
|
412
559
|
}
|
|
@@ -414,7 +561,7 @@ export class MediatorCarrierAdapter {
|
|
|
414
561
|
this.#clock - sub.attemptStartedAt >= DECLARED.statusHorizonMs) {
|
|
415
562
|
return { state: 'awaiting-transport', reason: 'transport-unreachable' };
|
|
416
563
|
}
|
|
417
|
-
return sub.st;
|
|
564
|
+
return sub.st === null ? null : { ...sub.st };
|
|
418
565
|
}
|
|
419
566
|
acknowledged(id) {
|
|
420
567
|
if (this.#closed)
|
|
@@ -469,17 +616,33 @@ export class MediatorCarrierAdapter {
|
|
|
469
616
|
this.#inboxDigests.clear();
|
|
470
617
|
const id = 'c' + this.#nextColId++;
|
|
471
618
|
this.#outstanding.set(id, new Map(items.map((i) => [i.queueId, i])));
|
|
472
|
-
|
|
619
|
+
// the caller gets its own copies: the outstanding entries stay intact,
|
|
620
|
+
// whatever it does with the bytes, for a conclusion that keeps them
|
|
621
|
+
return { id, items: items.map((i) => ({ ...i, bytes: new Uint8Array(i.bytes) })) };
|
|
473
622
|
}
|
|
474
|
-
|
|
623
|
+
/** Concludes a collection (profile §4, Delivery 5a.8): its items'
|
|
624
|
+
* digests become ack intent. `keep` names the items whose disposition
|
|
625
|
+
* is transient (Delivery RLTP-DEL-5810, 5820: `failed(rate-limited)`,
|
|
626
|
+
* or a transaction that could not commit, see `concludable`): they are
|
|
627
|
+
* not concluded and not acknowledged, they go back to the buffer and the
|
|
628
|
+
* next collect offers them again, and the mediator keeps its copy. */
|
|
629
|
+
conclude(collection, rawNow, opts = {}) {
|
|
475
630
|
this.#open();
|
|
476
631
|
const now = this.#now(rawNow);
|
|
477
632
|
const col = this.#outstanding.get(collection.id);
|
|
478
633
|
if (!col)
|
|
479
634
|
return;
|
|
480
635
|
this.#outstanding.delete(collection.id);
|
|
636
|
+
const keep = new Set(opts.keep ?? []);
|
|
481
637
|
for (const item of col.values()) {
|
|
482
638
|
this.#outstandingBytes -= item.bytes.length;
|
|
639
|
+
if (keep.has(item.queueId)) {
|
|
640
|
+
// transient: back to the buffer, offered again, never acknowledged
|
|
641
|
+
this.#inbox.push(item);
|
|
642
|
+
this.#inboxBytes += item.bytes.length;
|
|
643
|
+
this.#inboxDigests.add(item.queueId);
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
483
646
|
this.#concluded.set(item.queueId, now);
|
|
484
647
|
this.#pendingAcks.add(item.queueId);
|
|
485
648
|
}
|
|
@@ -531,7 +694,8 @@ export class MediatorCarrierAdapter {
|
|
|
531
694
|
for (const id of live)
|
|
532
695
|
this.#flightAcks.add(id);
|
|
533
696
|
try {
|
|
534
|
-
|
|
697
|
+
// the wire gets its own list: what it does with it never reaches the bookkeeping below
|
|
698
|
+
await this.#wire.ackReceived(token, [...live]);
|
|
535
699
|
if (!this.#closed)
|
|
536
700
|
for (const id of live)
|
|
537
701
|
this.#pendingAcks.delete(id);
|