@codemation/core 0.13.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/dist/{CostCatalogContract-Dxq1BTyi.d.cts → CostCatalogContract-Dwo-ZamG.d.cts} +2 -2
  3. package/dist/EngineRuntimeRegistration.types-BiNasx3G.d.cts +54 -0
  4. package/dist/EngineRuntimeRegistration.types-Dq4ucrdo.d.ts +21 -0
  5. package/dist/{InMemoryRunDataFactory-Csy2evr_.d.cts → InMemoryRunDataFactory-D2U9azmZ.d.cts} +4 -20
  6. package/dist/{InMemoryRunEventBusRegistry-Sa86VxuV.cjs → InMemoryRunEventBusRegistry-DO0WM9Lw.cjs} +1 -1
  7. package/dist/{InMemoryRunEventBusRegistry-Sa86VxuV.cjs.map → InMemoryRunEventBusRegistry-DO0WM9Lw.cjs.map} +1 -1
  8. package/dist/{InMemoryRunEventBusRegistry-Bwunvt1T.js → InMemoryRunEventBusRegistry-Layt2xgm.js} +1 -1
  9. package/dist/{InMemoryRunEventBusRegistry-Bwunvt1T.js.map → InMemoryRunEventBusRegistry-Layt2xgm.js.map} +1 -1
  10. package/dist/{ItemsInputNormalizer-Bi8m-Ijs.d.cts → ItemsInputNormalizer-A5txcOWX.d.cts} +3 -98
  11. package/dist/{ItemsInputNormalizer-BbQTSEkZ.cjs → ItemsInputNormalizer-C1fv3sMW.cjs} +2 -2
  12. package/dist/ItemsInputNormalizer-C1fv3sMW.cjs.map +1 -0
  13. package/dist/{ItemsInputNormalizer-BYljnXU0.d.ts → ItemsInputNormalizer-D2vrMrX1.d.ts} +2 -62
  14. package/dist/{ItemsInputNormalizer-CSZGMgl3.js → ItemsInputNormalizer-fUYo4GLV.js} +2 -2
  15. package/dist/ItemsInputNormalizer-fUYo4GLV.js.map +1 -0
  16. package/dist/{RunIntentService-BitgkKaT.d.cts → RunIntentService-DKxuHTUz.d.cts} +2 -15
  17. package/dist/{RunIntentService-DYpqfu6D.d.ts → RunIntentService-DrpKli2k.d.ts} +2 -22
  18. package/dist/{agentMcpTypes-DGIwk6Ue.d.cts → agentMcpTypes-BHX4RQCC.d.cts} +25 -518
  19. package/dist/bootstrap/index.cjs +9 -5
  20. package/dist/bootstrap/index.d.cts +32 -106
  21. package/dist/bootstrap/index.d.ts +18 -17
  22. package/dist/bootstrap/index.js +6 -5
  23. package/dist/{bootstrap-DIv-vloi.cjs → bootstrap-CTB53rEF.cjs} +9 -60
  24. package/dist/bootstrap-CTB53rEF.cjs.map +1 -0
  25. package/dist/{bootstrap-Bkd-Nfbn.js → bootstrap-DmqKheCI.js} +6 -57
  26. package/dist/bootstrap-DmqKheCI.js.map +1 -0
  27. package/dist/browser.cjs +12 -11
  28. package/dist/browser.d.cts +4 -4
  29. package/dist/browser.d.ts +3 -3
  30. package/dist/browser.js +3 -2
  31. package/dist/contracts-7L1wJHdk.cjs +569 -0
  32. package/dist/contracts-7L1wJHdk.cjs.map +1 -0
  33. package/dist/contracts-CjJ5CZ7N.js +447 -0
  34. package/dist/contracts-CjJ5CZ7N.js.map +1 -0
  35. package/dist/contracts.cjs +9 -2
  36. package/dist/contracts.d.cts +5 -5
  37. package/dist/contracts.d.ts +2 -2
  38. package/dist/contracts.js +3 -2
  39. package/dist/{executionPersistenceContracts-CN9d7AnL.d.cts → deploymentManifestTypes-B8CDmZZK.d.cts} +65 -81
  40. package/dist/di-C6Ubf9o5.cjs +179 -0
  41. package/dist/di-C6Ubf9o5.cjs.map +1 -0
  42. package/dist/di-Cjiil7U-.js +114 -0
  43. package/dist/di-Cjiil7U-.js.map +1 -0
  44. package/dist/{index-rllWL4r-.d.ts → index-CRv3_pY3.d.ts} +112 -808
  45. package/dist/{index-BSQ2LoIh.d.ts → index-mnLS0iQl.d.ts} +39 -372
  46. package/dist/index.cjs +53 -111
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +50 -438
  49. package/dist/index.d.ts +5 -5
  50. package/dist/index.js +28 -94
  51. package/dist/index.js.map +1 -1
  52. package/dist/{params-DRUr0F5v.d.cts → params-CrK4iuG1.d.cts} +3 -13
  53. package/dist/{runtime-CWPdvJpC.js → runtime-CBFDpmiz.js} +112 -648
  54. package/dist/runtime-CBFDpmiz.js.map +1 -0
  55. package/dist/{runtime-_VdHwGkJ.cjs → runtime-Due-FOZ2.cjs} +137 -717
  56. package/dist/runtime-Due-FOZ2.cjs.map +1 -0
  57. package/dist/testing.cjs +8 -40
  58. package/dist/testing.cjs.map +1 -1
  59. package/dist/testing.d.cts +3 -32
  60. package/dist/testing.d.ts +3 -32
  61. package/dist/testing.js +6 -38
  62. package/dist/testing.js.map +1 -1
  63. package/dist/{di-LP2qSHkY.cjs → workflowTypes-BW6Hhee7.cjs} +4 -230
  64. package/dist/workflowTypes-BW6Hhee7.cjs.map +1 -0
  65. package/dist/{di-tom0pM2h.js → workflowTypes-DZtBTmKf.js} +3 -163
  66. package/dist/workflowTypes-DZtBTmKf.js.map +1 -0
  67. package/package.json +1 -1
  68. package/src/ai/AgentConnectionNodeCollector.ts +0 -4
  69. package/src/ai/AgentMessageConfigNormalizerFactory.ts +0 -4
  70. package/src/ai/AiHost.ts +0 -38
  71. package/src/ai/CallableToolConfig.ts +0 -9
  72. package/src/ai/CallableToolKindToken.ts +0 -4
  73. package/src/authoring/callableTool.types.ts +0 -3
  74. package/src/authoring/defineCollection.types.ts +0 -11
  75. package/src/authoring/defineHumanApprovalNode.types.ts +0 -116
  76. package/src/authoring/defineNode.types.ts +18 -32
  77. package/src/authoring/definePollingTrigger.types.ts +36 -155
  78. package/src/authoring/definePollingTriggerInternals.ts +0 -4
  79. package/src/authoring/index.ts +1 -0
  80. package/src/authoring/nodeBaseOptions.types.ts +4 -0
  81. package/src/binaries/boundedReadBinary.types.ts +0 -16
  82. package/src/bootstrap/index.ts +8 -2
  83. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +0 -5
  84. package/src/bootstrap/runtime/EngineRuntimeRegistration.types.ts +0 -23
  85. package/src/browser.ts +0 -3
  86. package/src/contracts/AgentBindError.ts +0 -5
  87. package/src/contracts/Clock.ts +0 -1
  88. package/src/contracts/CodemationTelemetryAttributeNames.ts +0 -10
  89. package/src/contracts/NoOpAgentMcpIntegration.ts +0 -6
  90. package/src/contracts/NoOpTelemetrySpanScope.ts +0 -7
  91. package/src/contracts/RetryPolicy.ts +0 -2
  92. package/src/contracts/agentMcpTypes.ts +0 -34
  93. package/src/contracts/assertionTypes.ts +0 -33
  94. package/src/contracts/baseTypes.ts +0 -6
  95. package/src/contracts/collectionTypes.ts +0 -25
  96. package/src/contracts/credentialTypes.ts +13 -60
  97. package/src/contracts/deploymentManifestTypes.ts +158 -0
  98. package/src/contracts/dispatchTypes.ts +29 -0
  99. package/src/contracts/executionPersistenceContracts.ts +0 -33
  100. package/src/contracts/hitlSeamTypes.ts +0 -14
  101. package/src/contracts/humanTaskStoreTypes.ts +0 -2
  102. package/src/contracts/inboxChannelTypes.ts +0 -9
  103. package/src/contracts/index.ts +3 -0
  104. package/src/contracts/itemExpr.ts +7 -21
  105. package/src/contracts/itemMeta.ts +0 -3
  106. package/src/contracts/mcpTypes.ts +0 -16
  107. package/src/contracts/retryPolicySpec.types.ts +0 -10
  108. package/src/contracts/runFinishedAtFactory.ts +0 -1
  109. package/src/contracts/runTypes.ts +0 -74
  110. package/src/contracts/runtimeTypes.ts +4 -131
  111. package/src/contracts/telemetryTypes.ts +0 -7
  112. package/src/contracts/testTriggerTypes.ts +0 -43
  113. package/src/contracts/triggerInvokerTypes.ts +6 -0
  114. package/src/contracts/webhookTypes.ts +0 -8
  115. package/src/contracts/workflowActivationPolicy.ts +0 -5
  116. package/src/contracts/workflowTypes.ts +5 -80
  117. package/src/contracts/workspaceFileTypes.ts +10 -42
  118. package/src/contracts.ts +18 -10
  119. package/src/credentials/CredentialMaterialProvider.types.ts +0 -28
  120. package/src/credentials/ManagedCredentialMaterialWriteError.ts +0 -6
  121. package/src/credentials/ManagedMaterialFetchError.ts +0 -6
  122. package/src/credentials/OAuthFlowExecutor.types.ts +0 -15
  123. package/src/di/CoreTokens.ts +2 -6
  124. package/src/events/ConnectionInvocationEventPublisher.ts +0 -7
  125. package/src/events/NodeEventPublisher.ts +0 -1
  126. package/src/events/runEvents.ts +0 -8
  127. package/src/execution/ActivationEnqueueService.ts +0 -10
  128. package/src/execution/ChildExecutionScopeFactory.ts +0 -13
  129. package/src/execution/FanInMergeByOriginMerger.ts +0 -11
  130. package/src/execution/InProcessRetryRunner.ts +0 -1
  131. package/src/execution/ItemExprResolver.ts +0 -3
  132. package/src/execution/NodeActivationRequestComposer.ts +0 -3
  133. package/src/execution/NodeActivationRequestInputPreparer.ts +0 -5
  134. package/src/execution/NodeExecutionSnapshotFactory.ts +0 -1
  135. package/src/execution/NodeExecutor.ts +1 -17
  136. package/src/execution/NodeOutputNormalizer.ts +8 -1
  137. package/src/execution/NodeSuspensionHandler.ts +1 -39
  138. package/src/execution/PersistedRunStateTerminalBuilder.ts +0 -5
  139. package/src/execution/RunSuspendedError.ts +0 -10
  140. package/src/execution/RunnableOutputBehaviorResolver.ts +12 -0
  141. package/src/execution/WorkflowRunExecutionContextFactory.ts +0 -3
  142. package/src/index.ts +10 -2
  143. package/src/orchestration/AbortControllerFactory.ts +0 -4
  144. package/src/orchestration/Engine.ts +0 -9
  145. package/src/orchestration/NodeExecutionRequestHandlerService.ts +3 -4
  146. package/src/orchestration/RunContinuationService.ts +7 -39
  147. package/src/orchestration/RunStartService.ts +0 -7
  148. package/src/orchestration/TestSuiteOrchestrator.ts +0 -18
  149. package/src/orchestration/TestSuiteRunIdFactory.ts +0 -4
  150. package/src/orchestration/TriggerRuntimeService.ts +3 -2
  151. package/src/planning/CurrentStateFrontierPlanner.ts +0 -1
  152. package/src/planning/RunQueuePlanner.ts +0 -6
  153. package/src/policies/executionLimits/EngineExecutionLimitsPolicy.ts +0 -8
  154. package/src/policies/executionLimits/EngineExecutionLimitsPolicyFactory.ts +0 -3
  155. package/src/runStorage/RunSummaryMapper.ts +0 -1
  156. package/src/runtime/EngineFactory.ts +6 -11
  157. package/src/runtime/RunIntentService.ts +0 -4
  158. package/src/runtime/WorkflowRepositoryWebhookTriggerMatcher.ts +0 -4
  159. package/src/runtime-types/InjectableRuntimeDecoratorComposerRegistry.ts +0 -4
  160. package/src/runtime-types/PersistedRuntimeTypeMetadataStoreRegistry.ts +0 -4
  161. package/src/runtime-types/PersistedRuntimeTypeNameResolver.ts +0 -1
  162. package/src/runtime-types/persistedRuntimeTypeModelRegistry.ts +0 -4
  163. package/src/runtime-types/runtimeTypeDecorators.types.ts +0 -12
  164. package/src/scheduler/ConfigDrivenOffloadPolicy.ts +0 -1
  165. package/src/scheduler/DefaultDrivingScheduler.ts +0 -6
  166. package/src/scheduler/InlineDrivingScheduler.ts +0 -13
  167. package/src/serialization/ItemsInputNormalizer.ts +0 -5
  168. package/src/testing/CapturingScheduler.ts +0 -3
  169. package/src/testing/EngineTestKitRunIdFactory.ts +0 -3
  170. package/src/testing/ItemHarnessNode.ts +0 -3
  171. package/src/testing/ItemHarnessNodeConfig.ts +0 -4
  172. package/src/testing/PrefixedSequentialIdGenerator.ts +0 -3
  173. package/src/testing/RegistrarEngineTestKit.types.ts +0 -2
  174. package/src/testing/RejectingCredentialSessionService.ts +0 -4
  175. package/src/testing/SubWorkflowRunnerTestNode.ts +0 -3
  176. package/src/testing/WorkflowTestHarnessManualTrigger.ts +0 -3
  177. package/src/testing/WorkflowTestKitBuilder.ts +0 -4
  178. package/src/testing/WorkflowTestKitRunNodeWorkflowFactory.ts +0 -3
  179. package/src/testing.ts +0 -3
  180. package/src/triggers/polling/PollingTriggerDedupWindow.ts +0 -4
  181. package/src/triggers/polling/PollingTriggerLogger.ts +0 -5
  182. package/src/triggers/polling/PollingTriggerRuntime.ts +0 -5
  183. package/src/types/index.ts +0 -6
  184. package/src/validation/WorkflowEdgePortValidator.ts +0 -5
  185. package/src/workflow/definition/ConnectionInvocationIdFactory.ts +0 -7
  186. package/src/workflow/definition/ConnectionNodeIdFactory.ts +0 -6
  187. package/src/workflow/definition/NodeIterationIdFactory.ts +0 -13
  188. package/src/workflow/definition/WorkflowExecutableNodeClassifier.ts +0 -6
  189. package/src/workflow/dsl/ChainCursorResolver.ts +8 -15
  190. package/src/workflow/dsl/NodeIdSlugifier.ts +0 -9
  191. package/src/workflow/dsl/WhenBuilder.ts +49 -2
  192. package/src/workflow/dsl/WorkflowDefinitionError.ts +0 -9
  193. package/src/workflow/dsl/workflowBuilderTypes.ts +5 -0
  194. package/src/workflowSnapshots/MissingRuntimeParityGuard.ts +24 -0
  195. package/src/workflowSnapshots/PersistedWorkflowTokenRegistry.ts +0 -6
  196. package/src/workflowSnapshots/WorkflowParityMismatchError.ts +18 -0
  197. package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +1 -5
  198. package/src/workflowSnapshots/index.ts +3 -0
  199. package/dist/EngineRuntimeRegistration.types-CqcTWexS.d.cts +0 -81
  200. package/dist/EngineRuntimeRegistration.types-Cr75cSfL.d.ts +0 -44
  201. package/dist/ItemsInputNormalizer-BbQTSEkZ.cjs.map +0 -1
  202. package/dist/ItemsInputNormalizer-CSZGMgl3.js.map +0 -1
  203. package/dist/bootstrap-Bkd-Nfbn.js.map +0 -1
  204. package/dist/bootstrap-DIv-vloi.cjs.map +0 -1
  205. package/dist/contracts-CK0x6w_G.cjs +0 -74
  206. package/dist/contracts-CK0x6w_G.cjs.map +0 -1
  207. package/dist/contracts-DXdfTdpW.js +0 -50
  208. package/dist/contracts-DXdfTdpW.js.map +0 -1
  209. package/dist/di-LP2qSHkY.cjs.map +0 -1
  210. package/dist/di-tom0pM2h.js.map +0 -1
  211. package/dist/runtime-CWPdvJpC.js.map +0 -1
  212. package/dist/runtime-_VdHwGkJ.cjs.map +0 -1
@@ -7,19 +7,6 @@ import type {
7
7
  TelemetrySpanScope,
8
8
  } from "../types";
9
9
 
10
- /**
11
- * Builds a re-rooted child execution context for sub-agent (and other deeply-nested) invocations.
12
- *
13
- * At the orchestrator's `agent.tool.call` boundary the inner runtime needs a ctx whose:
14
- * - `nodeId` is the tool's connection node id (so inner LLM/tool connection ids derive correctly),
15
- * - `activationId` is fresh (so its connection-invocation rows are uniquely identifiable),
16
- * - `telemetry` parents children under the tool-call span (not the orchestrator's node span),
17
- * - `binary` is scoped to the new (nodeId, activationId),
18
- * - `parentInvocationId` points back to the tool-call invocation for downstream lineage.
19
- *
20
- * Registered via factory in {@link EngineRuntimeRegistrar} so constructors stay free of parameter
21
- * decorators (Next/SWC and coverage tooling cannot parse them on in-repo sources).
22
- */
23
10
  export class ChildExecutionScopeFactory {
24
11
  constructor(private readonly activationIdFactory: ActivationIdFactory) {}
25
12
 
@@ -2,17 +2,6 @@ import type { InputPortKey, Item, Items, NodeInputsByPort } from "../types";
2
2
 
3
3
  import { getOriginIndexFromItem } from "../contracts/itemMeta";
4
4
 
5
- /**
6
- * Default fan-in: combine multi-port {@link NodeInputsByPort} into one {@link Items} batch for per-item nodes.
7
- *
8
- * This is used when a single-input per-item node has multiple inbound edges (for example, branch reconverge
9
- * after an `If` / `Switch`). The default behavior is **append / union** (preserving item payloads) with a
10
- * deterministic order:
11
- *
12
- * - When router origin metadata exists (`meta._cm.originIndex`), items are sorted by origin index so the
13
- * downstream batch preserves original ordering across branches.
14
- * - Otherwise, items are appended by port-key order, preserving each port's local order.
15
- */
16
5
  export class FanInMergeByOriginMerger {
17
6
  merge(inputsByPort: NodeInputsByPort): Items {
18
7
  const portKeys = Object.keys(inputsByPort).sort();
@@ -7,7 +7,6 @@ import type { AsyncSleeper } from "./asyncSleeper.types";
7
7
 
8
8
  export type { AsyncSleeper } from "./asyncSleeper.types";
9
9
 
10
- /** Maximum permitted retry attempts — workflow-declared values above this are clamped. */
11
10
  const HARD_MAX_RETRY_ATTEMPTS = 10;
12
11
 
13
12
  type NormalizedPolicy =
@@ -1,9 +1,6 @@
1
1
  import { resolveItemExprsForExecution } from "../contracts/itemExpr";
2
2
  import type { Item, NodeExecutionContext, RunnableNodeConfig } from "../types";
3
3
 
4
- /**
5
- * Resolves {@link import("../contracts/itemExpr").ItemExpr} leaves on runnable config before {@link RunnableNode.execute}.
6
- */
7
4
  export class ItemExprResolver {
8
5
  async resolveConfigForItem<TConfig extends RunnableNodeConfig<any, any>>(
9
6
  ctx: NodeExecutionContext<TConfig>,
@@ -46,9 +46,6 @@ export type PlannedNodeActivationRequest = NodeActivationContextArgs & {
46
46
  nodeDefinition: NodeExecutionDefinition;
47
47
  };
48
48
 
49
- /**
50
- * Builds {@link NodeActivationRequest} values shared by workflow starters and continuation.
51
- */
52
49
  export class NodeActivationRequestComposer {
53
50
  constructor(
54
51
  private readonly activationIdFactory: ActivationIdFactory,
@@ -5,11 +5,6 @@ import type { Item, NodeActivationRequest, RunnableNodeConfig, WorkflowNodeInsta
5
5
  import { FanInMergeByOriginMerger } from "./FanInMergeByOriginMerger";
6
6
  import { NodeInputContractError } from "./NodeInputContractError";
7
7
 
8
- /**
9
- * Validates per-item inputs for {@link RunnableNode} before enqueue persistence (Zod on `item.json`).
10
- * Does not rewrite `item.json` (wire stays as emitted upstream; engine passes parsed input via `execute` args).
11
- * Converts multi-input activations into a single-input batch when the node is per-item only (engine fan-in).
12
- */
13
8
  export class NodeActivationRequestInputPreparer {
14
9
  private readonly fanInMerger = new FanInMergeByOriginMerger();
15
10
 
@@ -71,7 +71,6 @@ export class NodeExecutionSnapshotFactory {
71
71
  inputsByPort: NodeInputsByPort;
72
72
  outputs: NodeOutputs;
73
73
  fromPinnedOutput?: boolean;
74
- /** Override the terminal status for HITL outcomes (defaults to `"completed"`). */
75
74
  hitlStatus?: Extract<
76
75
  NodeExecutionStatus,
77
76
  "hitl-approved" | "hitl-rejected" | "hitl-timeout" | "hitl-auto-accepted" | "hitl-cancelled"
@@ -38,9 +38,7 @@ export class NodeExecutor {
38
38
  private readonly retryRunner: InProcessRetryRunner,
39
39
  itemExprResolver?: ItemExprResolver,
40
40
  outputBehaviorResolver?: RunnableOutputBehaviorResolver,
41
- /** Required for HITL suspension support. When omitted, `SuspensionRequest` throws upward. */
42
41
  private readonly suspensionHandler?: NodeSuspensionHandler,
43
- /** Required alongside `suspensionHandler`. */
44
42
  private readonly loadRunState?: (runId: RunId) => Promise<PersistedRunState | undefined>,
45
43
  ) {
46
44
  this.itemExprResolver = itemExprResolver ?? new ItemExprResolver();
@@ -190,8 +188,6 @@ export class NodeExecutor {
190
188
  const runnableCtx = request.ctx as NodeExecutionContext<RunnableNodeConfig>;
191
189
  const resolvedCtx = await this.itemExprResolver.resolveConfigForItem(runnableCtx, item, i, inputBatch);
192
190
  const baseCtx = this.pickExecutionContext(runnableCtx, resolvedCtx);
193
- // Mint a per-item iteration id and stamp it (with the item index) onto the ctx so connection
194
- // invocations and telemetry written from inside `node.execute` carry the per-item identity.
195
191
  const iterationCtx = {
196
192
  ...baseCtx,
197
193
  iterationId: NodeIterationIdFactory.create(),
@@ -208,9 +204,6 @@ export class NodeExecutor {
208
204
  try {
209
205
  raw = await Promise.resolve(node.execute(args));
210
206
  } catch (e) {
211
- // Use both instanceof AND name check: under tsx/dev with mixed source/dist resolution,
212
- // SuspensionRequest may load as two distinct class objects and instanceof fails. The
213
- // name brand survives the duality because both copies set name="SuspensionRequest".
214
207
  const isSuspension =
215
208
  e instanceof SuspensionRequest ||
216
209
  (e instanceof Error &&
@@ -218,23 +211,17 @@ export class NodeExecutor {
218
211
  typeof (e as { request?: unknown }).request === "object");
219
212
  if (isSuspension) {
220
213
  if (!this.suspensionHandler || !this.loadRunState) {
221
- // Suspension not supported in this executor configuration — propagate as a regular error.
222
214
  throw new Error(
223
215
  `Node ${request.nodeId} threw SuspensionRequest but this NodeExecutor has no suspensionHandler configured.`,
224
216
  { cause: e },
225
217
  );
226
218
  }
227
- // Per-item suspension: load current state, persist the suspension entry, and
228
- // continue processing remaining items. If deliver throws it propagates upward.
229
219
  const state = await this.loadRunState(request.runId);
230
220
  if (!state) {
231
221
  throw new Error(`NodeExecutor: run state not found for runId ${request.runId} during suspension`, {
232
222
  cause: e,
233
223
  });
234
224
  }
235
- // handleSuspension throws RunSuspendedError after persisting — we re-throw it
236
- // to exit the loop immediately. Partial byPort outputs are intentionally dropped
237
- // (TODO: consider stashing outputs of non-suspended items alongside the suspension).
238
225
  await this.suspensionHandler.handle({
239
226
  runId: request.runId,
240
227
  nodeId: request.nodeId,
@@ -244,7 +231,7 @@ export class NodeExecutor {
244
231
  state,
245
232
  telemetry: iterationCtx.telemetry,
246
233
  });
247
- hasSuspension = true; // unreachable — handler always throws, but satisfies TS control-flow
234
+ hasSuspension = true;
248
235
  continue;
249
236
  }
250
237
  throw e;
@@ -264,14 +251,11 @@ export class NodeExecutor {
264
251
  }
265
252
  }
266
253
  if (hasSuspension) {
267
- // Unreachable in practice (suspensionHandler always throws RunSuspendedError) but
268
- // guards against future refactors that might change handler behaviour.
269
254
  throw new RunSuspendedError(request.runId, "unknown");
270
255
  }
271
256
  return byPort as NodeOutputs;
272
257
  }
273
258
 
274
- /** Use resolver ctx only when {@link NodeExecutionContext.config} is non-nullish. */
275
259
  private pickExecutionContext<TConfig extends RunnableNodeConfig<any, any>>(
276
260
  runnableCtx: NodeExecutionContext<TConfig>,
277
261
  resolvedCtx: NodeExecutionContext<TConfig> | null | undefined,
@@ -73,10 +73,17 @@ export class NodeOutputNormalizer {
73
73
  return typeof value === "object" && value !== null && "json" in value;
74
74
  }
75
75
 
76
+ private isPlainJsonObject(value: unknown): value is Record<string, unknown> {
77
+ return typeof value === "object" && value !== null && !Array.isArray(value);
78
+ }
79
+
76
80
  private applyOutput(baseItem: Item, next: Item, behavior: RunnableOutputBehavior): Item {
77
81
  const explicitBinary = next.binary;
78
82
  return {
79
- json: next.json,
83
+ json:
84
+ behavior.mergeJson && this.isPlainJsonObject(baseItem.json) && this.isPlainJsonObject(next.json)
85
+ ? { ...baseItem.json, ...next.json }
86
+ : next.json,
80
87
  ...(explicitBinary !== undefined
81
88
  ? { binary: explicitBinary }
82
89
  : behavior.keepBinaries && baseItem.binary
@@ -19,33 +19,12 @@ import { CodemationTelemetryAttributeNames } from "../contracts/CodemationTeleme
19
19
  import { RunSuspendedError } from "./RunSuspendedError";
20
20
  export { RunSuspendedError };
21
21
 
22
- /**
23
- * Handles per-item `SuspensionRequest` catches in the engine's item execution loop.
24
- *
25
- * Responsibilities:
26
- * 1. Generate a `taskId` (UUID v4).
27
- * 2. Persist a `HumanTask` row via `HumanTaskStore.create`.
28
- * 3. Sign a resume URL via `HitlResumeTokenSigner.sign`.
29
- * 4. Enqueue a delayed BullMQ timeout job via `HitlTimeoutJobScheduler.enqueue`.
30
- * 5. Build a `HumanTaskHandle` and call `deliver`.
31
- * 6. Append a `PersistedSuspensionEntry` to the run state and flip status to `"suspended"`.
32
- * 7. Persist via `WorkflowExecutionRepository.save`.
33
- * 8. Throw `RunSuspendedError` so the caller can exit cleanly.
34
- *
35
- * If `deliver` throws, the error propagates up to `NodeExecutionRequestHandlerService`
36
- * which routes it through `resumeFromNodeError` → run status becomes `"failed"`.
37
- *
38
- * `humanTaskStore`, `tokenSigner`, and `timeoutScheduler` are optional —
39
- * when not registered (e.g. in unit tests), the handler still suspends the run but
40
- * skips persistence, token signing, and job scheduling.
41
- */
42
22
  export class NodeSuspensionHandler {
43
23
  constructor(
44
24
  private readonly workflowExecutionRepository: WorkflowExecutionRepository,
45
25
  private readonly humanTaskStore?: HumanTaskStore,
46
26
  private readonly tokenSigner?: HitlResumeTokenSignerSeam,
47
27
  private readonly timeoutScheduler?: HitlTimeoutJobSchedulerSeam,
48
- /** Workspace ID to stamp on HumanTaskRecord in managed mode (T7 security fix). Null in non-managed mode. */
49
28
  private readonly workspaceId?: string,
50
29
  ) {}
51
30
 
@@ -56,7 +35,6 @@ export class NodeSuspensionHandler {
56
35
  itemIndex: number;
57
36
  suspensionRequest: SuspensionRequest;
58
37
  state: PersistedRunState;
59
- /** Telemetry scope of the node's per-item span. Used to emit `hitl.task.*` span events. */
60
38
  telemetry?: TelemetryScope;
61
39
  }): Promise<never> {
62
40
  const taskId = `htask_${globalThis.crypto.randomUUID()}`;
@@ -68,12 +46,11 @@ export class NodeSuspensionHandler {
68
46
  const decisionSchemaHash = this.hashSchema(decisionSchema);
69
47
  const decisionSchemaJson = this.schemaToJson(decisionSchema);
70
48
 
71
- // Build resume token (when signer is available)
72
49
  let resumeUrl = "";
73
50
  let resumeTokenHash = "";
74
51
  if (this.tokenSigner) {
75
52
  const token = this.tokenSigner.sign({ taskId, expiresAt, schemaHash: decisionSchemaHash });
76
- resumeUrl = token; // callers (deliver) receive the raw token; inbox layers wrap into a URL
53
+ resumeUrl = token;
77
54
  resumeTokenHash = this.tokenSigner.hashToken(token);
78
55
  }
79
56
 
@@ -86,7 +63,6 @@ export class NodeSuspensionHandler {
86
63
  ...(metadata !== undefined ? { metadata } : {}),
87
64
  };
88
65
 
89
- // Emit hitl.task.created before calling deliver.
90
66
  const channel = (metadata as Record<string, unknown> | undefined)?.["channel"];
91
67
  await args.telemetry?.addSpanEvent?.({
92
68
  name: "hitl.task.created",
@@ -99,8 +75,6 @@ export class NodeSuspensionHandler {
99
75
  },
100
76
  });
101
77
 
102
- // D5: deliver throws → emit hitl.task.delivery_failed, then propagate upward;
103
- // caller routes to resumeFromNodeError → "failed"
104
78
  let deliveryRef: Awaited<ReturnType<typeof deliver>>;
105
79
  try {
106
80
  deliveryRef = await deliver(handle);
@@ -116,15 +90,11 @@ export class NodeSuspensionHandler {
116
90
  throw deliverError;
117
91
  }
118
92
 
119
- // Persist HumanTask row
120
93
  if (this.humanTaskStore) {
121
94
  const record: HumanTaskRecord = {
122
95
  id: taskId,
123
96
  runId: args.runId,
124
97
  workflowId: args.state.workflowId,
125
- // T7: stamp workspaceId in managed mode so HitlCallbackHandler can assert workspace identity.
126
- // Non-managed mode leaves this undefined (null in DB) — the check in HitlCallbackHandler
127
- // is guarded by `task.workspaceId !== undefined` and is a no-op when null.
128
98
  workspaceId: this.workspaceId ?? undefined,
129
99
  nodeId: args.nodeId,
130
100
  activationId: args.activationId,
@@ -144,7 +114,6 @@ export class NodeSuspensionHandler {
144
114
  await this.humanTaskStore.create(record);
145
115
  }
146
116
 
147
- // Enqueue timeout job
148
117
  if (this.timeoutScheduler) {
149
118
  await this.timeoutScheduler.enqueueTimeoutJob({ taskId, expiresAt });
150
119
  }
@@ -172,13 +141,7 @@ export class NodeSuspensionHandler {
172
141
  throw new RunSuspendedError(args.runId, taskId);
173
142
  }
174
143
 
175
- /**
176
- * Parse a duration string into milliseconds.
177
- * Accepts ISO 8601 durations ("PT24H", "PT30M") and shorthand ("24h", "30m", "1d").
178
- * Throws for unrecognised formats.
179
- */
180
144
  private parseDurationMs(duration: string): number {
181
- // Shorthand: "24h", "30m", "7d", "3600s"
182
145
  const shorthand = /^(\d+(?:\.\d+)?)(s|m|h|d)$/i.exec(duration);
183
146
  if (shorthand) {
184
147
  const value = parseFloat(shorthand[1]!);
@@ -191,7 +154,6 @@ export class NodeSuspensionHandler {
191
154
  };
192
155
  return value * multipliers[unit]!;
193
156
  }
194
- // ISO 8601 duration subset: PTnHnMnS (days handled via P1D)
195
157
  const iso = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/.exec(duration);
196
158
  if (iso) {
197
159
  const days = parseFloat(iso[1] ?? "0");
@@ -1,19 +1,14 @@
1
1
  import type { EngineRunCounters, PersistedRunState, RunHaltReason, RunQueueEntry } from "../types";
2
2
 
3
- /**
4
- * Merges common terminal-run fields onto a loaded {@link PersistedRunState} without repeating object literals.
5
- */
6
3
  export class PersistedRunStateTerminalBuilder {
7
4
  mergeTerminal(args: {
8
5
  state: PersistedRunState;
9
6
  engineCounters: EngineRunCounters;
10
7
  status: "completed" | "failed" | "halted";
11
- /** Populated when `status === "halted"`. */
12
8
  reason?: RunHaltReason;
13
9
  queue: RunQueueEntry[];
14
10
  outputsByNode: PersistedRunState["outputsByNode"];
15
11
  nodeSnapshotsByNodeId: NonNullable<PersistedRunState["nodeSnapshotsByNodeId"]>;
16
- /** When set, persisted on the run root for listings and retention pruning. */
17
12
  finishedAtIso?: string;
18
13
  }): PersistedRunState {
19
14
  return {
@@ -1,15 +1,5 @@
1
1
  import type { RunId } from "../types";
2
2
 
3
- /**
4
- * Internal sentinel thrown by {@link NodeSuspensionHandler} after persisting a suspension
5
- * entry. `NodeExecutionRequestHandlerService` catches this specifically and returns cleanly —
6
- * no continuation call, preventing `resumeFromNodeResult` / `resumeFromNodeError` from
7
- * overwriting the `"suspended"` run status.
8
- *
9
- * The `Error` suffix satisfies the ESLint `no-manual-di-new` allowlist. This is NOT a
10
- * user-facing error — it is an engine-internal control-flow primitive and should NOT be
11
- * exported from the public barrel.
12
- */
13
3
  export class RunSuspendedError extends Error {
14
4
  constructor(
15
5
  readonly runId: RunId,
@@ -5,14 +5,21 @@ type BinaryKeepingRunnableNodeConfig = RunnableNodeConfig &
5
5
  keepBinaries?: boolean;
6
6
  }>;
7
7
 
8
+ type MergeJsonRunnableNodeConfig = RunnableNodeConfig &
9
+ Readonly<{
10
+ mergeJson?: boolean;
11
+ }>;
12
+
8
13
  export type RunnableOutputBehavior = Readonly<{
9
14
  keepBinaries: boolean;
15
+ mergeJson: boolean;
10
16
  }>;
11
17
 
12
18
  export class RunnableOutputBehaviorResolver {
13
19
  resolve(config: RunnableNodeConfig): RunnableOutputBehavior {
14
20
  return {
15
21
  keepBinaries: this.isKeepBinariesEnabled(config),
22
+ mergeJson: this.isMergeJsonEnabled(config),
16
23
  };
17
24
  }
18
25
 
@@ -20,4 +27,9 @@ export class RunnableOutputBehaviorResolver {
20
27
  const candidate = config as BinaryKeepingRunnableNodeConfig;
21
28
  return candidate.keepBinaries === true;
22
29
  }
30
+
31
+ private isMergeJsonEnabled(config: RunnableNodeConfig): boolean {
32
+ const candidate = config as MergeJsonRunnableNodeConfig;
33
+ return candidate.mergeJson === true;
34
+ }
23
35
  }
@@ -11,9 +11,6 @@ import type {
11
11
 
12
12
  import { CredentialResolverFactory } from "./CredentialResolverFactory";
13
13
 
14
- /**
15
- * Shared {@link ExecutionContextFactory#create} wiring for workflow runners (base context before node-specific fields).
16
- */
17
14
  export class WorkflowRunExecutionContextFactory {
18
15
  constructor(
19
16
  private readonly executionContextFactory: ExecutionContextFactory,
package/src/index.ts CHANGED
@@ -84,5 +84,13 @@ export type {
84
84
  export { IllegalMaterialSourceError } from "./credentials/CredentialMaterialProvider.types";
85
85
  export { ManagedCredentialMaterialWriteError } from "./credentials/ManagedCredentialMaterialWriteError";
86
86
  export { ManagedMaterialFetchError } from "./credentials/ManagedMaterialFetchError";
87
- export type { IWorkspaceFileStorage, WorkspaceFileMetadata } from "./contracts/workspaceFileTypes";
88
- export { WorkspaceFileNotFoundError, WorkspaceFileStorageToken } from "./contracts/workspaceFileTypes";
87
+ export type {
88
+ IWorkspaceFileStorage,
89
+ IWorkspaceFileRegistrar,
90
+ WorkspaceFileMetadata,
91
+ } from "./contracts/workspaceFileTypes";
92
+ export {
93
+ WorkspaceFileNotFoundError,
94
+ WorkspaceFileStorageToken,
95
+ WorkspaceFileRegistrarToken,
96
+ } from "./contracts/workspaceFileTypes";
@@ -1,7 +1,3 @@
1
- /**
2
- * Mints fresh {@link AbortController}s. Injected (rather than direct `new`) to honor the
3
- * codebase's no-direct-construction rule and to give tests a seam for substituting a fake.
4
- */
5
1
  export class AbortControllerFactory {
6
2
  create(): AbortController {
7
3
  return new AbortController();
@@ -98,11 +98,6 @@ export interface EngineFacadeDeps {
98
98
  nodeExecutionRequestHandler: EngineNodeExecutionRequestHandler;
99
99
  }
100
100
 
101
- /**
102
- * Runtime facade for orchestration, continuation, triggers, and webhook routing.
103
- * Prefer {@link import("../intents/RunIntentService").RunIntentService} for host/HTTP invocation boundaries.
104
- * The class token is exported from `@codemation/core/bootstrap` (not the main `@codemation/core` barrel).
105
- */
106
101
  export class Engine implements NodeActivationContinuation, NodeExecutionRequestHandler {
107
102
  constructor(private readonly deps: EngineFacadeDeps) {}
108
103
 
@@ -236,10 +231,6 @@ export class Engine implements NodeActivationContinuation, NodeExecutionRequestH
236
231
  return await this.deps.runContinuationService.waitForWebhookResponse(runId);
237
232
  }
238
233
 
239
- /**
240
- * Re-activate a suspended run item with a human decision (HITL).
241
- * The HTTP resume endpoint calls this; this method exposes the engine primitive.
242
- */
243
234
  async resumeRun(args: { runId: RunId; taskId: string; resumeContext: ResumeContext }): Promise<RunResult> {
244
235
  return await this.deps.runContinuationService.resumeRun(args);
245
236
  }
@@ -15,6 +15,7 @@ import { RunSuspendedError } from "../execution/RunSuspendedError";
15
15
  import { NodeActivationRequestComposer } from "../execution/NodeActivationRequestComposer";
16
16
  import { NodeRunStateWriterFactory } from "../execution/NodeRunStateWriterFactory";
17
17
  import { WorkflowRunExecutionContextFactory } from "../execution/WorkflowRunExecutionContextFactory";
18
+ import { MissingRuntimeParityGuard } from "../workflowSnapshots/MissingRuntimeParityGuard";
18
19
 
19
20
  type PersistedWorkflowLike = Readonly<{
20
21
  workflowId: PersistedRunState["workflowId"];
@@ -32,6 +33,7 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
32
33
  private readonly nodeExecutor: NodeExecutor,
33
34
  private readonly continuation: NodeActivationContinuation,
34
35
  private readonly executionLimitsPolicy: EngineExecutionLimitsPolicy,
36
+ private readonly parityGuard: MissingRuntimeParityGuard,
35
37
  ) {}
36
38
 
37
39
  async handleNodeExecutionRequest(request: NodeExecutionRequest): Promise<void> {
@@ -86,7 +88,6 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
86
88
  const portKeys = Object.keys(inputsByPort);
87
89
  const kind = portKeys.length === 1 && portKeys[0] === "in" ? ("single" as const) : ("multi" as const);
88
90
  const batchId = pendingExecution.batchId ?? "batch_1";
89
- // Splice resumeContext from pendingResume if this activation is a HITL resume.
90
91
  const pendingResume = state.pendingResume;
91
92
  const resumeContext: ResumeContext | undefined =
92
93
  pendingResume?.activationId === request.activationId && pendingResume?.nodeId === request.nodeId
@@ -127,7 +128,6 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
127
128
  input: inputsByPort.in ?? request.input ?? [],
128
129
  });
129
130
 
130
- // Clear pendingResume from state now that we have consumed it.
131
131
  if (resumeContext != null) {
132
132
  const clearedState = await this.workflowExecutionRepository.load(request.runId);
133
133
  if (clearedState?.pendingResume?.activationId === request.activationId) {
@@ -144,11 +144,10 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
144
144
 
145
145
  let outputs;
146
146
  try {
147
+ this.parityGuard.assertNone(workflow, [request.nodeId]);
147
148
  outputs = await this.nodeExecutor.execute(activationRequest);
148
149
  } catch (error) {
149
150
  if (error instanceof RunSuspendedError) {
150
- // The node threw SuspensionRequest; NodeSuspensionHandler already persisted the
151
- // suspension entry and flipped status to "suspended". Nothing more to do here.
152
151
  return;
153
152
  }
154
153
  await this.resumeAfterExecutionError(activationRequest, this.asError(error));
@@ -46,6 +46,7 @@ import { NodeActivationRequestComposer } from "../execution/NodeActivationReques
46
46
  import { PersistedRunStateTerminalBuilder } from "../execution/PersistedRunStateTerminalBuilder";
47
47
  import { RunStateSemantics } from "../execution/RunStateSemantics";
48
48
  import { WorkflowRunExecutionContextFactory } from "../execution/WorkflowRunExecutionContextFactory";
49
+ import { MissingRuntimeParityGuard } from "../workflowSnapshots/MissingRuntimeParityGuard";
49
50
 
50
51
  export class RunContinuationService {
51
52
  constructor(
@@ -66,6 +67,7 @@ export class RunContinuationService {
66
67
  private readonly policyErrorServices: WorkflowPolicyErrorServices,
67
68
  private readonly terminalPersistence: RunTerminalPersistenceCoordinator,
68
69
  private readonly executionLimitsPolicy: EngineExecutionLimitsPolicy,
70
+ private readonly parityGuard: MissingRuntimeParityGuard,
69
71
  ) {}
70
72
 
71
73
  async markNodeRunning(args: {
@@ -151,9 +153,6 @@ export class RunContinuationService {
151
153
  data.setOutputs(args.nodeId, args.outputs);
152
154
  const completedAt = new Date().toISOString();
153
155
 
154
- // Resolve HITL status from the node's decision output.
155
- // Only fires when the output carries `item.json.decision.status` written by a
156
- // defineHumanApprovalNode-based node. Non-HITL nodes never have this field.
157
156
  const hitlResolution = this.resolveHitlStatus(args.outputs);
158
157
 
159
158
  const completedSnapshot = this.semantics.createFinishedSnapshot({
@@ -170,7 +169,6 @@ export class RunContinuationService {
170
169
  hitlStatus: hitlResolution?.nodeStatus,
171
170
  });
172
171
 
173
- // Halt the run for HITL rejection / timeout outcomes.
174
172
  if (hitlResolution?.halt) {
175
173
  const haltedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
176
174
  state,
@@ -355,6 +353,7 @@ export class RunContinuationService {
355
353
  });
356
354
 
357
355
  try {
356
+ this.parityGuard.assertNone(wf, [next.nodeId]);
358
357
  const { queuedSnapshot, result } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
359
358
  runId: state.runId,
360
359
  workflowId: state.workflowId,
@@ -461,9 +460,7 @@ export class RunContinuationService {
461
460
  nodeId: args.nodeId,
462
461
  outputs: recovered,
463
462
  });
464
- } catch {
465
- // fall through to workflow-level failure
466
- }
463
+ } catch {}
467
464
  }
468
465
  }
469
466
 
@@ -591,20 +588,6 @@ export class RunContinuationService {
591
588
  return await this.waiters.waitForWebhookResponse(runId);
592
589
  }
593
590
 
594
- /**
595
- * Re-activate a previously suspended run item with a human decision.
596
- *
597
- * Called by the HITL resume endpoint. This method:
598
- * 1. Loads `PersistedRunState` and locates the suspension entry by `taskId`.
599
- * 2. Removes the entry from the `suspension` array; if empty, run stays `"suspended"` until
600
- * enqueue flips it to `"pending"`.
601
- * 3. Writes `pendingResume` onto the state so `NodeExecutionRequestHandlerService` can
602
- * splice `resumeContext` into the node's execution context.
603
- * 4. Reconstructs the original input from `outputsByNode` of the upstream node and
604
- * enqueues a new activation via `activationEnqueueService`.
605
- *
606
- * @throws if the run is not found, not suspended, or the `taskId` is unknown.
607
- */
608
591
  async resumeRun(args: { runId: RunId; taskId: string; resumeContext: ResumeContext }): Promise<RunResult> {
609
592
  const state = await this.workflowExecutionRepository.load(args.runId);
610
593
  if (!state) throw new Error(`Unknown runId: ${args.runId}`);
@@ -626,8 +609,6 @@ export class RunContinuationService {
626
609
  throw new Error(`Node ${suspensionEntry.nodeId} is not a runnable node`);
627
610
  }
628
611
 
629
- // Reconstruct input: find the parent node that fed this node and use its main output.
630
- // The single-item input corresponds to `itemIndex` in the original activation batch.
631
612
  const data = this.runDataFactory.create(state.outputsByNode);
632
613
  const limits = this.resolveEngineLimitsFromState(state);
633
614
  const base = this.runExecutionContextFactory.create({
@@ -644,13 +625,10 @@ export class RunContinuationService {
644
625
  testContext: state.executionOptions?.testContext,
645
626
  });
646
627
 
647
- // Find the original input items for this node from upstream outputs.
648
- // Use the workflow edges to resolve the parent node. If no parent found, fall back to empty.
649
628
  const parentEdges = wf.edges.filter((e) => e.to.nodeId === suspensionEntry.nodeId);
650
629
  const parentNodeId = parentEdges[0]?.from.nodeId;
651
630
  const parentOutputPort = parentEdges[0]?.from.output ?? "main";
652
631
  const allParentItems = parentNodeId ? (data.getOutputItems(parentNodeId, parentOutputPort) ?? []) : [];
653
- // Each suspended item gets its own resume; pass the single item at itemIndex.
654
632
  const resumeInput =
655
633
  allParentItems.length > suspensionEntry.itemIndex ? [allParentItems[suspensionEntry.itemIndex]!] : allParentItems;
656
634
 
@@ -663,10 +641,6 @@ export class RunContinuationService {
663
641
 
664
642
  const remainingSuspensions = (state.suspension ?? []).filter((s) => s.taskId !== args.taskId);
665
643
 
666
- // Thread resumeContext into the execution context so the inline scheduler path
667
- // (InlineDrivingScheduler) delivers it directly to the node's ctx.resumeContext.
668
- // On the worker path (BullMQ), NodeExecutionRequestHandlerService re-derives it
669
- // from state.pendingResume — passing it here is additive and harmless.
670
644
  const baseWithResume = { ...base, resumeContext: args.resumeContext };
671
645
 
672
646
  const batchId = `resume_${newActivationId}`;
@@ -683,6 +657,8 @@ export class RunContinuationService {
683
657
  input: resumeInput,
684
658
  });
685
659
 
660
+ this.parityGuard.assertNone(wf, [suspensionEntry.nodeId]);
661
+
686
662
  const { result, queuedSnapshot } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
687
663
  runId: state.runId,
688
664
  workflowId: state.workflowId,
@@ -944,6 +920,7 @@ export class RunContinuationService {
944
920
  });
945
921
 
946
922
  try {
923
+ this.parityGuard.assertNone(args.workflow, [next.nodeId]);
947
924
  const { queuedSnapshot, result } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
948
925
  runId: args.state.runId,
949
926
  workflowId: args.state.workflowId,
@@ -1084,10 +1061,6 @@ export class RunContinuationService {
1084
1061
  };
1085
1062
  }
1086
1063
 
1087
- /**
1088
- * Next activation could not be enqueued (e.g. input contract / mapping failed in the preparer).
1089
- * Marks the target node failed and terminates the run.
1090
- */
1091
1064
  private async terminateRunAfterActivationEnqueueRejected(
1092
1065
  args: Readonly<{
1093
1066
  wf: WorkflowDefinition;
@@ -1166,11 +1139,6 @@ export class RunContinuationService {
1166
1139
  return result;
1167
1140
  }
1168
1141
 
1169
- /**
1170
- * Inspects node outputs for a `decision.status` written by `defineHumanApprovalNode`.
1171
- * Returns the first-class HITL node status and halt classification, or `undefined`
1172
- * when the node is not a HITL approval node.
1173
- */
1174
1142
  private resolveHitlStatus(outputs: NodeOutputs):
1175
1143
  | {
1176
1144
  nodeStatus: Extract<
@@ -277,13 +277,6 @@ export class RunStartService {
277
277
  currentState: RunCurrentState | undefined,
278
278
  mutableState: NonNullable<Awaited<ReturnType<WorkflowExecutionRepository["load"]>>>["mutableState"],
279
279
  ): RunCurrentState {
280
- // Each `ConnectionInvocationRecord` represents a single, auditable LLM/tool call
281
- // that must belong to exactly one run. Reruns start from a fresh invocation
282
- // ledger so the new run only records what it actually invokes. The prior run's
283
- // invocations remain queryable on that run's persisted state (their true owner).
284
- // Carrying them over here would write duplicate `ExecutionInstance` rows whose
285
- // primary key (`invocationId`) already exists under the previous run, causing a
286
- // `Unique constraint failed on the fields: (instance_id)` violation on first save.
287
280
  return {
288
281
  outputsByNode: { ...(currentState?.outputsByNode ?? {}) },
289
282
  nodeSnapshotsByNodeId: { ...(currentState?.nodeSnapshotsByNodeId ?? {}) },