@exaudeus/workrail 0.12.0 → 0.14.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 (98) hide show
  1. package/dist/application/services/enhanced-loop-validator.js +3 -3
  2. package/dist/application/services/step-output-decoder.d.ts +6 -0
  3. package/dist/application/services/step-output-decoder.js +49 -0
  4. package/dist/application/services/validation-engine.d.ts +9 -0
  5. package/dist/application/services/validation-engine.js +142 -18
  6. package/dist/application/services/workflow-interpreter.d.ts +1 -1
  7. package/dist/application/services/workflow-interpreter.js +147 -81
  8. package/dist/application/services/workflow-service.d.ts +2 -0
  9. package/dist/application/services/workflow-service.js +9 -4
  10. package/dist/application/use-cases/validate-step-output.d.ts +2 -0
  11. package/dist/di/container.js +19 -3
  12. package/dist/di/tokens.d.ts +3 -0
  13. package/dist/di/tokens.js +3 -0
  14. package/dist/domain/execution/state.d.ts +6 -6
  15. package/dist/domain/workflow-id-policy.d.ts +17 -0
  16. package/dist/domain/workflow-id-policy.js +57 -0
  17. package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +33 -6
  18. package/dist/infrastructure/storage/file-workflow-storage.js +3 -1
  19. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +13 -8
  20. package/dist/manifest.json +261 -109
  21. package/dist/mcp/handlers/v2-execution.js +62 -79
  22. package/dist/mcp/handlers/v2-workflow.js +14 -9
  23. package/dist/mcp/server.js +53 -48
  24. package/dist/mcp/tool-descriptions.js +15 -15
  25. package/dist/mcp/tools.js +14 -14
  26. package/dist/mcp/types/tool-description-types.d.ts +1 -1
  27. package/dist/mcp/types/tool-description-types.js +5 -5
  28. package/dist/mcp/types.d.ts +2 -0
  29. package/dist/v2/durable-core/constants.d.ts +15 -0
  30. package/dist/v2/durable-core/constants.js +18 -0
  31. package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +32 -0
  32. package/dist/v2/durable-core/domain/ack-advance-append-plan.js +95 -0
  33. package/dist/v2/durable-core/domain/loop-runtime.d.ts +50 -0
  34. package/dist/v2/durable-core/domain/loop-runtime.js +95 -0
  35. package/dist/v2/durable-core/domain/notes-markdown.d.ts +4 -0
  36. package/dist/v2/durable-core/domain/notes-markdown.js +46 -0
  37. package/dist/v2/durable-core/domain/outputs.d.ts +12 -0
  38. package/dist/v2/durable-core/domain/outputs.js +18 -0
  39. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +113 -113
  40. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +11 -10
  41. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +7129 -0
  42. package/dist/v2/durable-core/schemas/export-bundle/index.js +82 -0
  43. package/dist/v2/durable-core/schemas/lib/decision-trace-ref.d.ts +80 -0
  44. package/dist/v2/durable-core/schemas/lib/decision-trace-ref.js +38 -0
  45. package/dist/v2/durable-core/schemas/lib/dedupe-key.d.ts +8 -0
  46. package/dist/v2/durable-core/schemas/lib/dedupe-key.js +28 -0
  47. package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.d.ts +6 -0
  48. package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.js +12 -0
  49. package/dist/v2/durable-core/schemas/session/events.d.ts +158 -12
  50. package/dist/v2/durable-core/schemas/session/events.js +47 -20
  51. package/dist/v2/durable-core/schemas/session/manifest.d.ts +1 -1
  52. package/dist/v2/durable-core/schemas/session/manifest.js +6 -1
  53. package/dist/v2/durable-core/schemas/session/preferences.d.ts +5 -0
  54. package/dist/v2/durable-core/schemas/session/preferences.js +6 -0
  55. package/dist/v2/durable-core/schemas/session/session-health.d.ts +3 -0
  56. package/dist/v2/durable-core/tokens/index.d.ts +0 -1
  57. package/dist/v2/durable-core/tokens/index.js +1 -4
  58. package/dist/v2/durable-core/tokens/token-codec.d.ts +3 -2
  59. package/dist/v2/durable-core/tokens/token-codec.js +12 -6
  60. package/dist/v2/durable-core/tokens/token-signer.d.ts +3 -2
  61. package/dist/v2/durable-core/tokens/token-signer.js +8 -9
  62. package/dist/v2/infra/local/base64url/index.d.ts +5 -0
  63. package/dist/v2/infra/local/base64url/index.js +48 -0
  64. package/dist/v2/infra/local/fs/index.js +8 -4
  65. package/dist/v2/infra/local/keyring/index.d.ts +5 -1
  66. package/dist/v2/infra/local/keyring/index.js +41 -32
  67. package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +3 -1
  68. package/dist/v2/infra/local/pinned-workflow-store/index.js +50 -62
  69. package/dist/v2/infra/local/random-entropy/index.d.ts +4 -0
  70. package/dist/v2/infra/local/random-entropy/index.js +10 -0
  71. package/dist/v2/infra/local/session-lock/index.d.ts +3 -1
  72. package/dist/v2/infra/local/session-lock/index.js +4 -3
  73. package/dist/v2/infra/local/session-store/index.js +26 -4
  74. package/dist/v2/infra/local/snapshot-store/index.js +20 -25
  75. package/dist/v2/infra/local/time-clock/index.d.ts +5 -0
  76. package/dist/v2/infra/local/time-clock/index.js +12 -0
  77. package/dist/v2/infra/local/utf8/index.d.ts +5 -0
  78. package/dist/v2/infra/local/utf8/index.js +12 -0
  79. package/dist/v2/ports/base64url.port.d.ts +12 -0
  80. package/dist/v2/ports/base64url.port.js +2 -0
  81. package/dist/v2/ports/random-entropy.port.d.ts +3 -0
  82. package/dist/v2/ports/random-entropy.port.js +2 -0
  83. package/dist/v2/ports/time-clock.port.d.ts +4 -0
  84. package/dist/v2/ports/time-clock.port.js +2 -0
  85. package/dist/v2/ports/utf8.port.d.ts +3 -0
  86. package/dist/v2/ports/utf8.port.js +2 -0
  87. package/dist/v2/projections/node-outputs.js +28 -11
  88. package/dist/v2/projections/preferences.d.ts +1 -2
  89. package/dist/v2/projections/preferences.js +11 -4
  90. package/dist/v2/projections/run-dag.js +40 -28
  91. package/dist/v2/projections/run-status-signals.d.ts +1 -2
  92. package/dist/v2/usecases/execution-session-gate.js +33 -34
  93. package/package.json +3 -1
  94. package/spec/workflow.schema.json +2 -2
  95. package/workflows/coding-task-workflow-agentic.json +213 -50
  96. package/workflows/relocation-workflow-us.json +430 -0
  97. package/dist/v2/durable-core/tokens/base64url.d.ts +0 -7
  98. package/dist/v2/durable-core/tokens/base64url.js +0 -16
@@ -13,6 +13,7 @@ const workflow_1 = require("../../types/workflow");
13
13
  const neverthrow_1 = require("neverthrow");
14
14
  const error_1 = require("../../domain/execution/error");
15
15
  const ids_1 = require("../../domain/execution/ids");
16
+ const loop_runtime_1 = require("../../v2/durable-core/domain/loop-runtime");
16
17
  let WorkflowInterpreter = class WorkflowInterpreter {
17
18
  applyEvent(state, event) {
18
19
  if (state.kind === 'complete')
@@ -132,103 +133,165 @@ let WorkflowInterpreter = class WorkflowInterpreter {
132
133
  if (!loopCompiled) {
133
134
  return (0, neverthrow_1.err)(error_1.Err.invalidLoop(frame.loopId, 'Loop not found in compiled metadata'));
134
135
  }
135
- const shouldContinue = this.shouldContinueLoop(loopCompiled.loop, frame, context);
136
- if (shouldContinue.isErr())
137
- return (0, neverthrow_1.err)(shouldContinue.error);
138
- if (!shouldContinue.value) {
139
- const popped = state.loopStack.slice(0, -1);
140
- return (0, neverthrow_1.ok)({
141
- state: {
142
- ...state,
143
- loopStack: popped,
144
- completed: [...state.completed, frame.loopId],
145
- },
146
- next: null,
147
- });
148
- }
149
136
  const body = loopCompiled.bodySteps;
150
- for (let i = frame.bodyIndex; i < body.length; i++) {
151
- const bodyStep = body[i];
152
- const instance = {
153
- stepId: bodyStep.id,
154
- loopPath: [...state.loopStack.map((f) => ({ loopId: f.loopId, iteration: f.iteration }))],
155
- };
156
- const key = (0, ids_1.toStepInstanceKey)(instance);
157
- if (state.completed.includes(key))
158
- continue;
159
- const projectedContext = this.projectLoopContext(loopCompiled.loop, frame, context);
160
- if (bodyStep.runCondition && !(0, condition_evaluator_1.evaluateCondition)(bodyStep.runCondition, projectedContext)) {
161
- continue;
162
- }
163
- const next = this.materializeStep(compiled, instance, projectedContext);
164
- if (next.isErr())
165
- return (0, neverthrow_1.err)(next.error);
166
- const updatedTop = { ...frame, bodyIndex: i };
167
- const updatedStack = [...state.loopStack.slice(0, -1), updatedTop];
168
- return (0, neverthrow_1.ok)({
169
- state: { ...state, loopStack: updatedStack, pendingStep: instance },
170
- next: next.value,
171
- });
172
- }
173
- if (frame.iteration + 1 > loopCompiled.loop.loop.maxIterations) {
174
- return (0, neverthrow_1.err)(error_1.Err.maxIterationsExceeded(frame.loopId, loopCompiled.loop.loop.maxIterations));
175
- }
176
- const advanced = { ...frame, iteration: frame.iteration + 1, bodyIndex: 0 };
177
- const updatedStack = [...state.loopStack.slice(0, -1), advanced];
178
- return (0, neverthrow_1.ok)({ state: { ...state, loopStack: updatedStack }, next: null });
179
- }
180
- shouldContinueLoop(loop, frame, context) {
181
- if (frame.iteration >= loop.loop.maxIterations) {
182
- return (0, neverthrow_1.ok)(false);
183
- }
184
- switch (loop.loop.type) {
185
- case 'for': {
186
- const count = loop.loop.count;
187
- if (typeof count === 'number') {
188
- return (0, neverthrow_1.ok)(frame.iteration < count);
189
- }
190
- if (typeof count === 'string') {
191
- const raw = context[count];
192
- if (typeof raw !== 'number') {
193
- return (0, neverthrow_1.err)(error_1.Err.missingContext(`for loop '${loop.id}' requires numeric context['${count}']`));
137
+ const loopPath = [...state.loopStack.map((f) => ({ loopId: f.loopId, iteration: f.iteration }))];
138
+ const completed = new Set(state.completed);
139
+ const ports = {
140
+ shouldEnterIteration: (iteration) => {
141
+ switch (loopCompiled.loop.loop.type) {
142
+ case 'for': {
143
+ const count = loopCompiled.loop.loop.count;
144
+ if (typeof count === 'number') {
145
+ return (0, neverthrow_1.ok)(iteration < count);
146
+ }
147
+ if (typeof count === 'string') {
148
+ const raw = context[count];
149
+ if (typeof raw !== 'number') {
150
+ return (0, neverthrow_1.err)({
151
+ code: 'LOOP_MISSING_CONTEXT',
152
+ loopId: loopCompiled.loop.id,
153
+ message: `for loop '${loopCompiled.loop.id}' requires numeric context['${count}']`,
154
+ });
155
+ }
156
+ return (0, neverthrow_1.ok)(iteration < raw);
157
+ }
158
+ return (0, neverthrow_1.err)({
159
+ code: 'LOOP_INVALID_CONFIG',
160
+ loopId: loopCompiled.loop.id,
161
+ message: `for loop '${loopCompiled.loop.id}' missing count`,
162
+ });
163
+ }
164
+ case 'forEach': {
165
+ const itemsVar = loopCompiled.loop.loop.items;
166
+ if (!itemsVar) {
167
+ return (0, neverthrow_1.err)({
168
+ code: 'LOOP_INVALID_CONFIG',
169
+ loopId: loopCompiled.loop.id,
170
+ message: `forEach loop '${loopCompiled.loop.id}' missing items`,
171
+ });
172
+ }
173
+ const raw = context[itemsVar];
174
+ if (!Array.isArray(raw)) {
175
+ return (0, neverthrow_1.err)({
176
+ code: 'LOOP_MISSING_CONTEXT',
177
+ loopId: loopCompiled.loop.id,
178
+ message: `forEach loop '${loopCompiled.loop.id}' requires array context['${itemsVar}']`,
179
+ });
180
+ }
181
+ return (0, neverthrow_1.ok)(iteration < raw.length);
194
182
  }
195
- return (0, neverthrow_1.ok)(frame.iteration < raw);
183
+ case 'while': {
184
+ if (!loopCompiled.loop.loop.condition) {
185
+ return (0, neverthrow_1.err)({
186
+ code: 'LOOP_INVALID_CONFIG',
187
+ loopId: loopCompiled.loop.id,
188
+ message: `while loop '${loopCompiled.loop.id}' missing condition`,
189
+ });
190
+ }
191
+ return (0, neverthrow_1.ok)((0, condition_evaluator_1.evaluateCondition)(loopCompiled.loop.loop.condition, this.projectLoopContextAtIteration(loopCompiled.loop, iteration, context)));
192
+ }
193
+ case 'until': {
194
+ if (!loopCompiled.loop.loop.condition) {
195
+ return (0, neverthrow_1.err)({
196
+ code: 'LOOP_INVALID_CONFIG',
197
+ loopId: loopCompiled.loop.id,
198
+ message: `until loop '${loopCompiled.loop.id}' missing condition`,
199
+ });
200
+ }
201
+ return (0, neverthrow_1.ok)(!(0, condition_evaluator_1.evaluateCondition)(loopCompiled.loop.loop.condition, this.projectLoopContextAtIteration(loopCompiled.loop, iteration, context)));
202
+ }
203
+ default:
204
+ return (0, neverthrow_1.err)({
205
+ code: 'LOOP_INVALID_CONFIG',
206
+ loopId: loopCompiled.loop.id,
207
+ message: `Unknown loop type '${loopCompiled.loop.loop.type}'`,
208
+ });
196
209
  }
197
- return (0, neverthrow_1.err)(error_1.Err.invalidLoop(loop.id, `for loop '${loop.id}' missing count`));
210
+ },
211
+ isBodyIndexEligible: (bodyIndex) => {
212
+ const bodyStep = body[bodyIndex];
213
+ if (!bodyStep)
214
+ return false;
215
+ const instance = { stepId: bodyStep.id, loopPath };
216
+ const key = (0, ids_1.toStepInstanceKey)(instance);
217
+ if (completed.has(key))
218
+ return false;
219
+ if (!bodyStep.runCondition)
220
+ return true;
221
+ const projectedContext = this.projectLoopContext(loopCompiled.loop, frame, context);
222
+ return (0, condition_evaluator_1.evaluateCondition)(bodyStep.runCondition, projectedContext);
223
+ },
224
+ };
225
+ const decision = (0, loop_runtime_1.computeLoopDecision)({
226
+ loopId: frame.loopId,
227
+ iteration: frame.iteration,
228
+ bodyIndex: frame.bodyIndex,
229
+ bodyLength: body.length,
230
+ maxIterations: loopCompiled.loop.loop.maxIterations,
231
+ ports,
232
+ });
233
+ if (decision.isErr()) {
234
+ const e = decision.error;
235
+ switch (e.code) {
236
+ case 'LOOP_MAX_ITERATIONS_REACHED':
237
+ return (0, neverthrow_1.err)(error_1.Err.maxIterationsExceeded(e.loopId, e.maxIterations));
238
+ case 'LOOP_MISSING_CONTEXT':
239
+ return (0, neverthrow_1.err)(error_1.Err.missingContext(e.message));
240
+ case 'LOOP_INVALID_CONFIG':
241
+ return (0, neverthrow_1.err)(error_1.Err.invalidLoop(e.loopId, e.message));
242
+ case 'LOOP_INVALID_STATE':
243
+ return (0, neverthrow_1.err)(error_1.Err.invalidState(e.message));
244
+ default:
245
+ return (0, neverthrow_1.err)(error_1.Err.invalidState('Unhandled loop kernel error'));
198
246
  }
199
- case 'forEach': {
200
- const itemsVar = loop.loop.items;
201
- if (!itemsVar)
202
- return (0, neverthrow_1.err)(error_1.Err.invalidLoop(loop.id, `forEach loop '${loop.id}' missing items`));
203
- const raw = context[itemsVar];
204
- if (!Array.isArray(raw)) {
205
- return (0, neverthrow_1.err)(error_1.Err.missingContext(`forEach loop '${loop.id}' requires array context['${itemsVar}']`));
206
- }
207
- return (0, neverthrow_1.ok)(frame.iteration < raw.length);
247
+ }
248
+ switch (decision.value.kind) {
249
+ case 'exit_loop': {
250
+ const popped = state.loopStack.slice(0, -1);
251
+ return (0, neverthrow_1.ok)({
252
+ state: {
253
+ ...state,
254
+ loopStack: popped,
255
+ completed: [...state.completed, frame.loopId],
256
+ },
257
+ next: null,
258
+ });
208
259
  }
209
- case 'while': {
210
- if (!loop.loop.condition)
211
- return (0, neverthrow_1.err)(error_1.Err.invalidLoop(loop.id, `while loop '${loop.id}' missing condition`));
212
- return (0, neverthrow_1.ok)((0, condition_evaluator_1.evaluateCondition)(loop.loop.condition, this.projectLoopContext(loop, frame, context)));
260
+ case 'advance_iteration': {
261
+ const advanced = { ...frame, iteration: decision.value.toIteration, bodyIndex: 0 };
262
+ const updatedStack = [...state.loopStack.slice(0, -1), advanced];
263
+ return (0, neverthrow_1.ok)({ state: { ...state, loopStack: updatedStack }, next: null });
213
264
  }
214
- case 'until': {
215
- if (!loop.loop.condition)
216
- return (0, neverthrow_1.err)(error_1.Err.invalidLoop(loop.id, `until loop '${loop.id}' missing condition`));
217
- return (0, neverthrow_1.ok)(!(0, condition_evaluator_1.evaluateCondition)(loop.loop.condition, this.projectLoopContext(loop, frame, context)));
265
+ case 'execute_body_step': {
266
+ const bodyStep = body[decision.value.bodyIndex];
267
+ if (!bodyStep) {
268
+ return (0, neverthrow_1.err)(error_1.Err.invalidState(`Loop '${frame.loopId}' selected missing bodyIndex ${decision.value.bodyIndex}`));
269
+ }
270
+ const instance = { stepId: bodyStep.id, loopPath };
271
+ const projectedContext = this.projectLoopContext(loopCompiled.loop, frame, context);
272
+ const next = this.materializeStep(compiled, instance, projectedContext);
273
+ if (next.isErr())
274
+ return (0, neverthrow_1.err)(next.error);
275
+ const updatedTop = { ...frame, bodyIndex: decision.value.bodyIndex };
276
+ const updatedStack = [...state.loopStack.slice(0, -1), updatedTop];
277
+ return (0, neverthrow_1.ok)({
278
+ state: { ...state, loopStack: updatedStack, pendingStep: instance },
279
+ next: next.value,
280
+ });
218
281
  }
219
282
  default:
220
- return (0, neverthrow_1.err)(error_1.Err.invalidLoop(loop.id, `Unknown loop type '${loop.loop.type}'`));
283
+ return (0, neverthrow_1.err)(error_1.Err.invalidState('Non-exhaustive loop decision'));
221
284
  }
222
285
  }
223
- projectLoopContext(loop, frame, base) {
286
+ projectLoopContextAtIteration(loop, iteration, base) {
224
287
  const out = { ...base };
225
288
  const iterationVar = loop.loop.iterationVar || 'currentIteration';
226
- out[iterationVar] = frame.iteration + 1;
289
+ out[iterationVar] = iteration + 1;
227
290
  if (loop.loop.type === 'forEach') {
228
291
  const itemsVar = loop.loop.items;
229
292
  const raw = base[itemsVar];
230
293
  if (Array.isArray(raw)) {
231
- const index = frame.iteration;
294
+ const index = iteration;
232
295
  const itemVar = loop.loop.itemVar || 'currentItem';
233
296
  const indexVar = loop.loop.indexVar || 'currentIndex';
234
297
  out[itemVar] = raw[index];
@@ -237,6 +300,9 @@ let WorkflowInterpreter = class WorkflowInterpreter {
237
300
  }
238
301
  return out;
239
302
  }
303
+ projectLoopContext(loop, frame, base) {
304
+ return this.projectLoopContextAtIteration(loop, frame.iteration, base);
305
+ }
240
306
  lookupStepInstance(compiled, id) {
241
307
  const step = compiled.stepById.get(id.stepId);
242
308
  if (!step)
@@ -19,6 +19,7 @@ export interface WorkflowService {
19
19
  valid: boolean;
20
20
  issues: readonly string[];
21
21
  suggestions: readonly string[];
22
+ warnings?: readonly string[];
22
23
  }>;
23
24
  }
24
25
  export declare class DefaultWorkflowService implements WorkflowService {
@@ -41,6 +42,7 @@ export declare class DefaultWorkflowService implements WorkflowService {
41
42
  valid: boolean;
42
43
  issues: readonly string[];
43
44
  suggestions: readonly string[];
45
+ warnings?: readonly string[];
44
46
  }>;
45
47
  private getOrCompile;
46
48
  __debugCompiledCacheSize(): number;
@@ -59,16 +59,21 @@ let DefaultWorkflowService = class DefaultWorkflowService {
59
59
  async validateStepOutput(workflowId, stepId, output) {
60
60
  const workflow = await this.storage.getWorkflowById(workflowId);
61
61
  if (!workflow) {
62
- return { valid: false, issues: [`Workflow '${workflowId}' not found`], suggestions: [] };
62
+ return { valid: false, issues: [`Workflow '${workflowId}' not found`], suggestions: [], warnings: undefined };
63
63
  }
64
64
  const step = workflow.definition.steps.find((s) => s.id === stepId);
65
65
  if (!step) {
66
- return { valid: false, issues: [`Step '${stepId}' not found in workflow '${workflowId}'`], suggestions: [] };
66
+ return { valid: false, issues: [`Step '${stepId}' not found in workflow '${workflowId}'`], suggestions: [], warnings: undefined };
67
67
  }
68
68
  const criteria = step.validationCriteria;
69
69
  if (!criteria)
70
- return { valid: true, issues: [], suggestions: [] };
71
- return this.validationEngine.validate(output, criteria);
70
+ return { valid: true, issues: [], suggestions: [], warnings: undefined };
71
+ const result = await this.validationEngine.validate(output, criteria);
72
+ const contextualizedWarnings = result.warnings?.map(w => `Step '${workflowId}/${stepId}': ${w}`);
73
+ return {
74
+ ...result,
75
+ warnings: contextualizedWarnings,
76
+ };
72
77
  }
73
78
  getOrCompile(workflowId, workflow) {
74
79
  const cached = this.compiledCache.get(workflowId);
@@ -3,9 +3,11 @@ export declare function createValidateStepOutput(service: WorkflowService): (wor
3
3
  valid: boolean;
4
4
  issues: readonly string[];
5
5
  suggestions: readonly string[];
6
+ warnings?: readonly string[];
6
7
  }>;
7
8
  export declare function validateStepOutput(service: WorkflowService, workflowId: string, stepId: string, output: string): Promise<{
8
9
  valid: boolean;
9
10
  issues: readonly string[];
10
11
  suggestions: readonly string[];
12
+ warnings?: readonly string[];
11
13
  }>;
@@ -178,6 +178,9 @@ async function registerV2Services() {
178
178
  const { NodeSha256V2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/sha256/index.js')));
179
179
  const { NodeCryptoV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/crypto/index.js')));
180
180
  const { NodeHmacSha256V2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/hmac-sha256/index.js')));
181
+ const { NodeBase64UrlV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/base64url/index.js')));
182
+ const { NodeRandomEntropyV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/random-entropy/index.js')));
183
+ const { NodeTimeClockV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/time-clock/index.js')));
181
184
  tsyringe_1.container.register(tokens_js_1.DI.V2.DataDir, {
182
185
  useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new LocalDataDirV2(process.env)),
183
186
  });
@@ -193,6 +196,15 @@ async function registerV2Services() {
193
196
  tsyringe_1.container.register(tokens_js_1.DI.V2.HmacSha256, {
194
197
  useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new NodeHmacSha256V2()),
195
198
  });
199
+ tsyringe_1.container.register(tokens_js_1.DI.V2.Base64Url, {
200
+ useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new NodeBase64UrlV2()),
201
+ });
202
+ tsyringe_1.container.register(tokens_js_1.DI.V2.RandomEntropy, {
203
+ useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new NodeRandomEntropyV2()),
204
+ });
205
+ tsyringe_1.container.register(tokens_js_1.DI.V2.TimeClock, {
206
+ useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new NodeTimeClockV2()),
207
+ });
196
208
  const { LocalKeyringV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/keyring/index.js')));
197
209
  const { LocalSessionEventLogStoreV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/session-store/index.js')));
198
210
  const { LocalSnapshotStoreV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/snapshot-store/index.js')));
@@ -202,7 +214,9 @@ async function registerV2Services() {
202
214
  useFactory: (0, tsyringe_1.instanceCachingFactory)((c) => {
203
215
  const dataDir = c.resolve(tokens_js_1.DI.V2.DataDir);
204
216
  const fs = c.resolve(tokens_js_1.DI.V2.FileSystem);
205
- return new LocalKeyringV2(dataDir, fs);
217
+ const base64url = c.resolve(tokens_js_1.DI.V2.Base64Url);
218
+ const entropy = c.resolve(tokens_js_1.DI.V2.RandomEntropy);
219
+ return new LocalKeyringV2(dataDir, fs, base64url, entropy);
206
220
  }),
207
221
  });
208
222
  tsyringe_1.container.register(tokens_js_1.DI.V2.SessionStore, {
@@ -224,14 +238,16 @@ async function registerV2Services() {
224
238
  tsyringe_1.container.register(tokens_js_1.DI.V2.PinnedWorkflowStore, {
225
239
  useFactory: (0, tsyringe_1.instanceCachingFactory)((c) => {
226
240
  const dataDir = c.resolve(tokens_js_1.DI.V2.DataDir);
227
- return new LocalPinnedWorkflowStoreV2(dataDir);
241
+ const fs = c.resolve(tokens_js_1.DI.V2.FileSystem);
242
+ return new LocalPinnedWorkflowStoreV2(dataDir, fs);
228
243
  }),
229
244
  });
230
245
  tsyringe_1.container.register(tokens_js_1.DI.V2.SessionLock, {
231
246
  useFactory: (0, tsyringe_1.instanceCachingFactory)((c) => {
232
247
  const dataDir = c.resolve(tokens_js_1.DI.V2.DataDir);
233
248
  const fs = c.resolve(tokens_js_1.DI.V2.FileSystem);
234
- return new LocalSessionLockV2(dataDir, fs);
249
+ const clock = c.resolve(tokens_js_1.DI.V2.TimeClock);
250
+ return new LocalSessionLockV2(dataDir, fs, clock);
235
251
  }),
236
252
  });
237
253
  const { ExecutionSessionGateV2 } = await Promise.resolve().then(() => __importStar(require('../v2/usecases/execution-session-gate.js')));
@@ -27,6 +27,9 @@ export declare const DI: {
27
27
  readonly Sha256: symbol;
28
28
  readonly Crypto: symbol;
29
29
  readonly HmacSha256: symbol;
30
+ readonly Base64Url: symbol;
31
+ readonly RandomEntropy: symbol;
32
+ readonly TimeClock: symbol;
30
33
  readonly Keyring: symbol;
31
34
  readonly SessionStore: symbol;
32
35
  readonly SnapshotStore: symbol;
package/dist/di/tokens.js CHANGED
@@ -30,6 +30,9 @@ exports.DI = {
30
30
  Sha256: Symbol('V2.Sha256'),
31
31
  Crypto: Symbol('V2.Crypto'),
32
32
  HmacSha256: Symbol('V2.HmacSha256'),
33
+ Base64Url: Symbol('V2.Base64Url'),
34
+ RandomEntropy: Symbol('V2.RandomEntropy'),
35
+ TimeClock: Symbol('V2.TimeClock'),
33
36
  Keyring: Symbol('V2.Keyring'),
34
37
  SessionStore: Symbol('V2.SessionStore'),
35
38
  SnapshotStore: Symbol('V2.SnapshotStore'),
@@ -20,12 +20,12 @@ export declare const LoopFrameSchema: z.ZodObject<{
20
20
  iteration: z.ZodNumber;
21
21
  bodyIndex: z.ZodNumber;
22
22
  }, "strip", z.ZodTypeAny, {
23
- iteration: number;
24
23
  loopId: string;
24
+ iteration: number;
25
25
  bodyIndex: number;
26
26
  }, {
27
- iteration: number;
28
27
  loopId: string;
28
+ iteration: number;
29
29
  bodyIndex: number;
30
30
  }>;
31
31
  export declare const StepInstanceIdSchema: z.ZodObject<{
@@ -34,23 +34,23 @@ export declare const StepInstanceIdSchema: z.ZodObject<{
34
34
  loopId: z.ZodString;
35
35
  iteration: z.ZodNumber;
36
36
  }, "strip", z.ZodTypeAny, {
37
- iteration: number;
38
37
  loopId: string;
39
- }, {
40
38
  iteration: number;
39
+ }, {
41
40
  loopId: string;
41
+ iteration: number;
42
42
  }>, "many">;
43
43
  }, "strip", z.ZodTypeAny, {
44
44
  stepId: string;
45
45
  loopPath: {
46
- iteration: number;
47
46
  loopId: string;
47
+ iteration: number;
48
48
  }[];
49
49
  }, {
50
50
  stepId: string;
51
51
  loopPath: {
52
- iteration: number;
53
52
  loopId: string;
53
+ iteration: number;
54
54
  }[];
55
55
  }>;
56
56
  export declare const ExecutionStateSchema: z.ZodType<ExecutionState>;
@@ -0,0 +1,17 @@
1
+ import type { WorkflowSourceKind } from '../types/workflow-source';
2
+ export type WorkflowIdParse = {
3
+ readonly kind: 'legacy';
4
+ readonly raw: string;
5
+ } | {
6
+ readonly kind: 'namespaced';
7
+ readonly raw: string;
8
+ readonly namespace: string;
9
+ readonly name: string;
10
+ };
11
+ export interface WorkflowIdLoadValidation {
12
+ readonly parsed: WorkflowIdParse;
13
+ readonly warnings: readonly string[];
14
+ }
15
+ export declare function parseWorkflowId(raw: string): WorkflowIdParse | null;
16
+ export declare function validateWorkflowIdForLoad(raw: string, sourceKind: WorkflowSourceKind): WorkflowIdLoadValidation;
17
+ export declare function validateWorkflowIdForSave(raw: string, sourceKind: WorkflowSourceKind): WorkflowIdParse;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseWorkflowId = parseWorkflowId;
4
+ exports.validateWorkflowIdForLoad = validateWorkflowIdForLoad;
5
+ exports.validateWorkflowIdForSave = validateWorkflowIdForSave;
6
+ const error_handler_1 = require("../core/error-handler");
7
+ const NAMESPACED_SEGMENT_RE = /^[a-z][a-z0-9_-]*$/;
8
+ const LEGACY_ID_RE = /^[a-z0-9_-]+$/;
9
+ function parseWorkflowId(raw) {
10
+ if (typeof raw !== 'string' || raw.length === 0)
11
+ return null;
12
+ if (!raw.includes('.')) {
13
+ if (!LEGACY_ID_RE.test(raw))
14
+ return null;
15
+ return { kind: 'legacy', raw };
16
+ }
17
+ const parts = raw.split('.');
18
+ if (parts.length !== 2)
19
+ return null;
20
+ const [namespace, name] = parts;
21
+ if (!namespace || !name)
22
+ return null;
23
+ if (!NAMESPACED_SEGMENT_RE.test(namespace))
24
+ return null;
25
+ if (!NAMESPACED_SEGMENT_RE.test(name))
26
+ return null;
27
+ return { kind: 'namespaced', raw, namespace, name };
28
+ }
29
+ function validateWorkflowIdForLoad(raw, sourceKind) {
30
+ const parsed = parseWorkflowId(raw);
31
+ if (!parsed) {
32
+ throw new error_handler_1.InvalidWorkflowError(raw || 'unknown', 'Invalid workflow id format');
33
+ }
34
+ if (parsed.kind === 'legacy') {
35
+ return {
36
+ parsed,
37
+ warnings: ['legacy_workflow_id'],
38
+ };
39
+ }
40
+ if (parsed.namespace === 'wr' && sourceKind !== 'bundled') {
41
+ throw new error_handler_1.InvalidWorkflowError(parsed.raw, `Reserved workflow namespace 'wr.*' is only allowed for bundled workflows (sourceKind=${sourceKind})`);
42
+ }
43
+ return { parsed, warnings: [] };
44
+ }
45
+ function validateWorkflowIdForSave(raw, sourceKind) {
46
+ const parsed = parseWorkflowId(raw);
47
+ if (!parsed) {
48
+ throw new error_handler_1.InvalidWorkflowError(raw || 'unknown', 'Invalid workflow id format');
49
+ }
50
+ if (parsed.kind === 'legacy') {
51
+ throw new error_handler_1.InvalidWorkflowError(parsed.raw, "Legacy workflow ids (no dot) are no longer allowed for new workflows; use 'namespace.name'");
52
+ }
53
+ if (parsed.namespace === 'wr' && sourceKind !== 'bundled') {
54
+ throw new error_handler_1.InvalidWorkflowError(parsed.raw, `Reserved workflow namespace 'wr.*' is only allowed for bundled workflows (sourceKind=${sourceKind})`);
55
+ }
56
+ return parsed;
57
+ }
@@ -133,15 +133,21 @@ class EnhancedMultiSourceWorkflowStorage {
133
133
  try {
134
134
  const workflows = await storage.loadAllWorkflows();
135
135
  for (const workflow of workflows) {
136
- if (seenIds.has(workflow.definition.id)) {
137
- const existingIndex = allWorkflows.findIndex(wf => wf.definition.id === workflow.definition.id);
136
+ const id = workflow.definition.id;
137
+ if (seenIds.has(id)) {
138
+ const existingIndex = allWorkflows.findIndex((wf) => wf.definition.id === id);
138
139
  if (existingIndex >= 0) {
140
+ const existing = allWorkflows[existingIndex];
141
+ const isWr = id.startsWith('wr.');
142
+ if (isWr && existing.source.kind === 'bundled') {
143
+ continue;
144
+ }
139
145
  allWorkflows[existingIndex] = workflow;
140
146
  }
141
147
  }
142
148
  else {
143
149
  allWorkflows.push(workflow);
144
- seenIds.add(workflow.definition.id);
150
+ seenIds.add(id);
145
151
  }
146
152
  }
147
153
  }
@@ -152,6 +158,21 @@ class EnhancedMultiSourceWorkflowStorage {
152
158
  return allWorkflows;
153
159
  }
154
160
  async getWorkflowById(id) {
161
+ if (id.startsWith('wr.')) {
162
+ for (let i = 0; i < this.storageInstances.length; i++) {
163
+ const storage = this.storageInstances[i];
164
+ if (storage.source.kind !== 'bundled')
165
+ continue;
166
+ try {
167
+ const workflow = await storage.getWorkflowById(id);
168
+ if (workflow)
169
+ return workflow;
170
+ }
171
+ catch (error) {
172
+ this.handleSourceError(`source-${i}`, error);
173
+ }
174
+ }
175
+ }
155
176
  for (let i = this.storageInstances.length - 1; i >= 0; i--) {
156
177
  const storage = this.storageInstances[i];
157
178
  try {
@@ -174,15 +195,21 @@ class EnhancedMultiSourceWorkflowStorage {
174
195
  try {
175
196
  const summaries = await storage.listWorkflowSummaries();
176
197
  for (const summary of summaries) {
177
- if (seenIds.has(summary.id)) {
178
- const existingIndex = allSummaries.findIndex(s => s.id === summary.id);
198
+ const id = summary.id;
199
+ if (seenIds.has(id)) {
200
+ const existingIndex = allSummaries.findIndex((s) => s.id === id);
179
201
  if (existingIndex >= 0) {
202
+ const existing = allSummaries[existingIndex];
203
+ const isWr = id.startsWith('wr.');
204
+ if (isWr && existing.source.kind === 'bundled') {
205
+ continue;
206
+ }
180
207
  allSummaries[existingIndex] = summary;
181
208
  }
182
209
  }
183
210
  else {
184
211
  allSummaries.push(summary);
185
- seenIds.add(summary.id);
212
+ seenIds.add(id);
186
213
  }
187
214
  }
188
215
  }
@@ -10,12 +10,13 @@ const fs_1 = require("fs");
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const workflow_1 = require("../../types/workflow");
12
12
  const error_handler_1 = require("../../core/error-handler");
13
+ const workflow_id_policy_1 = require("../../domain/workflow-id-policy");
13
14
  function sanitizeId(id) {
14
15
  if (id.includes('\u0000')) {
15
16
  throw new error_handler_1.SecurityError('Null byte detected in identifier', 'sanitizeId');
16
17
  }
17
18
  const normalised = id.normalize('NFC');
18
- const valid = /^[a-zA-Z0-9_-]+$/.test(normalised);
19
+ const valid = /^[a-zA-Z0-9_.-]+$/.test(normalised);
19
20
  if (!valid) {
20
21
  throw new error_handler_1.InvalidWorkflowError(id, 'Invalid characters in workflow id');
21
22
  }
@@ -182,6 +183,7 @@ class FileWorkflowStorage {
182
183
  if (!definition.id || !definition.name || !definition.steps) {
183
184
  throw new error_handler_1.InvalidWorkflowError(definition.id || 'unknown', 'Definition must have id, name, and steps');
184
185
  }
186
+ (0, workflow_id_policy_1.validateWorkflowIdForSave)(definition.id, this.source.kind);
185
187
  const safeId = sanitizeId(definition.id);
186
188
  const filename = `${safeId}.json`;
187
189
  const filePath = path_1.default.resolve(this.baseDirReal, filename);