@makaio/framework 1.0.0-dev-1787949057928 → 1.0.0-dev-1788511348215

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/.makaio-build.json +2 -2
  2. package/dist/attempt-record-codec-dC9LSFUE.mjs +1 -0
  3. package/dist/authority-state-bootstrap-eitx6Ukn.mjs +1 -0
  4. package/dist/bus/index.mjs +1 -1
  5. package/dist/capabilities-DEvmD9VJ.mjs +1 -0
  6. package/dist/contracts/artifact/index.d.mts +1 -1
  7. package/dist/contracts/capabilities/index.d.mts +2 -2
  8. package/dist/contracts/capabilities/index.mjs +1 -1
  9. package/dist/contracts/client/index.d.mts +1 -1
  10. package/dist/contracts/extension/index.d.mts +2 -2
  11. package/dist/contracts/index.d.mts +12 -12
  12. package/dist/contracts/index.mjs +1 -1
  13. package/dist/contracts/worker/index.d.mts +2 -0
  14. package/dist/contracts/worker/index.mjs +1 -0
  15. package/dist/contracts/workflow/index.d.mts +1 -1
  16. package/dist/contracts/workflow/index.mjs +1 -1
  17. package/dist/{execution-attempt-repository-BJ2W6wwx.d.mts → execution-attempt-repository-DY4KTptE.d.mts} +359 -57
  18. package/dist/{index-8NdPPzUh2.d.mts → index-6trZYnUP2.d.mts} +12 -12
  19. package/dist/{index-CPi0UpRh.d.mts → index-B_8bA2JW.d.mts} +1 -1
  20. package/dist/{index-DfUpqwBa2.d.mts → index-BmilK-co2.d.mts} +53 -53
  21. package/dist/{index-CMekqu_D.d.mts → index-C6aSnO8m.d.mts} +42 -42
  22. package/dist/{index-BUwDmh1I.d.mts → index-CDud4NAm.d.mts} +10 -10
  23. package/dist/{index-CYjgoYeX.d.mts → index-DHH-93YD.d.mts} +1 -1
  24. package/dist/{index-B9io6JAL.d.mts → index-DI2lS7VT.d.mts} +1 -1
  25. package/dist/{index-cLB38AWX.d.mts → index-DNI6UF9h.d.mts} +60 -60
  26. package/dist/{index-NUByQvlg.d.mts → index-DXtQRehh.d.mts} +14 -14
  27. package/dist/{index-CVNbBtA-.d.mts → index-Wbd_syN2.d.mts} +112 -112
  28. package/dist/kernel/extension/index.d.mts +1 -1
  29. package/dist/kernel/index.d.mts +2 -2
  30. package/dist/kernel/observability/index.d.mts +1 -1
  31. package/dist/{namespace-C06lJSuj.d.mts → namespace-K23m4b0n.d.mts} +6 -6
  32. package/dist/package-Cgjpm9om.mjs +7 -0
  33. package/dist/{package-BNFTUFLB.d.mts → package-efR90JGe.d.mts} +59 -30
  34. package/dist/package.json +1 -1
  35. package/dist/runtime-node/index.d.mts +1 -1
  36. package/dist/runtime-node/index.mjs +1 -1
  37. package/dist/runtime-node/makaio-config.d.mts +2 -2
  38. package/dist/runtime-node/makaio-config.mjs +1 -1
  39. package/dist/runtime-node/workflow-execution-bus-access.mjs +1 -1
  40. package/dist/runtime-node/workflow-worker/index.d.mts +2 -2
  41. package/dist/runtime-node/workflow-worker/index.mjs +1 -1
  42. package/dist/{schemas-OWvBn7AH.d.mts → schemas-BCXvEWyF.d.mts} +4 -4
  43. package/dist/{schemas-BAv9RB9M.d.mts → schemas-D7EeKBDJ.d.mts} +3 -3
  44. package/dist/services/adapter-subsystem/index.d.mts +2 -2
  45. package/dist/services/adapter-subsystem/namespace.d.mts +1 -1
  46. package/dist/services/filesystem/namespace.d.mts +6 -6
  47. package/dist/services/filesystem/schemas.d.mts +3 -3
  48. package/dist/services/index.d.mts +9 -9
  49. package/dist/services/provider-context/index.d.mts +1 -1
  50. package/dist/services/session/index.d.mts +1 -1
  51. package/dist/services/settings/namespace.d.mts +2 -2
  52. package/dist/services/subagent-template/index.d.mts +1 -1
  53. package/dist/services/subagent-template/schemas.d.mts +1 -1
  54. package/dist/worker-CARV52hT.mjs +1 -0
  55. package/dist/worker-CLsRbzpk.mjs +1 -0
  56. package/dist/workflow-BomH9DBB.mjs +1 -0
  57. package/dist/workflow-engine/execution-attempt-repository.d.mts +2 -2
  58. package/dist/workflow-engine/execution-attempt-repository.mjs +1 -1
  59. package/dist/workflow-engine/index.d.mts +178 -23
  60. package/dist/workflow-engine/index.mjs +1 -1
  61. package/dist/workflow-engine/package.d.mts +1 -1
  62. package/dist/workflow-engine/package.mjs +1 -1
  63. package/dist/workflow-engine/testing/index.d.mts +30 -9
  64. package/dist/workflow-engine/testing/index.mjs +1 -1
  65. package/dist/workflow-engine/testing/sqlite.d.mts +3 -2
  66. package/dist/workflow-engine/testing/sqlite.mjs +20 -20
  67. package/dist/workflow-engine/workflow-orchestrator.mjs +1 -1
  68. package/dist/workflow-worker-Dul-wcOR.mjs +1 -0
  69. package/package.json +4 -4
  70. package/dist/attempt-record-codec-BYfYkj4M.mjs +0 -1
  71. package/dist/authority-state-bootstrap-CLYus369.mjs +0 -1
  72. package/dist/capabilities-BUwuA7XX.mjs +0 -1
  73. package/dist/contracts/worker-node/index.d.mts +0 -2
  74. package/dist/contracts/worker-node/index.mjs +0 -1
  75. package/dist/package-B01gmu75.mjs +0 -7
  76. package/dist/worker-node-CjuQb9hl.mjs +0 -1
  77. package/dist/worker-node-Dtf9ilsR.mjs +0 -1
  78. package/dist/workflow-C4mck_NW.mjs +0 -1
  79. 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, ProviderAllocationRef, WorkerNodeAllocationLifetime, WorkflowRunResult } from "@makaio/framework/contracts";
2
+ import { BoundedRecoveryEvidence, 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 a workflow result that was accepted.
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 workflow run may be retried.
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
@@ -54,16 +54,196 @@ type ExecutionAttemptSettlementKind = (typeof EXECUTION_ATTEMPT_SETTLEMENT_KINDS
54
54
  */
55
55
  declare function sameAllocationRef(stored: ProviderAllocationRef, candidate: ProviderAllocationRef): boolean;
56
56
  /**
57
- * Compare two terminal workflow results as values.
58
- *
59
- * The rule {@link sameAllocationRef} states, for the same reason: a result may
60
- * carry caller-authored data whose key order is incidental, and a replay of the
61
- * identical result must be reported as `duplicate` by every realization.
62
- * @param committed - Result already committed for the attempt.
63
- * @param candidate - Result the caller presented.
64
- * @returns `true` when the two denote the same terminal result.
57
+ * Identifier of the durable aggregate that owns a series of attempts.
58
+ *
59
+ * For the workflow adapter this is the `WorkflowExecution` id. The generic
60
+ * port never interprets it; it is the fence key that decides which attempt is
61
+ * active for an owner. The alias is scoped to the repository port, the
62
+ * authority, the dispatch runner, and the convergence ports — wire claims and
63
+ * workflow-storage lookup keys keep plain `string`.
64
+ */
65
+ type ExecutionOwnerId = string;
66
+ /**
67
+ * Compare two durable outcome texts as values.
68
+ *
69
+ * The rule {@link sameAllocationRef} states, for the same reason: an outcome
70
+ * may carry caller-authored data whose key order is incidental, and a replay
71
+ * of the identical outcome must be reported as `duplicate` by every
72
+ * realization. The texts are parsed back before canonicalization because a
73
+ * codec is free to emit whatever key order it likes, and key order is not
74
+ * part of the value.
75
+ *
76
+ * Both operands are the codec's durable text — the only representation every
77
+ * realization shares. The stored one is what the attempt actually holds, read
78
+ * out of the realization's own record rather than reproduced from the decoded
79
+ * value: a codec may normalize while serializing, and the contract requires
80
+ * `parse(JSON.parse(serialize(outcome)))` only to succeed, never to serialize
81
+ * back to the same text. Re-serializing the decoded outcome would therefore
82
+ * compare a retry against a text the first commit never wrote, and would
83
+ * answer `conflict` for a worker's honest replay. Comparing texts also keeps
84
+ * a value such as a `bigint` — which a codec may legitimately encode, and
85
+ * which has no JSON form of its own — away from the canonicalizer entirely.
86
+ * @param stored - Durable text the attempt committed.
87
+ * @param candidate - Durable text the caller's submission would commit.
88
+ * @returns `true` when the two denote the same outcome.
89
+ */
90
+ declare function sameDurableOutcome(stored: string, candidate: string): boolean;
91
+ /**
92
+ * The durable text a submission commits as, with the value that text yields.
93
+ *
94
+ * The two are produced together, by one call to {@link durableOutcome}, because
95
+ * they are one durable fact: the text is what a realization writes, and the
96
+ * outcome is what a reload of that text returns. Deriving either from the
97
+ * other later would re-serialize an already-normalized value, and a codec is
98
+ * not required to serialize such a value back to the text it came from.
99
+ *
100
+ * It is the currency of the whole outcome path. A caller renders a submission
101
+ * once through {@link ExecutionAttemptRepository.canonicalizeOutcome},
102
+ * validates `outcome`, and hands the same rendering to
103
+ * {@link ExecutionAttemptRepository.commitOutcome}, which stores `text`
104
+ * verbatim. Nothing between those steps re-reads the submitter's object, so a
105
+ * mutable outcome the caller changes afterwards cannot make the committed
106
+ * value differ from the validated one.
107
+ * @typeParam TOutcome - Owner-specific outcome type committed per attempt.
108
+ */
109
+ interface DurableOutcome<TOutcome> {
110
+ /** The codec text a realization persists for this submission. */
111
+ readonly text: string;
112
+ /** The outcome that text reads back as, produced by the same rendering. */
113
+ readonly outcome: TOutcome;
114
+ }
115
+ /**
116
+ * Render a caller-supplied outcome as the durable fact a commit would write.
117
+ *
118
+ * The rendering rule every realization owes, stated once here for the same
119
+ * reason {@link sameDurableOutcome} is: it decides what an attempt holds, so
120
+ * two realizations deriving it independently could disagree about it.
121
+ *
122
+ * Three steps, in this order and each exactly once. `parse` validates the
123
+ * submission before any durable decision, so a value the codec rejects is
124
+ * refused at the same point by every realization. `serialize` produces the
125
+ * text to persist — the single serialization the pair is built from.
126
+ * `parse(JSON.parse(text))` yields what a reload returns, which is the
127
+ * outcome the port reports and an owner converges on; a codec may normalize
128
+ * while serializing, so it is not in general the submitted value.
129
+ *
130
+ * Nothing is cloned and nothing is frozen. The round trip already produces a
131
+ * value derived from freshly parsed JSON rather than from the caller's
132
+ * object, so no reference reaches back into anything the caller still holds.
133
+ * A `structuredClone` in front of it would reject an outcome type that is
134
+ * codec-serializable but not structured-cloneable, such as a `URL`, and an
135
+ * `Object.freeze` behind it would throw outright for one that is not
136
+ * freezable, such as a non-empty `Uint8Array` — both of which the codec
137
+ * contract allows. What keeps a committed outcome stable instead is that a
138
+ * realization stores the text and decodes it afresh on every read, so a
139
+ * caller that mutates the value it was handed changes nothing a later read
140
+ * reports.
141
+ * @param codec - Owner-injected codec that owns the durable representation.
142
+ * @param outcome - Outcome the caller presented.
143
+ * @returns The text to persist and the outcome it reads back as.
144
+ * @throws When the codec rejects the outcome or its own durable text.
145
+ * @typeParam TOutcome - Owner-specific outcome type committed per attempt.
146
+ */
147
+ declare function durableOutcome<TOutcome>(codec: OutcomeCodec<TOutcome>, outcome: TOutcome): DurableOutcome<TOutcome>;
148
+ /**
149
+ * Read a committed durable text back as the outcome it holds.
150
+ *
151
+ * The read rule every realization owes, stated once here for the same reason
152
+ * {@link durableOutcome} states the write rule: what a stored text yields
153
+ * must not depend on which realization reads it. `JSON.parse` undoes the
154
+ * transport form and `parse` validates the envelope, so a text the codec
155
+ * refuses — durable corruption, or a codec an owner changed under an existing
156
+ * row — fails loudly rather than reaching a caller as an ordinary outcome.
157
+ *
158
+ * Every read decodes afresh, and nothing is shared, cloned, or frozen. A
159
+ * codec may reconstruct a mutable object whose state a freeze does not even
160
+ * reach, such as a `URL`, so one reader mutating the value it was handed must
161
+ * not change what the next read reports; the stored text stays the only
162
+ * source of truth.
163
+ * @param codec - Owner-injected codec that owns the durable representation.
164
+ * @param text - Durable text an attempt committed.
165
+ * @returns The outcome that text holds, held by nobody else.
166
+ * @throws When the text is not JSON, or not JSON the codec accepts.
167
+ * @typeParam TOutcome - Owner-specific outcome type committed per attempt.
168
+ */
169
+ declare function decodeDurableOutcome<TOutcome>(codec: OutcomeCodec<TOutcome>, text: string): TOutcome;
170
+ /**
171
+ * Validates and serializes the owner-specific outcome type of an attempt.
172
+ *
173
+ * Injected into every {@link ExecutionAttemptRepository} realization so the
174
+ * generic port can enforce "input validation precedes every durable decision"
175
+ * without knowing the outcome shape. `parse` runs on every submitted outcome
176
+ * before the durable decision and on every committed outcome read back from
177
+ * storage; `serialize` produces the durable representation.
178
+ *
179
+ * **The durable representation is JSON text, and the two members round-trip
180
+ * through it.** A realization persists exactly what `serialize` returned,
181
+ * reads it back with `JSON.parse`, and hands the parsed value to `parse`; a
182
+ * codec must therefore accept `JSON.parse(serialize(outcome))` for every
183
+ * outcome it produced. Which envelope lives inside that text is the codec's
184
+ * own choice — no realization may assume it is `JSON.stringify(outcome)`.
185
+ *
186
+ * **The text is strict JSON, in the sense `JSON.stringify` defines.** Only
187
+ * values that function can represent are inside the contract: finite numbers,
188
+ * strings, booleans, `null`, arrays, and plain objects. A non-finite number —
189
+ * `Infinity`, `-Infinity`, `NaN`, written as `1e9999` or any other spelling —
190
+ * is outside it, as is anything else JSON has no form for, because the port
191
+ * decides outcome equality by canonicalizing the parsed text with
192
+ * `canonicalStringify` (see {@link sameDurableOutcome}), which renders every
193
+ * such value as `null` and would report two different outcomes as the same
194
+ * one. A codec whose outcomes need those values encodes them as values JSON
195
+ * does have — a string, or a tagged envelope — which is the codec's own job
196
+ * and not something a realization can do for it. Equality follows JSON
197
+ * semantics for the same reason: signed zero is outside the contract, `-0`
198
+ * and `0` are one and the same outcome, because `JSON.stringify` renders both
199
+ * as `0` and the canonical form cannot tell them apart. A codec that needs the
200
+ * sign encodes it explicitly, as it would any other value JSON does not carry.
201
+ *
202
+ * **Both members are deterministic, pure functions of their argument.** The
203
+ * same outcome serializes to the same text every time and the same value
204
+ * parses to the same outcome every time; neither may consult history, a
205
+ * counter, a clock, or any state outside the argument it was handed.
206
+ *
207
+ * The port itself renders a submission exactly once, through
208
+ * {@link ExecutionAttemptRepository.canonicalizeOutcome}, and carries that one
209
+ * {@link DurableOutcome} into {@link ExecutionAttemptRepository.commitOutcome}
210
+ * — so validation, the durable write, and convergence are one rendering and
211
+ * cannot drift apart. Determinism is still owed, because two renderings of
212
+ * the same outcome do meet: `commitOutcome` compares the text a retry renders
213
+ * against the text the first commit stored, and a codec that closed over a
214
+ * counter would misclassify a worker's honest retry.
215
+ *
216
+ * Determinism is not idempotence. `serialize` need not map its own decoded
217
+ * output back to the text it came from: a codec may normalize, and every
218
+ * decision the port makes about a committed outcome is made on the text that
219
+ * commit actually stored rather than on a re-serialization of it.
220
+ *
221
+ * `null` and `undefined` are not outcomes. A realization records "no outcome
222
+ * committed" as the absence of a stored value, so a codec that accepted a
223
+ * nullish outcome would make the two indistinguishable.
224
+ * @typeParam TOutcome - Owner-specific outcome type committed per attempt.
65
225
  */
66
- declare function sameWorkflowResult(committed: WorkflowRunResult, candidate: WorkflowRunResult): boolean;
226
+ interface OutcomeCodec<TOutcome> {
227
+ /**
228
+ * Validate an untrusted value as an outcome.
229
+ *
230
+ * Deterministic and pure: the same input always yields the same outcome.
231
+ * @param input - Value to validate.
232
+ * @returns The validated outcome.
233
+ * @throws When the value violates the outcome contract, including when it is nullish.
234
+ */
235
+ parse(input: unknown): TOutcome;
236
+ /**
237
+ * Serialize an outcome for durable storage.
238
+ *
239
+ * Deterministic and pure: the same outcome always yields the same text,
240
+ * because a retry's rendering is compared against the text an earlier
241
+ * commit stored.
242
+ * @param outcome - Outcome to serialize.
243
+ * @returns The JSON text to persist, which `parse` accepts after `JSON.parse`.
244
+ */
245
+ serialize(outcome: TOutcome): string;
246
+ }
67
247
  /**
68
248
  * Rejection of an attempt identifier that already names a durable attempt.
69
249
  *
@@ -92,8 +272,8 @@ declare class DuplicateExecutionAttemptError extends Error {
92
272
  interface ExecutionAttemptRecord {
93
273
  /** Authority-created attempt identifier. */
94
274
  readonly executionAttemptId: string;
95
- /** Workflow execution identifier this attempt belongs to. */
96
- readonly executionId: string;
275
+ /** Owner identifier of the aggregate this attempt belongs to. */
276
+ readonly executionId: ExecutionOwnerId;
97
277
  /** Current lifecycle status of the attempt. */
98
278
  readonly status: ExecutionAttemptStatus;
99
279
  /** Provider allocation reference, set after allocation recording. */
@@ -116,7 +296,7 @@ interface ExecutionAttemptRecord {
116
296
  * Immutable alongside {@link providerId}. Remediation reads it to decide
117
297
  * whether losing the provisioning process also loses the allocation.
118
298
  */
119
- readonly allocationLifetime: WorkerNodeAllocationLifetime | null;
299
+ readonly allocationLifetime: WorkerAllocationLifetime | null;
120
300
  /**
121
301
  * Provisioner process incarnation that performed the provider call, or
122
302
  * `null` before provisioning began.
@@ -182,7 +362,7 @@ interface RecoverableAttemptRecord extends ExecutionAttemptRecord {
182
362
  /** Always bound for recoverable attempts. */
183
363
  readonly providerId: string;
184
364
  /** Always bound for recoverable attempts. */
185
- readonly allocationLifetime: WorkerNodeAllocationLifetime;
365
+ readonly allocationLifetime: WorkerAllocationLifetime;
186
366
  /** Always bound for recoverable attempts. */
187
367
  readonly provisionerIncarnationId: string;
188
368
  }
@@ -194,8 +374,8 @@ interface RecoverableAttemptRecord extends ExecutionAttemptRecord {
194
374
  interface ExecutionAttemptCreate {
195
375
  /** Authority-created attempt identifier. */
196
376
  readonly executionAttemptId: string;
197
- /** Workflow execution identifier. */
198
- readonly executionId: string;
377
+ /** Owner identifier the attempt belongs to. */
378
+ readonly executionId: ExecutionOwnerId;
199
379
  }
200
380
  /**
201
381
  * Input for claiming the provisioning phase of an attempt.
@@ -208,12 +388,12 @@ interface ExecutionAttemptCreate {
208
388
  interface BeginProvisioningInput {
209
389
  /** Attempt whose provider call is about to begin. */
210
390
  readonly executionAttemptId: string;
211
- /** Workflow execution identifier the attempt must belong to. */
212
- readonly executionId: string;
391
+ /** Owner identifier the attempt must belong to. */
392
+ readonly executionId: ExecutionOwnerId;
213
393
  /** Provider to bind to the attempt, immutably. */
214
394
  readonly providerId: string;
215
395
  /** Allocation lifetime declared by that provider, immutably. */
216
- readonly allocationLifetime: WorkerNodeAllocationLifetime;
396
+ readonly allocationLifetime: WorkerAllocationLifetime;
217
397
  /** Provisioner process incarnation performing the call, immutably. */
218
398
  readonly provisionerIncarnationId: string;
219
399
  /** Controller process incarnation that will hold the initial claim. */
@@ -228,14 +408,22 @@ interface BeginProvisioningInput {
228
408
  * and returns the canonical outcome for convergence. Outcome commitment
229
409
  * carries no claim: a worker's answer never depends on who currently owns
230
410
  * the attempt's provider operation.
411
+ * @typeParam TOutcome - Owner-specific outcome type committed per attempt.
231
412
  */
232
- interface ExecutionAttemptOutcomeCommit {
413
+ interface ExecutionAttemptOutcomeCommit<TOutcome> {
233
414
  /** Authority-created attempt identifier. */
234
415
  readonly executionAttemptId: string;
235
- /** Workflow execution identifier. */
236
- readonly executionId: string;
237
- /** Terminal workflow result to commit. */
238
- readonly result: WorkflowRunResult;
416
+ /** Owner identifier the attempt belongs to. */
417
+ readonly executionId: ExecutionOwnerId;
418
+ /**
419
+ * The rendering to commit, from
420
+ * {@link ExecutionAttemptRepository.canonicalizeOutcome}.
421
+ *
422
+ * A rendering rather than a raw outcome so the value a caller validated and
423
+ * the value that becomes durable are the same one: the caller renders the
424
+ * submission once and never reads its own object again.
425
+ */
426
+ readonly result: DurableOutcome<TOutcome>;
239
427
  }
240
428
  /** Input for extending the lease of a currently held provider operation. */
241
429
  interface RenewProviderOperationClaimInput {
@@ -311,8 +499,8 @@ interface RecordAllocationInput {
311
499
  interface RecordProvisioningAbsentInput {
312
500
  /** Claim authorizing the record. */
313
501
  readonly claim: ProviderOperationClaim;
314
- /** Workflow execution identifier the attempt must belong to. */
315
- readonly executionId: string;
502
+ /** Owner identifier the attempt must belong to. */
503
+ readonly executionId: ExecutionOwnerId;
316
504
  /** Bounded evidence supporting the absence claim. */
317
505
  readonly evidence: BoundedRecoveryEvidence;
318
506
  }
@@ -327,8 +515,8 @@ interface RecordProvisioningAbsentInput {
327
515
  interface RecordProvisionerIncarnationLostInput {
328
516
  /** Claim authorizing the record. */
329
517
  readonly claim: ProviderOperationClaim;
330
- /** Workflow execution identifier the attempt must belong to. */
331
- readonly executionId: string;
518
+ /** Owner identifier the attempt must belong to. */
519
+ readonly executionId: ExecutionOwnerId;
332
520
  /** Proof that a specific provisioner process incarnation is gone. */
333
521
  readonly proof: ProcessBoundProvisionerLossProof;
334
522
  }
@@ -348,8 +536,8 @@ interface RecordAllocationTerminatedInput {
348
536
  interface RecordInfrastructureFailureInput {
349
537
  /** Claim authorizing the settlement. */
350
538
  readonly claim: ProviderOperationClaim;
351
- /** Workflow execution identifier the attempt must belong to. */
352
- readonly executionId: string;
539
+ /** Owner identifier the attempt must belong to. */
540
+ readonly executionId: ExecutionOwnerId;
353
541
  }
354
542
  /**
355
543
  * Input for a compare-and-set evolution of an allocation reference.
@@ -365,8 +553,8 @@ interface RecordInfrastructureFailureInput {
365
553
  interface AllocationRefEvolution {
366
554
  /** Claim authorizing the evolution. */
367
555
  readonly claim: ProviderOperationClaim;
368
- /** Workflow execution identifier the attempt must belong to. */
369
- readonly executionId: string;
556
+ /** Owner identifier the attempt must belong to. */
557
+ readonly executionId: ExecutionOwnerId;
370
558
  /**
371
559
  * The allocation reference the caller believes is currently stored.
372
560
  *
@@ -510,7 +698,7 @@ type ProvisionerIncarnationLossDecision = {
510
698
  readonly kind: 'recorded';
511
699
  } | {
512
700
  readonly kind: 'not-process-bound';
513
- readonly allocationLifetime: WorkerNodeAllocationLifetime | null;
701
+ readonly allocationLifetime: WorkerAllocationLifetime | null;
514
702
  } | {
515
703
  readonly kind: 'incarnation-mismatch';
516
704
  readonly provisionerIncarnationId: string | null;
@@ -620,20 +808,35 @@ type PendingAttemptAbandonmentDecision = {
620
808
  * Durable outcome decision returned by {@link ExecutionAttemptRepository.commitOutcome}.
621
809
  *
622
810
  * - `accepted`: the outcome was committed as canonical for the first time.
623
- * - `duplicate`: an outcome {@link sameWorkflowResult} judges identical to the
624
- * committed one was submitted again; this is a replay. The committed outcome
625
- * is reported, never the caller's copy of it.
811
+ * The stored text decoded is reported, never the caller's copy of it.
812
+ * - `duplicate`: an outcome whose durable text {@link sameDurableOutcome}
813
+ * judges identical to the committed one was submitted again; this is a
814
+ * replay. The committed outcome is reported, never the caller's copy of it.
626
815
  * - `conflict`: the attempt already reached a different terminal state — either
627
816
  * a different committed outcome, or a competing terminal transition that
628
817
  * settled it without one.
629
818
  * - `fenced`: the attempt is no longer the active attempt for this execution.
819
+ *
820
+ * Both settling kinds carry `text`: the durable text the attempt holds for
821
+ * this outcome. For `accepted` that is the text the commit just wrote; for
822
+ * `duplicate` it is the text the earlier commit wrote, which is not
823
+ * necessarily the retry's own rendering — {@link sameDurableOutcome} judges
824
+ * two texts the same outcome while member order, and anything else a codec
825
+ * may render differently, still differs between them. A caller that needs a
826
+ * copy of the committed outcome nobody else has held decodes this text
827
+ * through {@link ExecutionAttemptRepository.decodeOutcome}; decoding its own
828
+ * submission's text instead would hand out the retry's representation rather
829
+ * than the committed one.
830
+ * @typeParam TOutcome - Owner-specific outcome type committed per attempt.
630
831
  */
631
- type ExecutionAttemptOutcomeDecision = {
832
+ type ExecutionAttemptOutcomeDecision<TOutcome> = {
632
833
  readonly kind: 'accepted';
633
- readonly outcome: WorkflowRunResult;
834
+ readonly outcome: TOutcome;
835
+ readonly text: string;
634
836
  } | {
635
837
  readonly kind: 'duplicate';
636
- readonly outcome: WorkflowRunResult;
838
+ readonly outcome: TOutcome;
839
+ readonly text: string;
637
840
  } | {
638
841
  readonly kind: 'conflict';
639
842
  } | {
@@ -682,7 +885,7 @@ type ExecutionAttemptOutcomeDecision = {
682
885
  * let a caller assert that infrastructure ended without ever recording the
683
886
  * evidence that it did, and the settlement is irreversible.
684
887
  *
685
- * 2. **Provider-side evidence is claim-fenced; workflow outcome is not.**
888
+ * 2. **Provider-side evidence is claim-fenced; the worker outcome is not.**
686
889
  * Every provider-side mutation requires the current generation and token.
687
890
  * {@link commitOutcome} deliberately requires neither, so a worker can
688
891
  * always deliver its canonical answer.
@@ -725,9 +928,46 @@ type ExecutionAttemptOutcomeDecision = {
725
928
  * — including when the caller's claim is also stale. Validating after the
726
929
  * guards would make the rejection depend on ownership, so the same malformed
727
930
  * payload would throw against one implementation and return `stale` from
728
- * another.
931
+ * another. Submitted outcomes follow the same rule through the injected
932
+ * {@link OutcomeCodec}: `parse` runs before the durable outcome decision. A
933
+ * committed outcome is always a defined value — a nullish outcome is outside
934
+ * this port, because "no outcome committed" is itself recorded as the absence
935
+ * of a stored value and the two would be indistinguishable.
936
+ *
937
+ * **The codec's durable text is what an attempt holds, and it is rendered
938
+ * exactly once per submission.** {@link canonicalizeOutcome} produces the
939
+ * {@link DurableOutcome} — the text to persist and the outcome that text
940
+ * reads back as — and {@link commitOutcome} receives that rendering rather
941
+ * than a raw value, persisting `text` verbatim. What an attempt reports is
942
+ * therefore never the submitter's copy, which a normalizing codec makes a
943
+ * different value, and never a second serialization, which a codec whose
944
+ * serialization is not a fixed point makes a different text. Every later
945
+ * decision about that outcome is made on the stored text or on the value it
946
+ * yields: {@link commitOutcome} compares a retry's rendered text against the
947
+ * stored one through {@link sameDurableOutcome}.
948
+ *
949
+ * **Every stored outcome is parsed before it decides anything.** An
950
+ * implementation that finds a committed text takes it through `parse` ahead
951
+ * of the duplicate-or-conflict decision, not only on the branch that reports
952
+ * a value. A text the codec rejects is broken durable state — corruption, or
953
+ * a codec changed under an existing row — and it fails loudly rather than
954
+ * reaching the caller as an ordinary competing outcome. For the same reason
955
+ * an outcome is decoded on every read instead of being handed out as one
956
+ * shared instance: a codec may reconstruct a mutable object — one that no
957
+ * freeze even reaches, or that cannot be frozen at all — and one reader
958
+ * mutating it must not change what the next read reports.
959
+ * {@link decodeOutcome} is that read, stated on the port so a caller holding
960
+ * only a durable text can perform it too.
961
+ *
962
+ * **An `accepted` decision reports that same fresh decode of the text it just
963
+ * stored**, not the outcome half of the rendering it was handed. The two are
964
+ * one value only until someone touches it: a caller validates
965
+ * {@link DurableOutcome.outcome} before the commit, and a mutable outcome it
966
+ * changed there would otherwise be reported back as the committed one — a
967
+ * value no reload of the attempt ever yields.
968
+ * @typeParam TOutcome - Owner-specific outcome type committed per attempt.
729
969
  */
730
- interface ExecutionAttemptRepository {
970
+ interface ExecutionAttemptRepository<TOutcome> {
731
971
  /**
732
972
  * Persist a new execution attempt record.
733
973
  *
@@ -910,11 +1150,58 @@ interface ExecutionAttemptRepository {
910
1150
  *
911
1151
  * Returns `null` when the attempt does not exist or has been superseded
912
1152
  * (fenced) by a newer attempt for the same execution.
913
- * @param executionId - Workflow execution identifier.
1153
+ * @param executionId - Owner identifier the attempt belongs to.
914
1154
  * @param executionAttemptId - Attempt identifier to look up.
915
1155
  * @returns The attempt record if active, or `null`.
916
1156
  */
917
- getActiveAttempt(executionId: string, executionAttemptId: string): Promise<ExecutionAttemptRecord | null>;
1157
+ getActiveAttempt(executionId: ExecutionOwnerId, executionAttemptId: string): Promise<ExecutionAttemptRecord | null>;
1158
+ /**
1159
+ * Render a submission as the durable fact a commit of it would write.
1160
+ *
1161
+ * **The port renders an outcome exactly once, and this is where.** A
1162
+ * realization stores the codec's durable text and reports what a reload of
1163
+ * that text yields, so the value an owner converges on is not in general
1164
+ * the value the submitter handed in — a codec may normalize while
1165
+ * serializing. This member produces both halves of that fact together,
1166
+ * without committing anything, so a caller can validate the outcome it will
1167
+ * actually receive and then hand the very same rendering to
1168
+ * {@link commitOutcome}.
1169
+ *
1170
+ * Carrying the rendering rather than re-deriving it is what closes the gap
1171
+ * between the two calls: the submitter's object is read once, so neither a
1172
+ * mutation of it nor a second serialization can make the committed outcome
1173
+ * differ from the validated one.
1174
+ *
1175
+ * Synchronous and free of durable effects: it consults the injected
1176
+ * {@link OutcomeCodec} and nothing else — {@link durableOutcome} is the
1177
+ * rendering rule, and a realization has no freedom to deviate from it.
1178
+ * @param outcome - Outcome a caller is about to submit.
1179
+ * @returns The text a commit would persist and the outcome that text yields.
1180
+ * @throws When the codec rejects the outcome or its own durable text.
1181
+ */
1182
+ canonicalizeOutcome(outcome: TOutcome): DurableOutcome<TOutcome>;
1183
+ /**
1184
+ * Read a committed durable text back as the outcome it holds.
1185
+ *
1186
+ * The counterpart of {@link canonicalizeOutcome}: that member says what a
1187
+ * commit writes, this one says what a read of that text yields. Both are
1188
+ * the codec's rules rather than a realization's, so
1189
+ * {@link decodeDurableOutcome} is the rendering and a realization has no
1190
+ * freedom to deviate from it.
1191
+ *
1192
+ * It exists because the durable text is the only copy of an outcome nobody
1193
+ * can have touched. Every value the port hands out is decoded from it, and
1194
+ * a caller that has passed one on — to an owner validation, to convergence
1195
+ * — and needs the committed outcome again asks for a fresh decode instead
1196
+ * of reusing a value some other step may have mutated. Each call returns
1197
+ * its own value; a mutation of one changes nothing the next one reports.
1198
+ *
1199
+ * Synchronous and free of durable effects: the caller supplies the text.
1200
+ * @param text - Durable text an attempt committed, as {@link DurableOutcome.text} carries it.
1201
+ * @returns The outcome that text holds, held by nobody else.
1202
+ * @throws When the text is not JSON, or not JSON the codec accepts.
1203
+ */
1204
+ decodeOutcome(text: string): TOutcome;
918
1205
  /**
919
1206
  * Commit a terminal outcome for an attempt.
920
1207
  *
@@ -928,12 +1215,13 @@ interface ExecutionAttemptRepository {
928
1215
  *
929
1216
  * 1. `fenced` — the attempt is no longer the active attempt for its
930
1217
  * execution. Evaluated first because `accepted` and `duplicate` oblige
931
- * the caller to converge workflow state, and a superseded attempt must
1218
+ * the caller to converge owner state, and a superseded attempt must
932
1219
  * never drive that convergence.
933
1220
  * 2. `duplicate` / `conflict` — an outcome is already committed for this
934
- * attempt: `duplicate` when {@link sameWorkflowResult} judges it
935
- * canonically equal, including member-order-insensitive objects;
936
- * `conflict` when it differs under that rule.
1221
+ * attempt: `duplicate` when {@link sameDurableOutcome} judges the text
1222
+ * the submission would commit canonically equal to the stored one,
1223
+ * including member-order-insensitive objects; `conflict` when it differs
1224
+ * under that rule.
937
1225
  * 3. `conflict` — a competing terminal transition already settled the
938
1226
  * attempt without committing an outcome, that is
939
1227
  * {@link recordInfrastructureFailure} or
@@ -943,10 +1231,24 @@ interface ExecutionAttemptRepository {
943
1231
  * `settlementKind` nor reopen the attempt.
944
1232
  * 4. `accepted` — the outcome becomes canonical, the attempt settles as
945
1233
  * `outcome`, and its operation closes.
946
- * @param input - Attempt identity and terminal result to commit.
1234
+ *
1235
+ * The submission arrives already rendered, as the {@link DurableOutcome} the
1236
+ * caller obtained from {@link canonicalizeOutcome}. An implementation
1237
+ * persists `result.text` verbatim and never re-serializes: a second
1238
+ * rendering is what would let the durable answer differ from the one the
1239
+ * owner validated. What it reports for `accepted` is that text decoded
1240
+ * again — {@link decodeOutcome} of `result.text` — rather than
1241
+ * `result.outcome`, which the caller has held since before its own
1242
+ * validation and may have mutated there.
1243
+ *
1244
+ * A settling decision also reports the durable text the attempt holds:
1245
+ * `result.text` for `accepted`, and the stored text for `duplicate` — the
1246
+ * record's own, not the submission's, because the two are the same outcome
1247
+ * without being the same text.
1248
+ * @param input - Attempt identity and the rendering to commit.
947
1249
  * @returns The durable decision with the canonical outcome when applicable.
948
1250
  */
949
- commitOutcome(input: ExecutionAttemptOutcomeCommit): Promise<ExecutionAttemptOutcomeDecision>;
1251
+ commitOutcome(input: ExecutionAttemptOutcomeCommit<TOutcome>): Promise<ExecutionAttemptOutcomeDecision<TOutcome>>;
950
1252
  /**
951
1253
  * Settle a pending attempt when dispatch cannot continue before provisioning.
952
1254
  *
@@ -954,10 +1256,10 @@ interface ExecutionAttemptRepository {
954
1256
  * mean provisioning already began, so the caller must converge the provider
955
1257
  * operation instead of abandoning the attempt.
956
1258
  * @param executionAttemptId - Pending attempt to abandon.
957
- * @param executionId - Workflow execution identifier.
1259
+ * @param executionId - Owner identifier the attempt belongs to.
958
1260
  * @returns The durable abandonment decision.
959
1261
  */
960
- abandonPendingAttempt(executionAttemptId: string, executionId: string): Promise<PendingAttemptAbandonmentDecision>;
1262
+ abandonPendingAttempt(executionAttemptId: string, executionId: ExecutionOwnerId): Promise<PendingAttemptAbandonmentDecision>;
961
1263
  /**
962
1264
  * Recovery operations, present only on a recovery-capable repository.
963
1265
  *
@@ -1047,10 +1349,10 @@ interface ExecutionAttemptRecoveryOperations {
1047
1349
  * same millisecond from ordering differently on two stores. An
1048
1350
  * implementation that returned an arbitrary order would make a caller that
1049
1351
  * bounds its pass reclaim a different subset on each realization.
1050
- * @param executionId - Workflow execution identifier.
1352
+ * @param executionId - Owner identifier the attempts belong to.
1051
1353
  * @returns Allocated, non-settled attempts eligible for recovery, oldest first.
1052
1354
  */
1053
- getRecoverableAttempts(executionId: string): Promise<readonly RecoverableAttemptRecord[]>;
1355
+ getRecoverableAttempts(executionId: ExecutionOwnerId): Promise<readonly RecoverableAttemptRecord[]>;
1054
1356
  }
1055
1357
  //#endregion
1056
- export { RecoverableAttemptRecord as A, ProvisioningClaimDecision as C, RecordProviderOperationUncertaintyInput as D, RecordInfrastructureFailureInput as E, TakeOverProviderOperationInput as M, sameAllocationRef as N, RecordProvisionerIncarnationLostInput as O, sameWorkflowResult as P, ProvisioningAbsenceDecision as S, RecordAllocationTerminatedInput as T, ExecutionAttemptStatus as _, BeginProvisioningInput as a, PendingAttemptAbandonmentDecision as b, EXECUTION_ATTEMPT_SETTLEMENT_KINDS as c, ExecutionAttemptOutcomeCommit as d, ExecutionAttemptOutcomeDecision as f, ExecutionAttemptSettlementKind as g, ExecutionAttemptRepository as h, AllocationTerminationDecision as i, RenewProviderOperationClaimInput as j, RecordProvisioningAbsentInput as k, EXECUTION_ATTEMPT_STATUSES as l, ExecutionAttemptRecoveryOperations as m, AllocationRefEvolution as n, DiscoveredAllocationDecision as o, ExecutionAttemptRecord as p, AllocationRefEvolutionDecision as r, DuplicateExecutionAttemptError as s, AllocationRecordingDecision as t, ExecutionAttemptCreate as u, HandoffProviderOperationInput as v, RecordAllocationInput as w, ProvisionerIncarnationLossDecision as x, InfrastructureFailureDecision as y };
1358
+ export { RecordProviderOperationUncertaintyInput as A, PendingAttemptAbandonmentDecision as C, RecordAllocationInput as D, ProvisioningClaimDecision as E, TakeOverProviderOperationInput as F, decodeDurableOutcome as I, durableOutcome as L, RecordProvisioningAbsentInput as M, RecoverableAttemptRecord as N, RecordAllocationTerminatedInput as O, RenewProviderOperationClaimInput as P, sameAllocationRef as R, OutcomeCodec as S, ProvisioningAbsenceDecision as T, ExecutionAttemptSettlementKind as _, BeginProvisioningInput as a, HandoffProviderOperationInput as b, DurableOutcome as c, ExecutionAttemptCreate as d, ExecutionAttemptOutcomeCommit as f, ExecutionAttemptRepository as g, ExecutionAttemptRecoveryOperations as h, AllocationTerminationDecision as i, RecordProvisionerIncarnationLostInput as j, RecordInfrastructureFailureInput as k, EXECUTION_ATTEMPT_SETTLEMENT_KINDS as l, ExecutionAttemptRecord as m, AllocationRefEvolution as n, DiscoveredAllocationDecision as o, ExecutionAttemptOutcomeDecision as p, AllocationRefEvolutionDecision as r, DuplicateExecutionAttemptError as s, AllocationRecordingDecision as t, EXECUTION_ATTEMPT_STATUSES as u, ExecutionAttemptStatus as v, ProvisionerIncarnationLossDecision as w, InfrastructureFailureDecision as x, ExecutionOwnerId as y, sameDurableOutcome as z };