@affiant/core 0.1.0-alpha.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/LICENSE +202 -0
- package/README.md +354 -0
- package/dist/context.d.ts +136 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +30 -0
- package/dist/context.js.map +1 -0
- package/dist/docket/entry.d.ts +421 -0
- package/dist/docket/entry.d.ts.map +1 -0
- package/dist/docket/entry.js +155 -0
- package/dist/docket/entry.js.map +1 -0
- package/dist/docket/expiry.d.ts +82 -0
- package/dist/docket/expiry.d.ts.map +1 -0
- package/dist/docket/expiry.js +106 -0
- package/dist/docket/expiry.js.map +1 -0
- package/dist/docket/memory.d.ts +163 -0
- package/dist/docket/memory.d.ts.map +1 -0
- package/dist/docket/memory.js +528 -0
- package/dist/docket/memory.js.map +1 -0
- package/dist/docket/store.d.ts +387 -0
- package/dist/docket/store.d.ts.map +1 -0
- package/dist/docket/store.js +51 -0
- package/dist/docket/store.js.map +1 -0
- package/dist/errors.d.ts +153 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +164 -0
- package/dist/errors.js.map +1 -0
- package/dist/gate/coverage.d.ts +152 -0
- package/dist/gate/coverage.d.ts.map +1 -0
- package/dist/gate/coverage.js +114 -0
- package/dist/gate/coverage.js.map +1 -0
- package/dist/gate/decide.d.ts +207 -0
- package/dist/gate/decide.d.ts.map +1 -0
- package/dist/gate/decide.js +559 -0
- package/dist/gate/decide.js.map +1 -0
- package/dist/gate/gate.d.ts +212 -0
- package/dist/gate/gate.d.ts.map +1 -0
- package/dist/gate/gate.js +175 -0
- package/dist/gate/gate.js.map +1 -0
- package/dist/gate/pipeline.d.ts +285 -0
- package/dist/gate/pipeline.d.ts.map +1 -0
- package/dist/gate/pipeline.js +515 -0
- package/dist/gate/pipeline.js.map +1 -0
- package/dist/gate/policy.d.ts +272 -0
- package/dist/gate/policy.d.ts.map +1 -0
- package/dist/gate/policy.js +396 -0
- package/dist/gate/policy.js.map +1 -0
- package/dist/gate/wrap.d.ts +107 -0
- package/dist/gate/wrap.d.ts.map +1 -0
- package/dist/gate/wrap.js +164 -0
- package/dist/gate/wrap.js.map +1 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/index.js.map +1 -0
- package/dist/model/affidavit.d.ts +354 -0
- package/dist/model/affidavit.d.ts.map +1 -0
- package/dist/model/affidavit.js +417 -0
- package/dist/model/affidavit.js.map +1 -0
- package/dist/model/amendments.d.ts +160 -0
- package/dist/model/amendments.d.ts.map +1 -0
- package/dist/model/amendments.js +183 -0
- package/dist/model/amendments.js.map +1 -0
- package/dist/model/canonical.d.ts +311 -0
- package/dist/model/canonical.d.ts.map +1 -0
- package/dist/model/canonical.js +665 -0
- package/dist/model/canonical.js.map +1 -0
- package/dist/model/money.d.ts +127 -0
- package/dist/model/money.d.ts.map +1 -0
- package/dist/model/money.js +177 -0
- package/dist/model/money.js.map +1 -0
- package/dist/model/provenance.d.ts +315 -0
- package/dist/model/provenance.d.ts.map +1 -0
- package/dist/model/provenance.js +223 -0
- package/dist/model/provenance.js.map +1 -0
- package/dist/ports.d.ts +269 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +34 -0
- package/dist/ports.js.map +1 -0
- package/dist/store-memory.d.ts +21 -0
- package/dist/store-memory.d.ts.map +1 -0
- package/dist/store-memory.js +20 -0
- package/dist/store-memory.js.map +1 -0
- package/dist/telemetry-keys.d.ts +65 -0
- package/dist/telemetry-keys.d.ts.map +1 -0
- package/dist/telemetry-keys.js +72 -0
- package/dist/telemetry-keys.js.map +1 -0
- package/dist/telemetry.d.ts +77 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +43 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/testing.d.ts +574 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +1291 -0
- package/dist/testing.js.map +1 -0
- package/package.json +75 -0
- package/telemetry-keys.json +92 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Docket entry — the row every proposed write becomes, and the only record of
|
|
3
|
+
* approval authority the framework recognises.
|
|
4
|
+
*
|
|
5
|
+
* **Rules served: DK-1** (the review-outcome state machine, expiry as queryable
|
|
6
|
+
* state, lineage), **AZ-1** (the attestation record and its three attestor kinds),
|
|
7
|
+
* **AZ-4** (requirement levels and the `blocked` marker), **DK-4** (a row reads
|
|
8
|
+
* forward: later facts are appended, a recorded decision is never edited).
|
|
9
|
+
*
|
|
10
|
+
* DK-1 in one sentence: *`pending` goes to exactly one of `approved`, `rejected`
|
|
11
|
+
* or `expired`; an `approved` row carries an execution outcome so an
|
|
12
|
+
* approved-but-failed write is distinguishable from an approved-and-committed one;
|
|
13
|
+
* every transition out of `pending` is a guarded compare-and-set; and an entry past
|
|
14
|
+
* its expiry reads `expired` whether or not any sweep has run.*
|
|
15
|
+
*
|
|
16
|
+
* The last clause is why {@link readStatus} exists and why nothing in this package
|
|
17
|
+
* schedules a timer. Expiry is **state**, not an event: a host that never runs a
|
|
18
|
+
* sweep still cannot decide an expired entry, because every read applies the
|
|
19
|
+
* deadline. The sweep ({@link DocketStore.expireDue}) exists to make the state
|
|
20
|
+
* durable and to drive notifications, not to make it true.
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
import { PROTOCOL_VERSION } from "@affiant/contract";
|
|
25
|
+
import { isDue, requireInstant } from "./expiry.js";
|
|
26
|
+
/** Every {@link DocketStatus}, pinned as data so a runtime check and a fixture read the same list. */
|
|
27
|
+
export const DOCKET_STATUSES = [
|
|
28
|
+
"pending",
|
|
29
|
+
"approved",
|
|
30
|
+
"rejected",
|
|
31
|
+
"expired",
|
|
32
|
+
];
|
|
33
|
+
/** Every {@link ExecutionOutcome}, in the order a row moves through them. */
|
|
34
|
+
export const EXECUTION_OUTCOMES = [
|
|
35
|
+
"unexecuted",
|
|
36
|
+
"executed",
|
|
37
|
+
"failed",
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Whether `status` is terminal — everything except `pending`.
|
|
41
|
+
*
|
|
42
|
+
* `approved` counts as terminal for the *review*, which is what this predicate is
|
|
43
|
+
* about; whether the write has been executed is {@link DocketEntry.execution}, a
|
|
44
|
+
* separate question with a separate answer.
|
|
45
|
+
*/
|
|
46
|
+
export function isTerminal(status) {
|
|
47
|
+
return status !== "pending";
|
|
48
|
+
}
|
|
49
|
+
/** Every {@link RequirementKind}, in the rulebook's order. */
|
|
50
|
+
export const REQUIREMENT_KINDS = [
|
|
51
|
+
"StandingOrder",
|
|
52
|
+
"ReviewerConfirmation",
|
|
53
|
+
"ReferralRequired",
|
|
54
|
+
"MultiParty",
|
|
55
|
+
];
|
|
56
|
+
/** Every {@link BlockedCode}. */
|
|
57
|
+
export const BLOCKED_CODES = [
|
|
58
|
+
"requirement-not-implemented",
|
|
59
|
+
"coverage-refused",
|
|
60
|
+
];
|
|
61
|
+
/** Rejects an identifier that is empty or blank, which no store can key by. */
|
|
62
|
+
function requireIdentifier(value, what) {
|
|
63
|
+
if (value.trim() === "")
|
|
64
|
+
throw new RangeError(`${what} must be a non-empty string`);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A new Docket entry, with the defaults DK-1 fixes and the correlations it requires
|
|
69
|
+
* checked.
|
|
70
|
+
*
|
|
71
|
+
* Refusals are `RangeError`, not `AffiantError`: the {@link ErrorCode} registry
|
|
72
|
+
* names the reasons *the gate refuses a request*, and a caller who hands this
|
|
73
|
+
* factory a status and an execution outcome that contradict each other has made a
|
|
74
|
+
* programming error, not a request. Keeping the two apart is what stops a host from
|
|
75
|
+
* catching a wiring bug as though it were a reviewer's rejection.
|
|
76
|
+
*
|
|
77
|
+
* @throws RangeError when an identifier is blank, an instant is unreadable, the
|
|
78
|
+
* requirement is not one of the four, or `execution` contradicts `status`.
|
|
79
|
+
*/
|
|
80
|
+
export function newEntry(init) {
|
|
81
|
+
const status = init.status ?? "pending";
|
|
82
|
+
if (!DOCKET_STATUSES.includes(status)) {
|
|
83
|
+
throw new RangeError(`unknown docket status: ${String(status)}`);
|
|
84
|
+
}
|
|
85
|
+
if (!REQUIREMENT_KINDS.includes(init.requirement)) {
|
|
86
|
+
throw new RangeError(`unknown requirement kind: ${String(init.requirement)}`);
|
|
87
|
+
}
|
|
88
|
+
const execution = init.execution === undefined ? defaultExecution(status) : init.execution;
|
|
89
|
+
if (status === "approved" && execution === null) {
|
|
90
|
+
throw new RangeError("an approved entry carries an execution outcome (DK-1)");
|
|
91
|
+
}
|
|
92
|
+
if (status !== "approved" && execution !== null) {
|
|
93
|
+
throw new RangeError(`a ${status} entry carries no execution outcome (DK-1)`);
|
|
94
|
+
}
|
|
95
|
+
const filedAt = requireInstant(init.filedAt, "filedAt");
|
|
96
|
+
const expiresAt = requireInstant(init.expiresAt, "expiresAt");
|
|
97
|
+
const decidedAt = init.decidedAt === undefined ? (status === "pending" ? null : filedAt) : init.decidedAt;
|
|
98
|
+
if (status === "pending" && decidedAt !== null) {
|
|
99
|
+
throw new RangeError("a pending entry has no decidedAt (DK-1)");
|
|
100
|
+
}
|
|
101
|
+
if (status !== "pending" && decidedAt === null) {
|
|
102
|
+
throw new RangeError(`a ${status} entry records when it left pending (DK-1)`);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
entryId: requireIdentifier(init.entryId, "entryId"),
|
|
106
|
+
tenantId: requireIdentifier(init.tenantId, "tenantId"),
|
|
107
|
+
conversationId: requireIdentifier(init.conversationId, "conversationId"),
|
|
108
|
+
channel: requireIdentifier(init.channel, "channel"),
|
|
109
|
+
toolName: requireIdentifier(init.toolName, "toolName"),
|
|
110
|
+
affidavit: init.affidavit,
|
|
111
|
+
// A filing records what was proposed and nothing else: an amendment is a later
|
|
112
|
+
// fact, appended by a decision, never present at birth (DK-4).
|
|
113
|
+
amendedAffidavit: null,
|
|
114
|
+
requirement: init.requirement,
|
|
115
|
+
status,
|
|
116
|
+
execution,
|
|
117
|
+
blocked: init.blocked ?? null,
|
|
118
|
+
compositeRef: init.compositeRef ?? null,
|
|
119
|
+
attestation: init.attestation ?? null,
|
|
120
|
+
amendments: null,
|
|
121
|
+
preservedAmendments: null,
|
|
122
|
+
decision: null,
|
|
123
|
+
lineage: { supersedes: init.supersedes ?? null, supersededBy: null },
|
|
124
|
+
filedAt,
|
|
125
|
+
expiresAt,
|
|
126
|
+
decidedAt: decidedAt === null ? null : requireInstant(decidedAt, "decidedAt"),
|
|
127
|
+
executionDetail: null,
|
|
128
|
+
protocolVersion: init.protocolVersion ?? PROTOCOL_VERSION,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** The execution outcome a freshly filed entry in `status` carries. */
|
|
132
|
+
function defaultExecution(status) {
|
|
133
|
+
return status === "approved" ? "unexecuted" : null;
|
|
134
|
+
}
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Expiry as state (DK-1)
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
/**
|
|
139
|
+
* What `entry` reads as at `now` — the status every query path reports.
|
|
140
|
+
*
|
|
141
|
+
* A `pending` entry past its `expiresAt` reads `expired` **whether or not any sweep
|
|
142
|
+
* has run**. That is the whole of DK-1's expiry clause, and it is a pure function of
|
|
143
|
+
* the row and the instant precisely so that no code path can forget it: there is no
|
|
144
|
+
* background job to be down, no alarm to be dropped, and no window in which an entry
|
|
145
|
+
* is decidable because nobody swept it yet.
|
|
146
|
+
*
|
|
147
|
+
* Any other status is returned unchanged. Expiry only ever consumes `pending`; a row
|
|
148
|
+
* that was approved before its deadline stays approved forever after it.
|
|
149
|
+
*
|
|
150
|
+
* @throws RangeError when `now` or the entry's `expiresAt` is not a readable instant.
|
|
151
|
+
*/
|
|
152
|
+
export function readStatus(entry, now) {
|
|
153
|
+
return entry.status === "pending" && isDue(entry, now) ? "expired" : entry.status;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/docket/entry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expiry, as state rather than as an event.
|
|
3
|
+
*
|
|
4
|
+
* **Rules served: DK-1** (an entry past `expiresAt` reads `expired` on query
|
|
5
|
+
* whether or not any sweep has run), **DK-3** (the sweep is bounded, paged and
|
|
6
|
+
* host-scheduled; the core owns no timer).
|
|
7
|
+
*
|
|
8
|
+
* There is no `setTimeout` and no `setInterval` in this package, and a test greps
|
|
9
|
+
* `src/` to keep it that way. That is not tidiness — it is the rule. The shipped
|
|
10
|
+
* .NET implementation runs an unbounded 30-second sweep over every pending entry on
|
|
11
|
+
* every instance; a core that schedules its own work cannot run on a serverless
|
|
12
|
+
* isolate at all (RT-2), and a deadline that only takes effect when a background job
|
|
13
|
+
* happens to be up is a deadline a host can silently lose. So the deadline is a
|
|
14
|
+
* *predicate over the row*, applied on every read, and the host's sweep
|
|
15
|
+
* ({@link DocketStore.expireDue}) exists to make that state durable and to drive
|
|
16
|
+
* notifications — not to make it true.
|
|
17
|
+
*
|
|
18
|
+
* Every function here is pure and synchronous. They take the instant as a parameter
|
|
19
|
+
* rather than reading a clock, so a fixture can pin a deadline to the millisecond.
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
import type { DocketEntry } from "./entry.js";
|
|
24
|
+
/**
|
|
25
|
+
* `instant` unchanged, once it is known to be readable.
|
|
26
|
+
*
|
|
27
|
+
* An unreadable instant throws rather than comparing as `NaN`. A `NaN` comparison
|
|
28
|
+
* is `false` in both directions, so an entry with an unparseable `expiresAt` would
|
|
29
|
+
* be *permanently decidable* — the deadline would silently stop existing, which is
|
|
30
|
+
* the one failure mode this module is here to prevent. A row can only acquire an
|
|
31
|
+
* unreadable deadline if a caller built it by hand, and {@link newEntry} refuses to
|
|
32
|
+
* do that.
|
|
33
|
+
*
|
|
34
|
+
* @throws RangeError when `instant` is not a date string any runtime can read.
|
|
35
|
+
*/
|
|
36
|
+
export declare function requireInstant(instant: string, what: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* `instant` as milliseconds since the epoch.
|
|
39
|
+
*
|
|
40
|
+
* @throws RangeError when `instant` is not a date string any runtime can read.
|
|
41
|
+
*/
|
|
42
|
+
export declare function instantMs(instant: string, what?: string): number;
|
|
43
|
+
/** As much of an entry as the expiry predicates read. */
|
|
44
|
+
export type Expirable = Pick<DocketEntry, "status" | "expiresAt">;
|
|
45
|
+
/**
|
|
46
|
+
* Whether `entry` has passed its deadline at `now` — the predicate
|
|
47
|
+
* {@link readStatus} and the sweep both apply.
|
|
48
|
+
*
|
|
49
|
+
* Only a `pending` entry can be due. A row that was approved or rejected before its
|
|
50
|
+
* deadline stays that way forever after it, and a row already marked `expired` is
|
|
51
|
+
* not due *again* — `expireDue` must not keep finding the same entries.
|
|
52
|
+
*
|
|
53
|
+
* The deadline is inclusive of the instant itself: an entry whose `expiresAt` is
|
|
54
|
+
* exactly `now` is due. Half-open the other way would leave a one-millisecond window
|
|
55
|
+
* in which the row is decidable at its own deadline, and a fixture that pins the two
|
|
56
|
+
* to the same instant would then assert the wrong thing.
|
|
57
|
+
*
|
|
58
|
+
* @throws RangeError when `now` or `entry.expiresAt` is not a readable instant.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isDue(entry: Expirable, now: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* How long `entry` has left at `now`, in milliseconds.
|
|
63
|
+
*
|
|
64
|
+
* `0` for an entry that is due or is no longer `pending` — never a negative number.
|
|
65
|
+
* A caller asking "how long is left" is deciding whether to show a countdown or to
|
|
66
|
+
* stop offering the card, and a negative answer only invites a sign bug at the call
|
|
67
|
+
* site.
|
|
68
|
+
*
|
|
69
|
+
* @throws RangeError when `now` or `entry.expiresAt` is not a readable instant.
|
|
70
|
+
*/
|
|
71
|
+
export declare function remainingMs(entry: Expirable, now: string): number;
|
|
72
|
+
/**
|
|
73
|
+
* Whether `a` was filed before `b` — the total order every list, every sweep, every
|
|
74
|
+
* export and the rehydration sequence (DK-5) reads in.
|
|
75
|
+
*
|
|
76
|
+
* `filedAt` alone is not a total order: two entries filed in the same millisecond
|
|
77
|
+
* tie, and a store that let ties fall out in hash order would rehydrate a session
|
|
78
|
+
* differently on each reconnect. The entry id breaks the tie, by code point, so the
|
|
79
|
+
* order is the same everywhere and on every runtime.
|
|
80
|
+
*/
|
|
81
|
+
export declare function compareFilingOrder(a: Pick<DocketEntry, "filedAt" | "entryId">, b: Pick<DocketEntry, "filedAt" | "entryId">): number;
|
|
82
|
+
//# sourceMappingURL=expiry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry.d.ts","sourceRoot":"","sources":["../../src/docket/expiry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAY,GAAG,MAAM,CAMnE;AAED,yDAAyD;AACzD,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,WAAW,CAAC,CAAC;AAElE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,SAAS,CAAC,EAC3C,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,SAAS,CAAC,GAC1C,MAAM,CAKR"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expiry, as state rather than as an event.
|
|
3
|
+
*
|
|
4
|
+
* **Rules served: DK-1** (an entry past `expiresAt` reads `expired` on query
|
|
5
|
+
* whether or not any sweep has run), **DK-3** (the sweep is bounded, paged and
|
|
6
|
+
* host-scheduled; the core owns no timer).
|
|
7
|
+
*
|
|
8
|
+
* There is no `setTimeout` and no `setInterval` in this package, and a test greps
|
|
9
|
+
* `src/` to keep it that way. That is not tidiness — it is the rule. The shipped
|
|
10
|
+
* .NET implementation runs an unbounded 30-second sweep over every pending entry on
|
|
11
|
+
* every instance; a core that schedules its own work cannot run on a serverless
|
|
12
|
+
* isolate at all (RT-2), and a deadline that only takes effect when a background job
|
|
13
|
+
* happens to be up is a deadline a host can silently lose. So the deadline is a
|
|
14
|
+
* *predicate over the row*, applied on every read, and the host's sweep
|
|
15
|
+
* ({@link DocketStore.expireDue}) exists to make that state durable and to drive
|
|
16
|
+
* notifications — not to make it true.
|
|
17
|
+
*
|
|
18
|
+
* Every function here is pure and synchronous. They take the instant as a parameter
|
|
19
|
+
* rather than reading a clock, so a fixture can pin a deadline to the millisecond.
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* `instant` unchanged, once it is known to be readable.
|
|
25
|
+
*
|
|
26
|
+
* An unreadable instant throws rather than comparing as `NaN`. A `NaN` comparison
|
|
27
|
+
* is `false` in both directions, so an entry with an unparseable `expiresAt` would
|
|
28
|
+
* be *permanently decidable* — the deadline would silently stop existing, which is
|
|
29
|
+
* the one failure mode this module is here to prevent. A row can only acquire an
|
|
30
|
+
* unreadable deadline if a caller built it by hand, and {@link newEntry} refuses to
|
|
31
|
+
* do that.
|
|
32
|
+
*
|
|
33
|
+
* @throws RangeError when `instant` is not a date string any runtime can read.
|
|
34
|
+
*/
|
|
35
|
+
export function requireInstant(instant, what) {
|
|
36
|
+
if (Number.isNaN(Date.parse(instant))) {
|
|
37
|
+
throw new RangeError(`${what} must be a readable ISO 8601 instant, got: ${instant}`);
|
|
38
|
+
}
|
|
39
|
+
return instant;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `instant` as milliseconds since the epoch.
|
|
43
|
+
*
|
|
44
|
+
* @throws RangeError when `instant` is not a date string any runtime can read.
|
|
45
|
+
*/
|
|
46
|
+
export function instantMs(instant, what = "instant") {
|
|
47
|
+
const ms = Date.parse(instant);
|
|
48
|
+
if (Number.isNaN(ms)) {
|
|
49
|
+
throw new RangeError(`${what} must be a readable ISO 8601 instant, got: ${instant}`);
|
|
50
|
+
}
|
|
51
|
+
return ms;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether `entry` has passed its deadline at `now` — the predicate
|
|
55
|
+
* {@link readStatus} and the sweep both apply.
|
|
56
|
+
*
|
|
57
|
+
* Only a `pending` entry can be due. A row that was approved or rejected before its
|
|
58
|
+
* deadline stays that way forever after it, and a row already marked `expired` is
|
|
59
|
+
* not due *again* — `expireDue` must not keep finding the same entries.
|
|
60
|
+
*
|
|
61
|
+
* The deadline is inclusive of the instant itself: an entry whose `expiresAt` is
|
|
62
|
+
* exactly `now` is due. Half-open the other way would leave a one-millisecond window
|
|
63
|
+
* in which the row is decidable at its own deadline, and a fixture that pins the two
|
|
64
|
+
* to the same instant would then assert the wrong thing.
|
|
65
|
+
*
|
|
66
|
+
* @throws RangeError when `now` or `entry.expiresAt` is not a readable instant.
|
|
67
|
+
*/
|
|
68
|
+
export function isDue(entry, now) {
|
|
69
|
+
if (entry.status !== "pending")
|
|
70
|
+
return false;
|
|
71
|
+
return instantMs(entry.expiresAt, "expiresAt") <= instantMs(now, "now");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* How long `entry` has left at `now`, in milliseconds.
|
|
75
|
+
*
|
|
76
|
+
* `0` for an entry that is due or is no longer `pending` — never a negative number.
|
|
77
|
+
* A caller asking "how long is left" is deciding whether to show a countdown or to
|
|
78
|
+
* stop offering the card, and a negative answer only invites a sign bug at the call
|
|
79
|
+
* site.
|
|
80
|
+
*
|
|
81
|
+
* @throws RangeError when `now` or `entry.expiresAt` is not a readable instant.
|
|
82
|
+
*/
|
|
83
|
+
export function remainingMs(entry, now) {
|
|
84
|
+
if (entry.status !== "pending")
|
|
85
|
+
return 0;
|
|
86
|
+
const left = instantMs(entry.expiresAt, "expiresAt") - instantMs(now, "now");
|
|
87
|
+
return left > 0 ? left : 0;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Whether `a` was filed before `b` — the total order every list, every sweep, every
|
|
91
|
+
* export and the rehydration sequence (DK-5) reads in.
|
|
92
|
+
*
|
|
93
|
+
* `filedAt` alone is not a total order: two entries filed in the same millisecond
|
|
94
|
+
* tie, and a store that let ties fall out in hash order would rehydrate a session
|
|
95
|
+
* differently on each reconnect. The entry id breaks the tie, by code point, so the
|
|
96
|
+
* order is the same everywhere and on every runtime.
|
|
97
|
+
*/
|
|
98
|
+
export function compareFilingOrder(a, b) {
|
|
99
|
+
const byInstant = instantMs(a.filedAt, "filedAt") - instantMs(b.filedAt, "filedAt");
|
|
100
|
+
if (byInstant !== 0)
|
|
101
|
+
return byInstant;
|
|
102
|
+
if (a.entryId === b.entryId)
|
|
103
|
+
return 0;
|
|
104
|
+
return a.entryId < b.entryId ? -1 : 1;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=expiry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry.js","sourceRoot":"","sources":["../../src/docket/expiry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,IAAY;IAC1D,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,UAAU,CAAC,GAAG,IAAI,8CAA8C,OAAO,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,IAAI,GAAG,SAAS;IACzD,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,UAAU,CAAC,GAAG,IAAI,8CAA8C,OAAO,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAKD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,KAAK,CAAC,KAAgB,EAAE,GAAW;IACjD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,KAAgB,EAAE,GAAW;IACvD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,GAAG,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7E,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,CAA2C,EAC3C,CAA2C;IAE3C,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACpF,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACtC,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;QAAE,OAAO,CAAC,CAAC;IACtC,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reference Docket and Session stores, in memory.
|
|
3
|
+
*
|
|
4
|
+
* **Rules served: DK-1** (idempotent filing, the guarded compare-and-set, expiry as
|
|
5
|
+
* queryable state, preserved amendments on a late decision, execution outcome,
|
|
6
|
+
* lineage), **DK-3** (a bounded, paged sweep and no timer), **DK-4** (retention,
|
|
7
|
+
* purge, export, read-forward), **DK-5** (rehydration order), **AZ-2** (tenant
|
|
8
|
+
* isolation), **RT-2** (nothing unbounded but a purge).
|
|
9
|
+
*
|
|
10
|
+
* "In memory" is not "toy". This is the *reference* store: it is what the
|
|
11
|
+
* store-semantics fixtures are written against, and a Postgres store earns the name
|
|
12
|
+
* by passing the same ones. Everything a production store has to get right is here
|
|
13
|
+
* — the compare-and-set, the deadline applied on read, the opaque paging, the
|
|
14
|
+
* tenant partition — because a reference that skipped them would let a fixture pass
|
|
15
|
+
* for the wrong reason.
|
|
16
|
+
*
|
|
17
|
+
* **How the compare-and-set is atomic without a lock.** JavaScript is
|
|
18
|
+
* single-threaded, but `await` is an interleaving point: any method that reads state,
|
|
19
|
+
* awaits, and then writes it has a window in which another caller can run. So every
|
|
20
|
+
* mutating method here does its read and its write in **one synchronous block** —
|
|
21
|
+
* the `async` wrapper contains no `await` at all, which means the body runs to
|
|
22
|
+
* completion before the caller's next turn of the event loop. Two `transition` calls
|
|
23
|
+
* launched together with `Promise.all` therefore serialize, and exactly one wins the
|
|
24
|
+
* guard. A production store gets the same property from a conditional `UPDATE`; the
|
|
25
|
+
* shape of the fixture is identical either way.
|
|
26
|
+
*
|
|
27
|
+
* **Storage layout.** One `Map` per tenant, keyed by entry id, so a cross-tenant read
|
|
28
|
+
* is not a filtered read but an impossible one — there is no code path that looks in
|
|
29
|
+
* another tenant's map. `Map` preserves insertion order, which *is* filing order, so
|
|
30
|
+
* every list, sweep and export walks it directly rather than sorting. A sequence
|
|
31
|
+
* number per row makes that position addressable by an opaque cursor.
|
|
32
|
+
*
|
|
33
|
+
* @packageDocumentation
|
|
34
|
+
*/
|
|
35
|
+
import type { AmendmentMap } from "../model/amendments.js";
|
|
36
|
+
import type { Clock } from "../ports.js";
|
|
37
|
+
import type { DocketEntry, ExecutionOutcome } from "./entry.js";
|
|
38
|
+
import type { DocketStore, Page, PageResult, PreservedAct, PreserveAmendmentsResult, RecordExecutionResult, RecordSupersessionResult, RetentionPolicy, Scope, SessionStore, TransitionPatch, TransitionResult } from "./store.js";
|
|
39
|
+
/** How the in-memory Docket store is built. */
|
|
40
|
+
export interface InMemoryDocketStoreOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Where "now" comes from, for the deadline every read applies (DK-1).
|
|
43
|
+
*
|
|
44
|
+
* A store needs a clock rather than a `now` parameter on each method because the
|
|
45
|
+
* deadline is the store's answer to give: a method that let the caller supply the
|
|
46
|
+
* instant would let the caller decide whether an entry had expired. A fixture
|
|
47
|
+
* pins the instant by passing a stub clock here. Defaults to
|
|
48
|
+
* {@link defaultClock}.
|
|
49
|
+
*/
|
|
50
|
+
readonly clock?: Clock;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The reference Docket store: one `Map` per tenant, every rule in the DK area
|
|
54
|
+
* enforced, no timer anywhere.
|
|
55
|
+
*/
|
|
56
|
+
export declare class InMemoryDocketStore implements DocketStore {
|
|
57
|
+
#private;
|
|
58
|
+
constructor(options?: InMemoryDocketStoreOptions);
|
|
59
|
+
/**
|
|
60
|
+
* File `entry`, or return the one already filed under its id — never an error,
|
|
61
|
+
* never an overwrite, never a refreshed deadline (DK-1, GT-4).
|
|
62
|
+
*/
|
|
63
|
+
file(entry: DocketEntry): Promise<{
|
|
64
|
+
entry: DocketEntry;
|
|
65
|
+
created: boolean;
|
|
66
|
+
}>;
|
|
67
|
+
/** The entry as it reads now, or `null` when nothing in `scope` has that id. */
|
|
68
|
+
get(entryId: string, scope: Scope): Promise<DocketEntry | null>;
|
|
69
|
+
/**
|
|
70
|
+
* The guarded compare-and-set (DK-1).
|
|
71
|
+
*
|
|
72
|
+
* No `await` between the read of the current state and the write of the new one:
|
|
73
|
+
* that is what makes it atomic against every other caller in this isolate.
|
|
74
|
+
*/
|
|
75
|
+
transition(entryId: string, scope: Scope, expected: "pending", patch: TransitionPatch): Promise<TransitionResult>;
|
|
76
|
+
/**
|
|
77
|
+
* Preserve the amendments a late decision carried, for a resubmission (DK-1).
|
|
78
|
+
*
|
|
79
|
+
* Writes `preservedAmendments` and nothing else: not `status`, not `decision`, not
|
|
80
|
+
* `attestation`. The row's stored status stays as it was — it already *reads*
|
|
81
|
+
* `expired`, and recording a sweep the host did not run would be this method
|
|
82
|
+
* quietly doing a second job.
|
|
83
|
+
*/
|
|
84
|
+
preserveAmendments(entryId: string, scope: Scope, amendments: AmendmentMap, act: PreservedAct): Promise<PreserveAmendmentsResult>;
|
|
85
|
+
/**
|
|
86
|
+
* Record what the host's executor reported on an approved row, once (DK-1, DK-4).
|
|
87
|
+
*
|
|
88
|
+
* A guarded compare-and-set on the row's own `execution`, read and written in the
|
|
89
|
+
* same synchronous block as every other mutation here, so two reports racing
|
|
90
|
+
* serialize and exactly one wins. The second is refused rather than applied on
|
|
91
|
+
* top: an outcome is a recorded fact, and a row reads forward.
|
|
92
|
+
*/
|
|
93
|
+
recordExecution(entryId: string, scope: Scope, outcome: Exclude<ExecutionOutcome, "unexecuted">, detail: string | null, expected: "unexecuted"): Promise<RecordExecutionResult>;
|
|
94
|
+
/** Record the successor of a terminal row (DK-1); the row keeps its terminal state. */
|
|
95
|
+
recordSupersession(entryId: string, scope: Scope, supersededBy: string): Promise<RecordSupersessionResult>;
|
|
96
|
+
/** Everything that reads `pending` right now, in filing order, paged. */
|
|
97
|
+
listPending(scope: Scope, page: Page): Promise<PageResult<DocketEntry>>;
|
|
98
|
+
/** Everything approved and still unexecuted, in filing order, paged. */
|
|
99
|
+
listApprovedUnexecuted(scope: Scope, page: Page): Promise<PageResult<DocketEntry>>;
|
|
100
|
+
/**
|
|
101
|
+
* Mark at most `limit` due entries `expired`, in filing order, and say whether more
|
|
102
|
+
* remain (DK-3).
|
|
103
|
+
*
|
|
104
|
+
* `decidedAt` is set to the entry's own `expiresAt`, not to `now`: the row left
|
|
105
|
+
* `pending` at its deadline, which is when a reader who never ran a sweep would
|
|
106
|
+
* have seen it go. A swept row and an unswept one past the same deadline are then
|
|
107
|
+
* the same value, so a host cannot tell — and cannot come to depend on — whether
|
|
108
|
+
* the sweep has caught up.
|
|
109
|
+
*/
|
|
110
|
+
expireDue(now: string, scope: Scope, limit: number): Promise<{
|
|
111
|
+
expired: string[];
|
|
112
|
+
more: boolean;
|
|
113
|
+
}>;
|
|
114
|
+
/**
|
|
115
|
+
* Remove at most `limit` terminal entries whose terminal instant is before
|
|
116
|
+
* `policy.olderThan` (DK-4).
|
|
117
|
+
*
|
|
118
|
+
* An `approved` row that is still `unexecuted` is **never** eligible, however old.
|
|
119
|
+
* It is the only record that a write was authorised and has not yet happened, and
|
|
120
|
+
* the Docket is the sole record of approval authority (AZ-5) — ageing it out would
|
|
121
|
+
* delete the evidence and the outstanding work in one move.
|
|
122
|
+
*/
|
|
123
|
+
retention(policy: RetentionPolicy, scope: Scope, limit: number): Promise<{
|
|
124
|
+
removed: number;
|
|
125
|
+
more: boolean;
|
|
126
|
+
}>;
|
|
127
|
+
/** Remove everything belonging to `tenantId`, and nothing else (DK-4). */
|
|
128
|
+
purge(tenantId: string): Promise<{
|
|
129
|
+
removed: number;
|
|
130
|
+
}>;
|
|
131
|
+
/**
|
|
132
|
+
* Every entry in `scope`, in filing order, streamed (DK-4).
|
|
133
|
+
*
|
|
134
|
+
* The ids are snapshotted when iteration begins and each row is read as the
|
|
135
|
+
* consumer reaches it, so a concurrent transition is reflected and a concurrent
|
|
136
|
+
* retention pass cannot make the iterator yield a row that has been removed.
|
|
137
|
+
*/
|
|
138
|
+
export(scope: Scope): AsyncIterable<DocketEntry>;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The reference Session store: rehydration in the order DK-5 fixes.
|
|
142
|
+
*
|
|
143
|
+
* It is built **on** a {@link DocketStore} rather than beside one, and it uses only
|
|
144
|
+
* the public interface. That is deliberate: DK-5's order has to hold for any store a
|
|
145
|
+
* host plugs in, so the reference implementation proves the `DocketStore` contract
|
|
146
|
+
* is sufficient to produce it. A session store that reached into the in-memory
|
|
147
|
+
* store's internals would prove nothing.
|
|
148
|
+
*/
|
|
149
|
+
export declare class InMemorySessionStore implements SessionStore {
|
|
150
|
+
#private;
|
|
151
|
+
constructor(docket: DocketStore);
|
|
152
|
+
/**
|
|
153
|
+
* One page of: everything that reads `pending`, then everything `approved` and
|
|
154
|
+
* `unexecuted`, each in filing order (DK-5).
|
|
155
|
+
*
|
|
156
|
+
* A page boundary that falls inside the first group resumes there; one that drains
|
|
157
|
+
* it resumes at the start of the second. A reconnecting client therefore sees what
|
|
158
|
+
* still needs a decision before what still needs execution, however small its page
|
|
159
|
+
* size.
|
|
160
|
+
*/
|
|
161
|
+
rehydrate(scope: Scope, page: Page): Promise<PageResult<DocketEntry>>;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/docket/memory.ts"],"names":[],"mappings":"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"}
|