@fabricorg/platform-temporal 4.1.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d5fa7c2: Saga signals can now carry a stable identity, and a new receiver drops redeliveries (issue #23).
8
+
9
+ **Release note: new dedup semantics.** A sender that retries `client.signal()` after a timeout could previously cause a saga to act on the same signal twice. A workflow that receives signals through the new inbox now acts on each `signalId` once.
10
+
11
+ - `SagaSignalEnvelope` gains the optional `signalId` field. It identifies one logical signal, and senders should keep it the same on each redelivery, for example a webhook delivery ID or an approval decision ID. No existing field changed.
12
+ - `createSagaSignalDispatcher()` still forwards the whole envelope. It now throws synchronously on an envelope whose `signalId` is present but blank, before calling the client. It stays synchronous, so a synchronous throw from the injected client still propagates as before.
13
+ - New export `createSagaSignalInbox(options?)`, with the types `SagaSignalInbox`, `SagaSignalInboxOptions` and `SagaSignalInboxSnapshot`. It is a deterministic receiver for workflow code with no `@temporalio/workflow` import, and the package gains no new peer dependency.
14
+ - `receive(envelope)` buffers FIFO per signal name. It returns `false` and drops the envelope when its `signalId` was already received, under any signal name.
15
+ - Envelopes without a `signalId` are not deduplicated.
16
+ - `take(signalName)` dequeues the oldest buffered envelope, and `pending(signalName)` counts them, for use with `condition()`.
17
+ - Dedup state is per inbox instance, so it lasts for one workflow run. To carry it across continue-as-new, pass `snapshot()` into the next run and hand it back to `createSagaSignalInbox()`. The snapshot holds the seen IDs and any accepted-but-untaken envelopes, so neither is lost.
18
+ - The seen-ID set is not pruned and grows with each distinct `signalId`, including across continue-as-new.
19
+ - Workflows that do not use the inbox behave as before.
20
+
21
+ ## 4.1.2
22
+
23
+ ### Patch Changes
24
+
25
+ - fd1f88f: Publish the current packaged documentation, product names and resolved workspace dependency minimums under new patch versions. The release audit found retained package versions whose consumer-visible metadata differs from their registry tarballs. Runtime source is unchanged apart from regenerated generator version stamps.
26
+
27
+ The release guard now retains nested manifest fields while canonicalizing key order, so dependency, peer and export changes cannot disappear from the comparison. Development-only dependencies remain excluded.
28
+
3
29
  ## 4.1.1
4
30
 
5
31
  ### Patch Changes
package/README.md CHANGED
@@ -18,3 +18,38 @@ optional occurrence discriminator for callers constructing those identities dire
18
18
  identities to make a retried or replayed activity idempotent. `SAGA_SIGNALS` declares
19
19
  the stable signal names (`saga.proceed`, `saga.cancel`, `saga.approval`) a workflow may register
20
20
  handlers for.
21
+
22
+ ## Signal identity and the saga signal inbox
23
+
24
+ A sender may deliver the same signal more than once, for example when it retries
25
+ `client.signal()` after a timeout. `SagaSignalEnvelope` has an optional `signalId` that
26
+ identifies one logical signal; reuse the same value on every redelivery, such as a
27
+ webhook delivery ID or an approval decision ID. `createSagaSignalDispatcher()` forwards the
28
+ whole envelope and throws on a blank `signalId` before sending.
29
+
30
+ `createSagaSignalInbox()` is the receiving side. It has no Temporal import and reads no
31
+ clock, randomness or process-global state, so it can run inside workflow code:
32
+
33
+ ```ts
34
+ import * as wf from "@temporalio/workflow";
35
+ import { createSagaSignalInbox, SAGA_SIGNALS, type SagaSignalEnvelope } from "@fabricorg/platform-temporal";
36
+
37
+ const inbox = createSagaSignalInbox(input.inbox); // the previous run's inbox.snapshot(), if any
38
+ wf.setHandler(wf.defineSignal<[SagaSignalEnvelope]>(SAGA_SIGNALS.approval), (envelope) => {
39
+ inbox.receive(envelope);
40
+ });
41
+ if (await wf.condition(() => inbox.pending(SAGA_SIGNALS.approval) > 0, "1 day")) {
42
+ const approval = inbox.take(SAGA_SIGNALS.approval);
43
+ }
44
+ ```
45
+
46
+ - `receive()` buffers envelopes FIFO per signal name and returns `false`, dropping the
47
+ envelope, when its `signalId` was already received under any signal name.
48
+ - Envelopes without a `signalId` are buffered and not deduplicated.
49
+ - Dedup state belongs to the inbox instance, so it lasts for one workflow run. To keep it
50
+ across continue-as-new, pass `inbox.snapshot()` into the next run and hand it back to
51
+ `createSagaSignalInbox()`. The snapshot carries both the seen IDs and any envelope that was
52
+ accepted but not yet taken; carrying only `seenSignalIds()` would drop those envelopes and
53
+ then reject their redelivery.
54
+ - The remembered ID set grows with each distinct `signalId` and is not pruned, so
55
+ continue-as-new does not shrink it. Forgetting an ID would let its redelivery through.
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  createGovernedActionActivities: () => createGovernedActionActivities,
29
29
  createSagaParentActivities: () => createSagaParentActivities,
30
30
  createSagaSignalDispatcher: () => createSagaSignalDispatcher,
31
+ createSagaSignalInbox: () => createSagaSignalInbox,
31
32
  createTemporalSagaDispatcher: () => createTemporalSagaDispatcher,
32
33
  runCompensatedSaga: () => runCompensatedSaga,
33
34
  sagaChildCausation: () => sagaChildCausation,
@@ -59,7 +60,56 @@ function createGovernedActionActivities(invoker) {
59
60
  };
60
61
  }
61
62
  function createSagaSignalDispatcher(client) {
62
- return (workflowId, envelope) => client.signal(workflowId, envelope.signal, envelope);
63
+ return (workflowId, envelope) => {
64
+ assertSignalIdentity(envelope);
65
+ return client.signal(workflowId, envelope.signal, envelope);
66
+ };
67
+ }
68
+ function assertSignalIdentity(envelope) {
69
+ if (envelope.signalId === void 0) return;
70
+ if (typeof envelope.signalId !== "string" || !envelope.signalId.trim()) {
71
+ throw new Error(`Saga signal "${envelope.signal}" for saga "${envelope.sagaId}" has a blank signalId; omit it or supply a stable non-blank identity.`);
72
+ }
73
+ }
74
+ function signalIdentity(envelope) {
75
+ return typeof envelope.signalId === "string" && envelope.signalId.trim() ? envelope.signalId : void 0;
76
+ }
77
+ function createSagaSignalInbox(options = {}) {
78
+ const seen = new Set(options.seenSignalIds ?? []);
79
+ const queues = /* @__PURE__ */ new Map();
80
+ const enqueue = (envelope) => {
81
+ const queue = queues.get(envelope.signal);
82
+ if (queue) queue.push(envelope);
83
+ else queues.set(envelope.signal, [envelope]);
84
+ };
85
+ for (const envelope of options.pending ?? []) {
86
+ const id = signalIdentity(envelope);
87
+ if (id !== void 0) seen.add(id);
88
+ enqueue(envelope);
89
+ }
90
+ return {
91
+ receive(envelope) {
92
+ const id = signalIdentity(envelope);
93
+ if (id !== void 0) {
94
+ if (seen.has(id)) return false;
95
+ seen.add(id);
96
+ }
97
+ enqueue(envelope);
98
+ return true;
99
+ },
100
+ take(signalName) {
101
+ return queues.get(signalName)?.shift();
102
+ },
103
+ pending(signalName) {
104
+ return queues.get(signalName)?.length ?? 0;
105
+ },
106
+ seenSignalIds() {
107
+ return [...seen];
108
+ },
109
+ snapshot() {
110
+ return { seenSignalIds: [...seen], pending: [...queues.values()].flat() };
111
+ }
112
+ };
63
113
  }
64
114
  function startGovernedWorkflow(client, input) {
65
115
  return client.start({ ...input, idConflictPolicy: "use_existing" });
@@ -279,6 +329,7 @@ async function runCompensatedSaga(activity, input) {
279
329
  createGovernedActionActivities,
280
330
  createSagaParentActivities,
281
331
  createSagaSignalDispatcher,
332
+ createSagaSignalInbox,
282
333
  createTemporalSagaDispatcher,
283
334
  runCompensatedSaga,
284
335
  sagaChildCausation,
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> { sagaId: string; signal: string; payload: T; actorId: string; receivedAt: string }\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => client.signal(workflowId, envelope.signal, envelope);\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\nexport interface SagaWorkflowBinding {\n\tactionId: `${string}.${string}`;\n\tworkflowType: string;\n\ttaskQueue: string;\n}\n\n/** Instance-scoped registry; registration order never changes resolution. */\nexport class SagaWorkflowRegistry {\n\tprivate readonly bindings = new Map<string, SagaWorkflowBinding>();\n\n\tconstructor(bindings: readonly SagaWorkflowBinding[] = []) {\n\t\tfor (const binding of bindings) this.register(binding);\n\t}\n\n\tregister(binding: SagaWorkflowBinding): void {\n\t\tif (this.bindings.has(binding.actionId)) {\n\t\t\tthrow new Error(`Saga workflow binding already registered: ${binding.actionId}`);\n\t\t}\n\t\tif (!binding.workflowType.trim() || !binding.taskQueue.trim()) {\n\t\t\tthrow new Error(`Saga workflow binding ${binding.actionId} requires workflowType and taskQueue`);\n\t\t}\n\t\tthis.bindings.set(binding.actionId, { ...binding });\n\t}\n\n\trequire(actionId: string): SagaWorkflowBinding {\n\t\tconst binding = this.bindings.get(actionId);\n\t\tif (!binding) throw new Error(`No saga workflow binding registered for ${actionId}`);\n\t\treturn { ...binding };\n\t}\n}\n\nexport interface TemporalSagaDispatchInput {\n\tactionInvocationId: string;\n\tactionId: `${string}.${string}`;\n\ttenantId: string;\n\tspaceId: string;\n\tworkflowId: string;\n}\n\n/** Starts the registered parent workflow with an invocation-only durable identity. */\nexport function createTemporalSagaDispatcher<THandle>(\n\tclient: WorkflowStartClient<THandle>,\n\tregistry: SagaWorkflowRegistry,\n) {\n\treturn {\n\t\tdispatch: async (input: TemporalSagaDispatchInput) => {\n\t\t\tconst binding = registry.require(input.actionId);\n\t\t\tconst handle = await startGovernedWorkflow(client, {\n\t\t\t\tworkflowType: binding.workflowType,\n\t\t\t\tworkflowId: input.workflowId,\n\t\t\t\ttaskQueue: binding.taskQueue,\n\t\t\t\targs: [{\n\t\t\t\t\tparentInvocationId: input.actionInvocationId,\n\t\t\t\t\tactionId: input.actionId,\n\t\t\t\t\ttenantId: input.tenantId,\n\t\t\t\t\tspaceId: input.spaceId,\n\t\t\t\t}],\n\t\t\t});\n\t\t\tconst runId = runIdFromHandle(handle);\n\t\t\treturn { workflowId: input.workflowId, ...(runId ? { runId } : {}) };\n\t\t},\n\t};\n}\n\nfunction runIdFromHandle(handle: unknown): string | undefined {\n\tif (!handle || typeof handle !== \"object\") return undefined;\n\tconst candidate = (handle as { firstExecutionRunId?: unknown }).firstExecutionRunId;\n\treturn typeof candidate === \"string\" && candidate ? candidate : undefined;\n}\n\n/** Durable record of a single step completing within a saga parent. */\nexport interface SagaParentProgressRecord {\n\t/** The step that just completed. */\n\tstepId: string;\n\t/** Zero-based position of this step in the declared sequence. */\n\tindex: number;\n\t/** Total steps declared for this saga. */\n\ttotal: number;\n\t/** ISO-8601 timestamp the progress was recorded. */\n\tcompletedAt: string;\n}\n\nexport interface SagaParentProgressInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\tprogress: SagaParentProgressRecord;\n}\n\nexport interface SagaParentApprovalInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\t/** The step at which the saga paused for human approval. */\n\tstepId: string;\n\t/** Stable identifier for the approval gate, so a later signal can resume it. */\n\tapprovalId: string;\n\treason: string;\n}\n\nexport interface SagaParentCancellationInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\treason: string;\n\t/** Actor requesting the cancellation, for audit attribution. */\n\tactorId: string;\n}\n\nexport interface SagaParentLifecycle {\n\t/** Record durable progress as each step completes. */\n\trecordProgress(input: SagaParentProgressInput): Promise<void>;\n\t/** Park the parent for human approval at a specific step. */\n\tparkApproval(input: SagaParentApprovalInput): Promise<void>;\n\t/** Cancel the parent saga with an attributable reason. */\n\tcancel(input: SagaParentCancellationInput): Promise<void>;\n\t/** Complete the parent saga with its final result. */\n\tcomplete(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; result: Record<string, unknown> }): Promise<void>;\n\t/** Fail the parent saga, surfacing any compensation records. */\n\tfail(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; error: string; compensation: CompensationRecord[] }): Promise<void>;\n}\n\n/** Parent lifecycle remains explicit durable activities, never workflow-local state. */\nexport function createSagaParentActivities(lifecycle: SagaParentLifecycle) {\n\treturn {\n\t\trecordSagaProgress: (input: SagaParentProgressInput) => lifecycle.recordProgress({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"progress\",\n\t\t\t\t`${input.progress.stepId}:${input.progress.index}`,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tparkSagaApproval: (input: SagaParentApprovalInput) => lifecycle.parkApproval({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"approval\",\n\t\t\t\tinput.approvalId,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcancelSagaParent: (input: SagaParentCancellationInput) => lifecycle.cancel({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"cancel\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcompleteSagaParent: (input: Parameters<SagaParentLifecycle[\"complete\"]>[0]) =>\n\t\t\tlifecycle.complete({\n\t\t\t\t...input,\n\t\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\t\"complete\",\n\t\t\t\t).idempotencyKey,\n\t\t\t}),\n\t\tfailSagaParent: (input: Parameters<SagaParentLifecycle[\"fail\"]>[0]) => lifecycle.fail({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"fail\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t};\n}\n\n// ── Stable parent and child causation identities ───────────────────────────\n\n/**\n * Stable identity for a saga parent's own lifecycle activity (progress,\n * cancellation, approval parking, completion, failure). The activity stepId\n * is namespaced under `parent:` so it can never collide with a forward step or\n * a `compensate:` reversal.\n */\nexport function sagaParentActivityIdentity(\n\tworkflowId: string,\n\tactivity: SagaParentActivityKind,\n\tdiscriminator?: string,\n) {\n\treturn temporalInvocationIdentity({\n\t\tworkflowId,\n\t\tstepId: `parent:${activity}${discriminator ? `:${discriminator}` : \"\"}`,\n\t});\n}\n\nexport type SagaParentActivityKind = \"progress\" | \"approval\" | \"cancel\" | \"complete\" | \"fail\";\n\n/**\n * Stable causation identity linking a child step to its parent invocation.\n * The parent invocation ID becomes the causation ID; the step keeps its own\n * deterministic idempotency key and shares the workflow correlation ID.\n */\nexport function sagaChildCausation(workflowId: string, parentInvocationId: string, stepId: string) {\n\treturn { ...temporalInvocationIdentity({ workflowId, stepId }), causationId: parentInvocationId };\n}\n\n// ── Saga signal names ───────────────────────────────────────────────────────\n\n/** Stable signal names a saga workflow may register handlers for. */\nexport const SAGA_SIGNALS = {\n\tproceed: \"saga.proceed\",\n\tcancel: \"saga.cancel\",\n\tapproval: \"saga.approval\",\n} as const;\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n\n// ── Declared compensation ───────────────────────────────────────────────────\n\nexport interface CompensatedStep {\n\t/** Workflow-local step identity. Must match the compensation declared in the module manifest. */\n\tstepId: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\t/**\n\t * Governed action that reverses this step's effect. Omit for a step with\n\t * nothing to undo; a step whose effect is externally visible should declare one.\n\t */\n\tcompensateWith?: { actionId: string; parameters?: Record<string, unknown> };\n}\n\nexport interface CompensatedSagaInput {\n\tworkflowId: string;\n\trunId: string;\n\tsteps: CompensatedStep[];\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tparentInvocationId?: string;\n\t/** When supplied, every step's reversal must match what the manifest declares. */\n\tworkflow?: DeclaredWorkflow;\n}\n\nexport interface CompensationRecord {\n\tstepId: string;\n\tactionId: string;\n\t/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */\n\terror?: string;\n}\n\nexport interface CompensatedSagaResult {\n\tcompleted: string[];\n\tcompensated: CompensationRecord[];\n\tfailedStep?: { stepId: string; error: string };\n}\n\n/** Reversals are governed invocations too, so they carry their own stable identity. */\nexport function compensationInvocationIdentity(input: { workflowId: string; stepId: string }) {\n\treturn temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });\n}\n\nexport class SagaCompensationError extends Error {\n\toverride readonly name = \"SagaCompensationError\";\n\tconstructor(readonly result: CompensatedSagaResult) {\n\t\tsuper(`Saga step \"${result.failedStep?.stepId}\" failed: ${result.failedStep?.error}`);\n\t}\n}\n\ntype GovernedActivity = ReturnType<typeof createGovernedActionActivities>[\"invokeGovernedAction\"];\n\n/**\n * The governed Host resolves for outcomes that are not success — a blocked policy, a\n * failed validation, a reconciliation, an approval park. Treating a resolved promise\n * as a completed step would silently skip the unwind, so the status is inspected.\n */\nconst SUCCESSFUL_STATUS = \"completed\";\n\nfunction stepFailure(result: unknown, label: string): string | undefined {\n\tif (!result || typeof result !== \"object\") return undefined;\n\tconst status = (result as { status?: unknown }).status;\n\tif (typeof status !== \"string\" || status === SUCCESSFUL_STATUS) return undefined;\n\tconst error = (result as { error?: unknown }).error;\n\treturn `${label} resolved with status \"${status}\"${typeof error === \"string\" && error ? `: ${error}` : \"\"}`;\n}\n\nfunction assertDistinctSteps(steps: CompensatedStep[]): void {\n\tconst seen = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.stepId.trim()) throw new Error(\"Every saga step requires a stepId.\");\n\t\t// A forward step called \"compensate:x\" would share an identity with the reversal of \"x\".\n\t\tif (step.stepId.startsWith(\"compensate:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"compensate:\" prefix.`);\n\t\t// A forward step called \"parent:x\" would share an identity with a parent lifecycle activity.\n\t\tif (step.stepId.startsWith(\"parent:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"parent:\" prefix.`);\n\t\tif (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step \"${step.stepId}\"; step identity must be unique within a workflow.`);\n\t\tseen.add(step.stepId);\n\t}\n}\n\n/** The manifest declaration this saga is expected to honour, when one is supplied. */\nexport interface DeclaredWorkflow {\n\tname: string;\n\tcompensations?: Array<{ step: string; actionId: string }>;\n}\n\nfunction assertMatchesDeclaration(steps: CompensatedStep[], workflow: DeclaredWorkflow): void {\n\tconst declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));\n\tfor (const step of steps) {\n\t\tconst expected = declared.get(step.stepId);\n\t\tif (expected === undefined) {\n\t\t\tif (step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" supplies a reversal the manifest does not declare.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" omits the reversal \"${expected}\" its manifest declares.`);\n\t\tif (step.compensateWith.actionId !== expected) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" reverses with \"${step.compensateWith.actionId}\" but its manifest declares \"${expected}\".`);\n\t\t}\n\t}\n\tfor (const [step, actionId] of declared) {\n\t\tif (!steps.some((candidate) => candidate.stepId === step)) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" declares a reversal \"${actionId}\" for step \"${step}\", which this saga never runs.`);\n\t\t}\n\t}\n}\n\n/**\n * Runs steps in order, accumulating reversals as each succeeds. On failure the\n * accumulated reversals run newest-first, so an unwind never leaves an orphan.\n * Every forward step and every reversal enters through the governed Host with a\n * deterministic idempotency key, so a retried or replayed unwind is not double-applied.\n */\nexport async function runCompensatedSaga(\n\tactivity: GovernedActivity,\n\tinput: CompensatedSagaInput,\n): Promise<CompensatedSagaResult> {\n\tconst scope = {\n\t\ttenantId: input.tenantId,\n\t\tspaceId: input.spaceId,\n\t\tactorId: input.actorId,\n\t\tactorType: input.actorType,\n\t\tworkflowId: input.workflowId,\n\t\trunId: input.runId,\n\t\t...(input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}),\n\t};\n\tassertDistinctSteps(input.steps);\n\tif (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);\n\n\tconst completed: string[] = [];\n\tconst reversals: CompensatedStep[] = [];\n\n\tconst unwind = async (failedStep: { stepId: string; error: string }): Promise<never> => {\n\t\tconst compensated: CompensationRecord[] = [];\n\t\t// Newest-first: the last effect applied is the first one withdrawn.\n\t\tfor (const applied of [...reversals].reverse()) {\n\t\t\tconst reversal = applied.compensateWith!;\n\t\t\tconst record: CompensationRecord = { stepId: applied.stepId, actionId: reversal.actionId };\n\t\t\ttry {\n\t\t\t\tconst result = await activity({\n\t\t\t\t\t...scope,\n\t\t\t\t\tactionId: reversal.actionId,\n\t\t\t\t\tparameters: reversal.parameters ?? applied.parameters,\n\t\t\t\t\tstepId: `compensate:${applied.stepId}`,\n\t\t\t\t});\n\t\t\t\tconst refused = stepFailure(result, `reversal \"${reversal.actionId}\"`);\n\t\t\t\tif (refused) record.error = refused;\n\t\t\t} catch (compensationError) {\n\t\t\t\trecord.error = compensationError instanceof Error ? compensationError.message : String(compensationError);\n\t\t\t}\n\t\t\tcompensated.push(record);\n\t\t}\n\t\tthrow new SagaCompensationError({ completed, compensated, failedStep });\n\t};\n\n\tfor (const step of input.steps) {\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });\n\t\t} catch (error) {\n\t\t\treturn await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t\tconst refused = stepFailure(result, `step \"${step.stepId}\"`);\n\t\tif (refused) return await unwind({ stepId: step.stepId, error: refused });\n\t\tcompleted.push(step.stepId);\n\t\tif (step.compensateWith) reversals.push(step);\n\t}\n\n\treturn { completed, compensated: [] };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> {\n\tsagaId: string;\n\tsignal: string;\n\tpayload: T;\n\tactorId: string;\n\treceivedAt: string;\n\t/**\n\t * Identifies one logical signal. A sender should reuse the same value on each\n\t * redelivery of that signal, for example a webhook delivery ID or an approval\n\t * decision ID, so a retried `client.signal()` after a timeout carries the\n\t * same `signalId` as the first attempt.\n\t *\n\t * A receiver built on {@link createSagaSignalInbox} drops an envelope whose\n\t * `signalId` it has already received. Envelopes without a `signalId` are not\n\t * deduplicated. {@link createSagaSignalDispatcher} throws on a blank value\n\t * before sending.\n\t */\n\tsignalId?: string;\n}\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\t// Not async: a synchronous throw from the client keeps propagating synchronously, as before.\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => {\n\t\tassertSignalIdentity(envelope);\n\t\treturn client.signal(workflowId, envelope.signal, envelope);\n\t};\n}\n\nfunction assertSignalIdentity(envelope: SagaSignalEnvelope): void {\n\tif (envelope.signalId === undefined) return;\n\tif (typeof envelope.signalId !== \"string\" || !envelope.signalId.trim()) {\n\t\tthrow new Error(`Saga signal \"${envelope.signal}\" for saga \"${envelope.sagaId}\" has a blank signalId; omit it or supply a stable non-blank identity.`);\n\t}\n}\n\n/** A stable identity a receiver can deduplicate on, or undefined when the envelope carries none. */\nfunction signalIdentity(envelope: SagaSignalEnvelope): string | undefined {\n\treturn typeof envelope.signalId === \"string\" && envelope.signalId.trim() ? envelope.signalId : undefined;\n}\n\n/** Serializable inbox state, as returned by {@link SagaSignalInbox.snapshot}. */\nexport interface SagaSignalInboxSnapshot {\n\t/** Signal IDs already received, so a redelivery after the handover is still dropped. */\n\tseenSignalIds: string[];\n\t/** Envelopes received but not yet taken, FIFO within each signal name. */\n\tpending: SagaSignalEnvelope[];\n}\n\nexport interface SagaSignalInboxOptions {\n\t/**\n\t * Signal IDs already received by an earlier run of this saga. Prefer passing a\n\t * whole {@link SagaSignalInbox.snapshot}: carrying IDs without `pending` drops\n\t * any envelope that was accepted but not yet taken.\n\t */\n\tseenSignalIds?: readonly string[];\n\t/**\n\t * Envelopes an earlier run accepted but did not take. They are buffered again\n\t * in order without a dedup check, since they were already accepted once.\n\t */\n\tpending?: readonly SagaSignalEnvelope[];\n}\n\nexport interface SagaSignalInbox {\n\t/**\n\t * Buffers the envelope at the back of the queue for its signal name and returns\n\t * `true`. Returns `false` and drops the envelope when its `signalId` was already\n\t * received by this inbox under any signal name. An envelope without a `signalId`\n\t * (or with a blank one) is buffered and not remembered.\n\t */\n\treceive(envelope: SagaSignalEnvelope): boolean;\n\t/** Removes and returns the oldest buffered envelope for `signalName`, or `undefined` when none is buffered. */\n\ttake<T = unknown>(signalName: string): SagaSignalEnvelope<T> | undefined;\n\t/** Number of envelopes buffered for `signalName`, for use in `condition(() => inbox.pending(name) > 0, timeout)`. */\n\tpending(signalName: string): number;\n\t/** Signal IDs received so far, carried-forward ones first, in the order they were first received. */\n\tseenSignalIds(): string[];\n\t/**\n\t * Copies the seen IDs and every buffered envelope. Pass it into the next run across\n\t * continue-as-new and hand it back to `createSagaSignalInbox()`, so neither a\n\t * redelivery nor an accepted-but-untaken signal is lost.\n\t */\n\tsnapshot(): SagaSignalInboxSnapshot;\n}\n\n/**\n * Deterministic receiver for saga signals. It has no Temporal dependency and reads\n * no clock, randomness or process-global state, so it can run inside the workflow\n * sandbox. Wire it with\n * `setHandler(defineSignal(SAGA_SIGNALS.approval), (envelope) => { inbox.receive(envelope); })`.\n *\n * Buffering is FIFO per signal name. Deduplication is keyed on `signalId` across\n * every signal name received by this inbox. Dedup state lives in this instance, so\n * it lasts for one workflow run unless the caller carries\n * {@link SagaSignalInbox.snapshot} into the next run. The remembered set grows\n * with each distinct `signalId` and is not pruned, including across\n * continue-as-new, because forgetting an ID would let its redelivery through.\n */\nexport function createSagaSignalInbox(options: SagaSignalInboxOptions = {}): SagaSignalInbox {\n\tconst seen = new Set<string>(options.seenSignalIds ?? []);\n\tconst queues = new Map<string, SagaSignalEnvelope[]>();\n\tconst enqueue = (envelope: SagaSignalEnvelope) => {\n\t\tconst queue = queues.get(envelope.signal);\n\t\tif (queue) queue.push(envelope);\n\t\telse queues.set(envelope.signal, [envelope]);\n\t};\n\tfor (const envelope of options.pending ?? []) {\n\t\tconst id = signalIdentity(envelope);\n\t\tif (id !== undefined) seen.add(id);\n\t\tenqueue(envelope);\n\t}\n\treturn {\n\t\treceive(envelope) {\n\t\t\tconst id = signalIdentity(envelope);\n\t\t\tif (id !== undefined) {\n\t\t\t\tif (seen.has(id)) return false;\n\t\t\t\tseen.add(id);\n\t\t\t}\n\t\t\tenqueue(envelope);\n\t\t\treturn true;\n\t\t},\n\t\ttake<T = unknown>(signalName: string) {\n\t\t\treturn queues.get(signalName)?.shift() as SagaSignalEnvelope<T> | undefined;\n\t\t},\n\t\tpending(signalName) {\n\t\t\treturn queues.get(signalName)?.length ?? 0;\n\t\t},\n\t\tseenSignalIds() {\n\t\t\treturn [...seen];\n\t\t},\n\t\tsnapshot() {\n\t\t\treturn { seenSignalIds: [...seen], pending: [...queues.values()].flat() };\n\t\t},\n\t};\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\nexport interface SagaWorkflowBinding {\n\tactionId: `${string}.${string}`;\n\tworkflowType: string;\n\ttaskQueue: string;\n}\n\n/** Instance-scoped registry; registration order never changes resolution. */\nexport class SagaWorkflowRegistry {\n\tprivate readonly bindings = new Map<string, SagaWorkflowBinding>();\n\n\tconstructor(bindings: readonly SagaWorkflowBinding[] = []) {\n\t\tfor (const binding of bindings) this.register(binding);\n\t}\n\n\tregister(binding: SagaWorkflowBinding): void {\n\t\tif (this.bindings.has(binding.actionId)) {\n\t\t\tthrow new Error(`Saga workflow binding already registered: ${binding.actionId}`);\n\t\t}\n\t\tif (!binding.workflowType.trim() || !binding.taskQueue.trim()) {\n\t\t\tthrow new Error(`Saga workflow binding ${binding.actionId} requires workflowType and taskQueue`);\n\t\t}\n\t\tthis.bindings.set(binding.actionId, { ...binding });\n\t}\n\n\trequire(actionId: string): SagaWorkflowBinding {\n\t\tconst binding = this.bindings.get(actionId);\n\t\tif (!binding) throw new Error(`No saga workflow binding registered for ${actionId}`);\n\t\treturn { ...binding };\n\t}\n}\n\nexport interface TemporalSagaDispatchInput {\n\tactionInvocationId: string;\n\tactionId: `${string}.${string}`;\n\ttenantId: string;\n\tspaceId: string;\n\tworkflowId: string;\n}\n\n/** Starts the registered parent workflow with an invocation-only durable identity. */\nexport function createTemporalSagaDispatcher<THandle>(\n\tclient: WorkflowStartClient<THandle>,\n\tregistry: SagaWorkflowRegistry,\n) {\n\treturn {\n\t\tdispatch: async (input: TemporalSagaDispatchInput) => {\n\t\t\tconst binding = registry.require(input.actionId);\n\t\t\tconst handle = await startGovernedWorkflow(client, {\n\t\t\t\tworkflowType: binding.workflowType,\n\t\t\t\tworkflowId: input.workflowId,\n\t\t\t\ttaskQueue: binding.taskQueue,\n\t\t\t\targs: [{\n\t\t\t\t\tparentInvocationId: input.actionInvocationId,\n\t\t\t\t\tactionId: input.actionId,\n\t\t\t\t\ttenantId: input.tenantId,\n\t\t\t\t\tspaceId: input.spaceId,\n\t\t\t\t}],\n\t\t\t});\n\t\t\tconst runId = runIdFromHandle(handle);\n\t\t\treturn { workflowId: input.workflowId, ...(runId ? { runId } : {}) };\n\t\t},\n\t};\n}\n\nfunction runIdFromHandle(handle: unknown): string | undefined {\n\tif (!handle || typeof handle !== \"object\") return undefined;\n\tconst candidate = (handle as { firstExecutionRunId?: unknown }).firstExecutionRunId;\n\treturn typeof candidate === \"string\" && candidate ? candidate : undefined;\n}\n\n/** Durable record of a single step completing within a saga parent. */\nexport interface SagaParentProgressRecord {\n\t/** The step that just completed. */\n\tstepId: string;\n\t/** Zero-based position of this step in the declared sequence. */\n\tindex: number;\n\t/** Total steps declared for this saga. */\n\ttotal: number;\n\t/** ISO-8601 timestamp the progress was recorded. */\n\tcompletedAt: string;\n}\n\nexport interface SagaParentProgressInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\tprogress: SagaParentProgressRecord;\n}\n\nexport interface SagaParentApprovalInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\t/** The step at which the saga paused for human approval. */\n\tstepId: string;\n\t/** Stable identifier for the approval gate, so a later signal can resume it. */\n\tapprovalId: string;\n\treason: string;\n}\n\nexport interface SagaParentCancellationInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\treason: string;\n\t/** Actor requesting the cancellation, for audit attribution. */\n\tactorId: string;\n}\n\nexport interface SagaParentLifecycle {\n\t/** Record durable progress as each step completes. */\n\trecordProgress(input: SagaParentProgressInput): Promise<void>;\n\t/** Park the parent for human approval at a specific step. */\n\tparkApproval(input: SagaParentApprovalInput): Promise<void>;\n\t/** Cancel the parent saga with an attributable reason. */\n\tcancel(input: SagaParentCancellationInput): Promise<void>;\n\t/** Complete the parent saga with its final result. */\n\tcomplete(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; result: Record<string, unknown> }): Promise<void>;\n\t/** Fail the parent saga, surfacing any compensation records. */\n\tfail(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; error: string; compensation: CompensationRecord[] }): Promise<void>;\n}\n\n/** Parent lifecycle remains explicit durable activities, never workflow-local state. */\nexport function createSagaParentActivities(lifecycle: SagaParentLifecycle) {\n\treturn {\n\t\trecordSagaProgress: (input: SagaParentProgressInput) => lifecycle.recordProgress({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"progress\",\n\t\t\t\t`${input.progress.stepId}:${input.progress.index}`,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tparkSagaApproval: (input: SagaParentApprovalInput) => lifecycle.parkApproval({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"approval\",\n\t\t\t\tinput.approvalId,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcancelSagaParent: (input: SagaParentCancellationInput) => lifecycle.cancel({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"cancel\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcompleteSagaParent: (input: Parameters<SagaParentLifecycle[\"complete\"]>[0]) =>\n\t\t\tlifecycle.complete({\n\t\t\t\t...input,\n\t\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\t\"complete\",\n\t\t\t\t).idempotencyKey,\n\t\t\t}),\n\t\tfailSagaParent: (input: Parameters<SagaParentLifecycle[\"fail\"]>[0]) => lifecycle.fail({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"fail\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t};\n}\n\n// ── Stable parent and child causation identities ───────────────────────────\n\n/**\n * Stable identity for a saga parent's own lifecycle activity (progress,\n * cancellation, approval parking, completion, failure). The activity stepId\n * is namespaced under `parent:` so it can never collide with a forward step or\n * a `compensate:` reversal.\n */\nexport function sagaParentActivityIdentity(\n\tworkflowId: string,\n\tactivity: SagaParentActivityKind,\n\tdiscriminator?: string,\n) {\n\treturn temporalInvocationIdentity({\n\t\tworkflowId,\n\t\tstepId: `parent:${activity}${discriminator ? `:${discriminator}` : \"\"}`,\n\t});\n}\n\nexport type SagaParentActivityKind = \"progress\" | \"approval\" | \"cancel\" | \"complete\" | \"fail\";\n\n/**\n * Stable causation identity linking a child step to its parent invocation.\n * The parent invocation ID becomes the causation ID; the step keeps its own\n * deterministic idempotency key and shares the workflow correlation ID.\n */\nexport function sagaChildCausation(workflowId: string, parentInvocationId: string, stepId: string) {\n\treturn { ...temporalInvocationIdentity({ workflowId, stepId }), causationId: parentInvocationId };\n}\n\n// ── Saga signal names ───────────────────────────────────────────────────────\n\n/** Stable signal names a saga workflow may register handlers for. */\nexport const SAGA_SIGNALS = {\n\tproceed: \"saga.proceed\",\n\tcancel: \"saga.cancel\",\n\tapproval: \"saga.approval\",\n} as const;\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n\n// ── Declared compensation ───────────────────────────────────────────────────\n\nexport interface CompensatedStep {\n\t/** Workflow-local step identity. Must match the compensation declared in the module manifest. */\n\tstepId: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\t/**\n\t * Governed action that reverses this step's effect. Omit for a step with\n\t * nothing to undo; a step whose effect is externally visible should declare one.\n\t */\n\tcompensateWith?: { actionId: string; parameters?: Record<string, unknown> };\n}\n\nexport interface CompensatedSagaInput {\n\tworkflowId: string;\n\trunId: string;\n\tsteps: CompensatedStep[];\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tparentInvocationId?: string;\n\t/** When supplied, every step's reversal must match what the manifest declares. */\n\tworkflow?: DeclaredWorkflow;\n}\n\nexport interface CompensationRecord {\n\tstepId: string;\n\tactionId: string;\n\t/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */\n\terror?: string;\n}\n\nexport interface CompensatedSagaResult {\n\tcompleted: string[];\n\tcompensated: CompensationRecord[];\n\tfailedStep?: { stepId: string; error: string };\n}\n\n/** Reversals are governed invocations too, so they carry their own stable identity. */\nexport function compensationInvocationIdentity(input: { workflowId: string; stepId: string }) {\n\treturn temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });\n}\n\nexport class SagaCompensationError extends Error {\n\toverride readonly name = \"SagaCompensationError\";\n\tconstructor(readonly result: CompensatedSagaResult) {\n\t\tsuper(`Saga step \"${result.failedStep?.stepId}\" failed: ${result.failedStep?.error}`);\n\t}\n}\n\ntype GovernedActivity = ReturnType<typeof createGovernedActionActivities>[\"invokeGovernedAction\"];\n\n/**\n * The governed Host resolves for outcomes that are not success — a blocked policy, a\n * failed validation, a reconciliation, an approval park. Treating a resolved promise\n * as a completed step would silently skip the unwind, so the status is inspected.\n */\nconst SUCCESSFUL_STATUS = \"completed\";\n\nfunction stepFailure(result: unknown, label: string): string | undefined {\n\tif (!result || typeof result !== \"object\") return undefined;\n\tconst status = (result as { status?: unknown }).status;\n\tif (typeof status !== \"string\" || status === SUCCESSFUL_STATUS) return undefined;\n\tconst error = (result as { error?: unknown }).error;\n\treturn `${label} resolved with status \"${status}\"${typeof error === \"string\" && error ? `: ${error}` : \"\"}`;\n}\n\nfunction assertDistinctSteps(steps: CompensatedStep[]): void {\n\tconst seen = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.stepId.trim()) throw new Error(\"Every saga step requires a stepId.\");\n\t\t// A forward step called \"compensate:x\" would share an identity with the reversal of \"x\".\n\t\tif (step.stepId.startsWith(\"compensate:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"compensate:\" prefix.`);\n\t\t// A forward step called \"parent:x\" would share an identity with a parent lifecycle activity.\n\t\tif (step.stepId.startsWith(\"parent:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"parent:\" prefix.`);\n\t\tif (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step \"${step.stepId}\"; step identity must be unique within a workflow.`);\n\t\tseen.add(step.stepId);\n\t}\n}\n\n/** The manifest declaration this saga is expected to honour, when one is supplied. */\nexport interface DeclaredWorkflow {\n\tname: string;\n\tcompensations?: Array<{ step: string; actionId: string }>;\n}\n\nfunction assertMatchesDeclaration(steps: CompensatedStep[], workflow: DeclaredWorkflow): void {\n\tconst declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));\n\tfor (const step of steps) {\n\t\tconst expected = declared.get(step.stepId);\n\t\tif (expected === undefined) {\n\t\t\tif (step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" supplies a reversal the manifest does not declare.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" omits the reversal \"${expected}\" its manifest declares.`);\n\t\tif (step.compensateWith.actionId !== expected) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" reverses with \"${step.compensateWith.actionId}\" but its manifest declares \"${expected}\".`);\n\t\t}\n\t}\n\tfor (const [step, actionId] of declared) {\n\t\tif (!steps.some((candidate) => candidate.stepId === step)) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" declares a reversal \"${actionId}\" for step \"${step}\", which this saga never runs.`);\n\t\t}\n\t}\n}\n\n/**\n * Runs steps in order, accumulating reversals as each succeeds. On failure the\n * accumulated reversals run newest-first, so an unwind never leaves an orphan.\n * Every forward step and every reversal enters through the governed Host with a\n * deterministic idempotency key, so a retried or replayed unwind is not double-applied.\n */\nexport async function runCompensatedSaga(\n\tactivity: GovernedActivity,\n\tinput: CompensatedSagaInput,\n): Promise<CompensatedSagaResult> {\n\tconst scope = {\n\t\ttenantId: input.tenantId,\n\t\tspaceId: input.spaceId,\n\t\tactorId: input.actorId,\n\t\tactorType: input.actorType,\n\t\tworkflowId: input.workflowId,\n\t\trunId: input.runId,\n\t\t...(input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}),\n\t};\n\tassertDistinctSteps(input.steps);\n\tif (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);\n\n\tconst completed: string[] = [];\n\tconst reversals: CompensatedStep[] = [];\n\n\tconst unwind = async (failedStep: { stepId: string; error: string }): Promise<never> => {\n\t\tconst compensated: CompensationRecord[] = [];\n\t\t// Newest-first: the last effect applied is the first one withdrawn.\n\t\tfor (const applied of [...reversals].reverse()) {\n\t\t\tconst reversal = applied.compensateWith!;\n\t\t\tconst record: CompensationRecord = { stepId: applied.stepId, actionId: reversal.actionId };\n\t\t\ttry {\n\t\t\t\tconst result = await activity({\n\t\t\t\t\t...scope,\n\t\t\t\t\tactionId: reversal.actionId,\n\t\t\t\t\tparameters: reversal.parameters ?? applied.parameters,\n\t\t\t\t\tstepId: `compensate:${applied.stepId}`,\n\t\t\t\t});\n\t\t\t\tconst refused = stepFailure(result, `reversal \"${reversal.actionId}\"`);\n\t\t\t\tif (refused) record.error = refused;\n\t\t\t} catch (compensationError) {\n\t\t\t\trecord.error = compensationError instanceof Error ? compensationError.message : String(compensationError);\n\t\t\t}\n\t\t\tcompensated.push(record);\n\t\t}\n\t\tthrow new SagaCompensationError({ completed, compensated, failedStep });\n\t};\n\n\tfor (const step of input.steps) {\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });\n\t\t} catch (error) {\n\t\t\treturn await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t\tconst refused = stepFailure(result, `step \"${step.stepId}\"`);\n\t\tif (refused) return await unwind({ stepId: step.stepId, error: refused });\n\t\tcompleted.push(step.stepId);\n\t\tif (step.compensateWith) reversals.push(step);\n\t}\n\n\treturn { completed, compensated: [] };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BO,SAAS,2BAA2B,OAAmE;AAC7G,SAAO,EAAE,gBAAgB,YAAY,MAAM,UAAU,IAAI,MAAM,MAAM,IAAI,eAAe,YAAY,MAAM,UAAU,GAAG;AACxH;AAGO,SAAS,+BAA+B,SAAgC;AAC9E,SAAO;AAAA,IACN,sBAAsB,OAAO,UAAuC;AACnE,YAAM,WAAW,2BAA2B,KAAK;AACjD,aAAO,QAAQ,OAAO;AAAA,QACrB,UAAU,MAAM;AAAA,QAAU,YAAY,MAAM;AAAA,QAC5C,UAAU,MAAM;AAAA,QAAU,SAAS,MAAM;AAAA,QACzC,SAAS,MAAM;AAAA,QAAS,WAAW,MAAM;AAAA,QACzC,GAAG;AAAA,QACH,GAAI,MAAM,qBAAqB,EAAE,aAAa,MAAM,mBAAmB,IAAI,CAAC;AAAA,QAC5E,mBAAmB,EAAE,oBAAoB,MAAM,YAAY,eAAe,MAAM,OAAO,gBAAgB,MAAM,OAAO;AAAA,MACrH,CAAC;AAAA,IACF;AAAA,EACD;AACD;AAsBO,SAAS,2BAA2B,QAA8B;AAExE,SAAO,CAAC,YAAoB,aAAiC;AAC5D,yBAAqB,QAAQ;AAC7B,WAAO,OAAO,OAAO,YAAY,SAAS,QAAQ,QAAQ;AAAA,EAC3D;AACD;AAEA,SAAS,qBAAqB,UAAoC;AACjE,MAAI,SAAS,aAAa,OAAW;AACrC,MAAI,OAAO,SAAS,aAAa,YAAY,CAAC,SAAS,SAAS,KAAK,GAAG;AACvE,UAAM,IAAI,MAAM,gBAAgB,SAAS,MAAM,eAAe,SAAS,MAAM,wEAAwE;AAAA,EACtJ;AACD;AAGA,SAAS,eAAe,UAAkD;AACzE,SAAO,OAAO,SAAS,aAAa,YAAY,SAAS,SAAS,KAAK,IAAI,SAAS,WAAW;AAChG;AA2DO,SAAS,sBAAsB,UAAkC,CAAC,GAAoB;AAC5F,QAAM,OAAO,IAAI,IAAY,QAAQ,iBAAiB,CAAC,CAAC;AACxD,QAAM,SAAS,oBAAI,IAAkC;AACrD,QAAM,UAAU,CAAC,aAAiC;AACjD,UAAM,QAAQ,OAAO,IAAI,SAAS,MAAM;AACxC,QAAI,MAAO,OAAM,KAAK,QAAQ;AAAA,QACzB,QAAO,IAAI,SAAS,QAAQ,CAAC,QAAQ,CAAC;AAAA,EAC5C;AACA,aAAW,YAAY,QAAQ,WAAW,CAAC,GAAG;AAC7C,UAAM,KAAK,eAAe,QAAQ;AAClC,QAAI,OAAO,OAAW,MAAK,IAAI,EAAE;AACjC,YAAQ,QAAQ;AAAA,EACjB;AACA,SAAO;AAAA,IACN,QAAQ,UAAU;AACjB,YAAM,KAAK,eAAe,QAAQ;AAClC,UAAI,OAAO,QAAW;AACrB,YAAI,KAAK,IAAI,EAAE,EAAG,QAAO;AACzB,aAAK,IAAI,EAAE;AAAA,MACZ;AACA,cAAQ,QAAQ;AAChB,aAAO;AAAA,IACR;AAAA,IACA,KAAkB,YAAoB;AACrC,aAAO,OAAO,IAAI,UAAU,GAAG,MAAM;AAAA,IACtC;AAAA,IACA,QAAQ,YAAY;AACnB,aAAO,OAAO,IAAI,UAAU,GAAG,UAAU;AAAA,IAC1C;AAAA,IACA,gBAAgB;AACf,aAAO,CAAC,GAAG,IAAI;AAAA,IAChB;AAAA,IACA,WAAW;AACV,aAAO,EAAE,eAAe,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,KAAK,EAAE;AAAA,IACzE;AAAA,EACD;AACD;AAKO,SAAS,sBAA+B,QAAsC,OAAyF;AAC7K,SAAO,OAAO,MAAM,EAAE,GAAG,OAAO,kBAAkB,eAAe,CAAC;AACnE;AASO,IAAM,uBAAN,MAA2B;AAAA,EAChB,WAAW,oBAAI,IAAiC;AAAA,EAEjE,YAAY,WAA2C,CAAC,GAAG;AAC1D,eAAW,WAAW,SAAU,MAAK,SAAS,OAAO;AAAA,EACtD;AAAA,EAEA,SAAS,SAAoC;AAC5C,QAAI,KAAK,SAAS,IAAI,QAAQ,QAAQ,GAAG;AACxC,YAAM,IAAI,MAAM,6CAA6C,QAAQ,QAAQ,EAAE;AAAA,IAChF;AACA,QAAI,CAAC,QAAQ,aAAa,KAAK,KAAK,CAAC,QAAQ,UAAU,KAAK,GAAG;AAC9D,YAAM,IAAI,MAAM,yBAAyB,QAAQ,QAAQ,sCAAsC;AAAA,IAChG;AACA,SAAK,SAAS,IAAI,QAAQ,UAAU,EAAE,GAAG,QAAQ,CAAC;AAAA,EACnD;AAAA,EAEA,QAAQ,UAAuC;AAC9C,UAAM,UAAU,KAAK,SAAS,IAAI,QAAQ;AAC1C,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,2CAA2C,QAAQ,EAAE;AACnF,WAAO,EAAE,GAAG,QAAQ;AAAA,EACrB;AACD;AAWO,SAAS,6BACf,QACA,UACC;AACD,SAAO;AAAA,IACN,UAAU,OAAO,UAAqC;AACrD,YAAM,UAAU,SAAS,QAAQ,MAAM,QAAQ;AAC/C,YAAM,SAAS,MAAM,sBAAsB,QAAQ;AAAA,QAClD,cAAc,QAAQ;AAAA,QACtB,YAAY,MAAM;AAAA,QAClB,WAAW,QAAQ;AAAA,QACnB,MAAM,CAAC;AAAA,UACN,oBAAoB,MAAM;AAAA,UAC1B,UAAU,MAAM;AAAA,UAChB,UAAU,MAAM;AAAA,UAChB,SAAS,MAAM;AAAA,QAChB,CAAC;AAAA,MACF,CAAC;AACD,YAAM,QAAQ,gBAAgB,MAAM;AACpC,aAAO,EAAE,YAAY,MAAM,YAAY,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,EAAG;AAAA,IACpE;AAAA,EACD;AACD;AAEA,SAAS,gBAAgB,QAAqC;AAC7D,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,YAAa,OAA6C;AAChE,SAAO,OAAO,cAAc,YAAY,YAAY,YAAY;AACjE;AA6DO,SAAS,2BAA2B,WAAgC;AAC1E,SAAO;AAAA,IACN,oBAAoB,CAAC,UAAmC,UAAU,eAAe;AAAA,MAChF,GAAG;AAAA,MACH,aAAa,MAAM,eAAe;AAAA,QACjC,MAAM;AAAA,QACN;AAAA,QACA,GAAG,MAAM,SAAS,MAAM,IAAI,MAAM,SAAS,KAAK;AAAA,MACjD,EAAE;AAAA,IACH,CAAC;AAAA,IACD,kBAAkB,CAAC,UAAmC,UAAU,aAAa;AAAA,MAC5E,GAAG;AAAA,MACH,aAAa,MAAM,eAAe;AAAA,QACjC,MAAM;AAAA,QACN;AAAA,QACA,MAAM;AAAA,MACP,EAAE;AAAA,IACH,CAAC;AAAA,IACD,kBAAkB,CAAC,UAAuC,UAAU,OAAO;AAAA,MAC1E,GAAG;AAAA,MACH,aAAa,MAAM,eAAe;AAAA,QACjC,MAAM;AAAA,QACN;AAAA,MACD,EAAE;AAAA,IACH,CAAC;AAAA,IACD,oBAAoB,CAAC,UACpB,UAAU,SAAS;AAAA,MAClB,GAAG;AAAA,MACH,aAAa,MAAM,eAAe;AAAA,QACjC,MAAM;AAAA,QACN;AAAA,MACD,EAAE;AAAA,IACH,CAAC;AAAA,IACF,gBAAgB,CAAC,UAAsD,UAAU,KAAK;AAAA,MACrF,GAAG;AAAA,MACH,aAAa,MAAM,eAAe;AAAA,QACjC,MAAM;AAAA,QACN;AAAA,MACD,EAAE;AAAA,IACH,CAAC;AAAA,EACF;AACD;AAUO,SAAS,2BACf,YACA,UACA,eACC;AACD,SAAO,2BAA2B;AAAA,IACjC;AAAA,IACA,QAAQ,UAAU,QAAQ,GAAG,gBAAgB,IAAI,aAAa,KAAK,EAAE;AAAA,EACtE,CAAC;AACF;AASO,SAAS,mBAAmB,YAAoB,oBAA4B,QAAgB;AAClG,SAAO,EAAE,GAAG,2BAA2B,EAAE,YAAY,OAAO,CAAC,GAAG,aAAa,mBAAmB;AACjG;AAKO,IAAM,eAAe;AAAA,EAC3B,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AACX;AAGO,SAAS,4BAA4B,QAA6C;AACxF,SAAO;AAAA,IACN,OAAO,CAAC,EAAE,cAAc,YAAY,WAAW,KAAK,MACnD,OAAO,MAAM,cAAc,EAAE,YAAY,WAAW,MAAM,0BAA0B,eAAe,CAAC;AAAA,EACtG;AACD;AAGO,SAAS,4BAA4B,OAAoC;AAC/E,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,eAAe,KAAK,EAAG,OAAM,IAAI,MAAM,gFAAgF;AAC5J;AA2CO,SAAS,+BAA+B,OAA+C;AAC7F,SAAO,2BAA2B,EAAE,YAAY,MAAM,YAAY,QAAQ,cAAc,MAAM,MAAM,GAAG,CAAC;AACzG;AAEO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAEhD,YAAqB,QAA+B;AACnD,UAAM,cAAc,OAAO,YAAY,MAAM,aAAa,OAAO,YAAY,KAAK,EAAE;AADhE;AAAA,EAErB;AAAA,EAFqB;AAAA,EADH,OAAO;AAI1B;AASA,IAAM,oBAAoB;AAE1B,SAAS,YAAY,QAAiB,OAAmC;AACxE,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,SAAU,OAAgC;AAChD,MAAI,OAAO,WAAW,YAAY,WAAW,kBAAmB,QAAO;AACvE,QAAM,QAAS,OAA+B;AAC9C,SAAO,GAAG,KAAK,0BAA0B,MAAM,IAAI,OAAO,UAAU,YAAY,QAAQ,KAAK,KAAK,KAAK,EAAE;AAC1G;AAEA,SAAS,oBAAoB,OAAgC;AAC5D,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,OAAO;AACzB,QAAI,CAAC,KAAK,OAAO,KAAK,EAAG,OAAM,IAAI,MAAM,oCAAoC;AAE7E,QAAI,KAAK,OAAO,WAAW,aAAa,EAAG,OAAM,IAAI,MAAM,cAAc,KAAK,MAAM,mDAAmD;AAEvI,QAAI,KAAK,OAAO,WAAW,SAAS,EAAG,OAAM,IAAI,MAAM,cAAc,KAAK,MAAM,+CAA+C;AAC/H,QAAI,KAAK,IAAI,KAAK,MAAM,EAAG,OAAM,IAAI,MAAM,iCAAiC,KAAK,MAAM,oDAAoD;AAC3I,SAAK,IAAI,KAAK,MAAM;AAAA,EACrB;AACD;AAQA,SAAS,yBAAyB,OAA0B,UAAkC;AAC7F,QAAM,WAAW,IAAI,KAAK,SAAS,iBAAiB,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,MAAM,QAAQ,CAAC,CAAC;AACpG,aAAW,QAAQ,OAAO;AACzB,UAAM,WAAW,SAAS,IAAI,KAAK,MAAM;AACzC,QAAI,aAAa,QAAW;AAC3B,UAAI,KAAK,eAAgB,OAAM,IAAI,MAAM,aAAa,SAAS,IAAI,WAAW,KAAK,MAAM,sDAAsD;AAC/I;AAAA,IACD;AACA,QAAI,CAAC,KAAK,eAAgB,OAAM,IAAI,MAAM,aAAa,SAAS,IAAI,WAAW,KAAK,MAAM,yBAAyB,QAAQ,0BAA0B;AACrJ,QAAI,KAAK,eAAe,aAAa,UAAU;AAC9C,YAAM,IAAI,MAAM,aAAa,SAAS,IAAI,WAAW,KAAK,MAAM,oBAAoB,KAAK,eAAe,QAAQ,gCAAgC,QAAQ,IAAI;AAAA,IAC7J;AAAA,EACD;AACA,aAAW,CAAC,MAAM,QAAQ,KAAK,UAAU;AACxC,QAAI,CAAC,MAAM,KAAK,CAAC,cAAc,UAAU,WAAW,IAAI,GAAG;AAC1D,YAAM,IAAI,MAAM,aAAa,SAAS,IAAI,0BAA0B,QAAQ,eAAe,IAAI,gCAAgC;AAAA,IAChI;AAAA,EACD;AACD;AAQA,eAAsB,mBACrB,UACA,OACiC;AACjC,QAAM,QAAQ;AAAA,IACb,UAAU,MAAM;AAAA,IAChB,SAAS,MAAM;AAAA,IACf,SAAS,MAAM;AAAA,IACf,WAAW,MAAM;AAAA,IACjB,YAAY,MAAM;AAAA,IAClB,OAAO,MAAM;AAAA,IACb,GAAI,MAAM,qBAAqB,EAAE,oBAAoB,MAAM,mBAAmB,IAAI,CAAC;AAAA,EACpF;AACA,sBAAoB,MAAM,KAAK;AAC/B,MAAI,MAAM,SAAU,0BAAyB,MAAM,OAAO,MAAM,QAAQ;AAExE,QAAM,YAAsB,CAAC;AAC7B,QAAM,YAA+B,CAAC;AAEtC,QAAM,SAAS,OAAO,eAAkE;AACvF,UAAM,cAAoC,CAAC;AAE3C,eAAW,WAAW,CAAC,GAAG,SAAS,EAAE,QAAQ,GAAG;AAC/C,YAAM,WAAW,QAAQ;AACzB,YAAM,SAA6B,EAAE,QAAQ,QAAQ,QAAQ,UAAU,SAAS,SAAS;AACzF,UAAI;AACH,cAAM,SAAS,MAAM,SAAS;AAAA,UAC7B,GAAG;AAAA,UACH,UAAU,SAAS;AAAA,UACnB,YAAY,SAAS,cAAc,QAAQ;AAAA,UAC3C,QAAQ,cAAc,QAAQ,MAAM;AAAA,QACrC,CAAC;AACD,cAAM,UAAU,YAAY,QAAQ,aAAa,SAAS,QAAQ,GAAG;AACrE,YAAI,QAAS,QAAO,QAAQ;AAAA,MAC7B,SAAS,mBAAmB;AAC3B,eAAO,QAAQ,6BAA6B,QAAQ,kBAAkB,UAAU,OAAO,iBAAiB;AAAA,MACzG;AACA,kBAAY,KAAK,MAAM;AAAA,IACxB;AACA,UAAM,IAAI,sBAAsB,EAAE,WAAW,aAAa,WAAW,CAAC;AAAA,EACvE;AAEA,aAAW,QAAQ,MAAM,OAAO;AAC/B,QAAI;AACJ,QAAI;AACH,eAAS,MAAM,SAAS,EAAE,GAAG,OAAO,UAAU,KAAK,UAAU,YAAY,KAAK,YAAY,QAAQ,KAAK,OAAO,CAAC;AAAA,IAChH,SAAS,OAAO;AACf,aAAO,MAAM,OAAO,EAAE,QAAQ,KAAK,QAAQ,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,IAC3G;AACA,UAAM,UAAU,YAAY,QAAQ,SAAS,KAAK,MAAM,GAAG;AAC3D,QAAI,QAAS,QAAO,MAAM,OAAO,EAAE,QAAQ,KAAK,QAAQ,OAAO,QAAQ,CAAC;AACxE,cAAU,KAAK,KAAK,MAAM;AAC1B,QAAI,KAAK,eAAgB,WAAU,KAAK,IAAI;AAAA,EAC7C;AAEA,SAAO,EAAE,WAAW,aAAa,CAAC,EAAE;AACrC;","names":[]}
package/dist/index.d.cts CHANGED
@@ -46,11 +46,78 @@ interface SagaSignalEnvelope<T = unknown> {
46
46
  payload: T;
47
47
  actorId: string;
48
48
  receivedAt: string;
49
+ /**
50
+ * Identifies one logical signal. A sender should reuse the same value on each
51
+ * redelivery of that signal, for example a webhook delivery ID or an approval
52
+ * decision ID, so a retried `client.signal()` after a timeout carries the
53
+ * same `signalId` as the first attempt.
54
+ *
55
+ * A receiver built on {@link createSagaSignalInbox} drops an envelope whose
56
+ * `signalId` it has already received. Envelopes without a `signalId` are not
57
+ * deduplicated. {@link createSagaSignalDispatcher} throws on a blank value
58
+ * before sending.
59
+ */
60
+ signalId?: string;
49
61
  }
50
62
  interface TemporalSignalClient {
51
63
  signal(workflowId: string, signalName: string, payload: unknown): Promise<void>;
52
64
  }
53
65
  declare function createSagaSignalDispatcher(client: TemporalSignalClient): (workflowId: string, envelope: SagaSignalEnvelope) => Promise<void>;
66
+ /** Serializable inbox state, as returned by {@link SagaSignalInbox.snapshot}. */
67
+ interface SagaSignalInboxSnapshot {
68
+ /** Signal IDs already received, so a redelivery after the handover is still dropped. */
69
+ seenSignalIds: string[];
70
+ /** Envelopes received but not yet taken, FIFO within each signal name. */
71
+ pending: SagaSignalEnvelope[];
72
+ }
73
+ interface SagaSignalInboxOptions {
74
+ /**
75
+ * Signal IDs already received by an earlier run of this saga. Prefer passing a
76
+ * whole {@link SagaSignalInbox.snapshot}: carrying IDs without `pending` drops
77
+ * any envelope that was accepted but not yet taken.
78
+ */
79
+ seenSignalIds?: readonly string[];
80
+ /**
81
+ * Envelopes an earlier run accepted but did not take. They are buffered again
82
+ * in order without a dedup check, since they were already accepted once.
83
+ */
84
+ pending?: readonly SagaSignalEnvelope[];
85
+ }
86
+ interface SagaSignalInbox {
87
+ /**
88
+ * Buffers the envelope at the back of the queue for its signal name and returns
89
+ * `true`. Returns `false` and drops the envelope when its `signalId` was already
90
+ * received by this inbox under any signal name. An envelope without a `signalId`
91
+ * (or with a blank one) is buffered and not remembered.
92
+ */
93
+ receive(envelope: SagaSignalEnvelope): boolean;
94
+ /** Removes and returns the oldest buffered envelope for `signalName`, or `undefined` when none is buffered. */
95
+ take<T = unknown>(signalName: string): SagaSignalEnvelope<T> | undefined;
96
+ /** Number of envelopes buffered for `signalName`, for use in `condition(() => inbox.pending(name) > 0, timeout)`. */
97
+ pending(signalName: string): number;
98
+ /** Signal IDs received so far, carried-forward ones first, in the order they were first received. */
99
+ seenSignalIds(): string[];
100
+ /**
101
+ * Copies the seen IDs and every buffered envelope. Pass it into the next run across
102
+ * continue-as-new and hand it back to `createSagaSignalInbox()`, so neither a
103
+ * redelivery nor an accepted-but-untaken signal is lost.
104
+ */
105
+ snapshot(): SagaSignalInboxSnapshot;
106
+ }
107
+ /**
108
+ * Deterministic receiver for saga signals. It has no Temporal dependency and reads
109
+ * no clock, randomness or process-global state, so it can run inside the workflow
110
+ * sandbox. Wire it with
111
+ * `setHandler(defineSignal(SAGA_SIGNALS.approval), (envelope) => { inbox.receive(envelope); })`.
112
+ *
113
+ * Buffering is FIFO per signal name. Deduplication is keyed on `signalId` across
114
+ * every signal name received by this inbox. Dedup state lives in this instance, so
115
+ * it lasts for one workflow run unless the caller carries
116
+ * {@link SagaSignalInbox.snapshot} into the next run. The remembered set grows
117
+ * with each distinct `signalId` and is not pruned, including across
118
+ * continue-as-new, because forgetting an ID would let its redelivery through.
119
+ */
120
+ declare function createSagaSignalInbox(options?: SagaSignalInboxOptions): SagaSignalInbox;
54
121
  interface WorkflowStartClient<THandle = unknown> {
55
122
  start(input: {
56
123
  workflowType: string;
@@ -271,4 +338,4 @@ interface DeclaredWorkflow {
271
338
  */
272
339
  declare function runCompensatedSaga(activity: GovernedActivity, input: CompensatedSagaInput): Promise<CompensatedSagaResult>;
273
340
 
274
- export { type CompensatedSagaInput, type CompensatedSagaResult, type CompensatedStep, type CompensationRecord, type DeclaredWorkflow, type GovernedActionActivityInput, type GovernedActionInvoker, SAGA_SIGNALS, SagaCompensationError, type SagaParentActivityKind, type SagaParentApprovalInput, type SagaParentCancellationInput, type SagaParentLifecycle, type SagaParentProgressInput, type SagaParentProgressRecord, type SagaSignalEnvelope, type SagaWorkflowBinding, SagaWorkflowRegistry, type TemporalSagaDispatchInput, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, compensationInvocationIdentity, createGovernedActionActivities, createSagaParentActivities, createSagaSignalDispatcher, createTemporalSagaDispatcher, runCompensatedSaga, sagaChildCausation, sagaParentActivityIdentity, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
341
+ export { type CompensatedSagaInput, type CompensatedSagaResult, type CompensatedStep, type CompensationRecord, type DeclaredWorkflow, type GovernedActionActivityInput, type GovernedActionInvoker, SAGA_SIGNALS, SagaCompensationError, type SagaParentActivityKind, type SagaParentApprovalInput, type SagaParentCancellationInput, type SagaParentLifecycle, type SagaParentProgressInput, type SagaParentProgressRecord, type SagaSignalEnvelope, type SagaSignalInbox, type SagaSignalInboxOptions, type SagaSignalInboxSnapshot, type SagaWorkflowBinding, SagaWorkflowRegistry, type TemporalSagaDispatchInput, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, compensationInvocationIdentity, createGovernedActionActivities, createSagaParentActivities, createSagaSignalDispatcher, createSagaSignalInbox, createTemporalSagaDispatcher, runCompensatedSaga, sagaChildCausation, sagaParentActivityIdentity, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
package/dist/index.d.ts CHANGED
@@ -46,11 +46,78 @@ interface SagaSignalEnvelope<T = unknown> {
46
46
  payload: T;
47
47
  actorId: string;
48
48
  receivedAt: string;
49
+ /**
50
+ * Identifies one logical signal. A sender should reuse the same value on each
51
+ * redelivery of that signal, for example a webhook delivery ID or an approval
52
+ * decision ID, so a retried `client.signal()` after a timeout carries the
53
+ * same `signalId` as the first attempt.
54
+ *
55
+ * A receiver built on {@link createSagaSignalInbox} drops an envelope whose
56
+ * `signalId` it has already received. Envelopes without a `signalId` are not
57
+ * deduplicated. {@link createSagaSignalDispatcher} throws on a blank value
58
+ * before sending.
59
+ */
60
+ signalId?: string;
49
61
  }
50
62
  interface TemporalSignalClient {
51
63
  signal(workflowId: string, signalName: string, payload: unknown): Promise<void>;
52
64
  }
53
65
  declare function createSagaSignalDispatcher(client: TemporalSignalClient): (workflowId: string, envelope: SagaSignalEnvelope) => Promise<void>;
66
+ /** Serializable inbox state, as returned by {@link SagaSignalInbox.snapshot}. */
67
+ interface SagaSignalInboxSnapshot {
68
+ /** Signal IDs already received, so a redelivery after the handover is still dropped. */
69
+ seenSignalIds: string[];
70
+ /** Envelopes received but not yet taken, FIFO within each signal name. */
71
+ pending: SagaSignalEnvelope[];
72
+ }
73
+ interface SagaSignalInboxOptions {
74
+ /**
75
+ * Signal IDs already received by an earlier run of this saga. Prefer passing a
76
+ * whole {@link SagaSignalInbox.snapshot}: carrying IDs without `pending` drops
77
+ * any envelope that was accepted but not yet taken.
78
+ */
79
+ seenSignalIds?: readonly string[];
80
+ /**
81
+ * Envelopes an earlier run accepted but did not take. They are buffered again
82
+ * in order without a dedup check, since they were already accepted once.
83
+ */
84
+ pending?: readonly SagaSignalEnvelope[];
85
+ }
86
+ interface SagaSignalInbox {
87
+ /**
88
+ * Buffers the envelope at the back of the queue for its signal name and returns
89
+ * `true`. Returns `false` and drops the envelope when its `signalId` was already
90
+ * received by this inbox under any signal name. An envelope without a `signalId`
91
+ * (or with a blank one) is buffered and not remembered.
92
+ */
93
+ receive(envelope: SagaSignalEnvelope): boolean;
94
+ /** Removes and returns the oldest buffered envelope for `signalName`, or `undefined` when none is buffered. */
95
+ take<T = unknown>(signalName: string): SagaSignalEnvelope<T> | undefined;
96
+ /** Number of envelopes buffered for `signalName`, for use in `condition(() => inbox.pending(name) > 0, timeout)`. */
97
+ pending(signalName: string): number;
98
+ /** Signal IDs received so far, carried-forward ones first, in the order they were first received. */
99
+ seenSignalIds(): string[];
100
+ /**
101
+ * Copies the seen IDs and every buffered envelope. Pass it into the next run across
102
+ * continue-as-new and hand it back to `createSagaSignalInbox()`, so neither a
103
+ * redelivery nor an accepted-but-untaken signal is lost.
104
+ */
105
+ snapshot(): SagaSignalInboxSnapshot;
106
+ }
107
+ /**
108
+ * Deterministic receiver for saga signals. It has no Temporal dependency and reads
109
+ * no clock, randomness or process-global state, so it can run inside the workflow
110
+ * sandbox. Wire it with
111
+ * `setHandler(defineSignal(SAGA_SIGNALS.approval), (envelope) => { inbox.receive(envelope); })`.
112
+ *
113
+ * Buffering is FIFO per signal name. Deduplication is keyed on `signalId` across
114
+ * every signal name received by this inbox. Dedup state lives in this instance, so
115
+ * it lasts for one workflow run unless the caller carries
116
+ * {@link SagaSignalInbox.snapshot} into the next run. The remembered set grows
117
+ * with each distinct `signalId` and is not pruned, including across
118
+ * continue-as-new, because forgetting an ID would let its redelivery through.
119
+ */
120
+ declare function createSagaSignalInbox(options?: SagaSignalInboxOptions): SagaSignalInbox;
54
121
  interface WorkflowStartClient<THandle = unknown> {
55
122
  start(input: {
56
123
  workflowType: string;
@@ -271,4 +338,4 @@ interface DeclaredWorkflow {
271
338
  */
272
339
  declare function runCompensatedSaga(activity: GovernedActivity, input: CompensatedSagaInput): Promise<CompensatedSagaResult>;
273
340
 
274
- export { type CompensatedSagaInput, type CompensatedSagaResult, type CompensatedStep, type CompensationRecord, type DeclaredWorkflow, type GovernedActionActivityInput, type GovernedActionInvoker, SAGA_SIGNALS, SagaCompensationError, type SagaParentActivityKind, type SagaParentApprovalInput, type SagaParentCancellationInput, type SagaParentLifecycle, type SagaParentProgressInput, type SagaParentProgressRecord, type SagaSignalEnvelope, type SagaWorkflowBinding, SagaWorkflowRegistry, type TemporalSagaDispatchInput, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, compensationInvocationIdentity, createGovernedActionActivities, createSagaParentActivities, createSagaSignalDispatcher, createTemporalSagaDispatcher, runCompensatedSaga, sagaChildCausation, sagaParentActivityIdentity, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
341
+ export { type CompensatedSagaInput, type CompensatedSagaResult, type CompensatedStep, type CompensationRecord, type DeclaredWorkflow, type GovernedActionActivityInput, type GovernedActionInvoker, SAGA_SIGNALS, SagaCompensationError, type SagaParentActivityKind, type SagaParentApprovalInput, type SagaParentCancellationInput, type SagaParentLifecycle, type SagaParentProgressInput, type SagaParentProgressRecord, type SagaSignalEnvelope, type SagaSignalInbox, type SagaSignalInboxOptions, type SagaSignalInboxSnapshot, type SagaWorkflowBinding, SagaWorkflowRegistry, type TemporalSagaDispatchInput, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, compensationInvocationIdentity, createGovernedActionActivities, createSagaParentActivities, createSagaSignalDispatcher, createSagaSignalInbox, createTemporalSagaDispatcher, runCompensatedSaga, sagaChildCausation, sagaParentActivityIdentity, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
package/dist/index.js CHANGED
@@ -21,7 +21,56 @@ function createGovernedActionActivities(invoker) {
21
21
  };
22
22
  }
23
23
  function createSagaSignalDispatcher(client) {
24
- return (workflowId, envelope) => client.signal(workflowId, envelope.signal, envelope);
24
+ return (workflowId, envelope) => {
25
+ assertSignalIdentity(envelope);
26
+ return client.signal(workflowId, envelope.signal, envelope);
27
+ };
28
+ }
29
+ function assertSignalIdentity(envelope) {
30
+ if (envelope.signalId === void 0) return;
31
+ if (typeof envelope.signalId !== "string" || !envelope.signalId.trim()) {
32
+ throw new Error(`Saga signal "${envelope.signal}" for saga "${envelope.sagaId}" has a blank signalId; omit it or supply a stable non-blank identity.`);
33
+ }
34
+ }
35
+ function signalIdentity(envelope) {
36
+ return typeof envelope.signalId === "string" && envelope.signalId.trim() ? envelope.signalId : void 0;
37
+ }
38
+ function createSagaSignalInbox(options = {}) {
39
+ const seen = new Set(options.seenSignalIds ?? []);
40
+ const queues = /* @__PURE__ */ new Map();
41
+ const enqueue = (envelope) => {
42
+ const queue = queues.get(envelope.signal);
43
+ if (queue) queue.push(envelope);
44
+ else queues.set(envelope.signal, [envelope]);
45
+ };
46
+ for (const envelope of options.pending ?? []) {
47
+ const id = signalIdentity(envelope);
48
+ if (id !== void 0) seen.add(id);
49
+ enqueue(envelope);
50
+ }
51
+ return {
52
+ receive(envelope) {
53
+ const id = signalIdentity(envelope);
54
+ if (id !== void 0) {
55
+ if (seen.has(id)) return false;
56
+ seen.add(id);
57
+ }
58
+ enqueue(envelope);
59
+ return true;
60
+ },
61
+ take(signalName) {
62
+ return queues.get(signalName)?.shift();
63
+ },
64
+ pending(signalName) {
65
+ return queues.get(signalName)?.length ?? 0;
66
+ },
67
+ seenSignalIds() {
68
+ return [...seen];
69
+ },
70
+ snapshot() {
71
+ return { seenSignalIds: [...seen], pending: [...queues.values()].flat() };
72
+ }
73
+ };
25
74
  }
26
75
  function startGovernedWorkflow(client, input) {
27
76
  return client.start({ ...input, idConflictPolicy: "use_existing" });
@@ -240,6 +289,7 @@ export {
240
289
  createGovernedActionActivities,
241
290
  createSagaParentActivities,
242
291
  createSagaSignalDispatcher,
292
+ createSagaSignalInbox,
243
293
  createTemporalSagaDispatcher,
244
294
  runCompensatedSaga,
245
295
  sagaChildCausation,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> { sagaId: string; signal: string; payload: T; actorId: string; receivedAt: string }\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => client.signal(workflowId, envelope.signal, envelope);\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\nexport interface SagaWorkflowBinding {\n\tactionId: `${string}.${string}`;\n\tworkflowType: string;\n\ttaskQueue: string;\n}\n\n/** Instance-scoped registry; registration order never changes resolution. */\nexport class SagaWorkflowRegistry {\n\tprivate readonly bindings = new Map<string, SagaWorkflowBinding>();\n\n\tconstructor(bindings: readonly SagaWorkflowBinding[] = []) {\n\t\tfor (const binding of bindings) this.register(binding);\n\t}\n\n\tregister(binding: SagaWorkflowBinding): void {\n\t\tif (this.bindings.has(binding.actionId)) {\n\t\t\tthrow new Error(`Saga workflow binding already registered: ${binding.actionId}`);\n\t\t}\n\t\tif (!binding.workflowType.trim() || !binding.taskQueue.trim()) {\n\t\t\tthrow new Error(`Saga workflow binding ${binding.actionId} requires workflowType and taskQueue`);\n\t\t}\n\t\tthis.bindings.set(binding.actionId, { ...binding });\n\t}\n\n\trequire(actionId: string): SagaWorkflowBinding {\n\t\tconst binding = this.bindings.get(actionId);\n\t\tif (!binding) throw new Error(`No saga workflow binding registered for ${actionId}`);\n\t\treturn { ...binding };\n\t}\n}\n\nexport interface TemporalSagaDispatchInput {\n\tactionInvocationId: string;\n\tactionId: `${string}.${string}`;\n\ttenantId: string;\n\tspaceId: string;\n\tworkflowId: string;\n}\n\n/** Starts the registered parent workflow with an invocation-only durable identity. */\nexport function createTemporalSagaDispatcher<THandle>(\n\tclient: WorkflowStartClient<THandle>,\n\tregistry: SagaWorkflowRegistry,\n) {\n\treturn {\n\t\tdispatch: async (input: TemporalSagaDispatchInput) => {\n\t\t\tconst binding = registry.require(input.actionId);\n\t\t\tconst handle = await startGovernedWorkflow(client, {\n\t\t\t\tworkflowType: binding.workflowType,\n\t\t\t\tworkflowId: input.workflowId,\n\t\t\t\ttaskQueue: binding.taskQueue,\n\t\t\t\targs: [{\n\t\t\t\t\tparentInvocationId: input.actionInvocationId,\n\t\t\t\t\tactionId: input.actionId,\n\t\t\t\t\ttenantId: input.tenantId,\n\t\t\t\t\tspaceId: input.spaceId,\n\t\t\t\t}],\n\t\t\t});\n\t\t\tconst runId = runIdFromHandle(handle);\n\t\t\treturn { workflowId: input.workflowId, ...(runId ? { runId } : {}) };\n\t\t},\n\t};\n}\n\nfunction runIdFromHandle(handle: unknown): string | undefined {\n\tif (!handle || typeof handle !== \"object\") return undefined;\n\tconst candidate = (handle as { firstExecutionRunId?: unknown }).firstExecutionRunId;\n\treturn typeof candidate === \"string\" && candidate ? candidate : undefined;\n}\n\n/** Durable record of a single step completing within a saga parent. */\nexport interface SagaParentProgressRecord {\n\t/** The step that just completed. */\n\tstepId: string;\n\t/** Zero-based position of this step in the declared sequence. */\n\tindex: number;\n\t/** Total steps declared for this saga. */\n\ttotal: number;\n\t/** ISO-8601 timestamp the progress was recorded. */\n\tcompletedAt: string;\n}\n\nexport interface SagaParentProgressInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\tprogress: SagaParentProgressRecord;\n}\n\nexport interface SagaParentApprovalInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\t/** The step at which the saga paused for human approval. */\n\tstepId: string;\n\t/** Stable identifier for the approval gate, so a later signal can resume it. */\n\tapprovalId: string;\n\treason: string;\n}\n\nexport interface SagaParentCancellationInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\treason: string;\n\t/** Actor requesting the cancellation, for audit attribution. */\n\tactorId: string;\n}\n\nexport interface SagaParentLifecycle {\n\t/** Record durable progress as each step completes. */\n\trecordProgress(input: SagaParentProgressInput): Promise<void>;\n\t/** Park the parent for human approval at a specific step. */\n\tparkApproval(input: SagaParentApprovalInput): Promise<void>;\n\t/** Cancel the parent saga with an attributable reason. */\n\tcancel(input: SagaParentCancellationInput): Promise<void>;\n\t/** Complete the parent saga with its final result. */\n\tcomplete(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; result: Record<string, unknown> }): Promise<void>;\n\t/** Fail the parent saga, surfacing any compensation records. */\n\tfail(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; error: string; compensation: CompensationRecord[] }): Promise<void>;\n}\n\n/** Parent lifecycle remains explicit durable activities, never workflow-local state. */\nexport function createSagaParentActivities(lifecycle: SagaParentLifecycle) {\n\treturn {\n\t\trecordSagaProgress: (input: SagaParentProgressInput) => lifecycle.recordProgress({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"progress\",\n\t\t\t\t`${input.progress.stepId}:${input.progress.index}`,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tparkSagaApproval: (input: SagaParentApprovalInput) => lifecycle.parkApproval({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"approval\",\n\t\t\t\tinput.approvalId,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcancelSagaParent: (input: SagaParentCancellationInput) => lifecycle.cancel({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"cancel\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcompleteSagaParent: (input: Parameters<SagaParentLifecycle[\"complete\"]>[0]) =>\n\t\t\tlifecycle.complete({\n\t\t\t\t...input,\n\t\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\t\"complete\",\n\t\t\t\t).idempotencyKey,\n\t\t\t}),\n\t\tfailSagaParent: (input: Parameters<SagaParentLifecycle[\"fail\"]>[0]) => lifecycle.fail({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"fail\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t};\n}\n\n// ── Stable parent and child causation identities ───────────────────────────\n\n/**\n * Stable identity for a saga parent's own lifecycle activity (progress,\n * cancellation, approval parking, completion, failure). The activity stepId\n * is namespaced under `parent:` so it can never collide with a forward step or\n * a `compensate:` reversal.\n */\nexport function sagaParentActivityIdentity(\n\tworkflowId: string,\n\tactivity: SagaParentActivityKind,\n\tdiscriminator?: string,\n) {\n\treturn temporalInvocationIdentity({\n\t\tworkflowId,\n\t\tstepId: `parent:${activity}${discriminator ? `:${discriminator}` : \"\"}`,\n\t});\n}\n\nexport type SagaParentActivityKind = \"progress\" | \"approval\" | \"cancel\" | \"complete\" | \"fail\";\n\n/**\n * Stable causation identity linking a child step to its parent invocation.\n * The parent invocation ID becomes the causation ID; the step keeps its own\n * deterministic idempotency key and shares the workflow correlation ID.\n */\nexport function sagaChildCausation(workflowId: string, parentInvocationId: string, stepId: string) {\n\treturn { ...temporalInvocationIdentity({ workflowId, stepId }), causationId: parentInvocationId };\n}\n\n// ── Saga signal names ───────────────────────────────────────────────────────\n\n/** Stable signal names a saga workflow may register handlers for. */\nexport const SAGA_SIGNALS = {\n\tproceed: \"saga.proceed\",\n\tcancel: \"saga.cancel\",\n\tapproval: \"saga.approval\",\n} as const;\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n\n// ── Declared compensation ───────────────────────────────────────────────────\n\nexport interface CompensatedStep {\n\t/** Workflow-local step identity. Must match the compensation declared in the module manifest. */\n\tstepId: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\t/**\n\t * Governed action that reverses this step's effect. Omit for a step with\n\t * nothing to undo; a step whose effect is externally visible should declare one.\n\t */\n\tcompensateWith?: { actionId: string; parameters?: Record<string, unknown> };\n}\n\nexport interface CompensatedSagaInput {\n\tworkflowId: string;\n\trunId: string;\n\tsteps: CompensatedStep[];\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tparentInvocationId?: string;\n\t/** When supplied, every step's reversal must match what the manifest declares. */\n\tworkflow?: DeclaredWorkflow;\n}\n\nexport interface CompensationRecord {\n\tstepId: string;\n\tactionId: string;\n\t/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */\n\terror?: string;\n}\n\nexport interface CompensatedSagaResult {\n\tcompleted: string[];\n\tcompensated: CompensationRecord[];\n\tfailedStep?: { stepId: string; error: string };\n}\n\n/** Reversals are governed invocations too, so they carry their own stable identity. */\nexport function compensationInvocationIdentity(input: { workflowId: string; stepId: string }) {\n\treturn temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });\n}\n\nexport class SagaCompensationError extends Error {\n\toverride readonly name = \"SagaCompensationError\";\n\tconstructor(readonly result: CompensatedSagaResult) {\n\t\tsuper(`Saga step \"${result.failedStep?.stepId}\" failed: ${result.failedStep?.error}`);\n\t}\n}\n\ntype GovernedActivity = ReturnType<typeof createGovernedActionActivities>[\"invokeGovernedAction\"];\n\n/**\n * The governed Host resolves for outcomes that are not success — a blocked policy, a\n * failed validation, a reconciliation, an approval park. Treating a resolved promise\n * as a completed step would silently skip the unwind, so the status is inspected.\n */\nconst SUCCESSFUL_STATUS = \"completed\";\n\nfunction stepFailure(result: unknown, label: string): string | undefined {\n\tif (!result || typeof result !== \"object\") return undefined;\n\tconst status = (result as { status?: unknown }).status;\n\tif (typeof status !== \"string\" || status === SUCCESSFUL_STATUS) return undefined;\n\tconst error = (result as { error?: unknown }).error;\n\treturn `${label} resolved with status \"${status}\"${typeof error === \"string\" && error ? `: ${error}` : \"\"}`;\n}\n\nfunction assertDistinctSteps(steps: CompensatedStep[]): void {\n\tconst seen = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.stepId.trim()) throw new Error(\"Every saga step requires a stepId.\");\n\t\t// A forward step called \"compensate:x\" would share an identity with the reversal of \"x\".\n\t\tif (step.stepId.startsWith(\"compensate:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"compensate:\" prefix.`);\n\t\t// A forward step called \"parent:x\" would share an identity with a parent lifecycle activity.\n\t\tif (step.stepId.startsWith(\"parent:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"parent:\" prefix.`);\n\t\tif (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step \"${step.stepId}\"; step identity must be unique within a workflow.`);\n\t\tseen.add(step.stepId);\n\t}\n}\n\n/** The manifest declaration this saga is expected to honour, when one is supplied. */\nexport interface DeclaredWorkflow {\n\tname: string;\n\tcompensations?: Array<{ step: string; actionId: string }>;\n}\n\nfunction assertMatchesDeclaration(steps: CompensatedStep[], workflow: DeclaredWorkflow): void {\n\tconst declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));\n\tfor (const step of steps) {\n\t\tconst expected = declared.get(step.stepId);\n\t\tif (expected === undefined) {\n\t\t\tif (step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" supplies a reversal the manifest does not declare.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" omits the reversal \"${expected}\" its manifest declares.`);\n\t\tif (step.compensateWith.actionId !== expected) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" reverses with \"${step.compensateWith.actionId}\" but its manifest declares \"${expected}\".`);\n\t\t}\n\t}\n\tfor (const [step, actionId] of declared) {\n\t\tif (!steps.some((candidate) => candidate.stepId === step)) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" declares a reversal \"${actionId}\" for step \"${step}\", which this saga never runs.`);\n\t\t}\n\t}\n}\n\n/**\n * Runs steps in order, accumulating reversals as each succeeds. On failure the\n * accumulated reversals run newest-first, so an unwind never leaves an orphan.\n * Every forward step and every reversal enters through the governed Host with a\n * deterministic idempotency key, so a retried or replayed unwind is not double-applied.\n */\nexport async function runCompensatedSaga(\n\tactivity: GovernedActivity,\n\tinput: CompensatedSagaInput,\n): Promise<CompensatedSagaResult> {\n\tconst scope = {\n\t\ttenantId: input.tenantId,\n\t\tspaceId: input.spaceId,\n\t\tactorId: input.actorId,\n\t\tactorType: input.actorType,\n\t\tworkflowId: input.workflowId,\n\t\trunId: input.runId,\n\t\t...(input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}),\n\t};\n\tassertDistinctSteps(input.steps);\n\tif (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);\n\n\tconst completed: string[] = [];\n\tconst reversals: CompensatedStep[] = [];\n\n\tconst unwind = async (failedStep: { stepId: string; error: string }): Promise<never> => {\n\t\tconst compensated: CompensationRecord[] = [];\n\t\t// Newest-first: the last effect applied is the first one withdrawn.\n\t\tfor (const applied of [...reversals].reverse()) {\n\t\t\tconst reversal = applied.compensateWith!;\n\t\t\tconst record: CompensationRecord = { stepId: applied.stepId, actionId: reversal.actionId };\n\t\t\ttry {\n\t\t\t\tconst result = await activity({\n\t\t\t\t\t...scope,\n\t\t\t\t\tactionId: reversal.actionId,\n\t\t\t\t\tparameters: reversal.parameters ?? applied.parameters,\n\t\t\t\t\tstepId: `compensate:${applied.stepId}`,\n\t\t\t\t});\n\t\t\t\tconst refused = stepFailure(result, `reversal \"${reversal.actionId}\"`);\n\t\t\t\tif (refused) record.error = refused;\n\t\t\t} catch (compensationError) {\n\t\t\t\trecord.error = compensationError instanceof Error ? compensationError.message : String(compensationError);\n\t\t\t}\n\t\t\tcompensated.push(record);\n\t\t}\n\t\tthrow new SagaCompensationError({ completed, compensated, failedStep });\n\t};\n\n\tfor (const step of input.steps) {\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });\n\t\t} catch (error) {\n\t\t\treturn await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t\tconst refused = stepFailure(result, `step \"${step.stepId}\"`);\n\t\tif (refused) return await unwind({ stepId: step.stepId, error: refused });\n\t\tcompleted.push(step.stepId);\n\t\tif (step.compensateWith) reversals.push(step);\n\t}\n\n\treturn { completed, compensated: [] };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> {\n\tsagaId: string;\n\tsignal: string;\n\tpayload: T;\n\tactorId: string;\n\treceivedAt: string;\n\t/**\n\t * Identifies one logical signal. A sender should reuse the same value on each\n\t * redelivery of that signal, for example a webhook delivery ID or an approval\n\t * decision ID, so a retried `client.signal()` after a timeout carries the\n\t * same `signalId` as the first attempt.\n\t *\n\t * A receiver built on {@link createSagaSignalInbox} drops an envelope whose\n\t * `signalId` it has already received. Envelopes without a `signalId` are not\n\t * deduplicated. {@link createSagaSignalDispatcher} throws on a blank value\n\t * before sending.\n\t */\n\tsignalId?: string;\n}\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\t// Not async: a synchronous throw from the client keeps propagating synchronously, as before.\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => {\n\t\tassertSignalIdentity(envelope);\n\t\treturn client.signal(workflowId, envelope.signal, envelope);\n\t};\n}\n\nfunction assertSignalIdentity(envelope: SagaSignalEnvelope): void {\n\tif (envelope.signalId === undefined) return;\n\tif (typeof envelope.signalId !== \"string\" || !envelope.signalId.trim()) {\n\t\tthrow new Error(`Saga signal \"${envelope.signal}\" for saga \"${envelope.sagaId}\" has a blank signalId; omit it or supply a stable non-blank identity.`);\n\t}\n}\n\n/** A stable identity a receiver can deduplicate on, or undefined when the envelope carries none. */\nfunction signalIdentity(envelope: SagaSignalEnvelope): string | undefined {\n\treturn typeof envelope.signalId === \"string\" && envelope.signalId.trim() ? envelope.signalId : undefined;\n}\n\n/** Serializable inbox state, as returned by {@link SagaSignalInbox.snapshot}. */\nexport interface SagaSignalInboxSnapshot {\n\t/** Signal IDs already received, so a redelivery after the handover is still dropped. */\n\tseenSignalIds: string[];\n\t/** Envelopes received but not yet taken, FIFO within each signal name. */\n\tpending: SagaSignalEnvelope[];\n}\n\nexport interface SagaSignalInboxOptions {\n\t/**\n\t * Signal IDs already received by an earlier run of this saga. Prefer passing a\n\t * whole {@link SagaSignalInbox.snapshot}: carrying IDs without `pending` drops\n\t * any envelope that was accepted but not yet taken.\n\t */\n\tseenSignalIds?: readonly string[];\n\t/**\n\t * Envelopes an earlier run accepted but did not take. They are buffered again\n\t * in order without a dedup check, since they were already accepted once.\n\t */\n\tpending?: readonly SagaSignalEnvelope[];\n}\n\nexport interface SagaSignalInbox {\n\t/**\n\t * Buffers the envelope at the back of the queue for its signal name and returns\n\t * `true`. Returns `false` and drops the envelope when its `signalId` was already\n\t * received by this inbox under any signal name. An envelope without a `signalId`\n\t * (or with a blank one) is buffered and not remembered.\n\t */\n\treceive(envelope: SagaSignalEnvelope): boolean;\n\t/** Removes and returns the oldest buffered envelope for `signalName`, or `undefined` when none is buffered. */\n\ttake<T = unknown>(signalName: string): SagaSignalEnvelope<T> | undefined;\n\t/** Number of envelopes buffered for `signalName`, for use in `condition(() => inbox.pending(name) > 0, timeout)`. */\n\tpending(signalName: string): number;\n\t/** Signal IDs received so far, carried-forward ones first, in the order they were first received. */\n\tseenSignalIds(): string[];\n\t/**\n\t * Copies the seen IDs and every buffered envelope. Pass it into the next run across\n\t * continue-as-new and hand it back to `createSagaSignalInbox()`, so neither a\n\t * redelivery nor an accepted-but-untaken signal is lost.\n\t */\n\tsnapshot(): SagaSignalInboxSnapshot;\n}\n\n/**\n * Deterministic receiver for saga signals. It has no Temporal dependency and reads\n * no clock, randomness or process-global state, so it can run inside the workflow\n * sandbox. Wire it with\n * `setHandler(defineSignal(SAGA_SIGNALS.approval), (envelope) => { inbox.receive(envelope); })`.\n *\n * Buffering is FIFO per signal name. Deduplication is keyed on `signalId` across\n * every signal name received by this inbox. Dedup state lives in this instance, so\n * it lasts for one workflow run unless the caller carries\n * {@link SagaSignalInbox.snapshot} into the next run. The remembered set grows\n * with each distinct `signalId` and is not pruned, including across\n * continue-as-new, because forgetting an ID would let its redelivery through.\n */\nexport function createSagaSignalInbox(options: SagaSignalInboxOptions = {}): SagaSignalInbox {\n\tconst seen = new Set<string>(options.seenSignalIds ?? []);\n\tconst queues = new Map<string, SagaSignalEnvelope[]>();\n\tconst enqueue = (envelope: SagaSignalEnvelope) => {\n\t\tconst queue = queues.get(envelope.signal);\n\t\tif (queue) queue.push(envelope);\n\t\telse queues.set(envelope.signal, [envelope]);\n\t};\n\tfor (const envelope of options.pending ?? []) {\n\t\tconst id = signalIdentity(envelope);\n\t\tif (id !== undefined) seen.add(id);\n\t\tenqueue(envelope);\n\t}\n\treturn {\n\t\treceive(envelope) {\n\t\t\tconst id = signalIdentity(envelope);\n\t\t\tif (id !== undefined) {\n\t\t\t\tif (seen.has(id)) return false;\n\t\t\t\tseen.add(id);\n\t\t\t}\n\t\t\tenqueue(envelope);\n\t\t\treturn true;\n\t\t},\n\t\ttake<T = unknown>(signalName: string) {\n\t\t\treturn queues.get(signalName)?.shift() as SagaSignalEnvelope<T> | undefined;\n\t\t},\n\t\tpending(signalName) {\n\t\t\treturn queues.get(signalName)?.length ?? 0;\n\t\t},\n\t\tseenSignalIds() {\n\t\t\treturn [...seen];\n\t\t},\n\t\tsnapshot() {\n\t\t\treturn { seenSignalIds: [...seen], pending: [...queues.values()].flat() };\n\t\t},\n\t};\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\nexport interface SagaWorkflowBinding {\n\tactionId: `${string}.${string}`;\n\tworkflowType: string;\n\ttaskQueue: string;\n}\n\n/** Instance-scoped registry; registration order never changes resolution. */\nexport class SagaWorkflowRegistry {\n\tprivate readonly bindings = new Map<string, SagaWorkflowBinding>();\n\n\tconstructor(bindings: readonly SagaWorkflowBinding[] = []) {\n\t\tfor (const binding of bindings) this.register(binding);\n\t}\n\n\tregister(binding: SagaWorkflowBinding): void {\n\t\tif (this.bindings.has(binding.actionId)) {\n\t\t\tthrow new Error(`Saga workflow binding already registered: ${binding.actionId}`);\n\t\t}\n\t\tif (!binding.workflowType.trim() || !binding.taskQueue.trim()) {\n\t\t\tthrow new Error(`Saga workflow binding ${binding.actionId} requires workflowType and taskQueue`);\n\t\t}\n\t\tthis.bindings.set(binding.actionId, { ...binding });\n\t}\n\n\trequire(actionId: string): SagaWorkflowBinding {\n\t\tconst binding = this.bindings.get(actionId);\n\t\tif (!binding) throw new Error(`No saga workflow binding registered for ${actionId}`);\n\t\treturn { ...binding };\n\t}\n}\n\nexport interface TemporalSagaDispatchInput {\n\tactionInvocationId: string;\n\tactionId: `${string}.${string}`;\n\ttenantId: string;\n\tspaceId: string;\n\tworkflowId: string;\n}\n\n/** Starts the registered parent workflow with an invocation-only durable identity. */\nexport function createTemporalSagaDispatcher<THandle>(\n\tclient: WorkflowStartClient<THandle>,\n\tregistry: SagaWorkflowRegistry,\n) {\n\treturn {\n\t\tdispatch: async (input: TemporalSagaDispatchInput) => {\n\t\t\tconst binding = registry.require(input.actionId);\n\t\t\tconst handle = await startGovernedWorkflow(client, {\n\t\t\t\tworkflowType: binding.workflowType,\n\t\t\t\tworkflowId: input.workflowId,\n\t\t\t\ttaskQueue: binding.taskQueue,\n\t\t\t\targs: [{\n\t\t\t\t\tparentInvocationId: input.actionInvocationId,\n\t\t\t\t\tactionId: input.actionId,\n\t\t\t\t\ttenantId: input.tenantId,\n\t\t\t\t\tspaceId: input.spaceId,\n\t\t\t\t}],\n\t\t\t});\n\t\t\tconst runId = runIdFromHandle(handle);\n\t\t\treturn { workflowId: input.workflowId, ...(runId ? { runId } : {}) };\n\t\t},\n\t};\n}\n\nfunction runIdFromHandle(handle: unknown): string | undefined {\n\tif (!handle || typeof handle !== \"object\") return undefined;\n\tconst candidate = (handle as { firstExecutionRunId?: unknown }).firstExecutionRunId;\n\treturn typeof candidate === \"string\" && candidate ? candidate : undefined;\n}\n\n/** Durable record of a single step completing within a saga parent. */\nexport interface SagaParentProgressRecord {\n\t/** The step that just completed. */\n\tstepId: string;\n\t/** Zero-based position of this step in the declared sequence. */\n\tindex: number;\n\t/** Total steps declared for this saga. */\n\ttotal: number;\n\t/** ISO-8601 timestamp the progress was recorded. */\n\tcompletedAt: string;\n}\n\nexport interface SagaParentProgressInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\tprogress: SagaParentProgressRecord;\n}\n\nexport interface SagaParentApprovalInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\t/** The step at which the saga paused for human approval. */\n\tstepId: string;\n\t/** Stable identifier for the approval gate, so a later signal can resume it. */\n\tapprovalId: string;\n\treason: string;\n}\n\nexport interface SagaParentCancellationInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\treason: string;\n\t/** Actor requesting the cancellation, for audit attribution. */\n\tactorId: string;\n}\n\nexport interface SagaParentLifecycle {\n\t/** Record durable progress as each step completes. */\n\trecordProgress(input: SagaParentProgressInput): Promise<void>;\n\t/** Park the parent for human approval at a specific step. */\n\tparkApproval(input: SagaParentApprovalInput): Promise<void>;\n\t/** Cancel the parent saga with an attributable reason. */\n\tcancel(input: SagaParentCancellationInput): Promise<void>;\n\t/** Complete the parent saga with its final result. */\n\tcomplete(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; result: Record<string, unknown> }): Promise<void>;\n\t/** Fail the parent saga, surfacing any compensation records. */\n\tfail(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; error: string; compensation: CompensationRecord[] }): Promise<void>;\n}\n\n/** Parent lifecycle remains explicit durable activities, never workflow-local state. */\nexport function createSagaParentActivities(lifecycle: SagaParentLifecycle) {\n\treturn {\n\t\trecordSagaProgress: (input: SagaParentProgressInput) => lifecycle.recordProgress({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"progress\",\n\t\t\t\t`${input.progress.stepId}:${input.progress.index}`,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tparkSagaApproval: (input: SagaParentApprovalInput) => lifecycle.parkApproval({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"approval\",\n\t\t\t\tinput.approvalId,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcancelSagaParent: (input: SagaParentCancellationInput) => lifecycle.cancel({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"cancel\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcompleteSagaParent: (input: Parameters<SagaParentLifecycle[\"complete\"]>[0]) =>\n\t\t\tlifecycle.complete({\n\t\t\t\t...input,\n\t\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\t\"complete\",\n\t\t\t\t).idempotencyKey,\n\t\t\t}),\n\t\tfailSagaParent: (input: Parameters<SagaParentLifecycle[\"fail\"]>[0]) => lifecycle.fail({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"fail\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t};\n}\n\n// ── Stable parent and child causation identities ───────────────────────────\n\n/**\n * Stable identity for a saga parent's own lifecycle activity (progress,\n * cancellation, approval parking, completion, failure). The activity stepId\n * is namespaced under `parent:` so it can never collide with a forward step or\n * a `compensate:` reversal.\n */\nexport function sagaParentActivityIdentity(\n\tworkflowId: string,\n\tactivity: SagaParentActivityKind,\n\tdiscriminator?: string,\n) {\n\treturn temporalInvocationIdentity({\n\t\tworkflowId,\n\t\tstepId: `parent:${activity}${discriminator ? `:${discriminator}` : \"\"}`,\n\t});\n}\n\nexport type SagaParentActivityKind = \"progress\" | \"approval\" | \"cancel\" | \"complete\" | \"fail\";\n\n/**\n * Stable causation identity linking a child step to its parent invocation.\n * The parent invocation ID becomes the causation ID; the step keeps its own\n * deterministic idempotency key and shares the workflow correlation ID.\n */\nexport function sagaChildCausation(workflowId: string, parentInvocationId: string, stepId: string) {\n\treturn { ...temporalInvocationIdentity({ workflowId, stepId }), causationId: parentInvocationId };\n}\n\n// ── Saga signal names ───────────────────────────────────────────────────────\n\n/** Stable signal names a saga workflow may register handlers for. */\nexport const SAGA_SIGNALS = {\n\tproceed: \"saga.proceed\",\n\tcancel: \"saga.cancel\",\n\tapproval: \"saga.approval\",\n} as const;\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n\n// ── Declared compensation ───────────────────────────────────────────────────\n\nexport interface CompensatedStep {\n\t/** Workflow-local step identity. Must match the compensation declared in the module manifest. */\n\tstepId: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\t/**\n\t * Governed action that reverses this step's effect. Omit for a step with\n\t * nothing to undo; a step whose effect is externally visible should declare one.\n\t */\n\tcompensateWith?: { actionId: string; parameters?: Record<string, unknown> };\n}\n\nexport interface CompensatedSagaInput {\n\tworkflowId: string;\n\trunId: string;\n\tsteps: CompensatedStep[];\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tparentInvocationId?: string;\n\t/** When supplied, every step's reversal must match what the manifest declares. */\n\tworkflow?: DeclaredWorkflow;\n}\n\nexport interface CompensationRecord {\n\tstepId: string;\n\tactionId: string;\n\t/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */\n\terror?: string;\n}\n\nexport interface CompensatedSagaResult {\n\tcompleted: string[];\n\tcompensated: CompensationRecord[];\n\tfailedStep?: { stepId: string; error: string };\n}\n\n/** Reversals are governed invocations too, so they carry their own stable identity. */\nexport function compensationInvocationIdentity(input: { workflowId: string; stepId: string }) {\n\treturn temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });\n}\n\nexport class SagaCompensationError extends Error {\n\toverride readonly name = \"SagaCompensationError\";\n\tconstructor(readonly result: CompensatedSagaResult) {\n\t\tsuper(`Saga step \"${result.failedStep?.stepId}\" failed: ${result.failedStep?.error}`);\n\t}\n}\n\ntype GovernedActivity = ReturnType<typeof createGovernedActionActivities>[\"invokeGovernedAction\"];\n\n/**\n * The governed Host resolves for outcomes that are not success — a blocked policy, a\n * failed validation, a reconciliation, an approval park. Treating a resolved promise\n * as a completed step would silently skip the unwind, so the status is inspected.\n */\nconst SUCCESSFUL_STATUS = \"completed\";\n\nfunction stepFailure(result: unknown, label: string): string | undefined {\n\tif (!result || typeof result !== \"object\") return undefined;\n\tconst status = (result as { status?: unknown }).status;\n\tif (typeof status !== \"string\" || status === SUCCESSFUL_STATUS) return undefined;\n\tconst error = (result as { error?: unknown }).error;\n\treturn `${label} resolved with status \"${status}\"${typeof error === \"string\" && error ? `: ${error}` : \"\"}`;\n}\n\nfunction assertDistinctSteps(steps: CompensatedStep[]): void {\n\tconst seen = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.stepId.trim()) throw new Error(\"Every saga step requires a stepId.\");\n\t\t// A forward step called \"compensate:x\" would share an identity with the reversal of \"x\".\n\t\tif (step.stepId.startsWith(\"compensate:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"compensate:\" prefix.`);\n\t\t// A forward step called \"parent:x\" would share an identity with a parent lifecycle activity.\n\t\tif (step.stepId.startsWith(\"parent:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"parent:\" prefix.`);\n\t\tif (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step \"${step.stepId}\"; step identity must be unique within a workflow.`);\n\t\tseen.add(step.stepId);\n\t}\n}\n\n/** The manifest declaration this saga is expected to honour, when one is supplied. */\nexport interface DeclaredWorkflow {\n\tname: string;\n\tcompensations?: Array<{ step: string; actionId: string }>;\n}\n\nfunction assertMatchesDeclaration(steps: CompensatedStep[], workflow: DeclaredWorkflow): void {\n\tconst declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));\n\tfor (const step of steps) {\n\t\tconst expected = declared.get(step.stepId);\n\t\tif (expected === undefined) {\n\t\t\tif (step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" supplies a reversal the manifest does not declare.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" omits the reversal \"${expected}\" its manifest declares.`);\n\t\tif (step.compensateWith.actionId !== expected) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" reverses with \"${step.compensateWith.actionId}\" but its manifest declares \"${expected}\".`);\n\t\t}\n\t}\n\tfor (const [step, actionId] of declared) {\n\t\tif (!steps.some((candidate) => candidate.stepId === step)) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" declares a reversal \"${actionId}\" for step \"${step}\", which this saga never runs.`);\n\t\t}\n\t}\n}\n\n/**\n * Runs steps in order, accumulating reversals as each succeeds. On failure the\n * accumulated reversals run newest-first, so an unwind never leaves an orphan.\n * Every forward step and every reversal enters through the governed Host with a\n * deterministic idempotency key, so a retried or replayed unwind is not double-applied.\n */\nexport async function runCompensatedSaga(\n\tactivity: GovernedActivity,\n\tinput: CompensatedSagaInput,\n): Promise<CompensatedSagaResult> {\n\tconst scope = {\n\t\ttenantId: input.tenantId,\n\t\tspaceId: input.spaceId,\n\t\tactorId: input.actorId,\n\t\tactorType: input.actorType,\n\t\tworkflowId: input.workflowId,\n\t\trunId: input.runId,\n\t\t...(input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}),\n\t};\n\tassertDistinctSteps(input.steps);\n\tif (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);\n\n\tconst completed: string[] = [];\n\tconst reversals: CompensatedStep[] = [];\n\n\tconst unwind = async (failedStep: { stepId: string; error: string }): Promise<never> => {\n\t\tconst compensated: CompensationRecord[] = [];\n\t\t// Newest-first: the last effect applied is the first one withdrawn.\n\t\tfor (const applied of [...reversals].reverse()) {\n\t\t\tconst reversal = applied.compensateWith!;\n\t\t\tconst record: CompensationRecord = { stepId: applied.stepId, actionId: reversal.actionId };\n\t\t\ttry {\n\t\t\t\tconst result = await activity({\n\t\t\t\t\t...scope,\n\t\t\t\t\tactionId: reversal.actionId,\n\t\t\t\t\tparameters: reversal.parameters ?? applied.parameters,\n\t\t\t\t\tstepId: `compensate:${applied.stepId}`,\n\t\t\t\t});\n\t\t\t\tconst refused = stepFailure(result, `reversal \"${reversal.actionId}\"`);\n\t\t\t\tif (refused) record.error = refused;\n\t\t\t} catch (compensationError) {\n\t\t\t\trecord.error = compensationError instanceof Error ? compensationError.message : String(compensationError);\n\t\t\t}\n\t\t\tcompensated.push(record);\n\t\t}\n\t\tthrow new SagaCompensationError({ completed, compensated, failedStep });\n\t};\n\n\tfor (const step of input.steps) {\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });\n\t\t} catch (error) {\n\t\t\treturn await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t\tconst refused = stepFailure(result, `step \"${step.stepId}\"`);\n\t\tif (refused) return await unwind({ stepId: step.stepId, error: refused });\n\t\tcompleted.push(step.stepId);\n\t\tif (step.compensateWith) reversals.push(step);\n\t}\n\n\treturn { completed, compensated: [] };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fabricorg/platform-temporal",
3
- "version": "4.1.1",
4
- "description": "Temporal binding contracts for governed Fabric Platform actions and sagas.",
3
+ "version": "4.2.0",
4
+ "description": "Temporal binding contracts for governed TechFabric Platform actions and sagas.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.cjs",
@@ -37,8 +37,8 @@
37
37
  "tsup": "^8.5.0",
38
38
  "typescript": "^5.7.3",
39
39
  "vitest": "^4.1.5",
40
- "@fabricorg/platform": "1.5.1",
41
- "@fabricorg/platform-host": "7.2.1"
40
+ "@fabricorg/platform": "1.8.0",
41
+ "@fabricorg/platform-host": "7.4.0"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=20"