@makaio/framework 1.0.0-dev-1788258714339 → 1.0.0-dev-1788721567504
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/dist/.makaio-build.json +2 -2
- package/dist/attempt-record-codec-BORASgzn.mjs +1 -0
- package/dist/authority-state-bootstrap-eitx6Ukn.mjs +1 -0
- package/dist/bus/index.d.mts +19 -19
- package/dist/bus/index.mjs +1 -1
- package/dist/capabilities-DEvmD9VJ.mjs +1 -0
- package/dist/{context-resolution-CW4OtPrS.d.mts → context-resolution-CoKFKIqz.d.mts} +5 -5
- package/dist/contracts/adapter/index.d.mts +1 -1
- package/dist/contracts/artifact/index.d.mts +2 -2
- package/dist/contracts/canonical-model/index.d.mts +1 -1
- package/dist/contracts/capabilities/index.d.mts +2 -2
- package/dist/contracts/capabilities/index.mjs +1 -1
- package/dist/contracts/client/index.d.mts +1 -1
- package/dist/contracts/extension/index.d.mts +3 -3
- package/dist/contracts/harness/index.d.mts +1 -1
- package/dist/contracts/index.d.mts +618 -124
- package/dist/contracts/index.mjs +1 -1
- package/dist/contracts/materialization/index.d.mts +3 -3
- package/dist/contracts/session/index.d.mts +2 -2
- package/dist/contracts/shared/index.d.mts +1 -1
- package/dist/contracts/variant/index.d.mts +1 -1
- package/dist/contracts/worker/index.d.mts +2 -0
- package/dist/contracts/worker/index.mjs +1 -0
- package/dist/contracts/workflow/index.d.mts +2 -2
- package/dist/contracts/workflow/index.mjs +1 -1
- package/dist/{execution-attempt-repository-BJ2W6wwx.d.mts → execution-attempt-repository-CRdrgou3.d.mts} +869 -58
- package/dist/{index-jxPncqWZ2.d.mts → index--Ws52sWV2.d.mts} +4 -4
- package/dist/{index-OG402CBI.d.mts → index-37_b--BH.d.mts} +85 -85
- package/dist/{index-B-3I98nw.d.mts → index-AdcDBUnc.d.mts} +95 -95
- package/dist/{index-CMekqu_D.d.mts → index-BJdkVFX9.d.mts} +327 -63
- package/dist/{index-4KJ6IDE7.d.mts → index-BKIxdzIw.d.mts} +5 -5
- package/dist/{index-CPi0UpRh.d.mts → index-B_8bA2JW.d.mts} +1 -1
- package/dist/{index-CmwAJ8-5.d.mts → index-CFI6FxC7.d.mts} +1 -1
- package/dist/{index-BqlCDBfc.d.mts → index-CVcG4Q3X.d.mts} +5 -5
- package/dist/{index-DXqG0r3o.d.mts → index-CxwqnZpT.d.mts} +62 -62
- package/dist/{index-BkbnC65b.d.mts → index-CyWGSxrs.d.mts} +291 -291
- package/dist/{index-B9V3T4EQ.d.mts → index-D037TVsm.d.mts} +4 -4
- package/dist/{index-C146Eq_J.d.mts → index-D1wTWSLR.d.mts} +327 -327
- package/dist/{index-DWkFkvRn2.d.mts → index-D8D-yibS2.d.mts} +3 -3
- package/dist/{index-Bqk0pJBQ2.d.mts → index-DBD_oFEt2.d.mts} +1 -1
- package/dist/{index-DJ2KgJDJ2.d.mts → index-DV38-klA2.d.mts} +59 -83
- package/dist/{index-C3lu_fy5.d.mts → index-I5a37JCm.d.mts} +76 -76
- package/dist/{namespace-l-HG1rLF.d.mts → namespace-Bo94cenp.d.mts} +136 -136
- package/dist/{namespace-DknCutQZ.d.mts → namespace-CPm9dhi2.d.mts} +2 -2
- package/dist/{namespace-BNp7n_gu.d.mts → namespace-CsnrP8AD.d.mts} +3 -3
- package/dist/{namespace-BGRHcrmp.d.mts → namespace-CstawJgN.d.mts} +2 -2
- package/dist/{namespace-C06lJSuj.d.mts → namespace-K23m4b0n.d.mts} +6 -6
- package/dist/{package-BNFTUFLB.d.mts → package-DkZmsdCd.d.mts} +107 -30
- package/dist/package-UemD2NWn.mjs +7 -0
- package/dist/package.json +1 -1
- package/dist/runtime-node/index.d.mts +2 -2
- package/dist/runtime-node/index.mjs +24 -24
- package/dist/runtime-node/makaio-config.d.mts +2 -2
- package/dist/runtime-node/makaio-config.mjs +1 -1
- package/dist/runtime-node/workflow-execution-bus-access.mjs +1 -1
- package/dist/runtime-node/workflow-worker/index.d.mts +2 -2
- package/dist/runtime-node/workflow-worker/index.mjs +1 -1
- package/dist/runtime-node/workflow-worker/worker-entry.d.mts +49 -9
- package/dist/runtime-node/workflow-worker/worker-entry.mjs +1 -1
- package/dist/runtime-registration-client-BTtFpt_k.mjs +1 -0
- package/dist/{schemas-OWvBn7AH.d.mts → schemas-BCXvEWyF.d.mts} +4 -4
- package/dist/{schemas-Dd0po67F.d.mts → schemas-CJKHYluy.d.mts} +16 -16
- package/dist/{schemas-C1mxTfd7.d.mts → schemas-CenET9Cq.d.mts} +1 -1
- package/dist/{schemas-D7ScxLrf.d.mts → schemas-DTJtS14K.d.mts} +136 -136
- package/dist/services/adapter-runtime/index.d.mts +3 -3
- package/dist/services/adapter-runtime/namespace.d.mts +1 -1
- package/dist/services/adapter-runtime/schemas.d.mts +1 -1
- package/dist/services/adapter-subsystem/index.d.mts +2 -2
- package/dist/services/adapter-subsystem/namespace.d.mts +1 -1
- package/dist/services/agent-runtime/index.d.mts +2 -2
- package/dist/services/agent-runtime/namespace.d.mts +1 -1
- package/dist/services/agent-runtime/schemas.d.mts +1 -1
- package/dist/services/filesystem/namespace.d.mts +6 -6
- package/dist/services/filesystem/schemas.d.mts +3 -3
- package/dist/services/harness/index.d.mts +3 -3
- package/dist/services/index.d.mts +93 -93
- package/dist/services/provider-context/index.d.mts +1 -1
- package/dist/services/session/index.d.mts +2 -2
- package/dist/services/session/messages/namespace.d.mts +1 -1
- package/dist/services/settings/namespace.d.mts +12 -12
- package/dist/services/settings/storage/extension-configs/namespace.d.mts +3 -3
- package/dist/services/subagent-template/index.d.mts +2 -2
- package/dist/services/subagent-template/namespace.d.mts +1 -1
- package/dist/services/subagent-template/schemas.d.mts +1 -1
- package/dist/{transition-DXmCUq6w.d.mts → transition-DxsrTsn4.d.mts} +1 -1
- package/dist/{types-DcxBYlPA.d.mts → types-Czdm5op1.d.mts} +306 -306
- package/dist/ui-components/index.d.mts +4 -4
- package/dist/{view-builder-k82f5KFd.d.mts → view-builder-D-Wz_l_B.d.mts} +1 -1
- package/dist/worker-CLsRbzpk.mjs +1 -0
- package/dist/worker-DlRdLf2d.mjs +1 -0
- package/dist/workflow-BomH9DBB.mjs +1 -0
- package/dist/workflow-engine/execution-attempt-repository.d.mts +2 -2
- package/dist/workflow-engine/execution-attempt-repository.mjs +1 -1
- package/dist/workflow-engine/index.d.mts +352 -62
- package/dist/workflow-engine/index.mjs +1 -1
- package/dist/workflow-engine/package.d.mts +1 -1
- package/dist/workflow-engine/package.mjs +1 -1
- package/dist/workflow-engine/testing/index.d.mts +83 -10
- package/dist/workflow-engine/testing/index.mjs +1 -1
- package/dist/workflow-engine/testing/sqlite.d.mts +3 -2
- package/dist/workflow-engine/testing/sqlite.mjs +107 -39
- package/dist/workflow-engine/workflow-orchestrator.mjs +1 -1
- package/dist/workflow-worker-DjUfKshi.mjs +1 -0
- package/package.json +4 -4
- package/dist/attempt-record-codec-BYfYkj4M.mjs +0 -1
- package/dist/authority-state-bootstrap-CLYus369.mjs +0 -1
- package/dist/await-trigger-Bkg2BQfg.mjs +0 -1
- package/dist/capabilities-BUwuA7XX.mjs +0 -1
- package/dist/contracts/worker-node/index.d.mts +0 -2
- package/dist/contracts/worker-node/index.mjs +0 -1
- package/dist/package-B01gmu75.mjs +0 -7
- package/dist/worker-node-CjuQb9hl.mjs +0 -1
- package/dist/worker-node-Dtf9ilsR.mjs +0 -1
- package/dist/workflow-C4mck_NW.mjs +0 -1
- package/dist/workflow-worker-aNbdgqml.mjs +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as ProviderOperationClaim, i as ProcessBoundProvisionerLossProof, l as ProviderOperationOwnershipRecord, o as ProviderOperationClaimDecision, s as ProviderOperationMutationDecision } from "./provider-operation-DbdyFwNm.mjs";
|
|
2
|
-
import { BoundedRecoveryEvidence,
|
|
2
|
+
import { BoundedRecoveryEvidence, ExecutionAttemptOperationKind, ProviderAllocationRef, WorkerAllocationLifetime } from "@makaio/framework/contracts";
|
|
3
3
|
|
|
4
4
|
//#region subsystems/workflow-engine/src/execution-attempt-repository.d.ts
|
|
5
5
|
/**
|
|
@@ -28,9 +28,9 @@ declare const EXECUTION_ATTEMPT_SETTLEMENT_KINDS: readonly ["outcome", "abandone
|
|
|
28
28
|
/**
|
|
29
29
|
* How a settled attempt reached its terminal state.
|
|
30
30
|
*
|
|
31
|
-
* - `outcome`: a worker submitted
|
|
31
|
+
* - `outcome`: a worker submitted an outcome that was accepted.
|
|
32
32
|
* - `infrastructure-failure`: the provider allocation terminated without
|
|
33
|
-
* an acknowledged worker outcome. The
|
|
33
|
+
* an acknowledged worker outcome. The owner may retry the work.
|
|
34
34
|
* - `abandoned`: dispatch ended before allocation, including a positively
|
|
35
35
|
* proven absence recorded through
|
|
36
36
|
* {@link ExecutionAttemptRepository.recordProvisioningAbsent} or a
|
|
@@ -40,6 +40,25 @@ declare const EXECUTION_ATTEMPT_SETTLEMENT_KINDS: readonly ["outcome", "abandone
|
|
|
40
40
|
* `null` when the attempt has not yet settled.
|
|
41
41
|
*/
|
|
42
42
|
type ExecutionAttemptSettlementKind = (typeof EXECUTION_ATTEMPT_SETTLEMENT_KINDS)[number] | null;
|
|
43
|
+
/**
|
|
44
|
+
* Constant array of every state the attempt's operation start gate can hold.
|
|
45
|
+
*
|
|
46
|
+
* Declared as an array for the same reason the status vocabulary is: a stored
|
|
47
|
+
* value outside it is corruption, and a realization narrows what it read
|
|
48
|
+
* against this list rather than against a literal of its own.
|
|
49
|
+
*/
|
|
50
|
+
declare const ATTEMPT_OPERATION_START_GATES: readonly ["open", "closed"];
|
|
51
|
+
/**
|
|
52
|
+
* State of the single durable ordering point an attempt admits operations
|
|
53
|
+
* through.
|
|
54
|
+
*
|
|
55
|
+
* - `open`: the attempt may still admit operations, subject to every other
|
|
56
|
+
* guard.
|
|
57
|
+
* - `closed`: the attempt will never admit another operation. Closing is
|
|
58
|
+
* one-way and happens exactly where the attempt stops being a place work may
|
|
59
|
+
* begin — when a newer attempt supersedes it, and when it settles.
|
|
60
|
+
*/
|
|
61
|
+
type AttemptOperationStartGate = (typeof ATTEMPT_OPERATION_START_GATES)[number];
|
|
43
62
|
/**
|
|
44
63
|
* Compare two provider allocation references as values.
|
|
45
64
|
*
|
|
@@ -54,16 +73,196 @@ type ExecutionAttemptSettlementKind = (typeof EXECUTION_ATTEMPT_SETTLEMENT_KINDS
|
|
|
54
73
|
*/
|
|
55
74
|
declare function sameAllocationRef(stored: ProviderAllocationRef, candidate: ProviderAllocationRef): boolean;
|
|
56
75
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
|
|
76
|
+
* Identifier of the durable aggregate that owns a series of attempts.
|
|
77
|
+
*
|
|
78
|
+
* For the workflow adapter this is the `WorkflowExecution` id. The generic
|
|
79
|
+
* port never interprets it; it is the fence key that decides which attempt is
|
|
80
|
+
* active for an owner. The alias is scoped to the repository port, the
|
|
81
|
+
* authority, the dispatch runner, and the convergence ports — wire claims and
|
|
82
|
+
* workflow-storage lookup keys keep plain `string`.
|
|
83
|
+
*/
|
|
84
|
+
type ExecutionOwnerId = string;
|
|
85
|
+
/**
|
|
86
|
+
* Compare two durable outcome texts as values.
|
|
87
|
+
*
|
|
88
|
+
* The rule {@link sameAllocationRef} states, for the same reason: an outcome
|
|
89
|
+
* may carry caller-authored data whose key order is incidental, and a replay
|
|
90
|
+
* of the identical outcome must be reported as `duplicate` by every
|
|
91
|
+
* realization. The texts are parsed back before canonicalization because a
|
|
92
|
+
* codec is free to emit whatever key order it likes, and key order is not
|
|
93
|
+
* part of the value.
|
|
94
|
+
*
|
|
95
|
+
* Both operands are the codec's durable text — the only representation every
|
|
96
|
+
* realization shares. The stored one is what the attempt actually holds, read
|
|
97
|
+
* out of the realization's own record rather than reproduced from the decoded
|
|
98
|
+
* value: a codec may normalize while serializing, and the contract requires
|
|
99
|
+
* `parse(JSON.parse(serialize(outcome)))` only to succeed, never to serialize
|
|
100
|
+
* back to the same text. Re-serializing the decoded outcome would therefore
|
|
101
|
+
* compare a retry against a text the first commit never wrote, and would
|
|
102
|
+
* answer `conflict` for a worker's honest replay. Comparing texts also keeps
|
|
103
|
+
* a value such as a `bigint` — which a codec may legitimately encode, and
|
|
104
|
+
* which has no JSON form of its own — away from the canonicalizer entirely.
|
|
105
|
+
* @param stored - Durable text the attempt committed.
|
|
106
|
+
* @param candidate - Durable text the caller's submission would commit.
|
|
107
|
+
* @returns `true` when the two denote the same outcome.
|
|
65
108
|
*/
|
|
66
|
-
declare function
|
|
109
|
+
declare function sameDurableOutcome(stored: string, candidate: string): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* The durable text a submission commits as, with the value that text yields.
|
|
112
|
+
*
|
|
113
|
+
* The two are produced together, by one call to {@link durableOutcome}, because
|
|
114
|
+
* they are one durable fact: the text is what a realization writes, and the
|
|
115
|
+
* outcome is what a reload of that text returns. Deriving either from the
|
|
116
|
+
* other later would re-serialize an already-normalized value, and a codec is
|
|
117
|
+
* not required to serialize such a value back to the text it came from.
|
|
118
|
+
*
|
|
119
|
+
* It is the currency of the whole outcome path. A caller renders a submission
|
|
120
|
+
* once through {@link ExecutionAttemptRepository.canonicalizeOutcome},
|
|
121
|
+
* validates `outcome`, and hands the same rendering to
|
|
122
|
+
* {@link ExecutionAttemptRepository.commitOutcome}, which stores `text`
|
|
123
|
+
* verbatim. Nothing between those steps re-reads the submitter's object, so a
|
|
124
|
+
* mutable outcome the caller changes afterwards cannot make the committed
|
|
125
|
+
* value differ from the validated one.
|
|
126
|
+
* @typeParam TOutcome - Owner-specific outcome type committed per attempt.
|
|
127
|
+
*/
|
|
128
|
+
interface DurableOutcome<TOutcome> {
|
|
129
|
+
/** The codec text a realization persists for this submission. */
|
|
130
|
+
readonly text: string;
|
|
131
|
+
/** The outcome that text reads back as, produced by the same rendering. */
|
|
132
|
+
readonly outcome: TOutcome;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Render a caller-supplied outcome as the durable fact a commit would write.
|
|
136
|
+
*
|
|
137
|
+
* The rendering rule every realization owes, stated once here for the same
|
|
138
|
+
* reason {@link sameDurableOutcome} is: it decides what an attempt holds, so
|
|
139
|
+
* two realizations deriving it independently could disagree about it.
|
|
140
|
+
*
|
|
141
|
+
* Three steps, in this order and each exactly once. `parse` validates the
|
|
142
|
+
* submission before any durable decision, so a value the codec rejects is
|
|
143
|
+
* refused at the same point by every realization. `serialize` produces the
|
|
144
|
+
* text to persist — the single serialization the pair is built from.
|
|
145
|
+
* `parse(JSON.parse(text))` yields what a reload returns, which is the
|
|
146
|
+
* outcome the port reports and an owner converges on; a codec may normalize
|
|
147
|
+
* while serializing, so it is not in general the submitted value.
|
|
148
|
+
*
|
|
149
|
+
* Nothing is cloned and nothing is frozen. The round trip already produces a
|
|
150
|
+
* value derived from freshly parsed JSON rather than from the caller's
|
|
151
|
+
* object, so no reference reaches back into anything the caller still holds.
|
|
152
|
+
* A `structuredClone` in front of it would reject an outcome type that is
|
|
153
|
+
* codec-serializable but not structured-cloneable, such as a `URL`, and an
|
|
154
|
+
* `Object.freeze` behind it would throw outright for one that is not
|
|
155
|
+
* freezable, such as a non-empty `Uint8Array` — both of which the codec
|
|
156
|
+
* contract allows. What keeps a committed outcome stable instead is that a
|
|
157
|
+
* realization stores the text and decodes it afresh on every read, so a
|
|
158
|
+
* caller that mutates the value it was handed changes nothing a later read
|
|
159
|
+
* reports.
|
|
160
|
+
* @param codec - Owner-injected codec that owns the durable representation.
|
|
161
|
+
* @param outcome - Outcome the caller presented.
|
|
162
|
+
* @returns The text to persist and the outcome it reads back as.
|
|
163
|
+
* @throws When the codec rejects the outcome or its own durable text.
|
|
164
|
+
* @typeParam TOutcome - Owner-specific outcome type committed per attempt.
|
|
165
|
+
*/
|
|
166
|
+
declare function durableOutcome<TOutcome>(codec: OutcomeCodec<TOutcome>, outcome: TOutcome): DurableOutcome<TOutcome>;
|
|
167
|
+
/**
|
|
168
|
+
* Read a committed durable text back as the outcome it holds.
|
|
169
|
+
*
|
|
170
|
+
* The read rule every realization owes, stated once here for the same reason
|
|
171
|
+
* {@link durableOutcome} states the write rule: what a stored text yields
|
|
172
|
+
* must not depend on which realization reads it. `JSON.parse` undoes the
|
|
173
|
+
* transport form and `parse` validates the envelope, so a text the codec
|
|
174
|
+
* refuses — durable corruption, or a codec an owner changed under an existing
|
|
175
|
+
* row — fails loudly rather than reaching a caller as an ordinary outcome.
|
|
176
|
+
*
|
|
177
|
+
* Every read decodes afresh, and nothing is shared, cloned, or frozen. A
|
|
178
|
+
* codec may reconstruct a mutable object whose state a freeze does not even
|
|
179
|
+
* reach, such as a `URL`, so one reader mutating the value it was handed must
|
|
180
|
+
* not change what the next read reports; the stored text stays the only
|
|
181
|
+
* source of truth.
|
|
182
|
+
* @param codec - Owner-injected codec that owns the durable representation.
|
|
183
|
+
* @param text - Durable text an attempt committed.
|
|
184
|
+
* @returns The outcome that text holds, held by nobody else.
|
|
185
|
+
* @throws When the text is not JSON, or not JSON the codec accepts.
|
|
186
|
+
* @typeParam TOutcome - Owner-specific outcome type committed per attempt.
|
|
187
|
+
*/
|
|
188
|
+
declare function decodeDurableOutcome<TOutcome>(codec: OutcomeCodec<TOutcome>, text: string): TOutcome;
|
|
189
|
+
/**
|
|
190
|
+
* Validates and serializes the owner-specific outcome type of an attempt.
|
|
191
|
+
*
|
|
192
|
+
* Injected into every {@link ExecutionAttemptRepository} realization so the
|
|
193
|
+
* generic port can enforce "input validation precedes every durable decision"
|
|
194
|
+
* without knowing the outcome shape. `parse` runs on every submitted outcome
|
|
195
|
+
* before the durable decision and on every committed outcome read back from
|
|
196
|
+
* storage; `serialize` produces the durable representation.
|
|
197
|
+
*
|
|
198
|
+
* **The durable representation is JSON text, and the two members round-trip
|
|
199
|
+
* through it.** A realization persists exactly what `serialize` returned,
|
|
200
|
+
* reads it back with `JSON.parse`, and hands the parsed value to `parse`; a
|
|
201
|
+
* codec must therefore accept `JSON.parse(serialize(outcome))` for every
|
|
202
|
+
* outcome it produced. Which envelope lives inside that text is the codec's
|
|
203
|
+
* own choice — no realization may assume it is `JSON.stringify(outcome)`.
|
|
204
|
+
*
|
|
205
|
+
* **The text is strict JSON, in the sense `JSON.stringify` defines.** Only
|
|
206
|
+
* values that function can represent are inside the contract: finite numbers,
|
|
207
|
+
* strings, booleans, `null`, arrays, and plain objects. A non-finite number —
|
|
208
|
+
* `Infinity`, `-Infinity`, `NaN`, written as `1e9999` or any other spelling —
|
|
209
|
+
* is outside it, as is anything else JSON has no form for, because the port
|
|
210
|
+
* decides outcome equality by canonicalizing the parsed text with
|
|
211
|
+
* `canonicalStringify` (see {@link sameDurableOutcome}), which renders every
|
|
212
|
+
* such value as `null` and would report two different outcomes as the same
|
|
213
|
+
* one. A codec whose outcomes need those values encodes them as values JSON
|
|
214
|
+
* does have — a string, or a tagged envelope — which is the codec's own job
|
|
215
|
+
* and not something a realization can do for it. Equality follows JSON
|
|
216
|
+
* semantics for the same reason: signed zero is outside the contract, `-0`
|
|
217
|
+
* and `0` are one and the same outcome, because `JSON.stringify` renders both
|
|
218
|
+
* as `0` and the canonical form cannot tell them apart. A codec that needs the
|
|
219
|
+
* sign encodes it explicitly, as it would any other value JSON does not carry.
|
|
220
|
+
*
|
|
221
|
+
* **Both members are deterministic, pure functions of their argument.** The
|
|
222
|
+
* same outcome serializes to the same text every time and the same value
|
|
223
|
+
* parses to the same outcome every time; neither may consult history, a
|
|
224
|
+
* counter, a clock, or any state outside the argument it was handed.
|
|
225
|
+
*
|
|
226
|
+
* The port itself renders a submission exactly once, through
|
|
227
|
+
* {@link ExecutionAttemptRepository.canonicalizeOutcome}, and carries that one
|
|
228
|
+
* {@link DurableOutcome} into {@link ExecutionAttemptRepository.commitOutcome}
|
|
229
|
+
* — so validation, the durable write, and convergence are one rendering and
|
|
230
|
+
* cannot drift apart. Determinism is still owed, because two renderings of
|
|
231
|
+
* the same outcome do meet: `commitOutcome` compares the text a retry renders
|
|
232
|
+
* against the text the first commit stored, and a codec that closed over a
|
|
233
|
+
* counter would misclassify a worker's honest retry.
|
|
234
|
+
*
|
|
235
|
+
* Determinism is not idempotence. `serialize` need not map its own decoded
|
|
236
|
+
* output back to the text it came from: a codec may normalize, and every
|
|
237
|
+
* decision the port makes about a committed outcome is made on the text that
|
|
238
|
+
* commit actually stored rather than on a re-serialization of it.
|
|
239
|
+
*
|
|
240
|
+
* `null` and `undefined` are not outcomes. A realization records "no outcome
|
|
241
|
+
* committed" as the absence of a stored value, so a codec that accepted a
|
|
242
|
+
* nullish outcome would make the two indistinguishable.
|
|
243
|
+
* @typeParam TOutcome - Owner-specific outcome type committed per attempt.
|
|
244
|
+
*/
|
|
245
|
+
interface OutcomeCodec<TOutcome> {
|
|
246
|
+
/**
|
|
247
|
+
* Validate an untrusted value as an outcome.
|
|
248
|
+
*
|
|
249
|
+
* Deterministic and pure: the same input always yields the same outcome.
|
|
250
|
+
* @param input - Value to validate.
|
|
251
|
+
* @returns The validated outcome.
|
|
252
|
+
* @throws When the value violates the outcome contract, including when it is nullish.
|
|
253
|
+
*/
|
|
254
|
+
parse(input: unknown): TOutcome;
|
|
255
|
+
/**
|
|
256
|
+
* Serialize an outcome for durable storage.
|
|
257
|
+
*
|
|
258
|
+
* Deterministic and pure: the same outcome always yields the same text,
|
|
259
|
+
* because a retry's rendering is compared against the text an earlier
|
|
260
|
+
* commit stored.
|
|
261
|
+
* @param outcome - Outcome to serialize.
|
|
262
|
+
* @returns The JSON text to persist, which `parse` accepts after `JSON.parse`.
|
|
263
|
+
*/
|
|
264
|
+
serialize(outcome: TOutcome): string;
|
|
265
|
+
}
|
|
67
266
|
/**
|
|
68
267
|
* Rejection of an attempt identifier that already names a durable attempt.
|
|
69
268
|
*
|
|
@@ -83,17 +282,136 @@ declare class DuplicateExecutionAttemptError extends Error {
|
|
|
83
282
|
*/
|
|
84
283
|
constructor(executionAttemptId: string, options?: ErrorOptions);
|
|
85
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* The runtime and operation control state of one attempt.
|
|
287
|
+
*
|
|
288
|
+
* Ten facts that together decide who may act on an attempt's runtime endpoint
|
|
289
|
+
* and whether an operation may start. They live *on* the attempt rather than in
|
|
290
|
+
* a record beside it: every guard that reads them is also a guard the write has
|
|
291
|
+
* to repeat, and a single-row compare-and-set is what keeps that expressible.
|
|
292
|
+
*
|
|
293
|
+
* The three fences are independent and each answers a different question:
|
|
294
|
+
* {@link runtimeGeneration} says which runtime incarnation is current,
|
|
295
|
+
* {@link operationStartGate} says whether the attempt still starts work at all,
|
|
296
|
+
* and {@link activeOperationId} says whether it is already busy.
|
|
297
|
+
*
|
|
298
|
+
* It is also the shape {@link ExecutionAttemptRepository.getAttemptControlState}
|
|
299
|
+
* reports, so a process that lost its own memory of an attempt can read back
|
|
300
|
+
* exactly what a fresh decision would be made against.
|
|
301
|
+
*/
|
|
302
|
+
interface AttemptControlState {
|
|
303
|
+
/**
|
|
304
|
+
* Monotonic fence over runtime incarnations, `0` before any registered.
|
|
305
|
+
*
|
|
306
|
+
* Allocated by {@link ExecutionAttemptRepository.registerRuntime} and never
|
|
307
|
+
* proposed by a caller: a runtime only ever echoes the generation it was
|
|
308
|
+
* given. Anything presented against an older generation is
|
|
309
|
+
* `stale-generation`.
|
|
310
|
+
*/
|
|
311
|
+
readonly runtimeGeneration: number;
|
|
312
|
+
/**
|
|
313
|
+
* Runtime incarnation currently registered, or `null` before any was.
|
|
314
|
+
*
|
|
315
|
+
* The registration idempotency key. A report naming the stored incarnation
|
|
316
|
+
* is a replay and is answered `duplicate`; a different one is a new
|
|
317
|
+
* incarnation and takes the next generation.
|
|
318
|
+
*/
|
|
319
|
+
readonly runtimeIncarnationId: string | null;
|
|
320
|
+
/**
|
|
321
|
+
* ISO-8601 instant at which readiness was accepted for the current
|
|
322
|
+
* generation, or `null` while it has not been.
|
|
323
|
+
*
|
|
324
|
+
* Written by {@link ExecutionAttemptRepository.markRuntimeReady} and cleared
|
|
325
|
+
* by every {@link ExecutionAttemptRepository.registerRuntime} that allocates
|
|
326
|
+
* a new generation: readiness is a property of one incarnation, so it can
|
|
327
|
+
* never outlive the incarnation that proved it.
|
|
328
|
+
*/
|
|
329
|
+
readonly runtimeReadyAt: string | null;
|
|
330
|
+
/**
|
|
331
|
+
* Whether the attempt still admits operations.
|
|
332
|
+
*
|
|
333
|
+
* `open` from creation. Closed by {@link ExecutionAttemptRepository.createAttempt}
|
|
334
|
+
* on the attempt it supersedes, and by every terminal settlement on the
|
|
335
|
+
* attempt it settles. Closing is one-way.
|
|
336
|
+
*/
|
|
337
|
+
readonly operationStartGate: AttemptOperationStartGate;
|
|
338
|
+
/**
|
|
339
|
+
* Operation currently occupying the attempt, or `null` when it is idle.
|
|
340
|
+
*
|
|
341
|
+
* The at-most-one guard: an attempt runs one operation at a time. Cleared by
|
|
342
|
+
* {@link ExecutionAttemptRepository.completeOperation} together with
|
|
343
|
+
* {@link activeOperationKind}, {@link activeOperationKey},
|
|
344
|
+
* {@link activeOperationGeneration}, and {@link activeOperationAdmittedAt} —
|
|
345
|
+
* one write, because a half-cleared
|
|
346
|
+
* operation would be neither active nor absent.
|
|
347
|
+
*
|
|
348
|
+
* A terminal settlement deliberately leaves it in place, so a completion that
|
|
349
|
+
* arrives after the attempt settled reads `resolved` rather than
|
|
350
|
+
* `not-active`.
|
|
351
|
+
*/
|
|
352
|
+
readonly activeOperationId: string | null;
|
|
353
|
+
/**
|
|
354
|
+
* Kind of the active operation, or `null` when the attempt is idle.
|
|
355
|
+
*
|
|
356
|
+
* Kept because the bounded runtime probe and a durable owner's run are not
|
|
357
|
+
* interchangeable: the probe is admitted while readiness is still unproven
|
|
358
|
+
* and is never announced to anyone but its own runtime.
|
|
359
|
+
*/
|
|
360
|
+
readonly activeOperationKind: ExecutionAttemptOperationKind | null;
|
|
361
|
+
/**
|
|
362
|
+
* Idempotency key the active operation was admitted under, or `null` when the
|
|
363
|
+
* attempt is idle.
|
|
364
|
+
*
|
|
365
|
+
* A second admission presenting this key is the same admission retried, and
|
|
366
|
+
* is answered `duplicate` with the operation identifier the first one
|
|
367
|
+
* received.
|
|
368
|
+
*/
|
|
369
|
+
readonly activeOperationKey: string | null;
|
|
370
|
+
/**
|
|
371
|
+
* Runtime generation the active operation is fenced against, or `null` when
|
|
372
|
+
* the attempt is idle.
|
|
373
|
+
*
|
|
374
|
+
* A completion reported against an older generation belongs to a runtime that
|
|
375
|
+
* has since been superseded and is refused as `stale-generation`.
|
|
376
|
+
*/
|
|
377
|
+
readonly activeOperationGeneration: number | null;
|
|
378
|
+
/**
|
|
379
|
+
* ISO-8601 instant at which the active operation was admitted, or `null`
|
|
380
|
+
* when the attempt is idle.
|
|
381
|
+
*
|
|
382
|
+
* Recorded so a replayed admission announces the instant the authority
|
|
383
|
+
* admitted the operation, not the instant of the replay. Cleared together
|
|
384
|
+
* with the other active-operation members.
|
|
385
|
+
*/
|
|
386
|
+
readonly activeOperationAdmittedAt: string | null;
|
|
387
|
+
/**
|
|
388
|
+
* Operation identifier of the most recent completion, or `null` before any.
|
|
389
|
+
*
|
|
390
|
+
* What makes a replayed completion answerable: the active operation is gone
|
|
391
|
+
* by then, so without it the replay would be indistinguishable from a
|
|
392
|
+
* completion for an operation that never existed.
|
|
393
|
+
*/
|
|
394
|
+
readonly lastCompletedOperationId: string | null;
|
|
395
|
+
}
|
|
86
396
|
/**
|
|
87
397
|
* Durable record for one execution attempt.
|
|
88
398
|
*
|
|
89
399
|
* Produced by the injected {@link ExecutionAttemptRepository} and consumed by
|
|
90
400
|
* the Authority service. All fields are JSON-safe and contain no secrets.
|
|
401
|
+
*
|
|
402
|
+
* The {@link AttemptControlState} members are part of the record because they
|
|
403
|
+
* are columns on the attempt, not a separate aggregate — and they are required,
|
|
404
|
+
* unlike {@link claimable} and {@link settlementKind}. Every attempt holds all
|
|
405
|
+
* ten from {@link ExecutionAttemptRepository.createAttempt} onwards, so a
|
|
406
|
+
* record that omits one describes an attempt that cannot exist. Requiring them
|
|
407
|
+
* is what makes that omission a type error at the place the record is built,
|
|
408
|
+
* rather than a default silently resolved at each place it is read.
|
|
91
409
|
*/
|
|
92
|
-
interface ExecutionAttemptRecord {
|
|
410
|
+
interface ExecutionAttemptRecord extends AttemptControlState {
|
|
93
411
|
/** Authority-created attempt identifier. */
|
|
94
412
|
readonly executionAttemptId: string;
|
|
95
|
-
/**
|
|
96
|
-
readonly executionId:
|
|
413
|
+
/** Owner identifier of the aggregate this attempt belongs to. */
|
|
414
|
+
readonly executionId: ExecutionOwnerId;
|
|
97
415
|
/** Current lifecycle status of the attempt. */
|
|
98
416
|
readonly status: ExecutionAttemptStatus;
|
|
99
417
|
/** Provider allocation reference, set after allocation recording. */
|
|
@@ -116,7 +434,7 @@ interface ExecutionAttemptRecord {
|
|
|
116
434
|
* Immutable alongside {@link providerId}. Remediation reads it to decide
|
|
117
435
|
* whether losing the provisioning process also loses the allocation.
|
|
118
436
|
*/
|
|
119
|
-
readonly allocationLifetime:
|
|
437
|
+
readonly allocationLifetime: WorkerAllocationLifetime | null;
|
|
120
438
|
/**
|
|
121
439
|
* Provisioner process incarnation that performed the provider call, or
|
|
122
440
|
* `null` before provisioning began.
|
|
@@ -182,7 +500,7 @@ interface RecoverableAttemptRecord extends ExecutionAttemptRecord {
|
|
|
182
500
|
/** Always bound for recoverable attempts. */
|
|
183
501
|
readonly providerId: string;
|
|
184
502
|
/** Always bound for recoverable attempts. */
|
|
185
|
-
readonly allocationLifetime:
|
|
503
|
+
readonly allocationLifetime: WorkerAllocationLifetime;
|
|
186
504
|
/** Always bound for recoverable attempts. */
|
|
187
505
|
readonly provisionerIncarnationId: string;
|
|
188
506
|
}
|
|
@@ -194,8 +512,8 @@ interface RecoverableAttemptRecord extends ExecutionAttemptRecord {
|
|
|
194
512
|
interface ExecutionAttemptCreate {
|
|
195
513
|
/** Authority-created attempt identifier. */
|
|
196
514
|
readonly executionAttemptId: string;
|
|
197
|
-
/**
|
|
198
|
-
readonly executionId:
|
|
515
|
+
/** Owner identifier the attempt belongs to. */
|
|
516
|
+
readonly executionId: ExecutionOwnerId;
|
|
199
517
|
}
|
|
200
518
|
/**
|
|
201
519
|
* Input for claiming the provisioning phase of an attempt.
|
|
@@ -208,12 +526,12 @@ interface ExecutionAttemptCreate {
|
|
|
208
526
|
interface BeginProvisioningInput {
|
|
209
527
|
/** Attempt whose provider call is about to begin. */
|
|
210
528
|
readonly executionAttemptId: string;
|
|
211
|
-
/**
|
|
212
|
-
readonly executionId:
|
|
529
|
+
/** Owner identifier the attempt must belong to. */
|
|
530
|
+
readonly executionId: ExecutionOwnerId;
|
|
213
531
|
/** Provider to bind to the attempt, immutably. */
|
|
214
532
|
readonly providerId: string;
|
|
215
533
|
/** Allocation lifetime declared by that provider, immutably. */
|
|
216
|
-
readonly allocationLifetime:
|
|
534
|
+
readonly allocationLifetime: WorkerAllocationLifetime;
|
|
217
535
|
/** Provisioner process incarnation performing the call, immutably. */
|
|
218
536
|
readonly provisionerIncarnationId: string;
|
|
219
537
|
/** Controller process incarnation that will hold the initial claim. */
|
|
@@ -228,14 +546,22 @@ interface BeginProvisioningInput {
|
|
|
228
546
|
* and returns the canonical outcome for convergence. Outcome commitment
|
|
229
547
|
* carries no claim: a worker's answer never depends on who currently owns
|
|
230
548
|
* the attempt's provider operation.
|
|
549
|
+
* @typeParam TOutcome - Owner-specific outcome type committed per attempt.
|
|
231
550
|
*/
|
|
232
|
-
interface ExecutionAttemptOutcomeCommit {
|
|
551
|
+
interface ExecutionAttemptOutcomeCommit<TOutcome> {
|
|
233
552
|
/** Authority-created attempt identifier. */
|
|
234
553
|
readonly executionAttemptId: string;
|
|
235
|
-
/**
|
|
236
|
-
readonly executionId:
|
|
237
|
-
/**
|
|
238
|
-
|
|
554
|
+
/** Owner identifier the attempt belongs to. */
|
|
555
|
+
readonly executionId: ExecutionOwnerId;
|
|
556
|
+
/**
|
|
557
|
+
* The rendering to commit, from
|
|
558
|
+
* {@link ExecutionAttemptRepository.canonicalizeOutcome}.
|
|
559
|
+
*
|
|
560
|
+
* A rendering rather than a raw outcome so the value a caller validated and
|
|
561
|
+
* the value that becomes durable are the same one: the caller renders the
|
|
562
|
+
* submission once and never reads its own object again.
|
|
563
|
+
*/
|
|
564
|
+
readonly result: DurableOutcome<TOutcome>;
|
|
239
565
|
}
|
|
240
566
|
/** Input for extending the lease of a currently held provider operation. */
|
|
241
567
|
interface RenewProviderOperationClaimInput {
|
|
@@ -311,8 +637,8 @@ interface RecordAllocationInput {
|
|
|
311
637
|
interface RecordProvisioningAbsentInput {
|
|
312
638
|
/** Claim authorizing the record. */
|
|
313
639
|
readonly claim: ProviderOperationClaim;
|
|
314
|
-
/**
|
|
315
|
-
readonly executionId:
|
|
640
|
+
/** Owner identifier the attempt must belong to. */
|
|
641
|
+
readonly executionId: ExecutionOwnerId;
|
|
316
642
|
/** Bounded evidence supporting the absence claim. */
|
|
317
643
|
readonly evidence: BoundedRecoveryEvidence;
|
|
318
644
|
}
|
|
@@ -327,8 +653,8 @@ interface RecordProvisioningAbsentInput {
|
|
|
327
653
|
interface RecordProvisionerIncarnationLostInput {
|
|
328
654
|
/** Claim authorizing the record. */
|
|
329
655
|
readonly claim: ProviderOperationClaim;
|
|
330
|
-
/**
|
|
331
|
-
readonly executionId:
|
|
656
|
+
/** Owner identifier the attempt must belong to. */
|
|
657
|
+
readonly executionId: ExecutionOwnerId;
|
|
332
658
|
/** Proof that a specific provisioner process incarnation is gone. */
|
|
333
659
|
readonly proof: ProcessBoundProvisionerLossProof;
|
|
334
660
|
}
|
|
@@ -348,8 +674,8 @@ interface RecordAllocationTerminatedInput {
|
|
|
348
674
|
interface RecordInfrastructureFailureInput {
|
|
349
675
|
/** Claim authorizing the settlement. */
|
|
350
676
|
readonly claim: ProviderOperationClaim;
|
|
351
|
-
/**
|
|
352
|
-
readonly executionId:
|
|
677
|
+
/** Owner identifier the attempt must belong to. */
|
|
678
|
+
readonly executionId: ExecutionOwnerId;
|
|
353
679
|
}
|
|
354
680
|
/**
|
|
355
681
|
* Input for a compare-and-set evolution of an allocation reference.
|
|
@@ -365,8 +691,8 @@ interface RecordInfrastructureFailureInput {
|
|
|
365
691
|
interface AllocationRefEvolution {
|
|
366
692
|
/** Claim authorizing the evolution. */
|
|
367
693
|
readonly claim: ProviderOperationClaim;
|
|
368
|
-
/**
|
|
369
|
-
readonly executionId:
|
|
694
|
+
/** Owner identifier the attempt must belong to. */
|
|
695
|
+
readonly executionId: ExecutionOwnerId;
|
|
370
696
|
/**
|
|
371
697
|
* The allocation reference the caller believes is currently stored.
|
|
372
698
|
*
|
|
@@ -381,6 +707,78 @@ interface AllocationRefEvolution {
|
|
|
381
707
|
/** The new allocation reference to store if the CAS check passes. */
|
|
382
708
|
readonly nextRef: ProviderAllocationRef;
|
|
383
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* Input for registering a runtime incarnation as an attempt's endpoint.
|
|
712
|
+
*
|
|
713
|
+
* `executionId` is the active-attempt fence, not merely an ownership check: a
|
|
714
|
+
* superseded attempt must never acquire a fresh runtime endpoint, because
|
|
715
|
+
* nothing would ever address it again.
|
|
716
|
+
*
|
|
717
|
+
* No generation is supplied. The repository allocates it, exactly as
|
|
718
|
+
* {@link ExecutionAttemptRepository.beginProvisioning} mints the first claim,
|
|
719
|
+
* so a runtime can never propose a fence for itself.
|
|
720
|
+
*/
|
|
721
|
+
interface RegisterRuntimeInput {
|
|
722
|
+
/** Attempt whose runtime endpoint is being registered. */
|
|
723
|
+
readonly executionAttemptId: string;
|
|
724
|
+
/** Owner identifier the attempt must belong to, and be active for. */
|
|
725
|
+
readonly executionId: ExecutionOwnerId;
|
|
726
|
+
/** Identifier of this concrete runtime incarnation, unique per boot. */
|
|
727
|
+
readonly runtimeIncarnationId: string;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Input for admitting one operation through an attempt's start gate.
|
|
731
|
+
*
|
|
732
|
+
* `admissionKey` is the caller's idempotency key and the only thing that makes
|
|
733
|
+
* a retry answerable: the repository mints the operation identifier, so a
|
|
734
|
+
* caller that lost the reply has no other way to ask which operation it got.
|
|
735
|
+
*/
|
|
736
|
+
interface AdmitOperationInput {
|
|
737
|
+
/** Attempt the operation would run under. */
|
|
738
|
+
readonly executionAttemptId: string;
|
|
739
|
+
/** Owner identifier the attempt must belong to, and be active for. */
|
|
740
|
+
readonly executionId: ExecutionOwnerId;
|
|
741
|
+
/** Kind of operation being admitted. */
|
|
742
|
+
readonly operationKind: ExecutionAttemptOperationKind;
|
|
743
|
+
/** Caller-chosen idempotency key for this admission. */
|
|
744
|
+
readonly admissionKey: string;
|
|
745
|
+
/** Runtime generation the caller fences the admission against. */
|
|
746
|
+
readonly runtimeGeneration: number;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Input for completing the operation an attempt currently runs.
|
|
750
|
+
*
|
|
751
|
+
* Carries no `executionId`: completion frees a slot the attempt already
|
|
752
|
+
* occupies, and a superseded attempt owes that release just as much as an
|
|
753
|
+
* active one does. Refusing it would strand the operation forever.
|
|
754
|
+
*/
|
|
755
|
+
interface CompleteOperationInput {
|
|
756
|
+
/** Attempt whose active operation is completing. */
|
|
757
|
+
readonly executionAttemptId: string;
|
|
758
|
+
/** Operation the caller believes it is completing. */
|
|
759
|
+
readonly operationId: string;
|
|
760
|
+
/** Runtime generation the completion is fenced against. */
|
|
761
|
+
readonly runtimeGeneration: number;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Input for recording that a registered runtime proved itself ready.
|
|
765
|
+
*
|
|
766
|
+
* Carries `executionId`, unlike {@link CompleteOperationInput}: completion
|
|
767
|
+
* releases a slot the attempt already occupies, but readiness asserts that the
|
|
768
|
+
* attempt's endpoint is current, and an attempt superseded between the probe's
|
|
769
|
+
* completion and this write no longer has a current endpoint. The generation
|
|
770
|
+
* fence alone cannot see that — a superseded attempt keeps its generation.
|
|
771
|
+
*/
|
|
772
|
+
interface MarkRuntimeReadyInput {
|
|
773
|
+
/** Attempt whose runtime proved ready. */
|
|
774
|
+
readonly executionAttemptId: string;
|
|
775
|
+
/** Owner identifier the attempt must belong to, and be active for. */
|
|
776
|
+
readonly executionId: ExecutionOwnerId;
|
|
777
|
+
/** Generation the readiness belongs to. */
|
|
778
|
+
readonly runtimeGeneration: number;
|
|
779
|
+
/** ISO-8601 instant at which readiness was observed. */
|
|
780
|
+
readonly readyAt: string;
|
|
781
|
+
}
|
|
384
782
|
/**
|
|
385
783
|
* Durable decision for claiming provider provisioning ownership.
|
|
386
784
|
*
|
|
@@ -510,7 +908,7 @@ type ProvisionerIncarnationLossDecision = {
|
|
|
510
908
|
readonly kind: 'recorded';
|
|
511
909
|
} | {
|
|
512
910
|
readonly kind: 'not-process-bound';
|
|
513
|
-
readonly allocationLifetime:
|
|
911
|
+
readonly allocationLifetime: WorkerAllocationLifetime | null;
|
|
514
912
|
} | {
|
|
515
913
|
readonly kind: 'incarnation-mismatch';
|
|
516
914
|
readonly provisionerIncarnationId: string | null;
|
|
@@ -620,25 +1018,220 @@ type PendingAttemptAbandonmentDecision = {
|
|
|
620
1018
|
* Durable outcome decision returned by {@link ExecutionAttemptRepository.commitOutcome}.
|
|
621
1019
|
*
|
|
622
1020
|
* - `accepted`: the outcome was committed as canonical for the first time.
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
1021
|
+
* The stored text decoded is reported, never the caller's copy of it.
|
|
1022
|
+
* - `duplicate`: an outcome whose durable text {@link sameDurableOutcome}
|
|
1023
|
+
* judges identical to the committed one was submitted again; this is a
|
|
1024
|
+
* replay. The committed outcome is reported, never the caller's copy of it.
|
|
626
1025
|
* - `conflict`: the attempt already reached a different terminal state — either
|
|
627
1026
|
* a different committed outcome, or a competing terminal transition that
|
|
628
1027
|
* settled it without one.
|
|
629
1028
|
* - `fenced`: the attempt is no longer the active attempt for this execution.
|
|
1029
|
+
*
|
|
1030
|
+
* Both settling kinds carry `text`: the durable text the attempt holds for
|
|
1031
|
+
* this outcome. For `accepted` that is the text the commit just wrote; for
|
|
1032
|
+
* `duplicate` it is the text the earlier commit wrote, which is not
|
|
1033
|
+
* necessarily the retry's own rendering — {@link sameDurableOutcome} judges
|
|
1034
|
+
* two texts the same outcome while member order, and anything else a codec
|
|
1035
|
+
* may render differently, still differs between them. A caller that needs a
|
|
1036
|
+
* copy of the committed outcome nobody else has held decodes this text
|
|
1037
|
+
* through {@link ExecutionAttemptRepository.decodeOutcome}; decoding its own
|
|
1038
|
+
* submission's text instead would hand out the retry's representation rather
|
|
1039
|
+
* than the committed one.
|
|
1040
|
+
* @typeParam TOutcome - Owner-specific outcome type committed per attempt.
|
|
630
1041
|
*/
|
|
631
|
-
type ExecutionAttemptOutcomeDecision = {
|
|
1042
|
+
type ExecutionAttemptOutcomeDecision<TOutcome> = {
|
|
632
1043
|
readonly kind: 'accepted';
|
|
633
|
-
readonly outcome:
|
|
1044
|
+
readonly outcome: TOutcome;
|
|
1045
|
+
readonly text: string;
|
|
634
1046
|
} | {
|
|
635
1047
|
readonly kind: 'duplicate';
|
|
636
|
-
readonly outcome:
|
|
1048
|
+
readonly outcome: TOutcome;
|
|
1049
|
+
readonly text: string;
|
|
637
1050
|
} | {
|
|
638
1051
|
readonly kind: 'conflict';
|
|
639
1052
|
} | {
|
|
640
1053
|
readonly kind: 'fenced';
|
|
641
1054
|
};
|
|
1055
|
+
/**
|
|
1056
|
+
* Durable decision for registering a runtime incarnation as an attempt's
|
|
1057
|
+
* endpoint.
|
|
1058
|
+
*
|
|
1059
|
+
* - `registered`: the incarnation now owns the attempt's runtime endpoint at
|
|
1060
|
+
* `runtimeGeneration`, and everything it later presents must carry that
|
|
1061
|
+
* generation. Readiness starts unproven.
|
|
1062
|
+
* - `duplicate`: the attempt already holds exactly this incarnation. The
|
|
1063
|
+
* report is a replay, so the stored generation is reported unchanged
|
|
1064
|
+
* together with the readiness that generation has — `runtimeReadyAt` is
|
|
1065
|
+
* `null` when readiness was not proven yet, and an instant when it was,
|
|
1066
|
+
* which is what lets a caller tell "register again" from "already ready"
|
|
1067
|
+
* without a second read.
|
|
1068
|
+
* - `not-found`: no such attempt, or it does not belong to the named owner.
|
|
1069
|
+
* - `resolved`: the attempt has settled, so it will never run anything again.
|
|
1070
|
+
* - `fenced`: the attempt is no longer the active attempt for its owner.
|
|
1071
|
+
* - `not-allocated`: no allocation is recorded, or the recorded one is durably
|
|
1072
|
+
* confirmed terminated and only awaits its settlement, so there is no
|
|
1073
|
+
* infrastructure a runtime could be the endpoint of.
|
|
1074
|
+
* - `operation-active`: a workload operation is running against the current
|
|
1075
|
+
* generation. Registering would fence it mid-flight, so the reported
|
|
1076
|
+
* operation must complete first. A `runtime-probe` left active is not in the
|
|
1077
|
+
* way: the probe is the authority's own proof of an endpoint, and one that
|
|
1078
|
+
* was never completed belongs to a handshake that died. Registration
|
|
1079
|
+
* reclaims it in the same write that allocates the new generation, so a
|
|
1080
|
+
* crashed handshake cannot block the attempt's next incarnation.
|
|
1081
|
+
*/
|
|
1082
|
+
type RuntimeRegistrationDecision = {
|
|
1083
|
+
readonly kind: 'registered';
|
|
1084
|
+
readonly runtimeGeneration: number;
|
|
1085
|
+
} | {
|
|
1086
|
+
readonly kind: 'duplicate';
|
|
1087
|
+
readonly runtimeGeneration: number;
|
|
1088
|
+
readonly runtimeReadyAt: string | null;
|
|
1089
|
+
} | {
|
|
1090
|
+
readonly kind: 'not-found';
|
|
1091
|
+
} | {
|
|
1092
|
+
readonly kind: 'resolved';
|
|
1093
|
+
} | {
|
|
1094
|
+
readonly kind: 'fenced';
|
|
1095
|
+
} | {
|
|
1096
|
+
readonly kind: 'not-allocated';
|
|
1097
|
+
} | {
|
|
1098
|
+
readonly kind: 'operation-active';
|
|
1099
|
+
readonly operationId: string;
|
|
1100
|
+
};
|
|
1101
|
+
/**
|
|
1102
|
+
* Durable decision for admitting one operation through an attempt's start gate.
|
|
1103
|
+
*
|
|
1104
|
+
* - `admitted`: the operation now occupies the attempt, under the reported
|
|
1105
|
+
* identifier and fenced against the reported generation.
|
|
1106
|
+
* - `duplicate`: an operation admitted under the same `admissionKey` is already
|
|
1107
|
+
* the active one. The retry receives that operation's identifier and the
|
|
1108
|
+
* generation it was admitted under rather than a second admission.
|
|
1109
|
+
* - `not-found`: no such attempt, or it does not belong to the named owner.
|
|
1110
|
+
* - `resolved`: the attempt has settled.
|
|
1111
|
+
* - `fenced`: the attempt is no longer the active attempt for its owner.
|
|
1112
|
+
* - `not-allocated`: no allocation is recorded, or the recorded one is durably
|
|
1113
|
+
* confirmed terminated and only awaits its settlement, so there is nothing
|
|
1114
|
+
* to run on.
|
|
1115
|
+
* - `operation-active`: a different operation already occupies the attempt.
|
|
1116
|
+
* - `gate-closed`: the attempt was superseded or settled, so it will never
|
|
1117
|
+
* admit another operation. Distinct from `fenced` and `resolved` because the
|
|
1118
|
+
* gate is the durable fact, and the two of them are how it came to be closed.
|
|
1119
|
+
* - `not-ready`: readiness has not been proven for the current generation.
|
|
1120
|
+
* Every kind but `runtime-probe` waits for it — the probe is precisely what
|
|
1121
|
+
* proves readiness, so it cannot require it.
|
|
1122
|
+
* - `stale-generation`: the caller fenced against a generation the attempt has
|
|
1123
|
+
* moved past. The current generation is reported so the caller can re-fence
|
|
1124
|
+
* rather than re-read.
|
|
1125
|
+
*/
|
|
1126
|
+
type OperationAdmissionDecision = {
|
|
1127
|
+
readonly kind: 'admitted';
|
|
1128
|
+
readonly operationId: string;
|
|
1129
|
+
readonly runtimeGeneration: number; /** ISO-8601 instant the admission was recorded at. */
|
|
1130
|
+
readonly admittedAt: string;
|
|
1131
|
+
} | {
|
|
1132
|
+
readonly kind: 'duplicate';
|
|
1133
|
+
readonly operationId: string;
|
|
1134
|
+
readonly runtimeGeneration: number; /** ISO-8601 instant the first pass recorded the admission at. */
|
|
1135
|
+
readonly admittedAt: string;
|
|
1136
|
+
} | {
|
|
1137
|
+
readonly kind: 'not-found';
|
|
1138
|
+
} | {
|
|
1139
|
+
readonly kind: 'resolved';
|
|
1140
|
+
} | {
|
|
1141
|
+
readonly kind: 'fenced';
|
|
1142
|
+
} | {
|
|
1143
|
+
readonly kind: 'not-allocated';
|
|
1144
|
+
} | {
|
|
1145
|
+
readonly kind: 'operation-active';
|
|
1146
|
+
readonly operationId: string;
|
|
1147
|
+
} | {
|
|
1148
|
+
readonly kind: 'gate-closed';
|
|
1149
|
+
} | {
|
|
1150
|
+
readonly kind: 'not-ready';
|
|
1151
|
+
} | {
|
|
1152
|
+
readonly kind: 'stale-generation';
|
|
1153
|
+
readonly runtimeGeneration: number;
|
|
1154
|
+
};
|
|
1155
|
+
/**
|
|
1156
|
+
* Durable decision for completing the operation an attempt currently runs.
|
|
1157
|
+
*
|
|
1158
|
+
* - `completed`: the operation was the active one and the attempt is idle
|
|
1159
|
+
* again.
|
|
1160
|
+
* - `duplicate`: this operation was already completed. A replay, answered from
|
|
1161
|
+
* {@link AttemptControlState.lastCompletedOperationId} rather than from an
|
|
1162
|
+
* active operation that is by then gone.
|
|
1163
|
+
* - `mismatch`: a different operation occupies the attempt. The active one is
|
|
1164
|
+
* reported, because the caller's next move depends on which it is.
|
|
1165
|
+
* - `not-active`: no operation occupies the attempt and this one is not the
|
|
1166
|
+
* last completed one, so the caller is completing something that never ran.
|
|
1167
|
+
* - `stale-generation`: the completion is fenced against an older generation
|
|
1168
|
+
* than the operation it names, so it comes from a superseded runtime.
|
|
1169
|
+
* - `resolved`: the attempt has settled. Reported ahead of `not-active`
|
|
1170
|
+
* because a terminal settlement leaves the active operation in place
|
|
1171
|
+
* precisely so a late completion learns why nobody is waiting for it.
|
|
1172
|
+
* - `not-found`: no such attempt.
|
|
1173
|
+
*/
|
|
1174
|
+
type OperationCompletionDecision = {
|
|
1175
|
+
readonly kind: 'completed';
|
|
1176
|
+
} | {
|
|
1177
|
+
readonly kind: 'duplicate';
|
|
1178
|
+
} | {
|
|
1179
|
+
readonly kind: 'mismatch';
|
|
1180
|
+
readonly activeOperationId: string;
|
|
1181
|
+
} | {
|
|
1182
|
+
readonly kind: 'not-active';
|
|
1183
|
+
} | {
|
|
1184
|
+
readonly kind: 'stale-generation';
|
|
1185
|
+
} | {
|
|
1186
|
+
readonly kind: 'resolved';
|
|
1187
|
+
} | {
|
|
1188
|
+
readonly kind: 'not-found';
|
|
1189
|
+
};
|
|
1190
|
+
/**
|
|
1191
|
+
* Durable decision for recording that a registered runtime proved itself ready.
|
|
1192
|
+
*
|
|
1193
|
+
* Evaluated in this order, in both realizations:
|
|
1194
|
+
* `not-found → resolved → fenced → not-allocated → stale-generation → duplicate → operation-active`.
|
|
1195
|
+
*
|
|
1196
|
+
* - `ready`: readiness is now durable for the fenced generation, at the
|
|
1197
|
+
* reported instant.
|
|
1198
|
+
* - `duplicate`: readiness was already recorded for this generation. The
|
|
1199
|
+
* instant reported is the stored one, not the caller's, so two callers
|
|
1200
|
+
* converge on one answer.
|
|
1201
|
+
* - `operation-active`: an operation occupies the attempt. Readiness is a
|
|
1202
|
+
* statement about an idle runtime, and recording it under a running
|
|
1203
|
+
* operation would claim a proof nothing performed.
|
|
1204
|
+
* - `stale-generation`: the caller fenced against a generation the attempt has
|
|
1205
|
+
* moved past, so the readiness it proved belongs to a runtime that is gone.
|
|
1206
|
+
* - `not-allocated`: no allocation is recorded, or the recorded one is durably
|
|
1207
|
+
* confirmed terminated and only awaits its settlement. A probe completed on
|
|
1208
|
+
* a dead allocation proves nothing that can be announced.
|
|
1209
|
+
* - `fenced`: the attempt is no longer the active attempt for its owner. Its
|
|
1210
|
+
* generation may still match, but a superseded attempt has no current
|
|
1211
|
+
* endpoint to declare ready.
|
|
1212
|
+
* - `resolved`: the attempt has settled.
|
|
1213
|
+
* - `not-found`: no such attempt for the given execution.
|
|
1214
|
+
*/
|
|
1215
|
+
type RuntimeReadinessDecision = {
|
|
1216
|
+
readonly kind: 'ready';
|
|
1217
|
+
readonly acceptedAt: string;
|
|
1218
|
+
} | {
|
|
1219
|
+
readonly kind: 'duplicate';
|
|
1220
|
+
readonly acceptedAt: string;
|
|
1221
|
+
} | {
|
|
1222
|
+
readonly kind: 'operation-active';
|
|
1223
|
+
readonly operationId: string;
|
|
1224
|
+
} | {
|
|
1225
|
+
readonly kind: 'stale-generation';
|
|
1226
|
+
} | {
|
|
1227
|
+
readonly kind: 'not-allocated';
|
|
1228
|
+
} | {
|
|
1229
|
+
readonly kind: 'fenced';
|
|
1230
|
+
} | {
|
|
1231
|
+
readonly kind: 'resolved';
|
|
1232
|
+
} | {
|
|
1233
|
+
readonly kind: 'not-found';
|
|
1234
|
+
};
|
|
642
1235
|
/**
|
|
643
1236
|
* Passive injected port for durable execution attempt persistence.
|
|
644
1237
|
*
|
|
@@ -682,11 +1275,37 @@ type ExecutionAttemptOutcomeDecision = {
|
|
|
682
1275
|
* let a caller assert that infrastructure ended without ever recording the
|
|
683
1276
|
* evidence that it did, and the settlement is irreversible.
|
|
684
1277
|
*
|
|
685
|
-
* 2. **Provider-side evidence is claim-fenced;
|
|
1278
|
+
* 2. **Provider-side evidence is claim-fenced; the worker outcome is not.**
|
|
686
1279
|
* Every provider-side mutation requires the current generation and token.
|
|
687
1280
|
* {@link commitOutcome} deliberately requires neither, so a worker can
|
|
688
1281
|
* always deliver its canonical answer.
|
|
689
1282
|
*
|
|
1283
|
+
* 3. **An attempt starts work through one gate, and the gate closes for
|
|
1284
|
+
* good.** {@link AttemptControlState.operationStartGate} is the single
|
|
1285
|
+
* durable ordering point between "this attempt may still run something" and
|
|
1286
|
+
* "it never will again". It opens at {@link createAttempt} and closes
|
|
1287
|
+
* exactly twice over an attempt's life, both times inside the transaction
|
|
1288
|
+
* that made the closing true: on the attempt a newer {@link createAttempt}
|
|
1289
|
+
* supersedes, and on the attempt a terminal settlement settles. Closing it
|
|
1290
|
+
* anywhere else, or reopening it, would let work begin on an attempt whose
|
|
1291
|
+
* answer is already fixed.
|
|
1292
|
+
*
|
|
1293
|
+
* The gate orders admission only. It never gates {@link commitOutcome}: a
|
|
1294
|
+
* settled attempt closes its gate and *keeps* its active operation, so a
|
|
1295
|
+
* worker's canonical answer and a late completion both still find the state
|
|
1296
|
+
* that explains them.
|
|
1297
|
+
*
|
|
1298
|
+
* The runtime fence beside it is monotonic in the same one-way sense.
|
|
1299
|
+
* {@link registerRuntime} allocates each generation, refuses while an
|
|
1300
|
+
* operation is active, and clears readiness when it advances — so a
|
|
1301
|
+
* generation always names one incarnation, and readiness always names one
|
|
1302
|
+
* generation.
|
|
1303
|
+
*
|
|
1304
|
+
* Every refusal these transitions report is evaluated in one fixed order,
|
|
1305
|
+
* stated with {@link RuntimeRegistrationDecision} and
|
|
1306
|
+
* {@link OperationAdmissionDecision}. A realization that reorders it is
|
|
1307
|
+
* non-conforming.
|
|
1308
|
+
*
|
|
690
1309
|
* An operation stays remediable after its attempt stops being the active
|
|
691
1310
|
* attempt for its execution. Claim-fenced discovery, absence, cleanup, and
|
|
692
1311
|
* terminal convergence may update such an attempt and close its operation,
|
|
@@ -725,9 +1344,46 @@ type ExecutionAttemptOutcomeDecision = {
|
|
|
725
1344
|
* — including when the caller's claim is also stale. Validating after the
|
|
726
1345
|
* guards would make the rejection depend on ownership, so the same malformed
|
|
727
1346
|
* payload would throw against one implementation and return `stale` from
|
|
728
|
-
* another.
|
|
1347
|
+
* another. Submitted outcomes follow the same rule through the injected
|
|
1348
|
+
* {@link OutcomeCodec}: `parse` runs before the durable outcome decision. A
|
|
1349
|
+
* committed outcome is always a defined value — a nullish outcome is outside
|
|
1350
|
+
* this port, because "no outcome committed" is itself recorded as the absence
|
|
1351
|
+
* of a stored value and the two would be indistinguishable.
|
|
1352
|
+
*
|
|
1353
|
+
* **The codec's durable text is what an attempt holds, and it is rendered
|
|
1354
|
+
* exactly once per submission.** {@link canonicalizeOutcome} produces the
|
|
1355
|
+
* {@link DurableOutcome} — the text to persist and the outcome that text
|
|
1356
|
+
* reads back as — and {@link commitOutcome} receives that rendering rather
|
|
1357
|
+
* than a raw value, persisting `text` verbatim. What an attempt reports is
|
|
1358
|
+
* therefore never the submitter's copy, which a normalizing codec makes a
|
|
1359
|
+
* different value, and never a second serialization, which a codec whose
|
|
1360
|
+
* serialization is not a fixed point makes a different text. Every later
|
|
1361
|
+
* decision about that outcome is made on the stored text or on the value it
|
|
1362
|
+
* yields: {@link commitOutcome} compares a retry's rendered text against the
|
|
1363
|
+
* stored one through {@link sameDurableOutcome}.
|
|
1364
|
+
*
|
|
1365
|
+
* **Every stored outcome is parsed before it decides anything.** An
|
|
1366
|
+
* implementation that finds a committed text takes it through `parse` ahead
|
|
1367
|
+
* of the duplicate-or-conflict decision, not only on the branch that reports
|
|
1368
|
+
* a value. A text the codec rejects is broken durable state — corruption, or
|
|
1369
|
+
* a codec changed under an existing row — and it fails loudly rather than
|
|
1370
|
+
* reaching the caller as an ordinary competing outcome. For the same reason
|
|
1371
|
+
* an outcome is decoded on every read instead of being handed out as one
|
|
1372
|
+
* shared instance: a codec may reconstruct a mutable object — one that no
|
|
1373
|
+
* freeze even reaches, or that cannot be frozen at all — and one reader
|
|
1374
|
+
* mutating it must not change what the next read reports.
|
|
1375
|
+
* {@link decodeOutcome} is that read, stated on the port so a caller holding
|
|
1376
|
+
* only a durable text can perform it too.
|
|
1377
|
+
*
|
|
1378
|
+
* **An `accepted` decision reports that same fresh decode of the text it just
|
|
1379
|
+
* stored**, not the outcome half of the rendering it was handed. The two are
|
|
1380
|
+
* one value only until someone touches it: a caller validates
|
|
1381
|
+
* {@link DurableOutcome.outcome} before the commit, and a mutable outcome it
|
|
1382
|
+
* changed there would otherwise be reported back as the committed one — a
|
|
1383
|
+
* value no reload of the attempt ever yields.
|
|
1384
|
+
* @typeParam TOutcome - Owner-specific outcome type committed per attempt.
|
|
729
1385
|
*/
|
|
730
|
-
interface ExecutionAttemptRepository {
|
|
1386
|
+
interface ExecutionAttemptRepository<TOutcome> {
|
|
731
1387
|
/**
|
|
732
1388
|
* Persist a new execution attempt record.
|
|
733
1389
|
*
|
|
@@ -735,6 +1391,17 @@ interface ExecutionAttemptRepository {
|
|
|
735
1391
|
* `executionAttemptId` generation; the repository only persists. The new
|
|
736
1392
|
* attempt atomically becomes the active attempt for its execution.
|
|
737
1393
|
*
|
|
1394
|
+
* The new attempt starts with its {@link AttemptControlState} at rest:
|
|
1395
|
+
* generation `0`, no incarnation, no readiness, no active operation, and
|
|
1396
|
+
* {@link AttemptControlState.operationStartGate} `open`.
|
|
1397
|
+
*
|
|
1398
|
+
* **The attempt it supersedes has its start gate closed in this same
|
|
1399
|
+
* transaction**, alongside the active pointer moving. A superseded attempt
|
|
1400
|
+
* whose gate stayed open could admit an operation between the pointer move
|
|
1401
|
+
* and any later cleanup, which is work begun on an attempt nobody addresses
|
|
1402
|
+
* any more. The pointer and the gate therefore become true together or not
|
|
1403
|
+
* at all.
|
|
1404
|
+
*
|
|
738
1405
|
* `executionAttemptId` is unique for all time. Creating an attempt whose
|
|
739
1406
|
* identifier already exists is a caller bug and is rejected — never
|
|
740
1407
|
* answered with a decision and never applied. There is no correct way to
|
|
@@ -905,16 +1572,145 @@ interface ExecutionAttemptRepository {
|
|
|
905
1572
|
* @returns The durable termination decision.
|
|
906
1573
|
*/
|
|
907
1574
|
recordAllocationTerminated(input: RecordAllocationTerminatedInput): Promise<AllocationTerminationDecision>;
|
|
1575
|
+
/**
|
|
1576
|
+
* Register a runtime incarnation as the attempt's endpoint.
|
|
1577
|
+
*
|
|
1578
|
+
* The repository allocates the generation — the caller supplies only the
|
|
1579
|
+
* incarnation identifier, which is both what it is registering and the
|
|
1580
|
+
* idempotency key for having registered it. A registration that succeeds
|
|
1581
|
+
* advances the generation by exactly one and clears
|
|
1582
|
+
* {@link AttemptControlState.runtimeReadyAt}, because the readiness the
|
|
1583
|
+
* previous incarnation proved says nothing about this one.
|
|
1584
|
+
*
|
|
1585
|
+
* It refuses while an operation is active. Advancing the generation there
|
|
1586
|
+
* would fence a running operation's own completion, so the reported
|
|
1587
|
+
* operation has to finish first — and this is why a replay by the incarnation
|
|
1588
|
+
* that is already registered is answered `duplicate` ahead of that refusal:
|
|
1589
|
+
* the operation in the way is frequently the very probe that registration
|
|
1590
|
+
* started.
|
|
1591
|
+
*
|
|
1592
|
+
* The refusal order is the fixed one; see {@link RuntimeRegistrationDecision}.
|
|
1593
|
+
* @param input - Attempt identity, owning execution, and the runtime incarnation.
|
|
1594
|
+
* @returns The durable registration decision.
|
|
1595
|
+
*/
|
|
1596
|
+
registerRuntime(input: RegisterRuntimeInput): Promise<RuntimeRegistrationDecision>;
|
|
1597
|
+
/**
|
|
1598
|
+
* Admit one operation through the attempt's start gate.
|
|
1599
|
+
*
|
|
1600
|
+
* At most one operation occupies an attempt at a time, and the repository
|
|
1601
|
+
* mints its identifier. `admissionKey` is what makes a retry answerable: a
|
|
1602
|
+
* second admission presenting the key the active operation was admitted
|
|
1603
|
+
* under receives that operation's identifier rather than a second slot.
|
|
1604
|
+
*
|
|
1605
|
+
* Every kind requires proven readiness except `runtime-probe`, which is
|
|
1606
|
+
* admitted while {@link AttemptControlState.runtimeReadyAt} is still `null` —
|
|
1607
|
+
* the probe is the bounded no-op that *proves* the endpoint, so requiring
|
|
1608
|
+
* readiness of it would make readiness unreachable.
|
|
1609
|
+
*
|
|
1610
|
+
* The refusal order is the fixed one; see {@link OperationAdmissionDecision}.
|
|
1611
|
+
* @param input - Attempt identity, owning execution, kind, idempotency key, and fence.
|
|
1612
|
+
* @returns The durable admission decision.
|
|
1613
|
+
*/
|
|
1614
|
+
admitOperation(input: AdmitOperationInput): Promise<OperationAdmissionDecision>;
|
|
1615
|
+
/**
|
|
1616
|
+
* Release the attempt's active operation.
|
|
1617
|
+
*
|
|
1618
|
+
* Clears the four active-operation members in one write and records the
|
|
1619
|
+
* completed identifier, so a replay is answered `duplicate` rather than
|
|
1620
|
+
* mistaken for a completion of something that never ran.
|
|
1621
|
+
*
|
|
1622
|
+
* Deliberately unfenced by the active-attempt pointer: a superseded attempt
|
|
1623
|
+
* owes the release exactly as much as an active one, and refusing it would
|
|
1624
|
+
* leave the operation occupying the attempt for good.
|
|
1625
|
+
* @param input - Attempt identity, the operation being completed, and its fence.
|
|
1626
|
+
* @returns The durable completion decision.
|
|
1627
|
+
*/
|
|
1628
|
+
completeOperation(input: CompleteOperationInput): Promise<OperationCompletionDecision>;
|
|
1629
|
+
/**
|
|
1630
|
+
* Record that the registered runtime proved itself ready.
|
|
1631
|
+
*
|
|
1632
|
+
* Written only for the generation the caller fences against, so a proof that
|
|
1633
|
+
* a superseded incarnation produced can never mark the current one ready, and
|
|
1634
|
+
* only while the attempt is still the active attempt for its owner, so a
|
|
1635
|
+
* proof that completed before the attempt was superseded is refused `fenced`
|
|
1636
|
+
* rather than announced for an endpoint nobody will address. Recording is
|
|
1637
|
+
* idempotent: a second call for the same generation reports the instant
|
|
1638
|
+
* already stored, never the caller's own.
|
|
1639
|
+
* @param input - Attempt identity, the generation the proof belongs to, and when it was observed.
|
|
1640
|
+
* @returns The durable readiness decision.
|
|
1641
|
+
*/
|
|
1642
|
+
markRuntimeReady(input: MarkRuntimeReadyInput): Promise<RuntimeReadinessDecision>;
|
|
1643
|
+
/**
|
|
1644
|
+
* Read an attempt's runtime and operation control state.
|
|
1645
|
+
*
|
|
1646
|
+
* The recovery read of {@link AttemptControlState}: a process that lost its
|
|
1647
|
+
* own memory of an attempt — a restart, a second controller — asks what the
|
|
1648
|
+
* durable state is instead of inferring it. Unfenced and regardless of
|
|
1649
|
+
* status, exactly like
|
|
1650
|
+
* {@link ExecutionAttemptRecoveryOperations.getAttemptWithAllocation}, because
|
|
1651
|
+
* the state of a superseded or settled attempt is precisely what a recovering
|
|
1652
|
+
* process needs to see.
|
|
1653
|
+
* @param executionAttemptId - Attempt whose control state to read.
|
|
1654
|
+
* @returns The control state, or `null` when no such attempt exists.
|
|
1655
|
+
*/
|
|
1656
|
+
getAttemptControlState(executionAttemptId: string): Promise<AttemptControlState | null>;
|
|
908
1657
|
/**
|
|
909
1658
|
* Retrieve the active attempt for a given execution.
|
|
910
1659
|
*
|
|
911
1660
|
* Returns `null` when the attempt does not exist or has been superseded
|
|
912
1661
|
* (fenced) by a newer attempt for the same execution.
|
|
913
|
-
* @param executionId -
|
|
1662
|
+
* @param executionId - Owner identifier the attempt belongs to.
|
|
914
1663
|
* @param executionAttemptId - Attempt identifier to look up.
|
|
915
1664
|
* @returns The attempt record if active, or `null`.
|
|
916
1665
|
*/
|
|
917
|
-
getActiveAttempt(executionId:
|
|
1666
|
+
getActiveAttempt(executionId: ExecutionOwnerId, executionAttemptId: string): Promise<ExecutionAttemptRecord | null>;
|
|
1667
|
+
/**
|
|
1668
|
+
* Render a submission as the durable fact a commit of it would write.
|
|
1669
|
+
*
|
|
1670
|
+
* **The port renders an outcome exactly once, and this is where.** A
|
|
1671
|
+
* realization stores the codec's durable text and reports what a reload of
|
|
1672
|
+
* that text yields, so the value an owner converges on is not in general
|
|
1673
|
+
* the value the submitter handed in — a codec may normalize while
|
|
1674
|
+
* serializing. This member produces both halves of that fact together,
|
|
1675
|
+
* without committing anything, so a caller can validate the outcome it will
|
|
1676
|
+
* actually receive and then hand the very same rendering to
|
|
1677
|
+
* {@link commitOutcome}.
|
|
1678
|
+
*
|
|
1679
|
+
* Carrying the rendering rather than re-deriving it is what closes the gap
|
|
1680
|
+
* between the two calls: the submitter's object is read once, so neither a
|
|
1681
|
+
* mutation of it nor a second serialization can make the committed outcome
|
|
1682
|
+
* differ from the validated one.
|
|
1683
|
+
*
|
|
1684
|
+
* Synchronous and free of durable effects: it consults the injected
|
|
1685
|
+
* {@link OutcomeCodec} and nothing else — {@link durableOutcome} is the
|
|
1686
|
+
* rendering rule, and a realization has no freedom to deviate from it.
|
|
1687
|
+
* @param outcome - Outcome a caller is about to submit.
|
|
1688
|
+
* @returns The text a commit would persist and the outcome that text yields.
|
|
1689
|
+
* @throws When the codec rejects the outcome or its own durable text.
|
|
1690
|
+
*/
|
|
1691
|
+
canonicalizeOutcome(outcome: TOutcome): DurableOutcome<TOutcome>;
|
|
1692
|
+
/**
|
|
1693
|
+
* Read a committed durable text back as the outcome it holds.
|
|
1694
|
+
*
|
|
1695
|
+
* The counterpart of {@link canonicalizeOutcome}: that member says what a
|
|
1696
|
+
* commit writes, this one says what a read of that text yields. Both are
|
|
1697
|
+
* the codec's rules rather than a realization's, so
|
|
1698
|
+
* {@link decodeDurableOutcome} is the rendering and a realization has no
|
|
1699
|
+
* freedom to deviate from it.
|
|
1700
|
+
*
|
|
1701
|
+
* It exists because the durable text is the only copy of an outcome nobody
|
|
1702
|
+
* can have touched. Every value the port hands out is decoded from it, and
|
|
1703
|
+
* a caller that has passed one on — to an owner validation, to convergence
|
|
1704
|
+
* — and needs the committed outcome again asks for a fresh decode instead
|
|
1705
|
+
* of reusing a value some other step may have mutated. Each call returns
|
|
1706
|
+
* its own value; a mutation of one changes nothing the next one reports.
|
|
1707
|
+
*
|
|
1708
|
+
* Synchronous and free of durable effects: the caller supplies the text.
|
|
1709
|
+
* @param text - Durable text an attempt committed, as {@link DurableOutcome.text} carries it.
|
|
1710
|
+
* @returns The outcome that text holds, held by nobody else.
|
|
1711
|
+
* @throws When the text is not JSON, or not JSON the codec accepts.
|
|
1712
|
+
*/
|
|
1713
|
+
decodeOutcome(text: string): TOutcome;
|
|
918
1714
|
/**
|
|
919
1715
|
* Commit a terminal outcome for an attempt.
|
|
920
1716
|
*
|
|
@@ -928,12 +1724,13 @@ interface ExecutionAttemptRepository {
|
|
|
928
1724
|
*
|
|
929
1725
|
* 1. `fenced` — the attempt is no longer the active attempt for its
|
|
930
1726
|
* execution. Evaluated first because `accepted` and `duplicate` oblige
|
|
931
|
-
* the caller to converge
|
|
1727
|
+
* the caller to converge owner state, and a superseded attempt must
|
|
932
1728
|
* never drive that convergence.
|
|
933
1729
|
* 2. `duplicate` / `conflict` — an outcome is already committed for this
|
|
934
|
-
* attempt: `duplicate` when {@link
|
|
935
|
-
* canonically equal
|
|
936
|
-
* `conflict` when it differs
|
|
1730
|
+
* attempt: `duplicate` when {@link sameDurableOutcome} judges the text
|
|
1731
|
+
* the submission would commit canonically equal to the stored one,
|
|
1732
|
+
* including member-order-insensitive objects; `conflict` when it differs
|
|
1733
|
+
* under that rule.
|
|
937
1734
|
* 3. `conflict` — a competing terminal transition already settled the
|
|
938
1735
|
* attempt without committing an outcome, that is
|
|
939
1736
|
* {@link recordInfrastructureFailure} or
|
|
@@ -943,10 +1740,24 @@ interface ExecutionAttemptRepository {
|
|
|
943
1740
|
* `settlementKind` nor reopen the attempt.
|
|
944
1741
|
* 4. `accepted` — the outcome becomes canonical, the attempt settles as
|
|
945
1742
|
* `outcome`, and its operation closes.
|
|
946
|
-
*
|
|
1743
|
+
*
|
|
1744
|
+
* The submission arrives already rendered, as the {@link DurableOutcome} the
|
|
1745
|
+
* caller obtained from {@link canonicalizeOutcome}. An implementation
|
|
1746
|
+
* persists `result.text` verbatim and never re-serializes: a second
|
|
1747
|
+
* rendering is what would let the durable answer differ from the one the
|
|
1748
|
+
* owner validated. What it reports for `accepted` is that text decoded
|
|
1749
|
+
* again — {@link decodeOutcome} of `result.text` — rather than
|
|
1750
|
+
* `result.outcome`, which the caller has held since before its own
|
|
1751
|
+
* validation and may have mutated there.
|
|
1752
|
+
*
|
|
1753
|
+
* A settling decision also reports the durable text the attempt holds:
|
|
1754
|
+
* `result.text` for `accepted`, and the stored text for `duplicate` — the
|
|
1755
|
+
* record's own, not the submission's, because the two are the same outcome
|
|
1756
|
+
* without being the same text.
|
|
1757
|
+
* @param input - Attempt identity and the rendering to commit.
|
|
947
1758
|
* @returns The durable decision with the canonical outcome when applicable.
|
|
948
1759
|
*/
|
|
949
|
-
commitOutcome(input: ExecutionAttemptOutcomeCommit): Promise<ExecutionAttemptOutcomeDecision
|
|
1760
|
+
commitOutcome(input: ExecutionAttemptOutcomeCommit<TOutcome>): Promise<ExecutionAttemptOutcomeDecision<TOutcome>>;
|
|
950
1761
|
/**
|
|
951
1762
|
* Settle a pending attempt when dispatch cannot continue before provisioning.
|
|
952
1763
|
*
|
|
@@ -954,10 +1765,10 @@ interface ExecutionAttemptRepository {
|
|
|
954
1765
|
* mean provisioning already began, so the caller must converge the provider
|
|
955
1766
|
* operation instead of abandoning the attempt.
|
|
956
1767
|
* @param executionAttemptId - Pending attempt to abandon.
|
|
957
|
-
* @param executionId -
|
|
1768
|
+
* @param executionId - Owner identifier the attempt belongs to.
|
|
958
1769
|
* @returns The durable abandonment decision.
|
|
959
1770
|
*/
|
|
960
|
-
abandonPendingAttempt(executionAttemptId: string, executionId:
|
|
1771
|
+
abandonPendingAttempt(executionAttemptId: string, executionId: ExecutionOwnerId): Promise<PendingAttemptAbandonmentDecision>;
|
|
961
1772
|
/**
|
|
962
1773
|
* Recovery operations, present only on a recovery-capable repository.
|
|
963
1774
|
*
|
|
@@ -1047,10 +1858,10 @@ interface ExecutionAttemptRecoveryOperations {
|
|
|
1047
1858
|
* same millisecond from ordering differently on two stores. An
|
|
1048
1859
|
* implementation that returned an arbitrary order would make a caller that
|
|
1049
1860
|
* bounds its pass reclaim a different subset on each realization.
|
|
1050
|
-
* @param executionId -
|
|
1861
|
+
* @param executionId - Owner identifier the attempts belong to.
|
|
1051
1862
|
* @returns Allocated, non-settled attempts eligible for recovery, oldest first.
|
|
1052
1863
|
*/
|
|
1053
|
-
getRecoverableAttempts(executionId:
|
|
1864
|
+
getRecoverableAttempts(executionId: ExecutionOwnerId): Promise<readonly RecoverableAttemptRecord[]>;
|
|
1054
1865
|
}
|
|
1055
1866
|
//#endregion
|
|
1056
|
-
export {
|
|
1867
|
+
export { OutcomeCodec as A, RecordProvisioningAbsentInput as B, ExecutionAttemptStatus as C, MarkRuntimeReadyInput as D, InfrastructureFailureDecision as E, RecordAllocationInput as F, RuntimeRegistrationDecision as G, RegisterRuntimeInput as H, RecordAllocationTerminatedInput as I, durableOutcome as J, TakeOverProviderOperationInput as K, RecordInfrastructureFailureInput as L, ProvisionerIncarnationLossDecision as M, ProvisioningAbsenceDecision as N, OperationAdmissionDecision as O, ProvisioningClaimDecision as P, RecordProviderOperationUncertaintyInput as R, ExecutionAttemptSettlementKind as S, HandoffProviderOperationInput as T, RenewProviderOperationClaimInput as U, RecoverableAttemptRecord as V, RuntimeReadinessDecision as W, sameDurableOutcome as X, sameAllocationRef as Y, ExecutionAttemptOutcomeCommit as _, AllocationRefEvolutionDecision as a, ExecutionAttemptRecoveryOperations as b, AttemptOperationStartGate as c, DiscoveredAllocationDecision as d, DuplicateExecutionAttemptError as f, ExecutionAttemptCreate as g, EXECUTION_ATTEMPT_STATUSES as h, AllocationRefEvolution as i, PendingAttemptAbandonmentDecision as j, OperationCompletionDecision as k, BeginProvisioningInput as l, EXECUTION_ATTEMPT_SETTLEMENT_KINDS as m, AdmitOperationInput as n, AllocationTerminationDecision as o, DurableOutcome as p, decodeDurableOutcome as q, AllocationRecordingDecision as r, AttemptControlState as s, ATTEMPT_OPERATION_START_GATES as t, CompleteOperationInput as u, ExecutionAttemptOutcomeDecision as v, ExecutionOwnerId as w, ExecutionAttemptRepository as x, ExecutionAttemptRecord as y, RecordProvisionerIncarnationLostInput as z };
|