@exaudeus/workrail 1.1.0 → 1.2.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 (94) 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 +267 -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/decision-trace-builder.d.ts +33 -0
  43. package/dist/v2/durable-core/domain/decision-trace-builder.js +92 -0
  44. package/dist/v2/durable-core/domain/function-definition-expander.js +1 -1
  45. package/dist/v2/durable-core/domain/loop-control-evaluator.d.ts +13 -0
  46. package/dist/v2/durable-core/domain/loop-control-evaluator.js +24 -0
  47. package/dist/v2/durable-core/domain/observation-builder.d.ts +16 -0
  48. package/dist/v2/durable-core/domain/observation-builder.js +42 -0
  49. package/dist/v2/durable-core/domain/outputs.js +2 -2
  50. package/dist/v2/durable-core/domain/prompt-renderer.js +37 -4
  51. package/dist/v2/durable-core/domain/reason-model.d.ts +3 -1
  52. package/dist/v2/durable-core/domain/reason-model.js +16 -3
  53. package/dist/v2/durable-core/domain/recommendation-warnings.d.ts +20 -0
  54. package/dist/v2/durable-core/domain/recommendation-warnings.js +35 -0
  55. package/dist/v2/durable-core/domain/risk-policy-guardrails.d.ts +15 -0
  56. package/dist/v2/durable-core/domain/risk-policy-guardrails.js +78 -0
  57. package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +8 -0
  58. package/dist/v2/durable-core/domain/validation-criteria-validator.js +30 -0
  59. package/dist/v2/durable-core/domain/validation-event-builder.d.ts +26 -0
  60. package/dist/v2/durable-core/domain/validation-event-builder.js +100 -0
  61. package/dist/v2/durable-core/domain/validation-loader.d.ts +11 -0
  62. package/dist/v2/durable-core/domain/validation-loader.js +21 -0
  63. package/dist/v2/durable-core/projections/snapshot-state.js +1 -1
  64. package/dist/v2/durable-core/schemas/artifacts/index.d.ts +4 -0
  65. package/dist/v2/durable-core/schemas/artifacts/index.js +18 -0
  66. package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +66 -0
  67. package/dist/v2/durable-core/schemas/artifacts/loop-control.js +47 -0
  68. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +598 -0
  69. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.js +89 -0
  70. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +3801 -57
  71. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +12 -2
  72. package/dist/v2/durable-core/schemas/execution-snapshot/index.d.ts +2 -0
  73. package/dist/v2/durable-core/schemas/execution-snapshot/index.js +3 -1
  74. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +4682 -758
  75. package/dist/v2/durable-core/schemas/session/events.d.ts +158 -45
  76. package/dist/v2/durable-core/schemas/session/events.js +8 -1
  77. package/dist/v2/durable-core/schemas/session/validation-event.d.ts +68 -0
  78. package/dist/v2/durable-core/schemas/session/validation-event.js +52 -0
  79. package/dist/v2/durable-core/tokens/payloads.d.ts +16 -16
  80. package/dist/v2/infra/local/workspace-anchor/index.d.ts +9 -0
  81. package/dist/v2/infra/local/workspace-anchor/index.js +44 -0
  82. package/dist/v2/ports/workspace-anchor.port.d.ts +18 -0
  83. package/dist/v2/ports/workspace-anchor.port.js +2 -0
  84. package/dist/v2/projections/artifacts.d.ts +22 -0
  85. package/dist/v2/projections/artifacts.js +53 -0
  86. package/dist/v2/projections/projection-timing.d.ts +13 -0
  87. package/dist/v2/projections/projection-timing.js +23 -0
  88. package/dist/v2/projections/run-dag.d.ts +1 -1
  89. package/dist/v2/projections/run-status-signals.js +3 -8
  90. package/package.json +1 -1
  91. package/spec/workflow.schema.json +60 -0
  92. package/spec/workflow.schema.v0.0.1.json +38 -0
  93. package/workflows/coding-task-workflow-agentic.json +11 -18
  94. package/workflows/test-artifact-loop-control.json +59 -0
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildBlockedNodeSnapshot = buildBlockedNodeSnapshot;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const attempt_id_derivation_js_1 = require("../ids/attempt-id-derivation.js");
6
+ function toContractViolationReason(reason) {
7
+ switch (reason.kind) {
8
+ case 'invalid_required_output':
9
+ return { kind: 'invalid_required_output', contractRef: reason.contractRef };
10
+ case 'missing_required_output':
11
+ return { kind: 'missing_required_output', contractRef: reason.contractRef };
12
+ case 'missing_context_key':
13
+ return { kind: 'missing_context_key', key: reason.key };
14
+ case 'context_budget_exceeded':
15
+ return { kind: 'context_budget_exceeded' };
16
+ default:
17
+ return null;
18
+ }
19
+ }
20
+ function toTerminalReason(reason) {
21
+ switch (reason.kind) {
22
+ case 'user_only_dependency':
23
+ return { kind: 'user_only_dependency', detail: reason.detail, stepId: reason.stepId };
24
+ case 'required_capability_unknown':
25
+ return { kind: 'required_capability_unknown', capability: reason.capability };
26
+ case 'required_capability_unavailable':
27
+ return { kind: 'required_capability_unavailable', capability: reason.capability };
28
+ case 'invariant_violation':
29
+ return { kind: 'invariant_violation' };
30
+ case 'storage_corruption_detected':
31
+ return { kind: 'storage_corruption_detected' };
32
+ case 'evaluation_error':
33
+ return { kind: 'evaluation_error' };
34
+ default:
35
+ return null;
36
+ }
37
+ }
38
+ function buildBlockedPayload(args) {
39
+ const retryable = toContractViolationReason(args.primaryReason);
40
+ if (retryable) {
41
+ const retryAttemptId = (0, attempt_id_derivation_js_1.deriveChildAttemptId)(args.attemptId, args.sha256);
42
+ return (0, neverthrow_1.ok)({
43
+ kind: 'retryable_block',
44
+ reason: retryable,
45
+ retryAttemptId: String(retryAttemptId),
46
+ validationRef: args.validationRef,
47
+ blockers: args.blockers,
48
+ });
49
+ }
50
+ const terminal = toTerminalReason(args.primaryReason);
51
+ if (terminal) {
52
+ return (0, neverthrow_1.ok)({
53
+ kind: 'terminal_block',
54
+ reason: terminal,
55
+ validationRef: args.validationRef,
56
+ blockers: args.blockers,
57
+ });
58
+ }
59
+ return (0, neverthrow_1.err)({
60
+ code: 'BLOCKED_NODE_INVARIANT_VIOLATION',
61
+ message: `Unsupported primary reason for blocked snapshot: ${args.primaryReason.kind}`,
62
+ });
63
+ }
64
+ function buildBlockedNodeSnapshot(args) {
65
+ const state = args.priorSnapshot.enginePayload.engineState;
66
+ if (state.kind !== 'running' && state.kind !== 'blocked') {
67
+ return (0, neverthrow_1.err)({
68
+ code: 'BLOCKED_NODE_UNSUPPORTED_STATE',
69
+ message: `Blocked nodes can only be created from running or blocked state (got: ${state.kind})`,
70
+ });
71
+ }
72
+ const blockedRes = buildBlockedPayload({
73
+ primaryReason: args.primaryReason,
74
+ attemptId: args.attemptId,
75
+ validationRef: args.validationRef,
76
+ blockers: args.blockers,
77
+ sha256: args.sha256,
78
+ });
79
+ if (blockedRes.isErr())
80
+ return (0, neverthrow_1.err)(blockedRes.error);
81
+ return (0, neverthrow_1.ok)({
82
+ ...args.priorSnapshot,
83
+ enginePayload: {
84
+ ...args.priorSnapshot.enginePayload,
85
+ engineState: {
86
+ kind: 'blocked',
87
+ completed: state.completed,
88
+ loopStack: state.loopStack,
89
+ pending: state.pending,
90
+ blocked: blockedRes.value,
91
+ },
92
+ },
93
+ });
94
+ }
@@ -0,0 +1,33 @@
1
+ import type { Result } from 'neverthrow';
2
+ export type DecisionTraceEntryKind = 'selected_next_step' | 'evaluated_condition' | 'entered_loop' | 'exited_loop' | 'detected_non_tip_advance';
3
+ export type DecisionTraceRef = {
4
+ readonly kind: 'step_id';
5
+ readonly stepId: string;
6
+ } | {
7
+ readonly kind: 'loop_id';
8
+ readonly loopId: string;
9
+ } | {
10
+ readonly kind: 'condition_id';
11
+ readonly conditionId: string;
12
+ } | {
13
+ readonly kind: 'iteration';
14
+ readonly value: number;
15
+ };
16
+ export interface DecisionTraceEntry {
17
+ readonly kind: DecisionTraceEntryKind;
18
+ readonly summary: string;
19
+ readonly refs?: readonly DecisionTraceRef[];
20
+ }
21
+ export declare function traceEnteredLoop(loopId: string, iteration: number): DecisionTraceEntry;
22
+ export declare function traceEvaluatedCondition(loopId: string, iteration: number, result: boolean, source: 'artifact' | 'context' | 'legacy'): DecisionTraceEntry;
23
+ export declare function traceExitedLoop(loopId: string, reason: string): DecisionTraceEntry;
24
+ export declare function traceSelectedNextStep(stepId: string): DecisionTraceEntry;
25
+ export declare function applyTraceBudget(entries: readonly DecisionTraceEntry[]): readonly DecisionTraceEntry[];
26
+ export declare function buildDecisionTraceEventData(traceId: string, entries: readonly DecisionTraceEntry[]): Result<{
27
+ readonly traceId: string;
28
+ readonly entries: readonly {
29
+ readonly kind: string;
30
+ readonly summary: string;
31
+ readonly refs?: readonly DecisionTraceRef[];
32
+ }[];
33
+ }, never>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.traceEnteredLoop = traceEnteredLoop;
4
+ exports.traceEvaluatedCondition = traceEvaluatedCondition;
5
+ exports.traceExitedLoop = traceExitedLoop;
6
+ exports.traceSelectedNextStep = traceSelectedNextStep;
7
+ exports.applyTraceBudget = applyTraceBudget;
8
+ exports.buildDecisionTraceEventData = buildDecisionTraceEventData;
9
+ const neverthrow_1 = require("neverthrow");
10
+ const constants_js_1 = require("../constants.js");
11
+ function traceEnteredLoop(loopId, iteration) {
12
+ return {
13
+ kind: 'entered_loop',
14
+ summary: `Entered loop '${loopId}' at iteration ${iteration}.`,
15
+ refs: [{ kind: 'loop_id', loopId }, { kind: 'iteration', value: iteration }],
16
+ };
17
+ }
18
+ function traceEvaluatedCondition(loopId, iteration, result, source) {
19
+ const decision = result ? 'continue' : 'exit';
20
+ return {
21
+ kind: 'evaluated_condition',
22
+ summary: `Evaluated ${source} condition for loop '${loopId}' at iteration ${iteration}: ${decision}.`,
23
+ refs: [{ kind: 'loop_id', loopId }, { kind: 'iteration', value: iteration }],
24
+ };
25
+ }
26
+ function traceExitedLoop(loopId, reason) {
27
+ return {
28
+ kind: 'exited_loop',
29
+ summary: `Exited loop '${loopId}': ${reason}.`,
30
+ refs: [{ kind: 'loop_id', loopId }],
31
+ };
32
+ }
33
+ function traceSelectedNextStep(stepId) {
34
+ return {
35
+ kind: 'selected_next_step',
36
+ summary: `Selected next step '${stepId}'.`,
37
+ refs: [{ kind: 'step_id', stepId }],
38
+ };
39
+ }
40
+ const textEncoder = new TextEncoder();
41
+ function utf8ByteLength(s) {
42
+ return textEncoder.encode(s).length;
43
+ }
44
+ function applyTraceBudget(entries) {
45
+ const capped = entries.slice(0, constants_js_1.MAX_DECISION_TRACE_ENTRIES);
46
+ let totalBytes = 0;
47
+ const result = [];
48
+ for (const entry of capped) {
49
+ let summary = entry.summary;
50
+ if (utf8ByteLength(summary) > constants_js_1.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES) {
51
+ summary = truncateToUtf8Budget(summary, constants_js_1.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES);
52
+ }
53
+ const entryBytes = utf8ByteLength(summary);
54
+ if (totalBytes + entryBytes > constants_js_1.MAX_DECISION_TRACE_TOTAL_BYTES) {
55
+ const remaining = constants_js_1.MAX_DECISION_TRACE_TOTAL_BYTES - totalBytes;
56
+ if (remaining > utf8ByteLength(constants_js_1.TRUNCATION_MARKER) + 10) {
57
+ summary = truncateToUtf8Budget(summary, remaining);
58
+ result.push({ ...entry, summary });
59
+ }
60
+ break;
61
+ }
62
+ totalBytes += entryBytes;
63
+ result.push(summary === entry.summary ? entry : { ...entry, summary });
64
+ }
65
+ return result;
66
+ }
67
+ function truncateToUtf8Budget(s, maxBytes) {
68
+ const markerBytes = utf8ByteLength(constants_js_1.TRUNCATION_MARKER);
69
+ const targetBytes = maxBytes - markerBytes;
70
+ if (targetBytes <= 0)
71
+ return constants_js_1.TRUNCATION_MARKER;
72
+ const encoded = textEncoder.encode(s);
73
+ if (encoded.length <= maxBytes)
74
+ return s;
75
+ let end = targetBytes;
76
+ while (end > 0 && (encoded[end] & 0xc0) === 0x80) {
77
+ end--;
78
+ }
79
+ const decoder = new TextDecoder();
80
+ return decoder.decode(encoded.slice(0, end)) + constants_js_1.TRUNCATION_MARKER;
81
+ }
82
+ function buildDecisionTraceEventData(traceId, entries) {
83
+ const budgeted = applyTraceBudget(entries);
84
+ return (0, neverthrow_1.ok)({
85
+ traceId,
86
+ entries: budgeted.map((e) => ({
87
+ kind: e.kind,
88
+ summary: e.summary,
89
+ refs: e.refs && e.refs.length > 0 ? e.refs : undefined,
90
+ })),
91
+ });
92
+ }
@@ -57,7 +57,7 @@ function expandFunctionDefinitions(args) {
57
57
  const bPri = scopePriority[bScope] ?? 2;
58
58
  if (aPri !== bPri)
59
59
  return aPri - bPri;
60
- return a.name.localeCompare(b.name);
60
+ return a.name.localeCompare(b.name, 'en-US');
61
61
  });
62
62
  return (0, neverthrow_1.ok)(sorted);
63
63
  }
@@ -0,0 +1,13 @@
1
+ import { type LoopControlDecision, type LoopControlArtifactV1 } from '../schemas/artifacts/index.js';
2
+ export type LoopControlEvaluationResult = {
3
+ readonly kind: 'found';
4
+ readonly decision: LoopControlDecision;
5
+ readonly artifact: LoopControlArtifactV1;
6
+ } | {
7
+ readonly kind: 'not_found';
8
+ readonly reason: string;
9
+ } | {
10
+ readonly kind: 'invalid';
11
+ readonly reason: string;
12
+ };
13
+ export declare function evaluateLoopControlFromArtifacts(artifacts: readonly unknown[], loopId: string): LoopControlEvaluationResult;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateLoopControlFromArtifacts = evaluateLoopControlFromArtifacts;
4
+ const index_js_1 = require("../schemas/artifacts/index.js");
5
+ function evaluateLoopControlFromArtifacts(artifacts, loopId) {
6
+ if (artifacts.length === 0) {
7
+ return {
8
+ kind: 'not_found',
9
+ reason: `No artifacts provided to evaluate loop control for loopId=${loopId}`,
10
+ };
11
+ }
12
+ const artifact = (0, index_js_1.findLoopControlArtifact)(artifacts, loopId);
13
+ if (!artifact) {
14
+ return {
15
+ kind: 'not_found',
16
+ reason: `No loop control artifact found for loopId=${loopId}`,
17
+ };
18
+ }
19
+ return {
20
+ kind: 'found',
21
+ decision: artifact.decision,
22
+ artifact,
23
+ };
24
+ }
@@ -0,0 +1,16 @@
1
+ import type { WorkspaceAnchor } from '../../ports/workspace-anchor.port.js';
2
+ export interface ObservationEventData {
3
+ readonly key: 'git_branch' | 'git_head_sha' | 'repo_root_hash';
4
+ readonly value: {
5
+ readonly type: 'short_string';
6
+ readonly value: string;
7
+ } | {
8
+ readonly type: 'git_sha1';
9
+ readonly value: string;
10
+ } | {
11
+ readonly type: 'sha256';
12
+ readonly value: string;
13
+ };
14
+ readonly confidence: 'low' | 'med' | 'high';
15
+ }
16
+ export declare function anchorsToObservations(anchors: readonly WorkspaceAnchor[]): readonly ObservationEventData[];
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.anchorsToObservations = anchorsToObservations;
4
+ function anchorsToObservations(anchors) {
5
+ const observations = [];
6
+ for (const anchor of anchors) {
7
+ switch (anchor.key) {
8
+ case 'git_branch':
9
+ if (anchor.value.length > 80)
10
+ break;
11
+ observations.push({
12
+ key: 'git_branch',
13
+ value: { type: 'short_string', value: anchor.value },
14
+ confidence: 'high',
15
+ });
16
+ break;
17
+ case 'git_head_sha':
18
+ if (!/^[0-9a-f]{40}$/.test(anchor.value))
19
+ break;
20
+ observations.push({
21
+ key: 'git_head_sha',
22
+ value: { type: 'git_sha1', value: anchor.value },
23
+ confidence: 'high',
24
+ });
25
+ break;
26
+ case 'repo_root_hash':
27
+ if (!/^sha256:[0-9a-f]{64}$/.test(anchor.value))
28
+ break;
29
+ observations.push({
30
+ key: 'repo_root_hash',
31
+ value: { type: 'sha256', value: anchor.value },
32
+ confidence: 'high',
33
+ });
34
+ break;
35
+ default: {
36
+ const _exhaustive = anchor;
37
+ return _exhaustive;
38
+ }
39
+ }
40
+ }
41
+ return observations;
42
+ }
@@ -9,10 +9,10 @@ function normalizeOutputsForAppend(outputs) {
9
9
  const aSha = a.payload.sha256 ?? '';
10
10
  const bSha = b.payload.sha256 ?? '';
11
11
  if (aSha !== bSha)
12
- return aSha.localeCompare(bSha);
12
+ return aSha.localeCompare(bSha, 'en-US');
13
13
  const aType = a.payload.contentType ?? '';
14
14
  const bType = b.payload.contentType ?? '';
15
- return aType.localeCompare(bType);
15
+ return aType.localeCompare(bType, 'en-US');
16
16
  });
17
17
  return [...recapFirst, ...sortedArtifacts];
18
18
  }
@@ -9,6 +9,8 @@ const node_outputs_js_1 = require("../../projections/node-outputs.js");
9
9
  const recap_recovery_js_1 = require("./recap-recovery.js");
10
10
  const function_definition_expander_js_1 = require("./function-definition-expander.js");
11
11
  const constants_js_1 = require("../constants.js");
12
+ const validation_requirements_extractor_js_1 = require("./validation-requirements-extractor.js");
13
+ const index_js_2 = require("../schemas/artifacts/index.js");
12
14
  function buildNonTipSections(args) {
13
15
  const sections = [];
14
16
  const childSummary = (0, recap_recovery_js_1.buildChildSummary)({ nodeId: args.nodeId, dag: args.run });
@@ -112,6 +114,24 @@ function applyPromptBudget(combinedPrompt) {
112
114
  const decoder = new TextDecoder('utf-8');
113
115
  return decoder.decode(truncatedBytes) + markerText + omissionNote;
114
116
  }
117
+ function formatOutputContractRequirements(outputContract) {
118
+ const contractRef = outputContract?.contractRef;
119
+ if (!contractRef)
120
+ return [];
121
+ switch (contractRef) {
122
+ case index_js_2.LOOP_CONTROL_CONTRACT_REF:
123
+ return [
124
+ `Artifact contract: ${index_js_2.LOOP_CONTROL_CONTRACT_REF}`,
125
+ `Provide an artifact with kind: "wr.loop_control"`,
126
+ `Fields: loopId (lowercase, delimiter-safe), decision ("continue" | "stop")`,
127
+ ];
128
+ default:
129
+ return [
130
+ `Artifact contract: ${contractRef}`,
131
+ `Provide an artifact matching the contract schema`,
132
+ ];
133
+ }
134
+ }
115
135
  function loadRecoveryProjections(args) {
116
136
  const dagRes = (0, run_dag_js_1.projectRunDagV2)(args.truth.events);
117
137
  if (dagRes.isErr()) {
@@ -134,15 +154,28 @@ function renderPendingPrompt(args) {
134
154
  const basePrompt = step?.prompt ?? `Pending step: ${args.stepId}`;
135
155
  const requireConfirmation = Boolean(step?.requireConfirmation);
136
156
  const functionReferences = step?.functionReferences ?? [];
157
+ const validationCriteria = step?.validationCriteria;
158
+ const requirements = (0, validation_requirements_extractor_js_1.extractValidationRequirements)(validationCriteria);
159
+ const requirementsSection = requirements.length > 0
160
+ ? `\n\n**OUTPUT REQUIREMENTS:**\n${requirements.map(r => `- ${r}`).join('\n')}`
161
+ : '';
162
+ const outputContract = step && typeof step === 'object' && 'outputContract' in step
163
+ ? step.outputContract
164
+ : undefined;
165
+ const contractRequirements = formatOutputContractRequirements(outputContract);
166
+ const contractSection = contractRequirements.length > 0
167
+ ? `\n\n**OUTPUT REQUIREMENTS (System):**\n${contractRequirements.map(r => `- ${r}`).join('\n')}`
168
+ : '';
169
+ const enhancedPrompt = basePrompt + requirementsSection + contractSection;
137
170
  if (!args.rehydrateOnly) {
138
- return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: basePrompt, requireConfirmation });
171
+ return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: enhancedPrompt, requireConfirmation });
139
172
  }
140
173
  const projectionsRes = loadRecoveryProjections({ truth: args.truth, runId: args.runId });
141
174
  if (projectionsRes.isErr()) {
142
175
  return (0, neverthrow_1.ok)({
143
176
  stepId: args.stepId,
144
177
  title: baseTitle,
145
- prompt: basePrompt + '\n\n' + projectionsRes.error,
178
+ prompt: enhancedPrompt + '\n\n' + projectionsRes.error,
146
179
  requireConfirmation,
147
180
  });
148
181
  }
@@ -158,10 +191,10 @@ function renderPendingPrompt(args) {
158
191
  functionReferences,
159
192
  });
160
193
  if (sections.length === 0) {
161
- return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: basePrompt, requireConfirmation });
194
+ return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: enhancedPrompt, requireConfirmation });
162
195
  }
163
196
  const recoveryText = `## Recovery Context\n\n${sections.join('\n\n')}`;
164
- const combinedPrompt = `${basePrompt}\n\n${recoveryText}`;
197
+ const combinedPrompt = `${enhancedPrompt}\n\n${recoveryText}`;
165
198
  const finalPrompt = applyPromptBudget(combinedPrompt);
166
199
  return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: finalPrompt, requireConfirmation });
167
200
  }
@@ -14,7 +14,7 @@ export type GapReasonV1 = {
14
14
  readonly detail: 'required_capability_unavailable' | 'required_capability_unknown';
15
15
  } | {
16
16
  readonly category: 'unexpected';
17
- readonly detail: 'invariant_violation' | 'storage_corruption_detected';
17
+ readonly detail: 'invariant_violation' | 'storage_corruption_detected' | 'evaluation_error';
18
18
  };
19
19
  export type BlockerCodeV1 = 'USER_ONLY_DEPENDENCY' | 'MISSING_REQUIRED_OUTPUT' | 'INVALID_REQUIRED_OUTPUT' | 'REQUIRED_CAPABILITY_UNKNOWN' | 'REQUIRED_CAPABILITY_UNAVAILABLE' | 'INVARIANT_VIOLATION' | 'STORAGE_CORRUPTION_DETECTED';
20
20
  export type BlockerPointerV1 = {
@@ -66,6 +66,8 @@ export type ReasonV1 = {
66
66
  readonly kind: 'invariant_violation';
67
67
  } | {
68
68
  readonly kind: 'storage_corruption_detected';
69
+ } | {
70
+ readonly kind: 'evaluation_error';
69
71
  };
70
72
  export type ReasonModelError = {
71
73
  readonly code: 'INVALID_DELIMITER_SAFE_ID';
@@ -78,6 +78,12 @@ function reasonToGap(reason) {
78
78
  reason: { category: 'unexpected', detail: 'storage_corruption_detected' },
79
79
  summary: 'Storage corruption detected',
80
80
  };
81
+ case 'evaluation_error':
82
+ return {
83
+ severity: 'critical',
84
+ reason: { category: 'unexpected', detail: 'evaluation_error' },
85
+ summary: 'Validation evaluation failed',
86
+ };
81
87
  case 'missing_context_key':
82
88
  return {
83
89
  severity: 'critical',
@@ -152,7 +158,7 @@ function reasonToBlocker(reason) {
152
158
  code: 'MISSING_REQUIRED_OUTPUT',
153
159
  pointer: { kind: 'output_contract', contractRef },
154
160
  message: `Missing required output (contractRef=${contractRef}).`,
155
- suggestedFix: 'Provide output.notesMarkdown that satisfies the step output requirements.',
161
+ suggestedFix: 'Call continue_workflow WITHOUT ackToken to rehydrate and receive a fresh ackToken, then retry with output.notesMarkdown that satisfies the step output requirements.',
156
162
  }))
157
163
  .andThen(ensureBlockerTextBudgets);
158
164
  case 'invalid_required_output':
@@ -161,7 +167,7 @@ function reasonToBlocker(reason) {
161
167
  code: 'INVALID_REQUIRED_OUTPUT',
162
168
  pointer: { kind: 'output_contract', contractRef },
163
169
  message: `Invalid output for contractRef=${contractRef}.`,
164
- suggestedFix: 'Adjust output.notesMarkdown to satisfy validation and retry continue_workflow with the same ackToken.',
170
+ suggestedFix: 'Update output.notesMarkdown to satisfy validation. Then call continue_workflow WITHOUT ackToken (rehydrate) to receive a fresh ackToken, and retry advance with that new ackToken. Replaying the same ackToken is idempotent and will keep returning this blocked result.',
165
171
  }))
166
172
  .andThen(ensureBlockerTextBudgets);
167
173
  case 'required_capability_unknown':
@@ -201,6 +207,13 @@ function reasonToBlocker(reason) {
201
207
  message: 'Storage corruption detected: durable session data failed validation.',
202
208
  suggestedFix: 'Stop and investigate the session store; do not continue advancing this session.',
203
209
  });
210
+ case 'evaluation_error':
211
+ return ensureBlockerTextBudgets({
212
+ code: 'INVARIANT_VIOLATION',
213
+ pointer: { kind: 'context_budget' },
214
+ message: 'Validation evaluation failed: ValidationEngine encountered an error.',
215
+ suggestedFix: 'Check validation criteria for malformed rules or circular references.',
216
+ });
204
217
  default: {
205
218
  const _exhaustive = reason;
206
219
  return _exhaustive;
@@ -218,7 +231,7 @@ function buildBlockerReport(reasons) {
218
231
  return (0, neverthrow_1.err)(b.error);
219
232
  blockers.push(b.value);
220
233
  }
221
- blockers.sort((a, b) => blockerSortKey(a).localeCompare(blockerSortKey(b)));
234
+ blockers.sort((a, b) => blockerSortKey(a).localeCompare(blockerSortKey(b), 'en-US'));
222
235
  return (0, neverthrow_1.ok)({ blockers: blockers.slice(0, constants_js_1.MAX_BLOCKERS) });
223
236
  }
224
237
  function shouldBlock(autonomy, reasons) {
@@ -0,0 +1,20 @@
1
+ import type { AutonomyV2, RiskPolicyV2 } from '../schemas/session/preferences.js';
2
+ export interface RecommendedPreferencesV2 {
3
+ readonly recommendedAutonomy?: AutonomyV2;
4
+ readonly recommendedRiskPolicy?: RiskPolicyV2;
5
+ }
6
+ export type RecommendationWarning = {
7
+ readonly kind: 'autonomy_exceeds_recommendation';
8
+ readonly effective: AutonomyV2;
9
+ readonly recommended: AutonomyV2;
10
+ readonly summary: string;
11
+ } | {
12
+ readonly kind: 'risk_policy_exceeds_recommendation';
13
+ readonly effective: RiskPolicyV2;
14
+ readonly recommended: RiskPolicyV2;
15
+ readonly summary: string;
16
+ };
17
+ export declare function checkRecommendationExceedance(effective: {
18
+ readonly autonomy: AutonomyV2;
19
+ readonly riskPolicy: RiskPolicyV2;
20
+ }, recommended: RecommendedPreferencesV2): readonly RecommendationWarning[];
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkRecommendationExceedance = checkRecommendationExceedance;
4
+ const AUTONOMY_ORDER = {
5
+ guided: 0,
6
+ full_auto_stop_on_user_deps: 1,
7
+ full_auto_never_stop: 2,
8
+ };
9
+ const RISK_POLICY_ORDER = {
10
+ conservative: 0,
11
+ balanced: 1,
12
+ aggressive: 2,
13
+ };
14
+ function checkRecommendationExceedance(effective, recommended) {
15
+ const warnings = [];
16
+ if (recommended.recommendedAutonomy !== undefined &&
17
+ AUTONOMY_ORDER[effective.autonomy] > AUTONOMY_ORDER[recommended.recommendedAutonomy]) {
18
+ warnings.push({
19
+ kind: 'autonomy_exceeds_recommendation',
20
+ effective: effective.autonomy,
21
+ recommended: recommended.recommendedAutonomy,
22
+ summary: `Effective autonomy '${effective.autonomy}' exceeds workflow recommendation '${recommended.recommendedAutonomy}'.`,
23
+ });
24
+ }
25
+ if (recommended.recommendedRiskPolicy !== undefined &&
26
+ RISK_POLICY_ORDER[effective.riskPolicy] > RISK_POLICY_ORDER[recommended.recommendedRiskPolicy]) {
27
+ warnings.push({
28
+ kind: 'risk_policy_exceeds_recommendation',
29
+ effective: effective.riskPolicy,
30
+ recommended: recommended.recommendedRiskPolicy,
31
+ summary: `Effective riskPolicy '${effective.riskPolicy}' exceeds workflow recommendation '${recommended.recommendedRiskPolicy}'.`,
32
+ });
33
+ }
34
+ return warnings;
35
+ }
@@ -0,0 +1,15 @@
1
+ import type { RiskPolicyV2 } from '../schemas/session/preferences.js';
2
+ import type { ReasonV1 } from './reason-model.js';
3
+ export type GuardrailOutcome = {
4
+ readonly disposition: 'block';
5
+ readonly reason: ReasonV1;
6
+ } | {
7
+ readonly disposition: 'downgrade_to_warning';
8
+ readonly reason: ReasonV1;
9
+ readonly rationale: string;
10
+ };
11
+ export declare function applyGuardrail(policy: RiskPolicyV2, reason: ReasonV1): GuardrailOutcome;
12
+ export declare function applyGuardrails(policy: RiskPolicyV2, reasons: readonly ReasonV1[]): {
13
+ readonly blocking: readonly ReasonV1[];
14
+ readonly downgraded: readonly GuardrailOutcome[];
15
+ };
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyGuardrail = applyGuardrail;
4
+ exports.applyGuardrails = applyGuardrails;
5
+ function isNeverDowngradable(reason) {
6
+ switch (reason.kind) {
7
+ case 'missing_required_output':
8
+ case 'invalid_required_output':
9
+ return true;
10
+ case 'user_only_dependency':
11
+ return true;
12
+ case 'invariant_violation':
13
+ case 'storage_corruption_detected':
14
+ case 'evaluation_error':
15
+ return true;
16
+ case 'missing_context_key':
17
+ case 'context_budget_exceeded':
18
+ return true;
19
+ case 'required_capability_unknown':
20
+ case 'required_capability_unavailable':
21
+ return false;
22
+ default: {
23
+ const _exhaustive = reason;
24
+ return _exhaustive;
25
+ }
26
+ }
27
+ }
28
+ function applyGuardrail(policy, reason) {
29
+ if (isNeverDowngradable(reason)) {
30
+ return { disposition: 'block', reason };
31
+ }
32
+ switch (policy) {
33
+ case 'conservative':
34
+ return { disposition: 'block', reason };
35
+ case 'balanced':
36
+ if (reason.kind === 'required_capability_unknown') {
37
+ return {
38
+ disposition: 'downgrade_to_warning',
39
+ reason,
40
+ rationale: `riskPolicy=balanced: capability '${reason.capability}' status unknown — proceeding with warning`,
41
+ };
42
+ }
43
+ return { disposition: 'block', reason };
44
+ case 'aggressive':
45
+ if (reason.kind === 'required_capability_unknown' || reason.kind === 'required_capability_unavailable') {
46
+ return {
47
+ disposition: 'downgrade_to_warning',
48
+ reason,
49
+ rationale: `riskPolicy=aggressive: capability '${reason.capability}' issue downgraded to warning`,
50
+ };
51
+ }
52
+ return { disposition: 'block', reason };
53
+ default: {
54
+ const _exhaustive = policy;
55
+ return _exhaustive;
56
+ }
57
+ }
58
+ }
59
+ function applyGuardrails(policy, reasons) {
60
+ const blocking = [];
61
+ const downgraded = [];
62
+ for (const reason of reasons) {
63
+ const outcome = applyGuardrail(policy, reason);
64
+ switch (outcome.disposition) {
65
+ case 'block':
66
+ blocking.push(outcome.reason);
67
+ break;
68
+ case 'downgrade_to_warning':
69
+ downgraded.push(outcome);
70
+ break;
71
+ default: {
72
+ const _exhaustive = outcome;
73
+ return _exhaustive;
74
+ }
75
+ }
76
+ }
77
+ return { blocking, downgraded };
78
+ }
@@ -1,4 +1,5 @@
1
1
  import type { ValidationCriteria, ValidationResult } from '../../../types/validation.js';
2
+ import type { OutputContract } from '../../../types/workflow-definition.js';
2
3
  import type { OutputRequirementStatus } from './blocking-decision.js';
3
4
  export declare const VALIDATION_CRITERIA_CONTRACT_REF: "wr.validationCriteria";
4
5
  export declare function getOutputRequirementStatusV1(args: {
@@ -6,3 +7,10 @@ export declare function getOutputRequirementStatusV1(args: {
6
7
  readonly notesMarkdown: string | undefined;
7
8
  readonly validation: ValidationResult | undefined;
8
9
  }): OutputRequirementStatus;
10
+ export declare function getOutputRequirementStatusWithArtifactsV1(args: {
11
+ readonly outputContract: OutputContract | undefined;
12
+ readonly artifacts: readonly unknown[];
13
+ readonly validationCriteria: ValidationCriteria | undefined;
14
+ readonly notesMarkdown: string | undefined;
15
+ readonly validation: ValidationResult | undefined;
16
+ }): OutputRequirementStatus;