@exaudeus/workrail 1.1.0 → 1.3.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 (102) hide show
  1. package/dist/application/services/validation-engine.d.ts +15 -1
  2. package/dist/application/services/validation-engine.js +81 -51
  3. package/dist/application/services/workflow-compiler.d.ts +3 -0
  4. package/dist/application/services/workflow-compiler.js +26 -0
  5. package/dist/application/services/workflow-interpreter.d.ts +4 -1
  6. package/dist/application/services/workflow-interpreter.js +85 -24
  7. package/dist/application/services/workflow-service.js +19 -2
  8. package/dist/manifest.json +299 -83
  9. package/dist/mcp/handlers/shared/with-timeout.d.ts +1 -0
  10. package/dist/mcp/handlers/shared/with-timeout.js +9 -0
  11. package/dist/mcp/handlers/v2-advance-core.d.ts +45 -0
  12. package/dist/mcp/handlers/v2-advance-core.js +433 -0
  13. package/dist/mcp/handlers/v2-context-budget.d.ts +17 -0
  14. package/dist/mcp/handlers/v2-context-budget.js +169 -0
  15. package/dist/mcp/handlers/v2-error-mapping.d.ts +34 -0
  16. package/dist/mcp/handlers/v2-error-mapping.js +125 -0
  17. package/dist/mcp/handlers/v2-execution-helpers.js +4 -1
  18. package/dist/mcp/handlers/v2-execution.d.ts +19 -0
  19. package/dist/mcp/handlers/v2-execution.js +278 -765
  20. package/dist/mcp/handlers/v2-state-conversion.d.ts +40 -0
  21. package/dist/mcp/handlers/v2-state-conversion.js +132 -0
  22. package/dist/mcp/handlers/v2-token-ops.d.ts +33 -0
  23. package/dist/mcp/handlers/v2-token-ops.js +62 -0
  24. package/dist/mcp/handlers/v2-workflow.js +3 -8
  25. package/dist/mcp/handlers/workflow.js +4 -11
  26. package/dist/mcp/output-schemas.d.ts +272 -20
  27. package/dist/mcp/output-schemas.js +20 -1
  28. package/dist/mcp/server.js +2 -0
  29. package/dist/mcp/tool-descriptions.js +67 -51
  30. package/dist/mcp/types.d.ts +2 -0
  31. package/dist/mcp/v2/tools.d.ts +23 -2
  32. package/dist/mcp/v2/tools.js +35 -4
  33. package/dist/types/workflow-definition.d.ts +19 -0
  34. package/dist/v2/durable-core/constants.d.ts +2 -0
  35. package/dist/v2/durable-core/constants.js +3 -1
  36. package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +1 -0
  37. package/dist/v2/durable-core/domain/ack-advance-append-plan.js +11 -1
  38. package/dist/v2/durable-core/domain/artifact-contract-validator.d.ts +31 -0
  39. package/dist/v2/durable-core/domain/artifact-contract-validator.js +98 -0
  40. package/dist/v2/durable-core/domain/blocked-node-builder.d.ts +20 -0
  41. package/dist/v2/durable-core/domain/blocked-node-builder.js +94 -0
  42. package/dist/v2/durable-core/domain/bundle-builder.d.ts +27 -0
  43. package/dist/v2/durable-core/domain/bundle-builder.js +93 -0
  44. package/dist/v2/durable-core/domain/bundle-validator.d.ts +4 -0
  45. package/dist/v2/durable-core/domain/bundle-validator.js +129 -0
  46. package/dist/v2/durable-core/domain/decision-trace-builder.d.ts +33 -0
  47. package/dist/v2/durable-core/domain/decision-trace-builder.js +92 -0
  48. package/dist/v2/durable-core/domain/function-definition-expander.js +1 -1
  49. package/dist/v2/durable-core/domain/loop-control-evaluator.d.ts +13 -0
  50. package/dist/v2/durable-core/domain/loop-control-evaluator.js +24 -0
  51. package/dist/v2/durable-core/domain/observation-builder.d.ts +16 -0
  52. package/dist/v2/durable-core/domain/observation-builder.js +42 -0
  53. package/dist/v2/durable-core/domain/outputs.js +2 -2
  54. package/dist/v2/durable-core/domain/prompt-renderer.js +37 -4
  55. package/dist/v2/durable-core/domain/reason-model.d.ts +3 -1
  56. package/dist/v2/durable-core/domain/reason-model.js +16 -3
  57. package/dist/v2/durable-core/domain/recommendation-warnings.d.ts +20 -0
  58. package/dist/v2/durable-core/domain/recommendation-warnings.js +35 -0
  59. package/dist/v2/durable-core/domain/risk-policy-guardrails.d.ts +15 -0
  60. package/dist/v2/durable-core/domain/risk-policy-guardrails.js +78 -0
  61. package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +8 -0
  62. package/dist/v2/durable-core/domain/validation-criteria-validator.js +30 -0
  63. package/dist/v2/durable-core/domain/validation-event-builder.d.ts +26 -0
  64. package/dist/v2/durable-core/domain/validation-event-builder.js +100 -0
  65. package/dist/v2/durable-core/domain/validation-loader.d.ts +11 -0
  66. package/dist/v2/durable-core/domain/validation-loader.js +21 -0
  67. package/dist/v2/durable-core/projections/snapshot-state.js +1 -1
  68. package/dist/v2/durable-core/schemas/artifacts/index.d.ts +4 -0
  69. package/dist/v2/durable-core/schemas/artifacts/index.js +18 -0
  70. package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +66 -0
  71. package/dist/v2/durable-core/schemas/artifacts/loop-control.js +47 -0
  72. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +598 -0
  73. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.js +89 -0
  74. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +3801 -57
  75. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +12 -2
  76. package/dist/v2/durable-core/schemas/execution-snapshot/index.d.ts +2 -0
  77. package/dist/v2/durable-core/schemas/execution-snapshot/index.js +3 -1
  78. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +4682 -758
  79. package/dist/v2/durable-core/schemas/session/events.d.ts +158 -45
  80. package/dist/v2/durable-core/schemas/session/events.js +8 -1
  81. package/dist/v2/durable-core/schemas/session/validation-event.d.ts +68 -0
  82. package/dist/v2/durable-core/schemas/session/validation-event.js +52 -0
  83. package/dist/v2/durable-core/tokens/payloads.d.ts +16 -16
  84. package/dist/v2/infra/local/workspace-anchor/index.d.ts +9 -0
  85. package/dist/v2/infra/local/workspace-anchor/index.js +44 -0
  86. package/dist/v2/ports/workspace-anchor.port.d.ts +18 -0
  87. package/dist/v2/ports/workspace-anchor.port.js +2 -0
  88. package/dist/v2/projections/artifacts.d.ts +22 -0
  89. package/dist/v2/projections/artifacts.js +53 -0
  90. package/dist/v2/projections/projection-timing.d.ts +13 -0
  91. package/dist/v2/projections/projection-timing.js +23 -0
  92. package/dist/v2/projections/run-dag.d.ts +1 -1
  93. package/dist/v2/projections/run-status-signals.js +3 -8
  94. package/dist/v2/usecases/export-session.d.ts +47 -0
  95. package/dist/v2/usecases/export-session.js +92 -0
  96. package/dist/v2/usecases/import-session.d.ts +34 -0
  97. package/dist/v2/usecases/import-session.js +57 -0
  98. package/package.json +1 -1
  99. package/spec/workflow.schema.json +60 -0
  100. package/spec/workflow.schema.v0.0.1.json +38 -0
  101. package/workflows/coding-task-workflow-agentic.json +11 -18
  102. package/workflows/test-artifact-loop-control.json +59 -0
@@ -1,8 +1,22 @@
1
1
  import { ConditionContext } from '../../utils/condition-evaluator';
2
+ import { type Result } from 'neverthrow';
2
3
  import type { LoopStepDefinition } from '../../types/workflow-definition';
3
4
  import type { Workflow } from '../../types/workflow';
4
5
  import { ValidationCriteria, ValidationResult } from '../../types/validation';
5
6
  import { EnhancedLoopValidator } from './enhanced-loop-validator';
7
+ export type ValidationEngineError = {
8
+ readonly kind: 'schema_compilation_failed';
9
+ readonly message: string;
10
+ readonly details?: unknown;
11
+ } | {
12
+ readonly kind: 'invalid_criteria_format';
13
+ readonly message: string;
14
+ readonly details?: unknown;
15
+ } | {
16
+ readonly kind: 'evaluation_threw';
17
+ readonly message: string;
18
+ readonly details?: unknown;
19
+ };
6
20
  export declare class ValidationEngine {
7
21
  private ajv;
8
22
  private schemaCache;
@@ -18,7 +32,7 @@ export declare class ValidationEngine {
18
32
  private evaluateSingleCriteria;
19
33
  private isValidationRule;
20
34
  private isValidationComposition;
21
- validate(output: string, criteria: ValidationCriteria, context?: ConditionContext): Promise<ValidationResult>;
35
+ validate(output: string, criteria: ValidationCriteria, context?: ConditionContext): Promise<Result<ValidationResult, ValidationEngineError>>;
22
36
  private evaluateRule;
23
37
  private looksLikeQuotedJsonSnippet;
24
38
  private maybePushJsonObjectNotStringSuggestion;
@@ -18,9 +18,9 @@ var ValidationEngine_1;
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.ValidationEngine = void 0;
20
20
  const tsyringe_1 = require("tsyringe");
21
- const error_handler_1 = require("../../core/error-handler");
22
21
  const condition_evaluator_1 = require("../../utils/condition-evaluator");
23
22
  const ajv_1 = __importDefault(require("ajv"));
23
+ const neverthrow_1 = require("neverthrow");
24
24
  const workflow_definition_1 = require("../../types/workflow-definition");
25
25
  const enhanced_loop_validator_1 = require("./enhanced-loop-validator");
26
26
  const step_output_decoder_1 = require("./step-output-decoder");
@@ -33,15 +33,19 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
33
33
  compileSchema(schema) {
34
34
  const schemaKey = JSON.stringify(schema);
35
35
  if (this.schemaCache.has(schemaKey)) {
36
- return this.schemaCache.get(schemaKey);
36
+ return (0, neverthrow_1.ok)(this.schemaCache.get(schemaKey));
37
37
  }
38
38
  try {
39
39
  const compiledSchema = this.ajv.compile(schema);
40
40
  this.schemaCache.set(schemaKey, compiledSchema);
41
- return compiledSchema;
41
+ return (0, neverthrow_1.ok)(compiledSchema);
42
42
  }
43
43
  catch (error) {
44
- throw new error_handler_1.ValidationError(`Invalid JSON schema: ${error}`);
44
+ return (0, neverthrow_1.err)({
45
+ kind: 'schema_compilation_failed',
46
+ message: 'Invalid JSON schema',
47
+ details: { error: String(error) },
48
+ });
45
49
  }
46
50
  }
47
51
  evaluateCriteria(output, criteria, context) {
@@ -51,20 +55,17 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
51
55
  }
52
56
  if (this.isValidationComposition(criteria)) {
53
57
  const compositionResult = this.evaluateComposition(output, criteria, context);
54
- return {
55
- valid: compositionResult,
56
- issues: compositionResult ? [] : ['Validation composition failed'],
57
- suggestions: compositionResult ? [] : ['Review validation criteria and adjust output accordingly.'],
58
+ return compositionResult.map((okRes) => ({
59
+ valid: okRes,
60
+ issues: okRes ? [] : ['Validation composition failed'],
61
+ suggestions: okRes ? [] : ['Review validation criteria and adjust output accordingly.'],
58
62
  warnings: undefined,
59
- };
63
+ }));
60
64
  }
61
- throw new error_handler_1.ValidationError('Invalid validation criteria format');
65
+ return (0, neverthrow_1.err)({ kind: 'invalid_criteria_format', message: 'Invalid validation criteria format' });
62
66
  }
63
67
  catch (error) {
64
- if (error instanceof error_handler_1.ValidationError) {
65
- throw error;
66
- }
67
- throw new error_handler_1.ValidationError(`Error evaluating validation criteria: ${error}`);
68
+ return (0, neverthrow_1.err)({ kind: 'evaluation_threw', message: 'Error evaluating validation criteria', details: { error: String(error) } });
68
69
  }
69
70
  }
70
71
  evaluateRuleArray(output, rules, context) {
@@ -72,53 +73,69 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
72
73
  const suggestions = [];
73
74
  const warnings = [];
74
75
  for (const rule of rules) {
75
- try {
76
+ if (typeof rule === 'object' && rule && 'condition' in rule) {
76
77
  if (rule.condition && !(0, condition_evaluator_1.evaluateCondition)(rule.condition, context)) {
77
78
  continue;
78
79
  }
79
- this.evaluateRule(output, rule, issues, suggestions, warnings);
80
- }
81
- catch (error) {
82
- if (error instanceof error_handler_1.ValidationError) {
83
- throw error;
84
- }
85
- throw new error_handler_1.ValidationError(`Error evaluating validation rule: ${error}`);
86
80
  }
81
+ const ruleRes = this.evaluateRule(output, rule, issues, suggestions, warnings);
82
+ if (ruleRes.isErr())
83
+ return (0, neverthrow_1.err)(ruleRes.error);
87
84
  }
88
- return {
85
+ return (0, neverthrow_1.ok)({
89
86
  valid: issues.length === 0,
90
87
  issues,
91
88
  suggestions: issues.length > 0 ? this.uniqueSuggestions([...suggestions, ValidationEngine_1.DEFAULT_FAILURE_SUGGESTION]) : [],
92
89
  warnings: warnings.length > 0 ? this.uniqueSuggestions(warnings) : undefined,
93
- };
90
+ });
94
91
  }
95
92
  evaluateComposition(output, composition, context) {
96
93
  if (composition.and) {
97
- return composition.and.every(criteria => this.evaluateSingleCriteria(output, criteria, context));
94
+ for (const criteria of composition.and) {
95
+ const res = this.evaluateSingleCriteria(output, criteria, context);
96
+ if (res.isErr())
97
+ return (0, neverthrow_1.err)(res.error);
98
+ if (!res.value)
99
+ return (0, neverthrow_1.ok)(false);
100
+ }
101
+ return (0, neverthrow_1.ok)(true);
98
102
  }
99
103
  if (composition.or) {
100
- return composition.or.some(criteria => this.evaluateSingleCriteria(output, criteria, context));
104
+ let sawTrue = false;
105
+ for (const criteria of composition.or) {
106
+ const res = this.evaluateSingleCriteria(output, criteria, context);
107
+ if (res.isErr())
108
+ return (0, neverthrow_1.err)(res.error);
109
+ if (res.value) {
110
+ sawTrue = true;
111
+ break;
112
+ }
113
+ }
114
+ return (0, neverthrow_1.ok)(sawTrue);
101
115
  }
102
116
  if (composition.not) {
103
- return !this.evaluateSingleCriteria(output, composition.not, context);
117
+ const res = this.evaluateSingleCriteria(output, composition.not, context);
118
+ return res.map((v) => !v);
104
119
  }
105
- return true;
120
+ return (0, neverthrow_1.ok)(true);
106
121
  }
107
122
  evaluateSingleCriteria(output, criteria, context) {
108
123
  if (this.isValidationRule(criteria)) {
109
124
  if (criteria.condition && !(0, condition_evaluator_1.evaluateCondition)(criteria.condition, context)) {
110
- return true;
125
+ return (0, neverthrow_1.ok)(true);
111
126
  }
112
127
  const issues = [];
113
128
  const suggestions = [];
114
129
  const warnings = [];
115
- this.evaluateRule(output, criteria, issues, suggestions, warnings);
116
- return issues.length === 0;
130
+ const res = this.evaluateRule(output, criteria, issues, suggestions, warnings);
131
+ if (res.isErr())
132
+ return (0, neverthrow_1.err)(res.error);
133
+ return (0, neverthrow_1.ok)(issues.length === 0);
117
134
  }
118
135
  if (this.isValidationComposition(criteria)) {
119
136
  return this.evaluateComposition(output, criteria, context);
120
137
  }
121
- throw new error_handler_1.ValidationError('Invalid validation criteria type');
138
+ return (0, neverthrow_1.err)({ kind: 'invalid_criteria_format', message: 'Invalid validation criteria type' });
122
139
  }
123
140
  isValidationRule(criteria) {
124
141
  return typeof criteria === 'object' && 'type' in criteria;
@@ -135,23 +152,25 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
135
152
  if (typeof output !== 'string' || output.trim().length === 0) {
136
153
  issues.push('Output is empty or invalid.');
137
154
  }
138
- return {
155
+ return (0, neverthrow_1.ok)({
139
156
  valid: issues.length === 0,
140
157
  issues,
141
158
  suggestions: issues.length > 0 ? ['Provide valid output content.'] : []
142
- };
159
+ });
143
160
  }
144
161
  const evaluation = this.evaluateCriteria(output, criteria, context || {});
145
- if (!evaluation.valid) {
146
- issues.push(...evaluation.issues);
162
+ if (evaluation.isErr())
163
+ return (0, neverthrow_1.err)(evaluation.error);
164
+ if (!evaluation.value.valid) {
165
+ issues.push(...evaluation.value.issues);
147
166
  }
148
167
  const valid = issues.length === 0;
149
- return {
168
+ return (0, neverthrow_1.ok)({
150
169
  valid,
151
170
  issues,
152
- suggestions: valid ? [] : this.uniqueSuggestions([...evaluation.suggestions, ValidationEngine_1.DEFAULT_FAILURE_SUGGESTION]),
153
- warnings: evaluation.warnings,
154
- };
171
+ suggestions: valid ? [] : this.uniqueSuggestions([...evaluation.value.suggestions, ValidationEngine_1.DEFAULT_FAILURE_SUGGESTION]),
172
+ warnings: evaluation.value.warnings,
173
+ });
155
174
  }
156
175
  evaluateRule(output, rule, issues, suggestions, warnings) {
157
176
  if (typeof rule === 'string') {
@@ -159,7 +178,7 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
159
178
  if (!re.test(output)) {
160
179
  issues.push(`Output does not match pattern: ${rule}`);
161
180
  }
162
- return;
181
+ return (0, neverthrow_1.ok)(undefined);
163
182
  }
164
183
  if (rule && typeof rule === 'object') {
165
184
  switch (rule.type) {
@@ -183,8 +202,12 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
183
202
  }
184
203
  }
185
204
  }
186
- catch {
187
- throw new error_handler_1.ValidationError(`Invalid regex pattern in validationCriteria: ${rule.pattern}`);
205
+ catch (error) {
206
+ return (0, neverthrow_1.err)({
207
+ kind: 'invalid_criteria_format',
208
+ message: 'Invalid regex pattern in validationCriteria',
209
+ details: { pattern: rule.pattern, error: String(error) },
210
+ });
188
211
  }
189
212
  break;
190
213
  }
@@ -228,7 +251,10 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
228
251
  suggestions.push(ValidationEngine_1.JSON_STRING_CONTAINS_JSON_SUGGESTION);
229
252
  }
230
253
  }
231
- const validate = this.compileSchema(rule.schema);
254
+ const validateRes = this.compileSchema(rule.schema);
255
+ if (validateRes.isErr())
256
+ return (0, neverthrow_1.err)(validateRes.error);
257
+ const validate = validateRes.value;
232
258
  const isValid = validate(decoded.value);
233
259
  if (!isValid) {
234
260
  const errorMessages = validate.errors?.map((error) => `Validation Error at '${error.instancePath}': ${error.message}`) || ['Schema validation failed'];
@@ -239,19 +265,23 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
239
265
  }
240
266
  }
241
267
  catch (error) {
242
- if (error instanceof error_handler_1.ValidationError) {
243
- throw error;
244
- }
245
- throw new error_handler_1.ValidationError(`Schema validation error: ${error}`);
268
+ return (0, neverthrow_1.err)({
269
+ kind: 'evaluation_threw',
270
+ message: 'Schema validation error',
271
+ details: { error: String(error) },
272
+ });
246
273
  }
247
274
  break;
248
275
  }
249
276
  default:
250
- throw new error_handler_1.ValidationError(`Unsupported validation rule type: ${rule.type}`);
277
+ return (0, neverthrow_1.err)({
278
+ kind: 'invalid_criteria_format',
279
+ message: `Unsupported validation rule type: ${rule.type}`,
280
+ });
251
281
  }
252
- return;
282
+ return (0, neverthrow_1.ok)(undefined);
253
283
  }
254
- throw new error_handler_1.ValidationError('Invalid validationCriteria format.');
284
+ return (0, neverthrow_1.err)({ kind: 'invalid_criteria_format', message: 'Invalid validationCriteria format.' });
255
285
  }
256
286
  looksLikeQuotedJsonSnippet(message) {
257
287
  if (typeof message !== 'string')
@@ -1,9 +1,11 @@
1
1
  import { Workflow, WorkflowStepDefinition, LoopStepDefinition } from '../../types/workflow';
2
+ import type { LoopConditionSource } from '../../types/workflow-definition';
2
3
  import type { Result } from 'neverthrow';
3
4
  import { type DomainError } from '../../domain/execution/error';
4
5
  export interface CompiledLoop {
5
6
  readonly loop: LoopStepDefinition;
6
7
  readonly bodySteps: readonly WorkflowStepDefinition[];
8
+ readonly conditionSource?: LoopConditionSource;
7
9
  }
8
10
  export interface CompiledWorkflow {
9
11
  readonly workflow: Workflow;
@@ -14,5 +16,6 @@ export interface CompiledWorkflow {
14
16
  }
15
17
  export declare class WorkflowCompiler {
16
18
  compile(workflow: Workflow): Result<CompiledWorkflow, DomainError>;
19
+ private deriveConditionSource;
17
20
  private resolveLoopBody;
18
21
  }
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.WorkflowCompiler = void 0;
10
10
  const tsyringe_1 = require("tsyringe");
11
11
  const workflow_1 = require("../../types/workflow");
12
+ const index_1 = require("../../v2/durable-core/schemas/artifacts/index");
12
13
  const neverthrow_1 = require("neverthrow");
13
14
  const error_1 = require("../../domain/execution/error");
14
15
  let WorkflowCompiler = class WorkflowCompiler {
@@ -33,9 +34,11 @@ let WorkflowCompiler = class WorkflowCompiler {
33
34
  for (const bodyStep of bodyResolved.value) {
34
35
  loopBodyStepIds.add(bodyStep.id);
35
36
  }
37
+ const conditionSource = this.deriveConditionSource(loop, bodyResolved.value);
36
38
  compiledLoops.set(loop.id, {
37
39
  loop,
38
40
  bodySteps: bodyResolved.value,
41
+ conditionSource,
39
42
  });
40
43
  }
41
44
  return (0, neverthrow_1.ok)({
@@ -46,6 +49,29 @@ let WorkflowCompiler = class WorkflowCompiler {
46
49
  loopBodyStepIds,
47
50
  });
48
51
  }
52
+ deriveConditionSource(loop, bodySteps) {
53
+ if (loop.loop.type !== 'while' && loop.loop.type !== 'until') {
54
+ return undefined;
55
+ }
56
+ if (loop.loop.conditionSource) {
57
+ return loop.loop.conditionSource;
58
+ }
59
+ const hasLoopControlContract = bodySteps.some((s) => s.outputContract?.contractRef === index_1.LOOP_CONTROL_CONTRACT_REF);
60
+ if (hasLoopControlContract) {
61
+ return {
62
+ kind: 'artifact_contract',
63
+ contractRef: index_1.LOOP_CONTROL_CONTRACT_REF,
64
+ loopId: loop.id,
65
+ };
66
+ }
67
+ if (loop.loop.condition) {
68
+ return {
69
+ kind: 'context_variable',
70
+ condition: loop.loop.condition,
71
+ };
72
+ }
73
+ return undefined;
74
+ }
49
75
  resolveLoopBody(loop, stepById, workflow) {
50
76
  if (Array.isArray(loop.body)) {
51
77
  for (const s of loop.body) {
@@ -5,6 +5,7 @@ import { type DomainError } from '../../domain/execution/error';
5
5
  import { ExecutionState } from '../../domain/execution/state';
6
6
  import { WorkflowEvent } from '../../domain/execution/event';
7
7
  import { StepInstanceId } from '../../domain/execution/ids';
8
+ import { type DecisionTraceEntry } from '../../v2/durable-core/domain/decision-trace-builder';
8
9
  export interface NextStep {
9
10
  readonly step: WorkflowStepDefinition;
10
11
  readonly stepInstanceId: StepInstanceId;
@@ -17,13 +18,15 @@ export interface InterpreterOutput {
17
18
  readonly state: ExecutionState;
18
19
  readonly next: NextStep | null;
19
20
  readonly isComplete: boolean;
21
+ readonly trace: readonly DecisionTraceEntry[];
20
22
  }
21
23
  export declare class WorkflowInterpreter {
22
24
  applyEvent(state: ExecutionState, event: WorkflowEvent): Result<ExecutionState, DomainError>;
23
- next(compiled: CompiledWorkflow, state: ExecutionState, context?: Record<string, unknown>): Result<InterpreterOutput, DomainError>;
25
+ next(compiled: CompiledWorkflow, state: ExecutionState, context?: Record<string, unknown>, artifacts?: readonly unknown[]): Result<InterpreterOutput, DomainError>;
24
26
  private ensureRunning;
25
27
  private nextTopLevel;
26
28
  private nextInCurrentLoop;
29
+ private evaluateWhileUntilCondition;
27
30
  private projectLoopContextAtIteration;
28
31
  private projectLoopContext;
29
32
  private lookupStepInstance;
@@ -14,6 +14,8 @@ const neverthrow_1 = require("neverthrow");
14
14
  const error_1 = require("../../domain/execution/error");
15
15
  const ids_1 = require("../../domain/execution/ids");
16
16
  const loop_runtime_1 = require("../../v2/durable-core/domain/loop-runtime");
17
+ const loop_control_evaluator_1 = require("../../v2/durable-core/domain/loop-control-evaluator");
18
+ const decision_trace_builder_1 = require("../../v2/durable-core/domain/decision-trace-builder");
17
19
  let WorkflowInterpreter = class WorkflowInterpreter {
18
20
  applyEvent(state, event) {
19
21
  if (state.kind === 'complete')
@@ -44,14 +46,15 @@ let WorkflowInterpreter = class WorkflowInterpreter {
44
46
  }
45
47
  }
46
48
  }
47
- next(compiled, state, context = {}) {
49
+ next(compiled, state, context = {}, artifacts = []) {
48
50
  if (state.kind === 'complete') {
49
- return (0, neverthrow_1.ok)({ state, next: null, isComplete: true });
51
+ return (0, neverthrow_1.ok)({ state, next: null, isComplete: true, trace: [] });
50
52
  }
51
53
  const runningRes = this.ensureRunning(state);
52
54
  if (runningRes.isErr())
53
55
  return (0, neverthrow_1.err)(runningRes.error);
54
56
  let running = runningRes.value;
57
+ const trace = [];
55
58
  if (running.pendingStep) {
56
59
  const step = this.lookupStepInstance(compiled, running.pendingStep);
57
60
  if (step.isErr())
@@ -60,17 +63,19 @@ let WorkflowInterpreter = class WorkflowInterpreter {
60
63
  state: running,
61
64
  next: step.value,
62
65
  isComplete: false,
66
+ trace,
63
67
  });
64
68
  }
65
69
  for (let guard = 0; guard < 10000; guard++) {
66
70
  if (running.loopStack.length > 0) {
67
- const inLoop = this.nextInCurrentLoop(compiled, running, context);
71
+ const inLoop = this.nextInCurrentLoop(compiled, running, context, artifacts, trace);
68
72
  if (inLoop.isErr())
69
73
  return (0, neverthrow_1.err)(inLoop.error);
70
74
  const result = inLoop.value;
71
75
  running = result.state;
72
76
  if (result.next) {
73
- return (0, neverthrow_1.ok)({ state: running, next: result.next, isComplete: false });
77
+ trace.push((0, decision_trace_builder_1.traceSelectedNextStep)(result.next.stepInstanceId.stepId));
78
+ return (0, neverthrow_1.ok)({ state: running, next: result.next, isComplete: false, trace });
74
79
  }
75
80
  if (running.loopStack.length > 0) {
76
81
  continue;
@@ -82,12 +87,13 @@ let WorkflowInterpreter = class WorkflowInterpreter {
82
87
  const out = top.value;
83
88
  running = out.state;
84
89
  if (out.next) {
85
- return (0, neverthrow_1.ok)({ state: running, next: out.next, isComplete: false });
90
+ trace.push((0, decision_trace_builder_1.traceSelectedNextStep)(out.next.stepInstanceId.stepId));
91
+ return (0, neverthrow_1.ok)({ state: running, next: out.next, isComplete: false, trace });
86
92
  }
87
93
  if (running.loopStack.length > 0) {
88
94
  continue;
89
95
  }
90
- return (0, neverthrow_1.ok)({ state: { kind: 'complete' }, next: null, isComplete: true });
96
+ return (0, neverthrow_1.ok)({ state: { kind: 'complete' }, next: null, isComplete: true, trace });
91
97
  }
92
98
  return (0, neverthrow_1.err)(error_1.Err.invalidState('Interpreter exceeded guard iterations (possible infinite loop)'));
93
99
  }
@@ -127,7 +133,7 @@ let WorkflowInterpreter = class WorkflowInterpreter {
127
133
  }
128
134
  return (0, neverthrow_1.ok)({ state, next: null });
129
135
  }
130
- nextInCurrentLoop(compiled, state, context) {
136
+ nextInCurrentLoop(compiled, state, context, artifacts, trace) {
131
137
  const frame = state.loopStack[state.loopStack.length - 1];
132
138
  const loopCompiled = compiled.compiledLoops.get(frame.loopId);
133
139
  if (!loopCompiled) {
@@ -142,7 +148,10 @@ let WorkflowInterpreter = class WorkflowInterpreter {
142
148
  case 'for': {
143
149
  const count = loopCompiled.loop.loop.count;
144
150
  if (typeof count === 'number') {
145
- return (0, neverthrow_1.ok)(iteration < count);
151
+ const shouldEnter = iteration < count;
152
+ if (shouldEnter && iteration === 0)
153
+ trace.push((0, decision_trace_builder_1.traceEnteredLoop)(frame.loopId, iteration));
154
+ return (0, neverthrow_1.ok)(shouldEnter);
146
155
  }
147
156
  if (typeof count === 'string') {
148
157
  const raw = context[count];
@@ -153,7 +162,10 @@ let WorkflowInterpreter = class WorkflowInterpreter {
153
162
  message: `for loop '${loopCompiled.loop.id}' requires numeric context['${count}']`,
154
163
  });
155
164
  }
156
- return (0, neverthrow_1.ok)(iteration < raw);
165
+ const shouldEnter = iteration < raw;
166
+ if (shouldEnter && iteration === 0)
167
+ trace.push((0, decision_trace_builder_1.traceEnteredLoop)(frame.loopId, iteration));
168
+ return (0, neverthrow_1.ok)(shouldEnter);
157
169
  }
158
170
  return (0, neverthrow_1.err)({
159
171
  code: 'LOOP_INVALID_CONFIG',
@@ -178,27 +190,34 @@ let WorkflowInterpreter = class WorkflowInterpreter {
178
190
  message: `forEach loop '${loopCompiled.loop.id}' requires array context['${itemsVar}']`,
179
191
  });
180
192
  }
181
- return (0, neverthrow_1.ok)(iteration < raw.length);
193
+ const shouldEnter = iteration < raw.length;
194
+ if (shouldEnter && iteration === 0)
195
+ trace.push((0, decision_trace_builder_1.traceEnteredLoop)(frame.loopId, iteration));
196
+ return (0, neverthrow_1.ok)(shouldEnter);
182
197
  }
183
198
  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
- });
199
+ const res = this.evaluateWhileUntilCondition(loopCompiled, iteration, context, artifacts, frame, false);
200
+ if (res.isOk()) {
201
+ const source = loopCompiled.conditionSource?.kind === 'artifact_contract' ? 'artifact'
202
+ : loopCompiled.conditionSource?.kind === 'context_variable' ? 'context'
203
+ : 'legacy';
204
+ trace.push((0, decision_trace_builder_1.traceEvaluatedCondition)(frame.loopId, iteration, res.value, source));
205
+ if (res.value && iteration === 0)
206
+ trace.push((0, decision_trace_builder_1.traceEnteredLoop)(frame.loopId, iteration));
190
207
  }
191
- return (0, neverthrow_1.ok)((0, condition_evaluator_1.evaluateCondition)(loopCompiled.loop.loop.condition, this.projectLoopContextAtIteration(loopCompiled.loop, iteration, context)));
208
+ return res;
192
209
  }
193
210
  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
- });
211
+ const res = this.evaluateWhileUntilCondition(loopCompiled, iteration, context, artifacts, frame, true);
212
+ if (res.isOk()) {
213
+ const source = loopCompiled.conditionSource?.kind === 'artifact_contract' ? 'artifact'
214
+ : loopCompiled.conditionSource?.kind === 'context_variable' ? 'context'
215
+ : 'legacy';
216
+ trace.push((0, decision_trace_builder_1.traceEvaluatedCondition)(frame.loopId, iteration, res.value, source));
217
+ if (res.value && iteration === 0)
218
+ trace.push((0, decision_trace_builder_1.traceEnteredLoop)(frame.loopId, iteration));
200
219
  }
201
- return (0, neverthrow_1.ok)(!(0, condition_evaluator_1.evaluateCondition)(loopCompiled.loop.loop.condition, this.projectLoopContextAtIteration(loopCompiled.loop, iteration, context)));
220
+ return res;
202
221
  }
203
222
  default:
204
223
  return (0, neverthrow_1.err)({
@@ -247,6 +266,7 @@ let WorkflowInterpreter = class WorkflowInterpreter {
247
266
  }
248
267
  switch (decision.value.kind) {
249
268
  case 'exit_loop': {
269
+ trace.push((0, decision_trace_builder_1.traceExitedLoop)(frame.loopId, `Condition no longer met after ${frame.iteration} iteration(s)`));
250
270
  const popped = state.loopStack.slice(0, -1);
251
271
  return (0, neverthrow_1.ok)({
252
272
  state: {
@@ -283,6 +303,47 @@ let WorkflowInterpreter = class WorkflowInterpreter {
283
303
  return (0, neverthrow_1.err)(error_1.Err.invalidState('Non-exhaustive loop decision'));
284
304
  }
285
305
  }
306
+ evaluateWhileUntilCondition(loopCompiled, iteration, context, artifacts, frame, invertForUntil) {
307
+ const source = loopCompiled.conditionSource;
308
+ if (!source) {
309
+ if (!loopCompiled.loop.loop.condition) {
310
+ return (0, neverthrow_1.err)({
311
+ code: 'LOOP_INVALID_CONFIG',
312
+ loopId: loopCompiled.loop.id,
313
+ message: `${loopCompiled.loop.loop.type} loop '${loopCompiled.loop.id}' missing condition and conditionSource`,
314
+ });
315
+ }
316
+ const raw = (0, condition_evaluator_1.evaluateCondition)(loopCompiled.loop.loop.condition, this.projectLoopContextAtIteration(loopCompiled.loop, iteration, context));
317
+ return (0, neverthrow_1.ok)(invertForUntil ? !raw : raw);
318
+ }
319
+ switch (source.kind) {
320
+ case 'artifact_contract': {
321
+ const result = (0, loop_control_evaluator_1.evaluateLoopControlFromArtifacts)(artifacts, source.loopId);
322
+ if (result.kind === 'found') {
323
+ return (0, neverthrow_1.ok)(result.decision === 'continue');
324
+ }
325
+ return (0, neverthrow_1.err)({
326
+ code: 'LOOP_MISSING_CONTEXT',
327
+ loopId: loopCompiled.loop.id,
328
+ message: result.kind === 'not_found'
329
+ ? `Loop '${source.loopId}' requires a wr.loop_control artifact but none was provided`
330
+ : `Loop '${source.loopId}' has an invalid loop control artifact: ${result.reason}`,
331
+ });
332
+ }
333
+ case 'context_variable': {
334
+ const raw = (0, condition_evaluator_1.evaluateCondition)(source.condition, this.projectLoopContextAtIteration(loopCompiled.loop, iteration, context));
335
+ return (0, neverthrow_1.ok)(invertForUntil ? !raw : raw);
336
+ }
337
+ default: {
338
+ const _exhaustive = source;
339
+ return (0, neverthrow_1.err)({
340
+ code: 'LOOP_INVALID_CONFIG',
341
+ loopId: loopCompiled.loop.id,
342
+ message: `Unknown conditionSource kind: ${_exhaustive.kind}`,
343
+ });
344
+ }
345
+ }
346
+ }
286
347
  projectLoopContextAtIteration(loop, iteration, base) {
287
348
  const out = { ...base };
288
349
  const iterationVar = loop.loop.iterationVar || 'currentIteration';
@@ -65,13 +65,30 @@ let DefaultWorkflowService = class DefaultWorkflowService {
65
65
  if (!step) {
66
66
  return { valid: false, issues: [`Step '${stepId}' not found in workflow '${workflowId}'`], suggestions: [], warnings: undefined };
67
67
  }
68
+ const outputContract = step.outputContract;
69
+ if (outputContract) {
70
+ return {
71
+ valid: false,
72
+ issues: [`Step '${stepId}' uses outputContract (artifact validation); validate via v2 continue_workflow path, not v1 validateStepOutput`],
73
+ suggestions: ['Use the v2 continue_workflow tool with output.artifacts[] to validate against the artifact contract.'],
74
+ warnings: undefined,
75
+ };
76
+ }
68
77
  const criteria = step.validationCriteria;
69
78
  if (!criteria)
70
79
  return { valid: true, issues: [], suggestions: [], warnings: undefined };
71
80
  const result = await this.validationEngine.validate(output, criteria);
72
- const contextualizedWarnings = result.warnings?.map(w => `Step '${workflowId}/${stepId}': ${w}`);
81
+ if (result.isErr()) {
82
+ return {
83
+ valid: false,
84
+ issues: [`Validation engine error: ${result.error.kind} (${result.error.message})`],
85
+ suggestions: ['Check validationCriteria for invalid schema/format, and retry.'],
86
+ warnings: undefined,
87
+ };
88
+ }
89
+ const contextualizedWarnings = result.value.warnings?.map((w) => `Step '${workflowId}/${stepId}': ${w}`);
73
90
  return {
74
- ...result,
91
+ ...result.value,
75
92
  warnings: contextualizedWarnings,
76
93
  };
77
94
  }