@gobing-ai/ts-dual-workflow-engine 0.4.6 → 0.4.7

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/README.md CHANGED
@@ -39,7 +39,7 @@ The package exposes:
39
39
  | Export | Purpose |
40
40
  |--------|---------|
41
41
  | `MemoryWorkflowPersistenceAdapter` | In-memory persistence for tests and short-lived runs |
42
- | `DbWorkflowPersistenceAdapter` | DB-backed persistence over `@gobing-ai/ts-db` |
42
+ | `DbWorkflowPersistenceAdapter` | DB-backed persistence over `@gobing-ai/ts-db` with atomic `commitTransition` (ADR-020) |
43
43
  | `applyWorkflowEngineSchema()` | Installs the package-owned DB schema (idempotent; safe to call before every write) |
44
44
  | `WORKFLOW_ENGINE_SCHEMA_SQL` | Raw SQL DDL for the 5 workflow engine tables |
45
45
 
@@ -222,7 +222,7 @@ sequenceDiagram
222
222
  RL->>SM: invoke loop(lifecycle)
223
223
 
224
224
  loop Each state
225
- SM->>RL: enter(stateId, transitionsTaken)
225
+ SM->>RL: enter(stateId, transitionsTaken, persist=true)
226
226
  RL->>P: saveWorkflowState(stateId, data)
227
227
  RL->>P: savePhase(stateId, 'running')
228
228
 
@@ -267,8 +267,8 @@ sequenceDiagram
267
267
  H-->>SM: ActionResult
268
268
  end
269
269
 
270
- SM->>RL: recordTransition(from, to, trigger)
271
- RL->>P: saveTransition(runId, from, to, trigger)
270
+ SM->>RL: commitHop(from, to, trigger, transitionsTaken, phase?)
271
+ Note right of RL: atomic batch: transition + state + phase (ADR-020)
272
272
  SM->>SM: transitionsTaken += 1
273
273
 
274
274
  alt iteration bound exceeded
@@ -309,7 +309,7 @@ sequenceDiagram
309
309
  RL->>TF: invoke loop(lifecycle)
310
310
 
311
311
  loop Each node
312
- TF->>RL: enter(nodeId, transitionsTaken)
312
+ TF->>RL: enter(nodeId, transitionsTaken, persist=true)
313
313
  RL->>P: saveWorkflowState(nodeId, data)
314
314
  RL->>P: savePhase(nodeId, 'running')
315
315
 
@@ -353,8 +353,8 @@ sequenceDiagram
353
353
  Note over TF: proceed immediately
354
354
  end
355
355
 
356
- TF->>RL: recordTransition(from, to, trigger)
357
- RL->>P: saveTransition(runId, from, to, trigger)
356
+ TF->>RL: commitHop(from, to, trigger, transitionsTaken, phase?)
357
+ Note right of RL: atomic batch: transition + state + phase (ADR-020)
358
358
  TF->>TF: transitionsTaken += 1
359
359
 
360
360
  alt iteration bound exceeded
@@ -848,7 +848,7 @@ When no `events` option is provided, the engine incurs zero observability overhe
848
848
  `RunLifecycle` is the shared bookkeeping layer both drivers delegate to. It manages:
849
849
 
850
850
  - **Run identity** — generates a `runId` (or honors caller-provided), timestamps, and run record
851
- - **Persistence sequencing** — `createRun` → `savePhase`/`saveWorkflowState` per step → `finalizeRun` at the end
851
+ - **Persistence sequencing** — `createRun` → per-step atomic `commitHop` (transition + state + phase in one batch) → `finalizeRun` at the end (ADR-020)
852
852
  - **Observability** — wraps the full run in an OTel span, emits span events, logs each lifecycle event through `@gobing-ai/ts-infra` logger, and optionally emits `WorkflowEngineEvents` via an injected `EventBus`
853
853
  - **Error resilience** — `warnActionFailed()` logs non-fatal warnings for `onError: 'continue'` actions
854
854
 
@@ -869,6 +869,20 @@ const result = await RunLifecycle.run(
869
869
  );
870
870
  ```
871
871
 
872
+ For multi-step runs, use `commitHop` instead of separate `recordTransition` + `saveWorkflowState` calls:
873
+
874
+ ```ts
875
+ // commitHop atomically commits transition, state, and optional phase (ADR-020)
876
+ await lifecycle.commitHop(fromState, toState, trigger, transitionsTaken);
877
+ ```
878
+
879
+ On the next iteration, call `enter()` with `persist: false` to skip persistence (the state was already
880
+ persisted atomically by commitHop):
881
+
882
+ ```ts
883
+ await lifecycle.enter(targetState, transitionsTaken, false);
884
+ ```
885
+
872
886
  ## Error Handling
873
887
 
874
888
  | Error Class | When |
@@ -42,6 +42,12 @@ export interface LoadWorkflowExtensionsOptions {
42
42
  * own; the embedder supplies the import policy.
43
43
  */
44
44
  readonly moduleLoader: (absPath: string) => Promise<Record<string, unknown>>;
45
+ /**
46
+ * Optional canonical-path resolver for symlink-safe confinement (ADR-022).
47
+ * Forwarded to the shared loader. Node-facing embedders should supply
48
+ * `createNodeFileSystem().realPath` to enable symlink-escape rejection.
49
+ */
50
+ readonly realPath?: (absPath: string) => string;
45
51
  }
46
52
  /**
47
53
  * Import each extension module behind an explicit trust gate and register
@@ -1 +1 @@
1
- {"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAKlD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAGjD,6GAA6G;AAC7G,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACjC,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B;AAED,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAChF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAChD,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,SAAS,oBAAoB,EAAE,EACrC,OAAO,EAAE,6BAA6B,GACvC,OAAO,CAAC,IAAI,CAAC,CAqCf"}
1
+ {"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAKlD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAGjD,6GAA6G;AAC7G,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACjC,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B;AAED,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAChD,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,SAAS,oBAAoB,EAAE,EACrC,OAAO,EAAE,6BAA6B,GACvC,OAAO,CAAC,IAAI,CAAC,CAsCf"}
@@ -42,6 +42,7 @@ export async function loadWorkflowExtensionsIntoHost(host, refs, options) {
42
42
  allowExtensions: options.allowExtensions,
43
43
  logger: options.logger,
44
44
  moduleLoader: options.moduleLoader,
45
+ realPath: options.realPath,
45
46
  };
46
47
  await loadExtensionModules(sharedRefs, sharedOptions, async (sharedRef, extension) => {
47
48
  await registerExtensionOnHost(host, sharedRef, extension, options.logger);
@@ -24,6 +24,13 @@ export declare class DbWorkflowPersistenceAdapter implements WorkflowPersistence
24
24
  saveTransition(runId: string, from: string, to: string, trigger: string | null): Promise<void>;
25
25
  /** Save the latest workflow state snapshot. */
26
26
  saveWorkflowState(runId: string, state: string, data: Record<string, unknown>): Promise<void>;
27
+ commitTransition(runId: string, from: string, to: string, trigger: string | null, state: string, data: Record<string, unknown>, phase?: {
28
+ phase: string;
29
+ status: WorkflowStatus;
30
+ }): Promise<void>;
31
+ private transitionRow;
32
+ private stateRow;
33
+ private phaseRow;
27
34
  /** Insert a running action row. Returns the row id for later finalization. */
28
35
  saveActionStart(runId: string, node: string, kind: string): Promise<string>;
29
36
  /** Finalize an action row with duration, ok flag, and optional redacted result. */
@@ -89,6 +96,10 @@ export declare class MemoryWorkflowPersistenceAdapter implements WorkflowPersist
89
96
  saveTransition(runId: string, from: string, to: string, trigger: string | null): Promise<void>;
90
97
  /** Save the latest workflow state snapshot. */
91
98
  saveWorkflowState(runId: string, state: string, data: Record<string, unknown>): Promise<void>;
99
+ commitTransition(runId: string, from: string, to: string, trigger: string | null, state: string, data: Record<string, unknown>, phase?: {
100
+ phase: string;
101
+ status: WorkflowStatus;
102
+ }): Promise<void>;
92
103
  /** Load a single run by id. */
93
104
  loadRun(runId: string): Promise<WorkflowRunRecord | undefined>;
94
105
  /** List persisted workflow runs. */
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"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"}
@@ -40,21 +40,71 @@ export class DbWorkflowPersistenceAdapter {
40
40
  }
41
41
  /** Save one phase/state execution record. */
42
42
  async savePhase(runId, phase, status) {
43
- const now = Date.now();
44
- await this.db.run(`INSERT INTO phase_runs (id, run_id, phase, status, started_at, completed_at, created_at, updated_at)
45
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, `${runId}:phase:${phase}:${crypto.randomUUID()}`, runId, phase, status, new Date(now).toISOString(), status === 'running' ? null : new Date(now).toISOString(), now, now);
43
+ const op = this.phaseRow(runId, phase, status, Date.now());
44
+ await this.db.run(op.sql, ...op.params);
46
45
  }
47
46
  /** Save one transition record. */
48
47
  async saveTransition(runId, from, to, trigger) {
49
- const now = Date.now();
50
- await this.db.run(`INSERT INTO transition_runs (id, run_id, from_state, to_state, trigger, status, created_at, updated_at)
51
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, `${runId}:transition:${from}:${to}:${crypto.randomUUID()}`, runId, from, to, trigger, 'done', now, now);
48
+ const op = this.transitionRow(runId, from, to, trigger, Date.now());
49
+ await this.db.run(op.sql, ...op.params);
52
50
  }
53
51
  /** Save the latest workflow state snapshot. */
54
52
  async saveWorkflowState(runId, state, data) {
53
+ const op = this.stateRow(runId, state, data, Date.now());
54
+ await this.db.run(op.sql, ...op.params);
55
+ }
56
+ async commitTransition(runId, from, to, trigger, state, data, phase) {
57
+ await this.ensureSchema();
55
58
  const now = Date.now();
56
- await this.db.run(`INSERT INTO workflow_states (id, run_id, state, data_json, created_at, updated_at)
57
- VALUES (?, ?, ?, ?, ?, ?)`, `${runId}:state:${state}:${crypto.randomUUID()}`, runId, state, JSON.stringify(data), now, now);
59
+ const ops = [
60
+ this.transitionRow(runId, from, to, trigger, now),
61
+ this.stateRow(runId, state, data, now),
62
+ ];
63
+ if (phase) {
64
+ ops.push(this.phaseRow(runId, phase.phase, phase.status, now));
65
+ }
66
+ await this.db.batch(ops);
67
+ }
68
+ // Row builders shared by the individual save methods and commitTransition so
69
+ // the single-write and atomic-batch paths can never drift apart (ADR-020).
70
+ transitionRow(runId, from, to, trigger, now) {
71
+ return {
72
+ sql: `INSERT INTO transition_runs (id, run_id, from_state, to_state, trigger, status, created_at, updated_at)
73
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
74
+ params: [
75
+ `${runId}:transition:${from}:${to}:${crypto.randomUUID()}`,
76
+ runId,
77
+ from,
78
+ to,
79
+ trigger,
80
+ 'done',
81
+ now,
82
+ now,
83
+ ],
84
+ };
85
+ }
86
+ stateRow(runId, state, data, now) {
87
+ return {
88
+ sql: `INSERT INTO workflow_states (id, run_id, state, data_json, created_at, updated_at)
89
+ VALUES (?, ?, ?, ?, ?, ?)`,
90
+ params: [`${runId}:state:${state}:${crypto.randomUUID()}`, runId, state, JSON.stringify(data), now, now],
91
+ };
92
+ }
93
+ phaseRow(runId, phase, status, now) {
94
+ return {
95
+ sql: `INSERT INTO phase_runs (id, run_id, phase, status, started_at, completed_at, created_at, updated_at)
96
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
97
+ params: [
98
+ `${runId}:phase:${phase}:${crypto.randomUUID()}`,
99
+ runId,
100
+ phase,
101
+ status,
102
+ new Date(now).toISOString(),
103
+ status === 'running' ? null : new Date(now).toISOString(),
104
+ now,
105
+ now,
106
+ ],
107
+ };
58
108
  }
59
109
  /** Insert a running action row. Returns the row id for later finalization. */
60
110
  async saveActionStart(runId, node, kind) {
@@ -195,6 +245,14 @@ export class MemoryWorkflowPersistenceAdapter {
195
245
  async saveWorkflowState(runId, state, data) {
196
246
  this.states.push({ runId, state, data });
197
247
  }
248
+ async commitTransition(runId, from, to, trigger, state, data, phase) {
249
+ // Memory adapter: synchronous pushes are atomic by nature.
250
+ this.transitions.push({ runId, from, to, trigger });
251
+ this.states.push({ runId, state, data });
252
+ if (phase) {
253
+ this.phases.push({ runId, phase: phase.phase, status: phase.status });
254
+ }
255
+ }
198
256
  /** Load a single run by id. */
199
257
  async loadRun(runId) {
200
258
  return this.runs.get(runId);
@@ -1,6 +1,6 @@
1
1
  import { type EventBus, type Logger } from '@gobing-ai/ts-infra';
2
2
  import type { WorkflowEngineEvents } from './events';
3
- import type { WorkflowPersistenceAdapter, WorkflowRunOptions, WorkflowRunResult } from './types';
3
+ import type { WorkflowPersistenceAdapter, WorkflowRunOptions, WorkflowRunResult, WorkflowStatus } from './types';
4
4
  /** Workflow dialect carried on every run, span, and persisted record. */
5
5
  export type WorkflowMode = WorkflowRunResult['mode'];
6
6
  /** Keys of the runtime builtin namespace, single-sourced for resolver + validator. */
@@ -53,8 +53,25 @@ export declare class RunLifecycle {
53
53
  * The caller is responsible for ensuring the run exists and emitting the resumed event.
54
54
  */
55
55
  static resume(workflowName: string, mode: WorkflowMode, deps: RunLifecycleDeps, runId: string, externalKey: string | undefined, loop: (lifecycle: RunLifecycle) => Promise<WorkflowRunResult>): Promise<WorkflowRunResult>;
56
- /** Persist the current state/node snapshot and mark its phase running. */
57
- enter(stateOrNodeId: string, transitionsTaken: number): Promise<void>;
56
+ /**
57
+ * Persist the current state/node snapshot and mark its phase running.
58
+ * When `persist` is false (set by driver loops after `commitHop` already
59
+ * persisted the state atomically), only emit the enter observability —
60
+ * skip `saveWorkflowState` + `savePhase` to avoid duplicate INSERT rows.
61
+ */
62
+ enter(stateOrNodeId: string, transitionsTaken: number, persist?: boolean): Promise<void>;
63
+ /**
64
+ * Atomically commit a transition together with the resulting state snapshot
65
+ * and optional phase record, then emit the transition observability event.
66
+ * Replaces the old `recordTransition` + `enter` pair at every driver hop
67
+ * site so a crash between writes is impossible (ADR-020). Emits
68
+ * `workflow.node.transition` only after the batch commits — a rolled-back
69
+ * batch emits nothing (R1.6).
70
+ */
71
+ commitHop(from: string, to: string, trigger: string | null, transitionsTaken: number, phase?: {
72
+ phase: string;
73
+ status: WorkflowStatus;
74
+ }): Promise<void>;
58
75
  /** Persist a transition and emit its observability event. */
59
76
  recordTransition(from: string, to: string, trigger: string | null): Promise<void>;
60
77
  /** Finalize the run as succeeded and return its result. */
@@ -1 +1 @@
1
- {"version":3,"file":"run-lifecycle.d.ts","sourceRoot":"","sources":["../src/run-lifecycle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run-lifecycle.d.ts","sourceRoot":"","sources":["../src/run-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,QAAQ,EAAa,KAAK,MAAM,EAAc,MAAM,qBAAqB,CAAC;AAEtG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,KAAK,EACR,0BAA0B,EAC1B,kBAAkB,EAElB,iBAAiB,EACjB,cAAc,EACjB,MAAM,SAAS,CAAC;AAEjB,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAErD,sFAAsF;AACtF,eAAO,MAAM,oBAAoB,wFASvB,CAAC;AAEX,mGAAmG;AACnG,wBAAgB,eAAe,CAC3B,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,YAAY,GACnB,MAAM,CAAC,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAWhE;AAED,yEAAyE;AACzE,wBAAgB,UAAU,CACtB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAmB,GAC7D,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIxB;AAED,2DAA2D;AAC3D,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;IACjD,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AACH,qBAAa,YAAY;IASjB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI;IATzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6C;IACpE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,OAAO;IAcP;;;;;;;;OAQG;IACH,MAAM,CAAC,qBAAqB,CACxB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,MAAM,GAAG,SAAS,GAChC,YAAY;IAIf;;;;OAIG;WACU,GAAG,CACZ,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,CAAC,SAAS,EAAE,YAAY,KAAK,OAAO,CAAC,iBAAiB,CAAC,GAC9D,OAAO,CAAC,iBAAiB,CAAC;IAyC7B;;;OAGG;WACU,MAAM,CACf,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,IAAI,EAAE,CAAC,SAAS,EAAE,YAAY,KAAK,OAAO,CAAC,iBAAiB,CAAC,GAC9D,OAAO,CAAC,iBAAiB,CAAC;IAY7B;;;;;OAKG;IACG,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3F;;;;;;;OAOG;IACG,SAAS,CACX,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,gBAAgB,EAAE,MAAM,EACxB,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,cAAc,CAAA;KAAE,GAClD,OAAO,CAAC,IAAI,CAAC;IAiBhB,6DAA6D;IACvD,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBvF,2DAA2D;IACrD,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAcpF,wDAAwD;IAClD,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAcvG,wDAAwD;IAClD,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAcxF,mGAAmG;IACnG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/B,uEAAuE;IACvE,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKtD,mEAAmE;IACnE,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI;IAWtF,6GAA6G;IAC7G,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAgBvF,kFAAkF;IAClF,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAW7E,yDAAyD;IACzD,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI1D,qDAAqD;IACrD,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI;IAI7C,OAAO,CAAC,MAAM;IAiBd,OAAO,CAAC,MAAM,CAAC,SAAS;CAmB3B"}
@@ -109,10 +109,17 @@ export class RunLifecycle {
109
109
  return await loop(lifecycle);
110
110
  }, { attributes: { 'workflow.name': workflowName, 'workflow.mode': mode } });
111
111
  }
112
- /** Persist the current state/node snapshot and mark its phase running. */
113
- async enter(stateOrNodeId, transitionsTaken) {
114
- await this.persistence.saveWorkflowState(this.runId, stateOrNodeId, { transitionsTaken });
115
- await this.persistence.savePhase(this.runId, stateOrNodeId, 'running');
112
+ /**
113
+ * Persist the current state/node snapshot and mark its phase running.
114
+ * When `persist` is false (set by driver loops after `commitHop` already
115
+ * persisted the state atomically), only emit the enter observability —
116
+ * skip `saveWorkflowState` + `savePhase` to avoid duplicate INSERT rows.
117
+ */
118
+ async enter(stateOrNodeId, transitionsTaken, persist = true) {
119
+ if (persist) {
120
+ await this.persistence.saveWorkflowState(this.runId, stateOrNodeId, { transitionsTaken });
121
+ await this.persistence.savePhase(this.runId, stateOrNodeId, 'running');
122
+ }
116
123
  addSpanEvent('workflow.node.enter', {
117
124
  runId: this.runId,
118
125
  node: stateOrNodeId,
@@ -124,6 +131,30 @@ export class RunLifecycle {
124
131
  transitionsTaken,
125
132
  });
126
133
  }
134
+ /**
135
+ * Atomically commit a transition together with the resulting state snapshot
136
+ * and optional phase record, then emit the transition observability event.
137
+ * Replaces the old `recordTransition` + `enter` pair at every driver hop
138
+ * site so a crash between writes is impossible (ADR-020). Emits
139
+ * `workflow.node.transition` only after the batch commits — a rolled-back
140
+ * batch emits nothing (R1.6).
141
+ */
142
+ async commitHop(from, to, trigger, transitionsTaken, phase) {
143
+ await this.persistence.commitTransition(this.runId, from, to, trigger, to, { transitionsTaken }, phase);
144
+ addSpanEvent('workflow.node.transition', {
145
+ runId: this.runId,
146
+ from,
147
+ to,
148
+ ...(trigger === null ? {} : { trigger }),
149
+ });
150
+ void this.events?.emit('workflow.node.transition', {
151
+ runId: this.runId,
152
+ from,
153
+ to,
154
+ trigger,
155
+ externalKey: this.externalKey,
156
+ });
157
+ }
127
158
  /** Persist a transition and emit its observability event. */
128
159
  async recordTransition(from, to, trigger) {
129
160
  await this.persistence.saveTransition(this.runId, from, to, trigger);
package/dist/service.js CHANGED
@@ -191,11 +191,11 @@ export class WorkflowService {
191
191
  });
192
192
  }
193
193
  }
194
- // Commit: persist the transition + emit node.transition through the shared
195
- // lifecycle seam, then the external-only state snapshot and requested event.
194
+ // Commit: atomically persist the transition + state snapshot in a single
195
+ // batch (ADR-020), then emit the external-only requested event. No phase
196
+ // record — external transitions don't drive phase tracking.
196
197
  const trigger = transition.trigger ?? null;
197
- await lifecycle.recordTransition(currentState, toState, trigger);
198
- await this.persistence.saveWorkflowState(runId, toState, {});
198
+ await lifecycle.commitHop(currentState, toState, trigger, 0);
199
199
  void options?.events?.emit('workflow.transition.requested', {
200
200
  runId,
201
201
  from: currentState,
@@ -1 +1 @@
1
- {"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAEjD,OAAO,KAAK,EAER,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACpB,MAAM,SAAS,CAAC;AAGjB,yDAAyD;AACzD,MAAM,WAAW,yBAAyB;IACtC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;CACpD;AAED,wEAAwE;AACxE,qBAAa,kBAAkB;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,yBAAyB;IAE/D,6DAA6D;IACvD,GAAG,CAAC,QAAQ,EAAE,uBAAuB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAU1G,iFAAiF;IAC3E,MAAM,CACR,QAAQ,EAAE,uBAAuB,EACjC,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,GAAE,kBAAuB,GACjC,OAAO,CAAC,iBAAiB,CAAC;YAWf,IAAI;CAwHrB"}
1
+ {"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAEjD,OAAO,KAAK,EAER,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACpB,MAAM,SAAS,CAAC;AAGjB,yDAAyD;AACzD,MAAM,WAAW,yBAAyB;IACtC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;CACpD;AAED,wEAAwE;AACxE,qBAAa,kBAAkB;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,yBAAyB;IAE/D,6DAA6D;IACvD,GAAG,CAAC,QAAQ,EAAE,uBAAuB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAU1G,iFAAiF;IAC3E,MAAM,CACR,QAAQ,EAAE,uBAAuB,EACjC,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,GAAE,kBAAuB,GACjC,OAAO,CAAC,iBAAiB,CAAC;YAWf,IAAI;CAoIrB"}
@@ -28,6 +28,10 @@ export class StateMachineDriver {
28
28
  const iterationBound = workflow.iterationBound ?? 50;
29
29
  const defaultOnError = workflow.defaultOnError;
30
30
  let isResume = resumeFromState !== undefined;
31
+ // When `commitHop` has already persisted the new state's snapshot +
32
+ // phase atomically (every iteration after the first), `enter` must skip
33
+ // the persist half to avoid duplicate INSERT rows (ADR-020).
34
+ let persistedViaHop = false;
31
35
  if (current === undefined) {
32
36
  const label = resumeFromState ?? workflow.initialState;
33
37
  throw new FSMError(`State "${label}" is not declared`);
@@ -36,10 +40,12 @@ export class StateMachineDriver {
36
40
  if (isResume) {
37
41
  // Resume: skip enter actions on the first iteration (already ran before pause).
38
42
  isResume = false;
43
+ persistedViaHop = true; // state already persisted before the pause
39
44
  }
40
45
  else {
41
- // 1. Persist current state snapshot before work starts.
42
- await lifecycle.enter(current.id, transitionsTaken);
46
+ // 1. Persist current state snapshot before work starts (skipped when
47
+ // commitHop already persisted it atomically on the previous hop).
48
+ await lifecycle.enter(current.id, transitionsTaken, !persistedViaHop);
43
49
  // 2. Execute this state's on-enter actions in declaration order.
44
50
  const enter = options.dryRun
45
51
  ? EMPTY_OUTCOME
@@ -112,9 +118,14 @@ export class StateMachineDriver {
112
118
  vars = mergeSetVars(vars, exit.result.setVars);
113
119
  if (exit.outcome === 'fail')
114
120
  return await lifecycle.fail(current.id, transitionsTaken, exit.result?.error);
115
- // 7. Persist transition and move to the target state.
121
+ // 7. Atomically commit the transition + new state snapshot + phase in a
122
+ // single batch (ADR-020). The next iteration's `enter` becomes
123
+ // observe-only; a crash between writes is now impossible.
116
124
  transitionsTaken += 1;
117
- await lifecycle.recordTransition(current.id, nextTransition.to, nextTransition.trigger ?? null);
125
+ await lifecycle.commitHop(current.id, nextTransition.to, nextTransition.trigger ?? null, transitionsTaken, {
126
+ phase: nextTransition.to,
127
+ status: 'running',
128
+ });
118
129
  if (transitionsTaken > iterationBound) {
119
130
  return await lifecycle.fail(current.id, transitionsTaken, 'iteration-bound-exceeded');
120
131
  }
@@ -122,6 +133,7 @@ export class StateMachineDriver {
122
133
  if (nextState === undefined)
123
134
  throw new FSMError(`Transition target "${nextTransition.to}" is not declared`);
124
135
  current = nextState;
136
+ persistedViaHop = true;
125
137
  }
126
138
  }
127
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transition-flow.d.ts","sourceRoot":"","sources":["../src/transition-flow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAEjD,OAAO,KAAK,EAER,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACpB,MAAM,SAAS,CAAC;AAGjB,2DAA2D;AAC3D,MAAM,WAAW,2BAA2B;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;CACpD;AAED,0EAA0E;AAC1E,qBAAa,oBAAoB;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,2BAA2B;IAEjE,+DAA+D;IACzD,GAAG,CAAC,QAAQ,EAAE,yBAAyB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAU5G,qFAAqF;IAC/E,MAAM,CACR,QAAQ,EAAE,yBAAyB,EACnC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,GAAE,kBAAuB,GACjC,OAAO,CAAC,iBAAiB,CAAC;YAWf,IAAI;CAuGrB"}
1
+ {"version":3,"file":"transition-flow.d.ts","sourceRoot":"","sources":["../src/transition-flow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAEjD,OAAO,KAAK,EAER,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACpB,MAAM,SAAS,CAAC;AAGjB,2DAA2D;AAC3D,MAAM,WAAW,2BAA2B;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;CACpD;AAED,0EAA0E;AAC1E,qBAAa,oBAAoB;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,2BAA2B;IAEjE,+DAA+D;IACzD,GAAG,CAAC,QAAQ,EAAE,yBAAyB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAU5G,qFAAqF;IAC/E,MAAM,CACR,QAAQ,EAAE,yBAAyB,EACnC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,GAAE,kBAAuB,GACjC,OAAO,CAAC,iBAAiB,CAAC;YAWf,IAAI;CAmHrB"}
@@ -28,6 +28,10 @@ export class TransitionFlowDriver {
28
28
  const iterationBound = workflow.iterationBound ?? 50;
29
29
  const defaultOnError = workflow.defaultOnError;
30
30
  let isResume = resumeFromNode !== undefined;
31
+ // When `commitHop` has already persisted the new node's snapshot +
32
+ // phase atomically (every iteration after the first), `enter` must skip
33
+ // the persist half to avoid duplicate INSERT rows (ADR-020).
34
+ let persistedViaHop = false;
31
35
  if (current === undefined) {
32
36
  const label = resumeFromNode ?? workflow.initialNode;
33
37
  throw new FSMError(`Node "${label}" is not declared`);
@@ -36,10 +40,12 @@ export class TransitionFlowDriver {
36
40
  if (isResume) {
37
41
  // Resume: skip enter + node action on the first iteration (already ran before pause).
38
42
  isResume = false;
43
+ persistedViaHop = true; // node already persisted before the pause
39
44
  }
40
45
  else {
41
- // 1. Persist current node snapshot before action execution.
42
- await lifecycle.enter(current.id, transitionsTaken);
46
+ // 1. Persist current node snapshot before action execution (skipped when
47
+ // commitHop already persisted it atomically on the previous hop).
48
+ await lifecycle.enter(current.id, transitionsTaken, !persistedViaHop);
43
49
  // 2. Execute the node action when one is configured (skipped in dry-run).
44
50
  if (options.dryRun) {
45
51
  if (current.action !== undefined) {
@@ -90,9 +96,14 @@ export class TransitionFlowDriver {
90
96
  if (edge === undefined) {
91
97
  return await lifecycle.fail(current.id, transitionsTaken, 'no-passing-edge');
92
98
  }
93
- // 5. Persist the edge transition.
99
+ // 5. Atomically commit the edge transition + new node snapshot + phase in
100
+ // a single batch (ADR-020). The next iteration's `enter` becomes
101
+ // observe-only; a crash between writes is now impossible.
94
102
  transitionsTaken += 1;
95
- await lifecycle.recordTransition(current.id, edge.to, edge.condition?.kind ?? null);
103
+ await lifecycle.commitHop(current.id, edge.to, edge.condition?.kind ?? null, transitionsTaken, {
104
+ phase: edge.to,
105
+ status: 'running',
106
+ });
96
107
  // 6. Enforce the iteration bound after taking the transition.
97
108
  if (transitionsTaken > iterationBound) {
98
109
  return await lifecycle.fail(current.id, transitionsTaken, 'iteration-bound-exceeded');
@@ -102,6 +113,7 @@ export class TransitionFlowDriver {
102
113
  if (nextNode === undefined)
103
114
  throw new FSMError(`Edge target "${edge.to}" is not declared`);
104
115
  current = nextNode;
116
+ persistedViaHop = true;
105
117
  }
106
118
  }
107
119
  }
package/dist/types.d.ts CHANGED
@@ -229,6 +229,16 @@ export interface WorkflowPersistenceAdapter {
229
229
  savePhase(runId: string, phase: string, status: WorkflowStatus): Promise<void>;
230
230
  saveTransition(runId: string, from: string, to: string, trigger: string | null): Promise<void>;
231
231
  saveWorkflowState(runId: string, state: string, data: Record<string, unknown>): Promise<void>;
232
+ /**
233
+ * Atomically persist a transition together with its resulting state snapshot
234
+ * and optional phase record in a single DB batch — either all commit or none.
235
+ * Prevents partial-state windows when the process fails between saveTransition()
236
+ * and saveWorkflowState().
237
+ */
238
+ commitTransition(runId: string, from: string, to: string, trigger: string | null, state: string, data: Record<string, unknown>, phase?: {
239
+ phase: string;
240
+ status: WorkflowStatus;
241
+ }): Promise<void>;
232
242
  /** Two-phase action persistence: insert a running row at action start. Returns the action row id. */
233
243
  saveActionStart(runId: string, node: string, kind: string): Promise<string>;
234
244
  /** Finalize an action row with duration, ok, result. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-dual-workflow-engine",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "@gobing-ai/ts-dual-workflow-engine — State-machine and transition-flow workflow runtime.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -48,9 +48,9 @@
48
48
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-dual-workflow-engine-v<version> && git push --tags' && exit 1"
49
49
  },
50
50
  "dependencies": {
51
- "@gobing-ai/ts-db": "^0.4.6",
52
- "@gobing-ai/ts-infra": "^0.4.6",
53
- "@gobing-ai/ts-runtime": "^0.4.6",
51
+ "@gobing-ai/ts-db": "^0.4.7",
52
+ "@gobing-ai/ts-infra": "^0.4.7",
53
+ "@gobing-ai/ts-runtime": "^0.4.7",
54
54
  "zod": "^4.1.0"
55
55
  },
56
56
  "devDependencies": {
package/src/extensions.ts CHANGED
@@ -51,6 +51,12 @@ export interface LoadWorkflowExtensionsOptions {
51
51
  * own; the embedder supplies the import policy.
52
52
  */
53
53
  readonly moduleLoader: (absPath: string) => Promise<Record<string, unknown>>;
54
+ /**
55
+ * Optional canonical-path resolver for symlink-safe confinement (ADR-022).
56
+ * Forwarded to the shared loader. Node-facing embedders should supply
57
+ * `createNodeFileSystem().realPath` to enable symlink-escape rejection.
58
+ */
59
+ readonly realPath?: (absPath: string) => string;
54
60
  }
55
61
 
56
62
  /**
@@ -102,6 +108,7 @@ export async function loadWorkflowExtensionsIntoHost(
102
108
  allowExtensions: options.allowExtensions,
103
109
  logger: options.logger,
104
110
  moduleLoader: options.moduleLoader,
111
+ realPath: options.realPath,
105
112
  };
106
113
 
107
114
  await loadExtensionModules<WorkflowExtensionKind>(sharedRefs, sharedOptions, async (sharedRef, extension) => {
@@ -1,4 +1,4 @@
1
- import type { DbAdapter } from '@gobing-ai/ts-db';
1
+ import type { DbAdapter, DbBatchOp } from '@gobing-ai/ts-db';
2
2
  import { RunCollisionError } from './errors';
3
3
  import { WORKFLOW_ENGINE_SCHEMA_SQL } from './schema-sql';
4
4
  import type {
@@ -68,51 +68,86 @@ export class DbWorkflowPersistenceAdapter implements WorkflowPersistenceAdapter
68
68
 
69
69
  /** Save one phase/state execution record. */
70
70
  async savePhase(runId: string, phase: string, status: WorkflowStatus): Promise<void> {
71
- const now = Date.now();
72
- await this.db.run(
73
- `INSERT INTO phase_runs (id, run_id, phase, status, started_at, completed_at, created_at, updated_at)
74
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
75
- `${runId}:phase:${phase}:${crypto.randomUUID()}`,
76
- runId,
77
- phase,
78
- status,
79
- new Date(now).toISOString(),
80
- status === 'running' ? null : new Date(now).toISOString(),
81
- now,
82
- now,
83
- );
71
+ const op = this.phaseRow(runId, phase, status, Date.now());
72
+ await this.db.run(op.sql, ...op.params);
84
73
  }
85
74
 
86
75
  /** Save one transition record. */
87
76
  async saveTransition(runId: string, from: string, to: string, trigger: string | null): Promise<void> {
88
- const now = Date.now();
89
- await this.db.run(
90
- `INSERT INTO transition_runs (id, run_id, from_state, to_state, trigger, status, created_at, updated_at)
91
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
92
- `${runId}:transition:${from}:${to}:${crypto.randomUUID()}`,
93
- runId,
94
- from,
95
- to,
96
- trigger,
97
- 'done',
98
- now,
99
- now,
100
- );
77
+ const op = this.transitionRow(runId, from, to, trigger, Date.now());
78
+ await this.db.run(op.sql, ...op.params);
101
79
  }
102
80
 
103
81
  /** Save the latest workflow state snapshot. */
104
82
  async saveWorkflowState(runId: string, state: string, data: Record<string, unknown>): Promise<void> {
83
+ const op = this.stateRow(runId, state, data, Date.now());
84
+ await this.db.run(op.sql, ...op.params);
85
+ }
86
+
87
+ async commitTransition(
88
+ runId: string,
89
+ from: string,
90
+ to: string,
91
+ trigger: string | null,
92
+ state: string,
93
+ data: Record<string, unknown>,
94
+ phase?: { phase: string; status: WorkflowStatus },
95
+ ): Promise<void> {
96
+ await this.ensureSchema();
105
97
  const now = Date.now();
106
- await this.db.run(
107
- `INSERT INTO workflow_states (id, run_id, state, data_json, created_at, updated_at)
98
+ const ops: DbBatchOp[] = [
99
+ this.transitionRow(runId, from, to, trigger, now),
100
+ this.stateRow(runId, state, data, now),
101
+ ];
102
+ if (phase) {
103
+ ops.push(this.phaseRow(runId, phase.phase, phase.status, now));
104
+ }
105
+ await this.db.batch(ops);
106
+ }
107
+
108
+ // Row builders shared by the individual save methods and commitTransition so
109
+ // the single-write and atomic-batch paths can never drift apart (ADR-020).
110
+
111
+ private transitionRow(runId: string, from: string, to: string, trigger: string | null, now: number): DbBatchOp {
112
+ return {
113
+ sql: `INSERT INTO transition_runs (id, run_id, from_state, to_state, trigger, status, created_at, updated_at)
114
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
115
+ params: [
116
+ `${runId}:transition:${from}:${to}:${crypto.randomUUID()}`,
117
+ runId,
118
+ from,
119
+ to,
120
+ trigger,
121
+ 'done',
122
+ now,
123
+ now,
124
+ ],
125
+ };
126
+ }
127
+
128
+ private stateRow(runId: string, state: string, data: Record<string, unknown>, now: number): DbBatchOp {
129
+ return {
130
+ sql: `INSERT INTO workflow_states (id, run_id, state, data_json, created_at, updated_at)
108
131
  VALUES (?, ?, ?, ?, ?, ?)`,
109
- `${runId}:state:${state}:${crypto.randomUUID()}`,
110
- runId,
111
- state,
112
- JSON.stringify(data),
113
- now,
114
- now,
115
- );
132
+ params: [`${runId}:state:${state}:${crypto.randomUUID()}`, runId, state, JSON.stringify(data), now, now],
133
+ };
134
+ }
135
+
136
+ private phaseRow(runId: string, phase: string, status: WorkflowStatus, now: number): DbBatchOp {
137
+ return {
138
+ sql: `INSERT INTO phase_runs (id, run_id, phase, status, started_at, completed_at, created_at, updated_at)
139
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
140
+ params: [
141
+ `${runId}:phase:${phase}:${crypto.randomUUID()}`,
142
+ runId,
143
+ phase,
144
+ status,
145
+ new Date(now).toISOString(),
146
+ status === 'running' ? null : new Date(now).toISOString(),
147
+ now,
148
+ now,
149
+ ],
150
+ };
116
151
  }
117
152
 
118
153
  /** Insert a running action row. Returns the row id for later finalization. */
@@ -318,6 +353,23 @@ export class MemoryWorkflowPersistenceAdapter implements WorkflowPersistenceAdap
318
353
  this.states.push({ runId, state, data });
319
354
  }
320
355
 
356
+ async commitTransition(
357
+ runId: string,
358
+ from: string,
359
+ to: string,
360
+ trigger: string | null,
361
+ state: string,
362
+ data: Record<string, unknown>,
363
+ phase?: { phase: string; status: WorkflowStatus },
364
+ ): Promise<void> {
365
+ // Memory adapter: synchronous pushes are atomic by nature.
366
+ this.transitions.push({ runId, from, to, trigger });
367
+ this.states.push({ runId, state, data });
368
+ if (phase) {
369
+ this.phases.push({ runId, phase: phase.phase, status: phase.status });
370
+ }
371
+ }
372
+
321
373
  /** Load a single run by id. */
322
374
  async loadRun(runId: string): Promise<WorkflowRunRecord | undefined> {
323
375
  return this.runs.get(runId);
@@ -183,10 +183,17 @@ export class RunLifecycle {
183
183
  );
184
184
  }
185
185
 
186
- /** Persist the current state/node snapshot and mark its phase running. */
187
- async enter(stateOrNodeId: string, transitionsTaken: number): Promise<void> {
188
- await this.persistence.saveWorkflowState(this.runId, stateOrNodeId, { transitionsTaken });
189
- await this.persistence.savePhase(this.runId, stateOrNodeId, 'running');
186
+ /**
187
+ * Persist the current state/node snapshot and mark its phase running.
188
+ * When `persist` is false (set by driver loops after `commitHop` already
189
+ * persisted the state atomically), only emit the enter observability —
190
+ * skip `saveWorkflowState` + `savePhase` to avoid duplicate INSERT rows.
191
+ */
192
+ async enter(stateOrNodeId: string, transitionsTaken: number, persist = true): Promise<void> {
193
+ if (persist) {
194
+ await this.persistence.saveWorkflowState(this.runId, stateOrNodeId, { transitionsTaken });
195
+ await this.persistence.savePhase(this.runId, stateOrNodeId, 'running');
196
+ }
190
197
  addSpanEvent('workflow.node.enter', {
191
198
  runId: this.runId,
192
199
  node: stateOrNodeId,
@@ -199,6 +206,37 @@ export class RunLifecycle {
199
206
  });
200
207
  }
201
208
 
209
+ /**
210
+ * Atomically commit a transition together with the resulting state snapshot
211
+ * and optional phase record, then emit the transition observability event.
212
+ * Replaces the old `recordTransition` + `enter` pair at every driver hop
213
+ * site so a crash between writes is impossible (ADR-020). Emits
214
+ * `workflow.node.transition` only after the batch commits — a rolled-back
215
+ * batch emits nothing (R1.6).
216
+ */
217
+ async commitHop(
218
+ from: string,
219
+ to: string,
220
+ trigger: string | null,
221
+ transitionsTaken: number,
222
+ phase?: { phase: string; status: WorkflowStatus },
223
+ ): Promise<void> {
224
+ await this.persistence.commitTransition(this.runId, from, to, trigger, to, { transitionsTaken }, phase);
225
+ addSpanEvent('workflow.node.transition', {
226
+ runId: this.runId,
227
+ from,
228
+ to,
229
+ ...(trigger === null ? {} : { trigger }),
230
+ });
231
+ void this.events?.emit('workflow.node.transition', {
232
+ runId: this.runId,
233
+ from,
234
+ to,
235
+ trigger,
236
+ externalKey: this.externalKey,
237
+ });
238
+ }
239
+
202
240
  /** Persist a transition and emit its observability event. */
203
241
  async recordTransition(from: string, to: string, trigger: string | null): Promise<void> {
204
242
  await this.persistence.saveTransition(this.runId, from, to, trigger);
package/src/service.ts CHANGED
@@ -257,11 +257,11 @@ export class WorkflowService {
257
257
  }
258
258
  }
259
259
 
260
- // Commit: persist the transition + emit node.transition through the shared
261
- // lifecycle seam, then the external-only state snapshot and requested event.
260
+ // Commit: atomically persist the transition + state snapshot in a single
261
+ // batch (ADR-020), then emit the external-only requested event. No phase
262
+ // record — external transitions don't drive phase tracking.
262
263
  const trigger = transition.trigger ?? null;
263
- await lifecycle.recordTransition(currentState, toState, trigger);
264
- await this.persistence.saveWorkflowState(runId, toState, {});
264
+ await lifecycle.commitHop(currentState, toState, trigger, 0);
265
265
  void options?.events?.emit('workflow.transition.requested', {
266
266
  runId,
267
267
  from: currentState,
@@ -67,6 +67,10 @@ export class StateMachineDriver {
67
67
  const iterationBound = workflow.iterationBound ?? 50;
68
68
  const defaultOnError = workflow.defaultOnError;
69
69
  let isResume = resumeFromState !== undefined;
70
+ // When `commitHop` has already persisted the new state's snapshot +
71
+ // phase atomically (every iteration after the first), `enter` must skip
72
+ // the persist half to avoid duplicate INSERT rows (ADR-020).
73
+ let persistedViaHop = false;
70
74
 
71
75
  if (current === undefined) {
72
76
  const label = resumeFromState ?? workflow.initialState;
@@ -77,9 +81,11 @@ export class StateMachineDriver {
77
81
  if (isResume) {
78
82
  // Resume: skip enter actions on the first iteration (already ran before pause).
79
83
  isResume = false;
84
+ persistedViaHop = true; // state already persisted before the pause
80
85
  } else {
81
- // 1. Persist current state snapshot before work starts.
82
- await lifecycle.enter(current.id, transitionsTaken);
86
+ // 1. Persist current state snapshot before work starts (skipped when
87
+ // commitHop already persisted it atomically on the previous hop).
88
+ await lifecycle.enter(current.id, transitionsTaken, !persistedViaHop);
83
89
 
84
90
  // 2. Execute this state's on-enter actions in declaration order.
85
91
  const enter = options.dryRun
@@ -159,15 +165,21 @@ export class StateMachineDriver {
159
165
  if (exit.result?.setVars) vars = mergeSetVars(vars, exit.result.setVars);
160
166
  if (exit.outcome === 'fail') return await lifecycle.fail(current.id, transitionsTaken, exit.result?.error);
161
167
 
162
- // 7. Persist transition and move to the target state.
168
+ // 7. Atomically commit the transition + new state snapshot + phase in a
169
+ // single batch (ADR-020). The next iteration's `enter` becomes
170
+ // observe-only; a crash between writes is now impossible.
163
171
  transitionsTaken += 1;
164
- await lifecycle.recordTransition(current.id, nextTransition.to, nextTransition.trigger ?? null);
172
+ await lifecycle.commitHop(current.id, nextTransition.to, nextTransition.trigger ?? null, transitionsTaken, {
173
+ phase: nextTransition.to,
174
+ status: 'running',
175
+ });
165
176
  if (transitionsTaken > iterationBound) {
166
177
  return await lifecycle.fail(current.id, transitionsTaken, 'iteration-bound-exceeded');
167
178
  }
168
179
  const nextState = states.get(nextTransition.to);
169
180
  if (nextState === undefined) throw new FSMError(`Transition target "${nextTransition.to}" is not declared`);
170
181
  current = nextState;
182
+ persistedViaHop = true;
171
183
  }
172
184
  }
173
185
  }
@@ -67,6 +67,10 @@ export class TransitionFlowDriver {
67
67
  const iterationBound = workflow.iterationBound ?? 50;
68
68
  const defaultOnError = workflow.defaultOnError;
69
69
  let isResume = resumeFromNode !== undefined;
70
+ // When `commitHop` has already persisted the new node's snapshot +
71
+ // phase atomically (every iteration after the first), `enter` must skip
72
+ // the persist half to avoid duplicate INSERT rows (ADR-020).
73
+ let persistedViaHop = false;
70
74
 
71
75
  if (current === undefined) {
72
76
  const label = resumeFromNode ?? workflow.initialNode;
@@ -77,9 +81,11 @@ export class TransitionFlowDriver {
77
81
  if (isResume) {
78
82
  // Resume: skip enter + node action on the first iteration (already ran before pause).
79
83
  isResume = false;
84
+ persistedViaHop = true; // node already persisted before the pause
80
85
  } else {
81
- // 1. Persist current node snapshot before action execution.
82
- await lifecycle.enter(current.id, transitionsTaken);
86
+ // 1. Persist current node snapshot before action execution (skipped when
87
+ // commitHop already persisted it atomically on the previous hop).
88
+ await lifecycle.enter(current.id, transitionsTaken, !persistedViaHop);
83
89
 
84
90
  // 2. Execute the node action when one is configured (skipped in dry-run).
85
91
  if (options.dryRun) {
@@ -138,9 +144,14 @@ export class TransitionFlowDriver {
138
144
  return await lifecycle.fail(current.id, transitionsTaken, 'no-passing-edge');
139
145
  }
140
146
 
141
- // 5. Persist the edge transition.
147
+ // 5. Atomically commit the edge transition + new node snapshot + phase in
148
+ // a single batch (ADR-020). The next iteration's `enter` becomes
149
+ // observe-only; a crash between writes is now impossible.
142
150
  transitionsTaken += 1;
143
- await lifecycle.recordTransition(current.id, edge.to, edge.condition?.kind ?? null);
151
+ await lifecycle.commitHop(current.id, edge.to, edge.condition?.kind ?? null, transitionsTaken, {
152
+ phase: edge.to,
153
+ status: 'running',
154
+ });
144
155
 
145
156
  // 6. Enforce the iteration bound after taking the transition.
146
157
  if (transitionsTaken > iterationBound) {
@@ -151,6 +162,7 @@ export class TransitionFlowDriver {
151
162
  const nextNode = nodes.get(edge.to);
152
163
  if (nextNode === undefined) throw new FSMError(`Edge target "${edge.to}" is not declared`);
153
164
  current = nextNode;
165
+ persistedViaHop = true;
154
166
  }
155
167
  }
156
168
  }
package/src/types.ts CHANGED
@@ -259,6 +259,21 @@ export interface WorkflowPersistenceAdapter {
259
259
  savePhase(runId: string, phase: string, status: WorkflowStatus): Promise<void>;
260
260
  saveTransition(runId: string, from: string, to: string, trigger: string | null): Promise<void>;
261
261
  saveWorkflowState(runId: string, state: string, data: Record<string, unknown>): Promise<void>;
262
+ /**
263
+ * Atomically persist a transition together with its resulting state snapshot
264
+ * and optional phase record in a single DB batch — either all commit or none.
265
+ * Prevents partial-state windows when the process fails between saveTransition()
266
+ * and saveWorkflowState().
267
+ */
268
+ commitTransition(
269
+ runId: string,
270
+ from: string,
271
+ to: string,
272
+ trigger: string | null,
273
+ state: string,
274
+ data: Record<string, unknown>,
275
+ phase?: { phase: string; status: WorkflowStatus },
276
+ ): Promise<void>;
262
277
  /** Two-phase action persistence: insert a running row at action start. Returns the action row id. */
263
278
  saveActionStart(runId: string, node: string, kind: string): Promise<string>;
264
279
  /** Finalize an action row with duration, ok, result. */