@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.
- package/dist/application/services/enhanced-loop-validator.js +3 -3
- package/dist/application/services/step-output-decoder.d.ts +6 -0
- package/dist/application/services/step-output-decoder.js +49 -0
- package/dist/application/services/validation-engine.d.ts +9 -0
- package/dist/application/services/validation-engine.js +142 -18
- package/dist/application/services/workflow-interpreter.d.ts +1 -1
- package/dist/application/services/workflow-interpreter.js +147 -81
- package/dist/application/services/workflow-service.d.ts +2 -0
- package/dist/application/services/workflow-service.js +9 -4
- package/dist/application/use-cases/validate-step-output.d.ts +2 -0
- package/dist/di/container.js +19 -3
- package/dist/di/tokens.d.ts +3 -0
- package/dist/di/tokens.js +3 -0
- package/dist/domain/execution/state.d.ts +6 -6
- package/dist/domain/workflow-id-policy.d.ts +17 -0
- package/dist/domain/workflow-id-policy.js +57 -0
- package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +33 -6
- package/dist/infrastructure/storage/file-workflow-storage.js +3 -1
- package/dist/infrastructure/storage/schema-validating-workflow-storage.js +13 -8
- package/dist/manifest.json +261 -109
- package/dist/mcp/handlers/v2-execution.js +62 -79
- package/dist/mcp/handlers/v2-workflow.js +14 -9
- package/dist/mcp/server.js +53 -48
- package/dist/mcp/tool-descriptions.js +15 -15
- package/dist/mcp/tools.js +14 -14
- package/dist/mcp/types/tool-description-types.d.ts +1 -1
- package/dist/mcp/types/tool-description-types.js +5 -5
- package/dist/mcp/types.d.ts +2 -0
- package/dist/v2/durable-core/constants.d.ts +15 -0
- package/dist/v2/durable-core/constants.js +18 -0
- package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +32 -0
- package/dist/v2/durable-core/domain/ack-advance-append-plan.js +95 -0
- package/dist/v2/durable-core/domain/loop-runtime.d.ts +50 -0
- package/dist/v2/durable-core/domain/loop-runtime.js +95 -0
- package/dist/v2/durable-core/domain/notes-markdown.d.ts +4 -0
- package/dist/v2/durable-core/domain/notes-markdown.js +46 -0
- package/dist/v2/durable-core/domain/outputs.d.ts +12 -0
- package/dist/v2/durable-core/domain/outputs.js +18 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +113 -113
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +11 -10
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +7129 -0
- package/dist/v2/durable-core/schemas/export-bundle/index.js +82 -0
- package/dist/v2/durable-core/schemas/lib/decision-trace-ref.d.ts +80 -0
- package/dist/v2/durable-core/schemas/lib/decision-trace-ref.js +38 -0
- package/dist/v2/durable-core/schemas/lib/dedupe-key.d.ts +8 -0
- package/dist/v2/durable-core/schemas/lib/dedupe-key.js +28 -0
- package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.d.ts +6 -0
- package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.js +12 -0
- package/dist/v2/durable-core/schemas/session/events.d.ts +158 -12
- package/dist/v2/durable-core/schemas/session/events.js +47 -20
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +1 -1
- package/dist/v2/durable-core/schemas/session/manifest.js +6 -1
- package/dist/v2/durable-core/schemas/session/preferences.d.ts +5 -0
- package/dist/v2/durable-core/schemas/session/preferences.js +6 -0
- package/dist/v2/durable-core/schemas/session/session-health.d.ts +3 -0
- package/dist/v2/durable-core/tokens/index.d.ts +0 -1
- package/dist/v2/durable-core/tokens/index.js +1 -4
- package/dist/v2/durable-core/tokens/token-codec.d.ts +3 -2
- package/dist/v2/durable-core/tokens/token-codec.js +12 -6
- package/dist/v2/durable-core/tokens/token-signer.d.ts +3 -2
- package/dist/v2/durable-core/tokens/token-signer.js +8 -9
- package/dist/v2/infra/local/base64url/index.d.ts +5 -0
- package/dist/v2/infra/local/base64url/index.js +48 -0
- package/dist/v2/infra/local/fs/index.js +8 -4
- package/dist/v2/infra/local/keyring/index.d.ts +5 -1
- package/dist/v2/infra/local/keyring/index.js +41 -32
- package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +3 -1
- package/dist/v2/infra/local/pinned-workflow-store/index.js +50 -62
- package/dist/v2/infra/local/random-entropy/index.d.ts +4 -0
- package/dist/v2/infra/local/random-entropy/index.js +10 -0
- package/dist/v2/infra/local/session-lock/index.d.ts +3 -1
- package/dist/v2/infra/local/session-lock/index.js +4 -3
- package/dist/v2/infra/local/session-store/index.js +26 -4
- package/dist/v2/infra/local/snapshot-store/index.js +20 -25
- package/dist/v2/infra/local/time-clock/index.d.ts +5 -0
- package/dist/v2/infra/local/time-clock/index.js +12 -0
- package/dist/v2/infra/local/utf8/index.d.ts +5 -0
- package/dist/v2/infra/local/utf8/index.js +12 -0
- package/dist/v2/ports/base64url.port.d.ts +12 -0
- package/dist/v2/ports/base64url.port.js +2 -0
- package/dist/v2/ports/random-entropy.port.d.ts +3 -0
- package/dist/v2/ports/random-entropy.port.js +2 -0
- package/dist/v2/ports/time-clock.port.d.ts +4 -0
- package/dist/v2/ports/time-clock.port.js +2 -0
- package/dist/v2/ports/utf8.port.d.ts +3 -0
- package/dist/v2/ports/utf8.port.js +2 -0
- package/dist/v2/projections/node-outputs.js +28 -11
- package/dist/v2/projections/preferences.d.ts +1 -2
- package/dist/v2/projections/preferences.js +11 -4
- package/dist/v2/projections/run-dag.js +40 -28
- package/dist/v2/projections/run-status-signals.d.ts +1 -2
- package/dist/v2/usecases/execution-session-gate.js +33 -34
- package/package.json +3 -1
- package/spec/workflow.schema.json +2 -2
- package/workflows/coding-task-workflow-agentic.json +213 -50
- package/workflows/relocation-workflow-us.json +430 -0
- package/dist/v2/durable-core/tokens/base64url.d.ts +0 -7
- 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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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 '
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return (0, neverthrow_1.ok)(
|
|
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 '
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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.
|
|
283
|
+
return (0, neverthrow_1.err)(error_1.Err.invalidState('Non-exhaustive loop decision'));
|
|
221
284
|
}
|
|
222
285
|
}
|
|
223
|
-
|
|
286
|
+
projectLoopContextAtIteration(loop, iteration, base) {
|
|
224
287
|
const out = { ...base };
|
|
225
288
|
const iterationVar = loop.loop.iterationVar || 'currentIteration';
|
|
226
|
-
out[iterationVar] =
|
|
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 =
|
|
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
|
-
|
|
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
|
}>;
|
package/dist/di/container.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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')));
|
package/dist/di/tokens.d.ts
CHANGED
|
@@ -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
|
-
|
|
137
|
-
|
|
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(
|
|
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
|
-
|
|
178
|
-
|
|
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(
|
|
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_
|
|
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);
|