@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,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Docket store contract — what a store must do, stated so that the in-memory
|
|
3
|
+
* reference store and a production store are held to the same fixtures.
|
|
4
|
+
*
|
|
5
|
+
* **Rules served: DK-1** (idempotent filing, the guarded compare-and-set, expiry as
|
|
6
|
+
* queryable state, preserved amendments on a late decision, lineage), **DK-3** (a
|
|
7
|
+
* bounded, paged, host-scheduled sweep), **DK-4** (retention, purge and export are
|
|
8
|
+
* hooks the host drives, and a row reads forward), **DK-5** (rehydration order),
|
|
9
|
+
* **AZ-2** (every operation is tenant-scoped), **RT-2** (no unbounded operation).
|
|
10
|
+
*
|
|
11
|
+
* Three properties are worth reading the interface for.
|
|
12
|
+
*
|
|
13
|
+
* **Every operation is scoped.** There is no method that reaches a row by id alone.
|
|
14
|
+
* An entry id is unique *within a tenant*, and a lookup with the wrong tenant is not
|
|
15
|
+
* an error — it is a miss, indistinguishable from an id that does not exist.
|
|
16
|
+
* Anything else leaks the existence of another tenant's rows to whoever can guess an
|
|
17
|
+
* id, and AZ-2 exists because that check is the one hosts hand-roll and get wrong.
|
|
18
|
+
*
|
|
19
|
+
* **Every read applies expiry.** `get`, `listPending` and `rehydrate` report what a
|
|
20
|
+
* row *reads* as, not what it says: a `pending` entry past its deadline reads
|
|
21
|
+
* `expired` whether or not the host's sweep has run (DK-1). A store therefore needs
|
|
22
|
+
* to know the time, which is why it is built with a {@link Clock} rather than being
|
|
23
|
+
* handed an instant at every call — a store that took `now` as a parameter would let
|
|
24
|
+
* a caller answer the deadline question for it.
|
|
25
|
+
*
|
|
26
|
+
* **Every list is bounded.** No method returns "all of them". Lists are paged with
|
|
27
|
+
* an opaque cursor; the sweep and the retention hook take a limit and report whether
|
|
28
|
+
* more remain; export is an `AsyncIterable`. RT-2's envelope — a serverless isolate
|
|
29
|
+
* with no persistent process — has no room for a method that loads a tenant's
|
|
30
|
+
* Docket into memory.
|
|
31
|
+
*
|
|
32
|
+
* *Three divergences from the v0.1 design record, recorded deliberately.* The
|
|
33
|
+
* design's sketch of `transition` takes `(entryId, expected, patch)` and returns
|
|
34
|
+
* `entry | "lost-race" | "not-pending"`. First, every other method on the interface
|
|
35
|
+
* is scoped, and an unscoped transition would be the one door in the store through
|
|
36
|
+
* which a caller could move another tenant's row by guessing an id — exactly what
|
|
37
|
+
* AZ-2 closes; the signature here takes a {@link Scope} in the same position `get`
|
|
38
|
+
* does. Second, the sketch has no way to say *no such entry*: reporting a missing id
|
|
39
|
+
* as "not pending" would make a caller turn it into `decision-expired`, which is a
|
|
40
|
+
* different and misleading refusal, so {@link TransitionResult} carries a
|
|
41
|
+
* `"not-found"` arm as well. Third, the sketch's two refusal names describe the
|
|
42
|
+
* wrong halves: the arm it spells `"not-pending"` is the *expired* row, while the
|
|
43
|
+
* arm it spells `"lost-race"` is the row somebody else already decided — and a store
|
|
44
|
+
* implementer writing `UPDATE … WHERE status = 'pending'` will read "not pending" as
|
|
45
|
+
* the union of both. The names here say which is which: `"expired"` and
|
|
46
|
+
* `"already-decided"`.
|
|
47
|
+
*
|
|
48
|
+
* @packageDocumentation
|
|
49
|
+
*/
|
|
50
|
+
import type { Affidavit } from "../model/affidavit.js";
|
|
51
|
+
import type { AmendmentMap } from "../model/amendments.js";
|
|
52
|
+
import type { Attestation, DecisionRecord, DocketEntry, DocketStatus, ExecutionOutcome, PreservedAmendments } from "./entry.js";
|
|
53
|
+
/**
|
|
54
|
+
* What a store operation is allowed to see.
|
|
55
|
+
*
|
|
56
|
+
* `tenantId` is mandatory: there is no cross-tenant read. `conversationId` narrows
|
|
57
|
+
* further, which is what a session surface asks for when it rehydrates one
|
|
58
|
+
* conversation rather than a whole tenant's Docket.
|
|
59
|
+
*/
|
|
60
|
+
export interface Scope {
|
|
61
|
+
/** The tenant. Mandatory — nothing in this interface reads across tenants. */
|
|
62
|
+
readonly tenantId: string;
|
|
63
|
+
/** One conversation within the tenant, when the caller wants only that (GT-2). */
|
|
64
|
+
readonly conversationId?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Where to continue and how much to take.
|
|
68
|
+
*
|
|
69
|
+
* `cursor` is **opaque**: it is produced by a store, understood only by the same
|
|
70
|
+
* store, and bound to the list that produced it — feeding a `listPending` cursor to
|
|
71
|
+
* `export` is a caller error, not a silently different answer.
|
|
72
|
+
*/
|
|
73
|
+
export interface Page {
|
|
74
|
+
/** The `cursor` from the previous {@link PageResult}, or absent/`null` to start. */
|
|
75
|
+
readonly cursor?: string | null;
|
|
76
|
+
/** How many entries to return at most. */
|
|
77
|
+
readonly limit: number;
|
|
78
|
+
}
|
|
79
|
+
/** One page of a list. */
|
|
80
|
+
export interface PageResult<T> {
|
|
81
|
+
/** The entries in this page, in filing order. */
|
|
82
|
+
readonly items: readonly T[];
|
|
83
|
+
/** Pass back as {@link Page.cursor} to continue, or `null` when the list is drained. */
|
|
84
|
+
readonly cursor: string | null;
|
|
85
|
+
/** Whether another page exists. `false` when `cursor` is `null`. */
|
|
86
|
+
readonly more: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* What the host's retention job is allowed to remove (DK-4).
|
|
90
|
+
*
|
|
91
|
+
* Retention is a hook rather than a policy the framework holds an opinion about:
|
|
92
|
+
* how long an approval record must be kept is a legal question with a different
|
|
93
|
+
* answer in every jurisdiction the gate runs in, and a default would be wrong
|
|
94
|
+
* somewhere important.
|
|
95
|
+
*/
|
|
96
|
+
export interface RetentionPolicy {
|
|
97
|
+
/**
|
|
98
|
+
* Remove terminal entries whose terminal instant is strictly before this ISO 8601
|
|
99
|
+
* instant.
|
|
100
|
+
*/
|
|
101
|
+
readonly olderThan: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* What a transition out of `pending` writes.
|
|
105
|
+
*
|
|
106
|
+
* The patch is exactly the set of later facts a decision produces, and nothing
|
|
107
|
+
* else: it cannot touch the requirement, the deadline, the filing instant or the
|
|
108
|
+
* entry's own id. That is DK-4's read-forward property in the type — a row
|
|
109
|
+
* accumulates facts, it is never rewritten.
|
|
110
|
+
*
|
|
111
|
+
* {@link TransitionPatch.amendedAffidavit} is the one apparent exception, and it is
|
|
112
|
+
* not one: it is written *beside* the proposal, never over it. The row keeps
|
|
113
|
+
* `affidavit` as the agent proposed it and gains `amendedAffidavit` as the state the
|
|
114
|
+
* approval accepted, produced by `applyAmendments` from the row's own. That operation
|
|
115
|
+
* adds a `UserStated` tag carrying a `reviewer-act` binding on top of each amended
|
|
116
|
+
* field's chain and leaves the displaced tag in the chain's history (PV-2), so a
|
|
117
|
+
* reader can still see what the machine proposed under what the reviewer decided. A
|
|
118
|
+
* store cannot check that, so the rule is stated here and enforced by the gate, which
|
|
119
|
+
* is the only caller that builds one.
|
|
120
|
+
*
|
|
121
|
+
* `supersedes` is absent on purpose: it is fixed when an entry is filed and names
|
|
122
|
+
* the entry this one *replaces*, which cannot change afterwards. `supersededBy` is
|
|
123
|
+
* here because a caller may want to record a successor in the same write that closes
|
|
124
|
+
* a row, though the ordinary path writes it with
|
|
125
|
+
* {@link DocketStore.recordSupersession} on an already-terminal entry.
|
|
126
|
+
*/
|
|
127
|
+
export interface TransitionPatch {
|
|
128
|
+
/** The state the entry moves to. Never back to `pending`. */
|
|
129
|
+
readonly status: Exclude<DocketStatus, "pending">;
|
|
130
|
+
/**
|
|
131
|
+
* The execution outcome. Required to be `"unexecuted"` (the default) when `status`
|
|
132
|
+
* is `"approved"`, and `null` otherwise — a store refuses a patch that contradicts
|
|
133
|
+
* its own status (DK-1).
|
|
134
|
+
*/
|
|
135
|
+
readonly execution?: ExecutionOutcome | null;
|
|
136
|
+
/** What the reviewer chose and why. Absent for a sweep, which nobody decided. */
|
|
137
|
+
readonly decision?: DecisionRecord | null;
|
|
138
|
+
/**
|
|
139
|
+
* The amendments the approval accepted. DK-2 holds inside the map: a `null` value
|
|
140
|
+
* clears the field, an absent key leaves it untouched.
|
|
141
|
+
*/
|
|
142
|
+
readonly amendments?: AmendmentMap | null;
|
|
143
|
+
/**
|
|
144
|
+
* The accepted state an approval's amendments produced (AF-4).
|
|
145
|
+
*
|
|
146
|
+
* Absent leaves the row carrying its proposal alone, which is what a rejection, a
|
|
147
|
+
* sweep and an unamended approval all want. Present, it must be the result of
|
|
148
|
+
* applying {@link TransitionPatch.amendments} to *this row's* `affidavit` — the
|
|
149
|
+
* three confidence numbers recomputed over the amended fields, and each amended
|
|
150
|
+
* field's provenance the reviewer's act with the machine's tag preserved beneath
|
|
151
|
+
* it. It is written beside `affidavit`, which a store never overwrites (DK-4).
|
|
152
|
+
*/
|
|
153
|
+
readonly amendedAffidavit?: Affidavit;
|
|
154
|
+
/** Who agreed (AZ-1). A decision without one is a decision nobody can be held to. */
|
|
155
|
+
readonly attestation?: Attestation | null;
|
|
156
|
+
/** When the row left `pending`. Defaults to the store's clock reading. */
|
|
157
|
+
readonly decidedAt?: string | null;
|
|
158
|
+
/** What the executor reported, when the caller already knows. */
|
|
159
|
+
readonly executionDetail?: string | null;
|
|
160
|
+
/** The successor link, for a caller closing and superseding a row in one write. */
|
|
161
|
+
readonly lineage?: {
|
|
162
|
+
readonly supersededBy?: string | null;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* What a guarded compare-and-set returns.
|
|
167
|
+
*
|
|
168
|
+
* The two refusals are distinct because they mean different things to a caller and
|
|
169
|
+
* map to different error codes, and each is named after the state it describes.
|
|
170
|
+
* `"already-decided"` means **someone else decided this entry** — the row is
|
|
171
|
+
* `approved` or `rejected`, a second decision arrived, and DK-1 requires it be
|
|
172
|
+
* refused rather than applied twice or silently overwritten. `"expired"` means **the
|
|
173
|
+
* row passed its deadline**, whether or not a sweep has recorded that yet. A caller
|
|
174
|
+
* turns the first into `decision-lost-race` and the second into `decision-expired`,
|
|
175
|
+
* and the second is the one that must also preserve the amendments the late decision
|
|
176
|
+
* carried ({@link DocketStore.preserveAmendments}).
|
|
177
|
+
*
|
|
178
|
+
* The names are part of the contract, not an implementation detail: the in-memory
|
|
179
|
+
* store is the reference a Postgres store earns the name by passing the same fixtures
|
|
180
|
+
* as, and a name that describes the *other* arm's state — "not pending", which is
|
|
181
|
+
* true of both — is one a second implementer will get backwards.
|
|
182
|
+
*
|
|
183
|
+
* Between them the two cover every state a row can be in that is not `pending`, so
|
|
184
|
+
* a caller never has to re-read to find out which refusal it got. `"not-found"` is
|
|
185
|
+
* the third answer: no entry with that id is visible in the scope — which, for a
|
|
186
|
+
* caller in the wrong tenant, is the only answer they get (AZ-2).
|
|
187
|
+
*/
|
|
188
|
+
export type TransitionResult = DocketEntry | "not-found" | "already-decided" | "expired";
|
|
189
|
+
/**
|
|
190
|
+
* The act a refused late decision was made by: its own instant and its own principal
|
|
191
|
+
* (DK-1, PV-2).
|
|
192
|
+
*
|
|
193
|
+
* The same two facts {@link PreservedAmendments} carries, named separately because
|
|
194
|
+
* this is what a caller passes in and that is what the row holds.
|
|
195
|
+
*/
|
|
196
|
+
export type PreservedAct = Omit<PreservedAmendments, "amendments">;
|
|
197
|
+
/** What {@link DocketStore.preserveAmendments} returns. */
|
|
198
|
+
export type PreserveAmendmentsResult = DocketEntry | "not-found" | "not-expired";
|
|
199
|
+
/**
|
|
200
|
+
* What {@link DocketStore.recordExecution} returns.
|
|
201
|
+
*
|
|
202
|
+
* `"execution-already-recorded"` is the execution transition's half of DK-1's
|
|
203
|
+
* guarded compare-and-set: the row already carries an outcome, so this report is
|
|
204
|
+
* refused rather than written over the first one. It is a different answer from
|
|
205
|
+
* `"not-approved"` — that row was never approved and has no authorised write behind
|
|
206
|
+
* it at all, while this one is approved and already has its outcome on the record.
|
|
207
|
+
*/
|
|
208
|
+
export type RecordExecutionResult = DocketEntry | "not-found" | "not-approved" | "execution-already-recorded";
|
|
209
|
+
/** What {@link DocketStore.recordSupersession} returns. */
|
|
210
|
+
export type RecordSupersessionResult = DocketEntry | "not-found" | "not-terminal";
|
|
211
|
+
/**
|
|
212
|
+
* The durable record of every proposed write and everything that happened to it.
|
|
213
|
+
*
|
|
214
|
+
* A store is the only thing in the framework that remembers, so it is also the only
|
|
215
|
+
* place the DK rules can be enforced rather than merely documented: the
|
|
216
|
+
* idempotent file, the guarded transition, the deadline applied on read, the bounded
|
|
217
|
+
* sweep and the append-only later facts are all properties of *this* interface. A
|
|
218
|
+
* host that swaps the in-memory reference store for Postgres inherits them by
|
|
219
|
+
* passing the same fixtures, not by re-reading the rulebook.
|
|
220
|
+
*/
|
|
221
|
+
export interface DocketStore {
|
|
222
|
+
/**
|
|
223
|
+
* File `entry`, or return the one already filed under its id (DK-1).
|
|
224
|
+
*
|
|
225
|
+
* Idempotent by `entryId` within the tenant, and idempotent in the strong sense:
|
|
226
|
+
* a re-file is **never** an error and **never** overwrites. It returns the stored
|
|
227
|
+
* entry as it stands, `created: false`, keeping its **existing** `expiresAt` — a
|
|
228
|
+
* re-file that refreshed the deadline would let a retrying agent hold a card open
|
|
229
|
+
* indefinitely, which is the .NET behaviour GT-4 corrects.
|
|
230
|
+
*/
|
|
231
|
+
file(entry: DocketEntry): Promise<{
|
|
232
|
+
entry: DocketEntry;
|
|
233
|
+
created: boolean;
|
|
234
|
+
}>;
|
|
235
|
+
/**
|
|
236
|
+
* The entry, with expiry applied (DK-1), or `null`.
|
|
237
|
+
*
|
|
238
|
+
* `null` covers both "no such id" and "not in this scope". A caller outside the
|
|
239
|
+
* tenant learns nothing about whether the id exists.
|
|
240
|
+
*/
|
|
241
|
+
get(entryId: string, scope: Scope): Promise<DocketEntry | null>;
|
|
242
|
+
/**
|
|
243
|
+
* Move an entry out of `pending`, if it is still `pending` (DK-1).
|
|
244
|
+
*
|
|
245
|
+
* A guarded compare-and-set: the read of the current state and the write of the
|
|
246
|
+
* new one happen with no interleaving point between them, so of two decisions that
|
|
247
|
+
* race, exactly one is applied and the other is refused. `expected` is `"pending"`
|
|
248
|
+
* and only `"pending"` — nothing else in the state machine has a transition out of
|
|
249
|
+
* it.
|
|
250
|
+
*/
|
|
251
|
+
transition(entryId: string, scope: Scope, expected: "pending", patch: TransitionPatch): Promise<TransitionResult>;
|
|
252
|
+
/**
|
|
253
|
+
* Record the amendments a decision carried after the entry had already expired, so
|
|
254
|
+
* a resubmission can prefill them (DK-1).
|
|
255
|
+
*
|
|
256
|
+
* This is the one write that applies to a row the transition guard refused, and it
|
|
257
|
+
* is a separate method for that reason: folding it into `transition` would make a
|
|
258
|
+
* refused compare-and-set write to the row, and "applied once or not at all" is
|
|
259
|
+
* exactly the property DK-1's fixture asserts. Under DK-4 this is an appended
|
|
260
|
+
* later fact on a terminal row, not an edit of a recorded decision — it touches
|
|
261
|
+
* `amendments` and nothing else, never `status`, `decision` or `attestation`.
|
|
262
|
+
*
|
|
263
|
+
* `act` is the refused decision's **own** instant and principal, not the store's
|
|
264
|
+
* clock and not the row's deadline. A resubmission prefills these values as a
|
|
265
|
+
* person's correction and binds each prefilled field's tag to that act (PV-2), so
|
|
266
|
+
* a record that dated them to the sweep would place the correction at a moment
|
|
267
|
+
* nobody typed anything.
|
|
268
|
+
*
|
|
269
|
+
* Returns `"not-expired"` when the entry does not read `expired`; a caller that
|
|
270
|
+
* gets it has a bug, because a decision that was not refused as expired has no
|
|
271
|
+
* amendments to preserve.
|
|
272
|
+
*/
|
|
273
|
+
preserveAmendments(entryId: string, scope: Scope, amendments: AmendmentMap, act: PreservedAct): Promise<PreserveAmendmentsResult>;
|
|
274
|
+
/**
|
|
275
|
+
* Record what the host's executor reported for an approved entry, **once**
|
|
276
|
+
* (DK-1, DK-4).
|
|
277
|
+
*
|
|
278
|
+
* `status` stays `approved`; only `execution` and `executionDetail` move. The
|
|
279
|
+
* framework never performs the write (AZ-7) — it records what the host says
|
|
280
|
+
* happened, so that an approved-but-failed write is distinguishable from an
|
|
281
|
+
* approved-and-committed one on the row.
|
|
282
|
+
*
|
|
283
|
+
* **A guarded compare-and-set, exactly like {@link DocketStore.transition}.**
|
|
284
|
+
* `expected` is `"unexecuted"` and only `"unexecuted"`: the execution transition
|
|
285
|
+
* runs once, out of the state a row is approved in, and a second report is refused
|
|
286
|
+
* with `"execution-already-recorded"` rather than written on top. Without the
|
|
287
|
+
* guard an `executed` row could be flipped to `failed` by a later caller — an edit
|
|
288
|
+
* in place of a recorded fact, which DK-4 forbids, leaving an approved-and-
|
|
289
|
+
* committed write indistinguishable from an approved-but-failed one, which DK-1
|
|
290
|
+
* forbids.
|
|
291
|
+
*
|
|
292
|
+
* The consequence for a host is one sentence: **a host that retries a write
|
|
293
|
+
* reports once, when it knows the outcome.** AZ-5 already says an outbox is a
|
|
294
|
+
* retry of an already-attested write and never a second authorization path; this
|
|
295
|
+
* is the same statement about the record — the retries are the host's business,
|
|
296
|
+
* the outcome is the Docket's.
|
|
297
|
+
*
|
|
298
|
+
* Returns `"not-approved"` for any row that is not `approved`, and
|
|
299
|
+
* `"execution-already-recorded"` for an approved row whose `execution` has already
|
|
300
|
+
* moved off `"unexecuted"`.
|
|
301
|
+
*/
|
|
302
|
+
recordExecution(entryId: string, scope: Scope, outcome: Exclude<ExecutionOutcome, "unexecuted">, detail: string | null, expected: "unexecuted"): Promise<RecordExecutionResult>;
|
|
303
|
+
/**
|
|
304
|
+
* Record that a terminal entry has been resubmitted as `supersededBy` (DK-1).
|
|
305
|
+
*
|
|
306
|
+
* The superseded entry keeps its terminal state; only the successor link is added.
|
|
307
|
+
* A resubmission is a new entry, never a reopened one, which is what lets the
|
|
308
|
+
* history read forward (DK-4).
|
|
309
|
+
*
|
|
310
|
+
* Returns `"not-terminal"` for a row that still reads `pending`.
|
|
311
|
+
*/
|
|
312
|
+
recordSupersession(entryId: string, scope: Scope, supersededBy: string): Promise<RecordSupersessionResult>;
|
|
313
|
+
/** Entries that read `pending` right now, in filing order, paged. */
|
|
314
|
+
listPending(scope: Scope, page: Page): Promise<PageResult<DocketEntry>>;
|
|
315
|
+
/**
|
|
316
|
+
* Entries that are `approved` and still `unexecuted`, in filing order, paged.
|
|
317
|
+
*
|
|
318
|
+
* The host's executor reads this: an approved write nobody has reported on is work
|
|
319
|
+
* outstanding, and after a restart it is the only record that the work exists.
|
|
320
|
+
*/
|
|
321
|
+
listApprovedUnexecuted(scope: Scope, page: Page): Promise<PageResult<DocketEntry>>;
|
|
322
|
+
/**
|
|
323
|
+
* Mark at most `limit` entries that are due at `now` as `expired`, in filing order
|
|
324
|
+
* (DK-3).
|
|
325
|
+
*
|
|
326
|
+
* The host schedules this; the core owns no timer. The sweep makes the state
|
|
327
|
+
* durable and gives the host a list to notify on — it does not *cause* expiry,
|
|
328
|
+
* which every read already applies (DK-1). `more` says whether another call would
|
|
329
|
+
* find more, so a host drains the queue in bounded steps rather than in one
|
|
330
|
+
* unbounded pass.
|
|
331
|
+
*/
|
|
332
|
+
expireDue(now: string, scope: Scope, limit: number): Promise<{
|
|
333
|
+
expired: string[];
|
|
334
|
+
more: boolean;
|
|
335
|
+
}>;
|
|
336
|
+
/**
|
|
337
|
+
* Remove at most `limit` terminal entries older than `policy.olderThan` (DK-4).
|
|
338
|
+
*
|
|
339
|
+
* Each call shrinks the eligible set, so a host drains retention by calling until
|
|
340
|
+
* `more` is `false` — the same bounded-step shape as the sweep, and for the same
|
|
341
|
+
* RT-2 reason.
|
|
342
|
+
*/
|
|
343
|
+
retention(policy: RetentionPolicy, scope: Scope, limit: number): Promise<{
|
|
344
|
+
removed: number;
|
|
345
|
+
more: boolean;
|
|
346
|
+
}>;
|
|
347
|
+
/**
|
|
348
|
+
* Remove everything belonging to `tenantId` (DK-4).
|
|
349
|
+
*
|
|
350
|
+
* Unbounded by design and by necessity: a tenant asking for their data to be
|
|
351
|
+
* deleted is asking for all of it, and a partial purge is not a purge. It is the
|
|
352
|
+
* one operation that is not paged, and the only one that takes a tenant id rather
|
|
353
|
+
* than a {@link Scope} — there is no such thing as purging half a tenant.
|
|
354
|
+
*/
|
|
355
|
+
purge(tenantId: string): Promise<{
|
|
356
|
+
removed: number;
|
|
357
|
+
}>;
|
|
358
|
+
/**
|
|
359
|
+
* Every entry in `scope`, in filing order, streamed (DK-4).
|
|
360
|
+
*
|
|
361
|
+
* An `AsyncIterable` rather than an array so a large Docket never has to fit in
|
|
362
|
+
* memory (RT-2). The portable document shape a host would export *to* is reserved
|
|
363
|
+
* for protocol v0.2; this yields the rows.
|
|
364
|
+
*/
|
|
365
|
+
export(scope: Scope): AsyncIterable<DocketEntry>;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* What a reconnecting client needs to see, in the order DK-5 fixes.
|
|
369
|
+
*
|
|
370
|
+
* **`pending` entries first, then `approved` and `unexecuted`**, each in filing
|
|
371
|
+
* order. The order is a rule rather than a preference because the two groups ask
|
|
372
|
+
* different things of the person reconnecting: the first still needs a decision, the
|
|
373
|
+
* second still needs execution, and a client that showed them interleaved would put
|
|
374
|
+
* work that is already agreed in front of work that is still blocked on the reader.
|
|
375
|
+
*/
|
|
376
|
+
export interface SessionStore {
|
|
377
|
+
/**
|
|
378
|
+
* One page of the rehydration sequence: everything that reads `pending`, then
|
|
379
|
+
* everything `approved` and `unexecuted`, each in filing order.
|
|
380
|
+
*
|
|
381
|
+
* Paged like every other list (RT-2). The cursor carries the position *within the
|
|
382
|
+
* sequence*, so a page boundary that falls between the two groups resumes at the
|
|
383
|
+
* start of the second rather than restarting the first.
|
|
384
|
+
*/
|
|
385
|
+
rehydrate(scope: Scope, page: Page): Promise<PageResult<DocketEntry>>;
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/docket/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Docket store contract — what a store must do, stated so that the in-memory
|
|
3
|
+
* reference store and a production store are held to the same fixtures.
|
|
4
|
+
*
|
|
5
|
+
* **Rules served: DK-1** (idempotent filing, the guarded compare-and-set, expiry as
|
|
6
|
+
* queryable state, preserved amendments on a late decision, lineage), **DK-3** (a
|
|
7
|
+
* bounded, paged, host-scheduled sweep), **DK-4** (retention, purge and export are
|
|
8
|
+
* hooks the host drives, and a row reads forward), **DK-5** (rehydration order),
|
|
9
|
+
* **AZ-2** (every operation is tenant-scoped), **RT-2** (no unbounded operation).
|
|
10
|
+
*
|
|
11
|
+
* Three properties are worth reading the interface for.
|
|
12
|
+
*
|
|
13
|
+
* **Every operation is scoped.** There is no method that reaches a row by id alone.
|
|
14
|
+
* An entry id is unique *within a tenant*, and a lookup with the wrong tenant is not
|
|
15
|
+
* an error — it is a miss, indistinguishable from an id that does not exist.
|
|
16
|
+
* Anything else leaks the existence of another tenant's rows to whoever can guess an
|
|
17
|
+
* id, and AZ-2 exists because that check is the one hosts hand-roll and get wrong.
|
|
18
|
+
*
|
|
19
|
+
* **Every read applies expiry.** `get`, `listPending` and `rehydrate` report what a
|
|
20
|
+
* row *reads* as, not what it says: a `pending` entry past its deadline reads
|
|
21
|
+
* `expired` whether or not the host's sweep has run (DK-1). A store therefore needs
|
|
22
|
+
* to know the time, which is why it is built with a {@link Clock} rather than being
|
|
23
|
+
* handed an instant at every call — a store that took `now` as a parameter would let
|
|
24
|
+
* a caller answer the deadline question for it.
|
|
25
|
+
*
|
|
26
|
+
* **Every list is bounded.** No method returns "all of them". Lists are paged with
|
|
27
|
+
* an opaque cursor; the sweep and the retention hook take a limit and report whether
|
|
28
|
+
* more remain; export is an `AsyncIterable`. RT-2's envelope — a serverless isolate
|
|
29
|
+
* with no persistent process — has no room for a method that loads a tenant's
|
|
30
|
+
* Docket into memory.
|
|
31
|
+
*
|
|
32
|
+
* *Three divergences from the v0.1 design record, recorded deliberately.* The
|
|
33
|
+
* design's sketch of `transition` takes `(entryId, expected, patch)` and returns
|
|
34
|
+
* `entry | "lost-race" | "not-pending"`. First, every other method on the interface
|
|
35
|
+
* is scoped, and an unscoped transition would be the one door in the store through
|
|
36
|
+
* which a caller could move another tenant's row by guessing an id — exactly what
|
|
37
|
+
* AZ-2 closes; the signature here takes a {@link Scope} in the same position `get`
|
|
38
|
+
* does. Second, the sketch has no way to say *no such entry*: reporting a missing id
|
|
39
|
+
* as "not pending" would make a caller turn it into `decision-expired`, which is a
|
|
40
|
+
* different and misleading refusal, so {@link TransitionResult} carries a
|
|
41
|
+
* `"not-found"` arm as well. Third, the sketch's two refusal names describe the
|
|
42
|
+
* wrong halves: the arm it spells `"not-pending"` is the *expired* row, while the
|
|
43
|
+
* arm it spells `"lost-race"` is the row somebody else already decided — and a store
|
|
44
|
+
* implementer writing `UPDATE … WHERE status = 'pending'` will read "not pending" as
|
|
45
|
+
* the union of both. The names here say which is which: `"expired"` and
|
|
46
|
+
* `"already-decided"`.
|
|
47
|
+
*
|
|
48
|
+
* @packageDocumentation
|
|
49
|
+
*/
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/docket/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error the gate throws, and the closed set of codes it throws with.
|
|
3
|
+
*
|
|
4
|
+
* **Rules served: CV-1** (hard-fail at wire-up; there is no disable switch),
|
|
5
|
+
* **AZ-4** (a requirement the implementation does not run is refused, never
|
|
6
|
+
* silently degraded).
|
|
7
|
+
*
|
|
8
|
+
* CV-1 in one sentence: *a misconfiguration the framework can detect fails at
|
|
9
|
+
* wire-up with a stated error, and no option turns the gate off for a tool it
|
|
10
|
+
* covers.* Every such failure is an {@link AffiantError} carrying one
|
|
11
|
+
* {@link ErrorCode} — a machine-readable reason a host can branch on and a
|
|
12
|
+
* conformance fixture can assert, rather than a message string that drifts.
|
|
13
|
+
*
|
|
14
|
+
* The codes are a closed union on purpose. A refusal that cannot be named here is
|
|
15
|
+
* a refusal no fixture can pin, and a host that cannot distinguish "you wired this
|
|
16
|
+
* wrong" from "the reviewer rejected it" will paper over the first.
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Every reason the gate refuses, keyed by itself.
|
|
22
|
+
*
|
|
23
|
+
* The key and the value are the same string because the string *is* the API: it is
|
|
24
|
+
* what a fixture asserts and what a host branches on, so there is exactly one
|
|
25
|
+
* spelling to remember. Read a code as `ErrorCode["substance-refused"]`, or write
|
|
26
|
+
* the literal — both type-check.
|
|
27
|
+
*
|
|
28
|
+
* Three codes are marked **provisional**: they are named by the v0.1 design but the
|
|
29
|
+
* protocol rulebook does not yet carry an `ErrorCode` registry. When it does, those
|
|
30
|
+
* three are the ones that may be renamed to match it; the other seven describe
|
|
31
|
+
* behaviour the rulebook already fixes.
|
|
32
|
+
*/
|
|
33
|
+
export declare const ErrorCode: {
|
|
34
|
+
/**
|
|
35
|
+
* A requirement this implementation recognises but does not run — a `MultiParty`
|
|
36
|
+
* approval, a `ReferralRequired` referral — reached the pipeline. The entry is
|
|
37
|
+
* filed `pending` and marked blocked; every decision on it is refused. Never
|
|
38
|
+
* degraded to a weaker requirement (AZ-4).
|
|
39
|
+
*
|
|
40
|
+
* **Provisional** until the protocol's `ErrorCode` registry lands.
|
|
41
|
+
*/
|
|
42
|
+
readonly "requirement-not-implemented": "requirement-not-implemented";
|
|
43
|
+
/**
|
|
44
|
+
* A tool the gate must cover cannot be intercepted — it is write-capable with no
|
|
45
|
+
* `execute` to replace, it is executed by the model provider, or it is a hosted
|
|
46
|
+
* MCP tool. Raised at wire-up (CV-4, CV-1), or carried on a proposal from a tool
|
|
47
|
+
* the host explicitly declared uncovered.
|
|
48
|
+
*
|
|
49
|
+
* **Provisional** until the protocol's `ErrorCode` registry lands.
|
|
50
|
+
*/
|
|
51
|
+
readonly "coverage-refused": "coverage-refused";
|
|
52
|
+
/**
|
|
53
|
+
* A proposal reached the substance gate with nothing to swear to: no field
|
|
54
|
+
* carrying provenance other than `Empty`, or a non-empty value sitting under
|
|
55
|
+
* `Empty` provenance. Refused before anything is filed (GT-3).
|
|
56
|
+
*/
|
|
57
|
+
readonly "substance-refused": "substance-refused";
|
|
58
|
+
/**
|
|
59
|
+
* A decision was refused on identity grounds: the context carried no resolved
|
|
60
|
+
* principal, the entry belongs to another tenant, or the host's authorization
|
|
61
|
+
* port said no. Refused before the store is touched (AZ-2).
|
|
62
|
+
*/
|
|
63
|
+
readonly "decision-unauthorized": "decision-unauthorized";
|
|
64
|
+
/** A decision was made on an entry that is no longer `pending` (DK-1). */
|
|
65
|
+
readonly "decision-not-pending": "decision-not-pending";
|
|
66
|
+
/**
|
|
67
|
+
* A decision was made on an entry that has passed its expiry. The entry reads
|
|
68
|
+
* `expired` whether or not the host's sweep has run; the decision's amendments
|
|
69
|
+
* are preserved on the row for a resubmission (DK-1).
|
|
70
|
+
*/
|
|
71
|
+
readonly "decision-expired": "decision-expired";
|
|
72
|
+
/**
|
|
73
|
+
* Two decisions raced for the same entry and this one lost the compare-and-set.
|
|
74
|
+
* A transition is applied once or not at all, never twice (DK-1).
|
|
75
|
+
*/
|
|
76
|
+
readonly "decision-lost-race": "decision-lost-race";
|
|
77
|
+
/**
|
|
78
|
+
* The gate was built wrong in a way it can detect: no store, no authorization
|
|
79
|
+
* port, a policy declaring a risk threshold with no scorer to compare against.
|
|
80
|
+
* Thrown from `createGate`, not on the first request (CV-1).
|
|
81
|
+
*/
|
|
82
|
+
readonly "wireup-invalid": "wireup-invalid";
|
|
83
|
+
/** No entry with that id is visible in the given scope (DK-1). */
|
|
84
|
+
readonly "entry-not-found": "entry-not-found";
|
|
85
|
+
/**
|
|
86
|
+
* An execution outcome was reported against a row that already carries one. The
|
|
87
|
+
* first report stands and the row is untouched: a decision, once recorded, is
|
|
88
|
+
* never edited in place, and an approved-but-failed write must stay
|
|
89
|
+
* distinguishable from an approved-and-committed one (DK-4, DK-1).
|
|
90
|
+
*
|
|
91
|
+
* A host that retries a write reports **once**, when it knows the outcome (AZ-5:
|
|
92
|
+
* an outbox is a retry of an already-attested write, not a second authorization
|
|
93
|
+
* path, and not a second fact about what happened).
|
|
94
|
+
*
|
|
95
|
+
* **Provisional** until the protocol's `ErrorCode` registry lands.
|
|
96
|
+
*/
|
|
97
|
+
readonly "execution-already-recorded": "execution-already-recorded";
|
|
98
|
+
};
|
|
99
|
+
/** One of the reasons in {@link ErrorCode}. */
|
|
100
|
+
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
101
|
+
/**
|
|
102
|
+
* Every {@link ErrorCode} value, in registry order. Pinned as data so a runtime
|
|
103
|
+
* check and a fixture can use the same list the type does.
|
|
104
|
+
*
|
|
105
|
+
* **The order only ever grows at the end.** A code is added by appending it, never
|
|
106
|
+
* by inserting one among the codes that already shipped: the list is what a host's
|
|
107
|
+
* exhaustiveness check and a parity manifest read, and a reordering would look like
|
|
108
|
+
* a rename to both.
|
|
109
|
+
*/
|
|
110
|
+
export declare const ERROR_CODES: readonly ["requirement-not-implemented", "coverage-refused", "substance-refused", "decision-unauthorized", "decision-not-pending", "decision-expired", "decision-lost-race", "wireup-invalid", "entry-not-found", "execution-already-recorded"];
|
|
111
|
+
/** Whether `value` is one of the codes in {@link ErrorCode}. */
|
|
112
|
+
export declare function isErrorCode(value: unknown): value is ErrorCode;
|
|
113
|
+
/**
|
|
114
|
+
* Structured context attached to an {@link AffiantError}: the entry id, the tool
|
|
115
|
+
* name, the policy that produced a verdict — whatever the throwing site can name.
|
|
116
|
+
*
|
|
117
|
+
* Values are `unknown` because the useful details differ per code. Callers narrow;
|
|
118
|
+
* loggers stringify. Never put a field value or an utterance in here — an error is
|
|
119
|
+
* not an audit record, and the audit record is the Affidavit.
|
|
120
|
+
*/
|
|
121
|
+
export interface AffiantErrorDetails {
|
|
122
|
+
readonly [key: string]: unknown;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The error every refusal in this package throws.
|
|
126
|
+
*
|
|
127
|
+
* The `code` is the contract; the `message` is for a human reading a log. A host
|
|
128
|
+
* that branches on the message is doing it wrong, and a fixture that asserts on
|
|
129
|
+
* the message is asserting on prose.
|
|
130
|
+
*/
|
|
131
|
+
export declare class AffiantError extends Error {
|
|
132
|
+
/** Why the gate refused. */
|
|
133
|
+
readonly code: ErrorCode;
|
|
134
|
+
/** Structured context for the refusal. `{}` when the throwing site supplied none. */
|
|
135
|
+
readonly details: AffiantErrorDetails;
|
|
136
|
+
/**
|
|
137
|
+
* @param code Why the gate refused.
|
|
138
|
+
* @param message A human-readable explanation. Defaults to the code itself.
|
|
139
|
+
* @param details Structured context for the refusal.
|
|
140
|
+
*/
|
|
141
|
+
constructor(code: ErrorCode, message?: string, details?: AffiantErrorDetails);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether `value` is an {@link AffiantError}.
|
|
145
|
+
*
|
|
146
|
+
* `instanceof` first, then a structural check: a host can end up with two copies of
|
|
147
|
+
* this package in one process (a bundler, two versions in a dependency tree), and a
|
|
148
|
+
* `catch` that spans that boundary still has to give a true answer. The structural
|
|
149
|
+
* arm is deliberately narrow — an `Error` named `AffiantError` carrying a `code`
|
|
150
|
+
* from {@link ERROR_CODES}.
|
|
151
|
+
*/
|
|
152
|
+
export declare function isAffiantError(value: unknown): value is AffiantError;
|
|
153
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS;IACpB;;;;;;;OAOG;;IAEH;;;;;;;OAOG;;IAEH;;;;OAIG;;IAEH;;;;OAIG;;IAEH,0EAA0E;;IAE1E;;;;OAIG;;IAEH;;;OAGG;;IAEH;;;;OAIG;;IAEH,kEAAkE;;IAElE;;;;;;;;;;;OAWG;;CAEK,CAAC;AAEX,+CAA+C;AAC/C,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,iPAWiB,CAAC;AAE1C,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IAEtC;;;;OAIG;gBACS,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB;CAM7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAOpE"}
|