@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.
Files changed (96) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +354 -0
  3. package/dist/context.d.ts +136 -0
  4. package/dist/context.d.ts.map +1 -0
  5. package/dist/context.js +30 -0
  6. package/dist/context.js.map +1 -0
  7. package/dist/docket/entry.d.ts +421 -0
  8. package/dist/docket/entry.d.ts.map +1 -0
  9. package/dist/docket/entry.js +155 -0
  10. package/dist/docket/entry.js.map +1 -0
  11. package/dist/docket/expiry.d.ts +82 -0
  12. package/dist/docket/expiry.d.ts.map +1 -0
  13. package/dist/docket/expiry.js +106 -0
  14. package/dist/docket/expiry.js.map +1 -0
  15. package/dist/docket/memory.d.ts +163 -0
  16. package/dist/docket/memory.d.ts.map +1 -0
  17. package/dist/docket/memory.js +528 -0
  18. package/dist/docket/memory.js.map +1 -0
  19. package/dist/docket/store.d.ts +387 -0
  20. package/dist/docket/store.d.ts.map +1 -0
  21. package/dist/docket/store.js +51 -0
  22. package/dist/docket/store.js.map +1 -0
  23. package/dist/errors.d.ts +153 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +164 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/gate/coverage.d.ts +152 -0
  28. package/dist/gate/coverage.d.ts.map +1 -0
  29. package/dist/gate/coverage.js +114 -0
  30. package/dist/gate/coverage.js.map +1 -0
  31. package/dist/gate/decide.d.ts +207 -0
  32. package/dist/gate/decide.d.ts.map +1 -0
  33. package/dist/gate/decide.js +559 -0
  34. package/dist/gate/decide.js.map +1 -0
  35. package/dist/gate/gate.d.ts +212 -0
  36. package/dist/gate/gate.d.ts.map +1 -0
  37. package/dist/gate/gate.js +175 -0
  38. package/dist/gate/gate.js.map +1 -0
  39. package/dist/gate/pipeline.d.ts +285 -0
  40. package/dist/gate/pipeline.d.ts.map +1 -0
  41. package/dist/gate/pipeline.js +515 -0
  42. package/dist/gate/pipeline.js.map +1 -0
  43. package/dist/gate/policy.d.ts +272 -0
  44. package/dist/gate/policy.d.ts.map +1 -0
  45. package/dist/gate/policy.js +396 -0
  46. package/dist/gate/policy.js.map +1 -0
  47. package/dist/gate/wrap.d.ts +107 -0
  48. package/dist/gate/wrap.d.ts.map +1 -0
  49. package/dist/gate/wrap.js +164 -0
  50. package/dist/gate/wrap.js.map +1 -0
  51. package/dist/index.d.ts +95 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +111 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/model/affidavit.d.ts +354 -0
  56. package/dist/model/affidavit.d.ts.map +1 -0
  57. package/dist/model/affidavit.js +417 -0
  58. package/dist/model/affidavit.js.map +1 -0
  59. package/dist/model/amendments.d.ts +160 -0
  60. package/dist/model/amendments.d.ts.map +1 -0
  61. package/dist/model/amendments.js +183 -0
  62. package/dist/model/amendments.js.map +1 -0
  63. package/dist/model/canonical.d.ts +311 -0
  64. package/dist/model/canonical.d.ts.map +1 -0
  65. package/dist/model/canonical.js +665 -0
  66. package/dist/model/canonical.js.map +1 -0
  67. package/dist/model/money.d.ts +127 -0
  68. package/dist/model/money.d.ts.map +1 -0
  69. package/dist/model/money.js +177 -0
  70. package/dist/model/money.js.map +1 -0
  71. package/dist/model/provenance.d.ts +315 -0
  72. package/dist/model/provenance.d.ts.map +1 -0
  73. package/dist/model/provenance.js +223 -0
  74. package/dist/model/provenance.js.map +1 -0
  75. package/dist/ports.d.ts +269 -0
  76. package/dist/ports.d.ts.map +1 -0
  77. package/dist/ports.js +34 -0
  78. package/dist/ports.js.map +1 -0
  79. package/dist/store-memory.d.ts +21 -0
  80. package/dist/store-memory.d.ts.map +1 -0
  81. package/dist/store-memory.js +20 -0
  82. package/dist/store-memory.js.map +1 -0
  83. package/dist/telemetry-keys.d.ts +65 -0
  84. package/dist/telemetry-keys.d.ts.map +1 -0
  85. package/dist/telemetry-keys.js +72 -0
  86. package/dist/telemetry-keys.js.map +1 -0
  87. package/dist/telemetry.d.ts +77 -0
  88. package/dist/telemetry.d.ts.map +1 -0
  89. package/dist/telemetry.js +43 -0
  90. package/dist/telemetry.js.map +1 -0
  91. package/dist/testing.d.ts +574 -0
  92. package/dist/testing.d.ts.map +1 -0
  93. package/dist/testing.js +1291 -0
  94. package/dist/testing.js.map +1 -0
  95. package/package.json +75 -0
  96. 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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"}