@real-life/trust-protocol 0.6.0 → 0.7.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 +143 -0
- package/README.md +48 -3
- package/dist/adapters/vti-mediator.d.ts +80 -5
- package/dist/adapters/vti-mediator.d.ts.map +1 -1
- package/dist/adapters/vti-mediator.js +280 -26
- package/dist/adapters/vti-mediator.js.map +1 -1
- package/dist/engine.d.ts +4 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +104 -8
- package/dist/engine.js.map +1 -1
- package/dist/holder-api.d.ts +7 -1
- package/dist/holder-api.d.ts.map +1 -1
- package/dist/holder-api.js +15 -0
- package/dist/holder-api.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/results.d.ts +37 -2
- package/dist/results.d.ts.map +1 -1
- package/dist/results.js.map +1 -1
- package/package.json +2 -2
package/dist/engine.js
CHANGED
|
@@ -42,7 +42,12 @@
|
|
|
42
42
|
// that give-up runs first as an operation of its own on the one commit
|
|
43
43
|
// path (resume's CommitOp, its timer carried to the replay's answer)
|
|
44
44
|
// - outbound of an act starts at attempt 1 with nextAttemptAt =
|
|
45
|
-
// when + ceil(backoff(1));
|
|
45
|
+
// when + ceil(backoff(1)); the act names that or the document's send
|
|
46
|
+
// deadline, whichever is first, in nextDueAt (a repeat names its live
|
|
47
|
+
// documents' the same way), so an idle holder's timer reaches the
|
|
48
|
+
// retries, the give-up and the journal's end. An act without documents
|
|
49
|
+
// names no journal deadline: the next resume names the end of its entry
|
|
50
|
+
// (a tick does not); resume() re-emits only items that are due
|
|
46
51
|
// and pauses re-emission while the last step is awaiting-transport
|
|
47
52
|
// (the adapter holds the item) and never re-emits an accepted one. It
|
|
48
53
|
// gives up every non-terminal item (submitted, held or accepted) at
|
|
@@ -58,7 +63,24 @@
|
|
|
58
63
|
// due for the give-up, at their current attempt; one due for it is
|
|
59
64
|
// given up in a commit of its own first (6115: never handed out again). Idempotency is bounded by the journal's retention:
|
|
60
65
|
// max(P90D, retry.horizonMs), the key-retention of Delivery §7 that
|
|
61
|
-
// bounds effects and their acks
|
|
66
|
+
// bounds effects and their acks, counted from the act's commit and
|
|
67
|
+
// from the last step of each of its documents, whichever is later (a
|
|
68
|
+
// document not yet terminal keeps the act): the act's result stays as
|
|
69
|
+
// long as its documents' completion does; after it the same actId is
|
|
70
|
+
// a new act. The retention runs in holder time (an entry leaves at the
|
|
71
|
+
// tick at or after its end, `when` raised to lastWhen): a clock set
|
|
72
|
+
// ahead by more than the retention ends it early in real time. The
|
|
73
|
+
// bound is one of time, not of count: one entry per human act (its
|
|
74
|
+
// events and document digests, the documents themselves kept beside
|
|
75
|
+
// it), removed by the tick that is due. A replay
|
|
76
|
+
// answers from the journal alone, before any preparation: no key, no
|
|
77
|
+
// entropy, no new document; the time it reads at changes nothing in it
|
|
78
|
+
// - replay(actId, when) is the journal read without the act: in the
|
|
79
|
+
// queue (after the settling of an unsettled commit), on the stored
|
|
80
|
+
// state, committing nothing, lastWhen included; it names which of the
|
|
81
|
+
// act's documents a repeat at max(when, lastWhen) would hand out, and
|
|
82
|
+
// gives none up. unknown-act: no entry under the id (never committed,
|
|
83
|
+
// or past the retention)
|
|
62
84
|
// - KeyProvider, Log and Access rejections inside a preparation become
|
|
63
85
|
// InfraError('key-provider' | 'log' | 'access') with the cause. Known
|
|
64
86
|
// limitation: a provider's argument errors (a module passing a label
|
|
@@ -659,8 +681,11 @@ class HolderEngine {
|
|
|
659
681
|
if (!u)
|
|
660
682
|
return;
|
|
661
683
|
const { state } = await this.#read();
|
|
684
|
+
// the journal entry at that commitSeq with these very events: another
|
|
685
|
+
// opening's commit of the same act at the same commitSeq is not this one
|
|
662
686
|
const landed = jcs(state) === u.text ||
|
|
663
|
-
(u.actId !== undefined && Object.hasOwn(state.acts, u.actId) && state.acts[u.actId].commitSeq === u.commitSeq
|
|
687
|
+
(u.actId !== undefined && Object.hasOwn(state.acts, u.actId) && state.acts[u.actId].commitSeq === u.commitSeq &&
|
|
688
|
+
jcs(state.acts[u.actId].events) === jcs(u.events));
|
|
664
689
|
this.#unsettled = null;
|
|
665
690
|
if (landed)
|
|
666
691
|
this.#announce(u.commitSeq, u.events);
|
|
@@ -720,6 +745,15 @@ class HolderEngine {
|
|
|
720
745
|
attempt--;
|
|
721
746
|
continue;
|
|
722
747
|
}
|
|
748
|
+
// the timer as the act named it: the next attempt of its live documents
|
|
749
|
+
// (and a give-up's own timer), so a repeat answers as the act did
|
|
750
|
+
const soon = (t) => { if (t !== undefined && t !== null)
|
|
751
|
+
replayWake = replayWake === undefined ? t : Math.min(replayWake, t); };
|
|
752
|
+
for (const d of live) {
|
|
753
|
+
const it = this.#itemOf(state, d);
|
|
754
|
+
soon(it.nextAttemptAt);
|
|
755
|
+
soon(notAfterOf(it, this.#policy.horizonMs));
|
|
756
|
+
}
|
|
723
757
|
return { ok: true, outbound: live.map((d) => this.#outboundOf(state, d)), events: freezeDeep(events), ...(replayWake !== undefined ? { nextDueAt: replayWake } : {}) };
|
|
724
758
|
}
|
|
725
759
|
// 2 — prepare, bound to this snapshot
|
|
@@ -798,6 +832,20 @@ class HolderEngine {
|
|
|
798
832
|
const effects = r.renames?.length ? renameAcks(r.state.effects, r.renames) : r.state.effects;
|
|
799
833
|
const next = { ...r.state, effects, commitSeq: seq, lastWhen: at, outbox, duties, acts };
|
|
800
834
|
const text = encodeState(next); // validates; an invalid next state is a programming error
|
|
835
|
+
// the timer for the documents this commit issues: each one's first
|
|
836
|
+
// retry or its send deadline, whichever comes first, beside the duty
|
|
837
|
+
// work the module's state opened
|
|
838
|
+
let full = wake;
|
|
839
|
+
if (r.nextDueAt === undefined && def !== this.#builtins.resume) {
|
|
840
|
+
const soon = (t) => { if (t !== undefined && t !== null)
|
|
841
|
+
full = full === undefined ? t : Math.min(full, t); };
|
|
842
|
+
// each document's first retry or its send deadline, whichever comes first
|
|
843
|
+
for (const o of outbound) {
|
|
844
|
+
soon(outbox[o.digest].nextAttemptAt);
|
|
845
|
+
soon(notAfterOf(outbox[o.digest], this.#policy.horizonMs));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
const fullExtra = { ...(full !== undefined ? { nextDueAt: full } : {}), ...(r.detail !== undefined ? { detail: r.detail } : {}) };
|
|
801
849
|
if (this.#check) {
|
|
802
850
|
const d = (r.detail ?? {});
|
|
803
851
|
const pick = (k) => (d[k] !== undefined ? { [k]: d[k] } : {});
|
|
@@ -817,7 +865,7 @@ class HolderEngine {
|
|
|
817
865
|
continue;
|
|
818
866
|
// 4 — release
|
|
819
867
|
this.#announce(seq, events);
|
|
820
|
-
return { ok: true, outbound: [...emit, ...release, ...outbound], events, ...
|
|
868
|
+
return { ok: true, outbound: [...emit, ...release, ...outbound], events, ...fullExtra };
|
|
821
869
|
}
|
|
822
870
|
throw new InfraError('commit-conflict', `no commit after ${this.#policy.commitAttempts} attempts`);
|
|
823
871
|
}
|
|
@@ -833,8 +881,11 @@ class HolderEngine {
|
|
|
833
881
|
out.push(p.expiresAt);
|
|
834
882
|
for (const e of Object.values(state.effects))
|
|
835
883
|
out.push(e.expiresAt);
|
|
836
|
-
for (const a of Object.values(state.acts))
|
|
837
|
-
|
|
884
|
+
for (const a of Object.values(state.acts)) {
|
|
885
|
+
const end = actEnd(state, a, retention);
|
|
886
|
+
if (end !== Infinity)
|
|
887
|
+
out.push(end);
|
|
888
|
+
}
|
|
838
889
|
for (const it of Object.values(state.outbox)) {
|
|
839
890
|
const t = triasOf(it);
|
|
840
891
|
if ((t === 'delivered' || t === 'failed') && it.history.length)
|
|
@@ -983,9 +1034,10 @@ class HolderEngine {
|
|
|
983
1034
|
let dropped = false;
|
|
984
1035
|
const keep = (rec, live) => {
|
|
985
1036
|
const out = {};
|
|
1037
|
+
// defined, not assigned: a key such as `__proto__` (an app's actId) stays an own member
|
|
986
1038
|
for (const [k, v] of Object.entries(rec)) {
|
|
987
1039
|
if (live(v, k))
|
|
988
|
-
out
|
|
1040
|
+
Object.defineProperty(out, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
989
1041
|
else
|
|
990
1042
|
dropped = true;
|
|
991
1043
|
}
|
|
@@ -995,7 +1047,7 @@ class HolderEngine {
|
|
|
995
1047
|
// act journal after the retention
|
|
996
1048
|
const effects = keep(state.effects, (x) => x.expiresAt > when);
|
|
997
1049
|
const prompts = keep(state.prompts, (x) => x.expiresAt === undefined || x.expiresAt > when);
|
|
998
|
-
const acts = keep(state.acts, (x) => x
|
|
1050
|
+
const acts = keep(state.acts, (x) => actEnd(state, x, retention) > when);
|
|
999
1051
|
// terminal outbox items, the retention after their last step, unless a
|
|
1000
1052
|
// kept journal entry still answers with them (replay needs the item)
|
|
1001
1053
|
const journaled = new Set(Object.values(acts).flatMap((a) => a.outboundDigests));
|
|
@@ -1589,6 +1641,31 @@ class HolderEngine {
|
|
|
1589
1641
|
return Object.freeze({ state, commitSeq: state.commitSeq });
|
|
1590
1642
|
});
|
|
1591
1643
|
}
|
|
1644
|
+
replay(actId, when) {
|
|
1645
|
+
try {
|
|
1646
|
+
checkWhen(when);
|
|
1647
|
+
}
|
|
1648
|
+
catch (e) {
|
|
1649
|
+
return Promise.reject(e);
|
|
1650
|
+
}
|
|
1651
|
+
if (typeof actId !== 'string' || actId === '')
|
|
1652
|
+
return Promise.resolve(Object.freeze({ ok: false, code: 'actid-missing' }));
|
|
1653
|
+
return this.#enqueue(async () => {
|
|
1654
|
+
const { state } = await this.#read();
|
|
1655
|
+
if (!Object.hasOwn(state.acts, actId))
|
|
1656
|
+
return Object.freeze({ ok: false, code: 'unknown-act' });
|
|
1657
|
+
const rec = state.acts[actId];
|
|
1658
|
+
// as a repeat of the act would see them at max(when, lastWhen), without
|
|
1659
|
+
// the give-up it would commit first: a pure read
|
|
1660
|
+
const at = Math.max(when, state.lastWhen);
|
|
1661
|
+
const outbound = rec.outboundDigests.map((digest) => {
|
|
1662
|
+
const it = Object.hasOwn(state.outbox, digest) ? state.outbox[digest] : undefined;
|
|
1663
|
+
const t = it ? triasOf(it) : undefined;
|
|
1664
|
+
return { digest, live: it !== undefined && t !== 'delivered' && t !== 'failed' && this.#giveUp(it, at) === null };
|
|
1665
|
+
});
|
|
1666
|
+
return freezeDeep({ ok: true, result: { actId: rec.actId, commitSeq: rec.commitSeq, at: rec.at, events: structuredClone(rec.events), outbound } });
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1592
1669
|
pendingTransport(when) {
|
|
1593
1670
|
try {
|
|
1594
1671
|
checkWhen(when);
|
|
@@ -1684,6 +1761,25 @@ class HolderEngine {
|
|
|
1684
1761
|
}
|
|
1685
1762
|
get closed() { return this.#closed; }
|
|
1686
1763
|
}
|
|
1764
|
+
/** When the journal entry of an act may leave (tick): the retention after
|
|
1765
|
+
* its commit and after the last step of each of its documents that is
|
|
1766
|
+
* terminal — the act's result stays as long as the completion of its
|
|
1767
|
+
* documents does; Infinity while one of them is not terminal yet (its
|
|
1768
|
+
* give-up makes it terminal, and the deadline follows then). */
|
|
1769
|
+
const actEnd = (state, a, retention) => {
|
|
1770
|
+
let end = a.at;
|
|
1771
|
+
for (const d of a.outboundDigests) {
|
|
1772
|
+
if (!Object.hasOwn(state.outbox, d))
|
|
1773
|
+
continue;
|
|
1774
|
+
const it = state.outbox[d];
|
|
1775
|
+
const t = triasOf(it);
|
|
1776
|
+
if (t !== 'delivered' && t !== 'failed')
|
|
1777
|
+
return Infinity;
|
|
1778
|
+
if (it.history.length)
|
|
1779
|
+
end = Math.max(end, it.history[it.history.length - 1].at);
|
|
1780
|
+
}
|
|
1781
|
+
return end + retention;
|
|
1782
|
+
};
|
|
1687
1783
|
/** The send deadline of an outbox item: the horizon from its first
|
|
1688
1784
|
* submission, or the sender lifetime its type declared if that is
|
|
1689
1785
|
* earlier (#giveUp's two bounds as one instant). */
|