@exaudeus/workrail 3.10.0 → 3.11.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 (59) hide show
  1. package/dist/application/services/validation-engine.js +134 -0
  2. package/dist/application/services/workflow-compiler.js +54 -0
  3. package/dist/manifest.json +138 -74
  4. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.d.ts +14 -0
  5. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.js +27 -0
  6. package/dist/mcp/handlers/v2-advance-core/assessment-validation.d.ts +16 -0
  7. package/dist/mcp/handlers/v2-advance-core/assessment-validation.js +213 -0
  8. package/dist/mcp/handlers/v2-advance-core/event-builders.d.ts +1 -0
  9. package/dist/mcp/handlers/v2-advance-core/event-builders.js +3 -2
  10. package/dist/mcp/handlers/v2-advance-core/index.js +23 -8
  11. package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +9 -1
  12. package/dist/mcp/handlers/v2-advance-core/input-validation.js +22 -2
  13. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.d.ts +2 -0
  14. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +69 -19
  15. package/dist/mcp/handlers/v2-advance-core/outcome-success.js +22 -0
  16. package/dist/mcp/handlers/v2-execution/replay.js +7 -0
  17. package/dist/mcp/output-schemas.d.ts +156 -42
  18. package/dist/mcp/output-schemas.js +15 -0
  19. package/dist/mcp/v2-response-formatter.js +7 -1
  20. package/dist/types/workflow-definition.d.ts +26 -0
  21. package/dist/types/workflow-definition.js +16 -1
  22. package/dist/v2/durable-core/constants.d.ts +2 -0
  23. package/dist/v2/durable-core/constants.js +2 -0
  24. package/dist/v2/durable-core/domain/assessment-consequence-event-builder.d.ts +23 -0
  25. package/dist/v2/durable-core/domain/assessment-consequence-event-builder.js +36 -0
  26. package/dist/v2/durable-core/domain/assessment-record.d.ts +12 -0
  27. package/dist/v2/durable-core/domain/assessment-record.js +2 -0
  28. package/dist/v2/durable-core/domain/assessment-recorded-event-builder.d.ts +22 -0
  29. package/dist/v2/durable-core/domain/assessment-recorded-event-builder.js +38 -0
  30. package/dist/v2/durable-core/domain/blocked-node-builder.d.ts +1 -1
  31. package/dist/v2/durable-core/domain/blocked-node-builder.js +8 -0
  32. package/dist/v2/durable-core/domain/blocking-decision.d.ts +6 -0
  33. package/dist/v2/durable-core/domain/blocking-decision.js +15 -0
  34. package/dist/v2/durable-core/domain/prompt-renderer.js +25 -1
  35. package/dist/v2/durable-core/domain/reason-model.d.ts +12 -2
  36. package/dist/v2/durable-core/domain/reason-model.js +27 -2
  37. package/dist/v2/durable-core/domain/risk-policy-guardrails.js +1 -0
  38. package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +1 -0
  39. package/dist/v2/durable-core/domain/validation-criteria-validator.js +11 -0
  40. package/dist/v2/durable-core/schemas/artifacts/assessment.d.ts +55 -0
  41. package/dist/v2/durable-core/schemas/artifacts/assessment.js +29 -0
  42. package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
  43. package/dist/v2/durable-core/schemas/artifacts/index.js +8 -1
  44. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +24 -24
  45. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +141 -21
  46. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.js +10 -1
  47. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +729 -171
  48. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +1442 -202
  49. package/dist/v2/durable-core/schemas/session/events.d.ts +231 -8
  50. package/dist/v2/durable-core/schemas/session/events.js +36 -0
  51. package/dist/v2/durable-core/schemas/session/gaps.d.ts +2 -2
  52. package/dist/v2/projections/assessment-consequences.d.ts +19 -0
  53. package/dist/v2/projections/assessment-consequences.js +33 -0
  54. package/dist/v2/projections/assessments.d.ts +21 -0
  55. package/dist/v2/projections/assessments.js +35 -0
  56. package/package.json +1 -1
  57. package/spec/workflow.schema.json +110 -0
  58. package/workflows/bug-investigation.agentic.v2.json +28 -2
  59. package/workflows/test-artifact-loop-control.json +28 -2
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateAssessmentConsequences = evaluateAssessmentConsequences;
4
+ exports.getDeclaredAssessmentConsequence = getDeclaredAssessmentConsequence;
5
+ function evaluateAssessmentConsequences(args) {
6
+ if (!args.step?.assessmentConsequences || args.step.assessmentConsequences.length === 0)
7
+ return undefined;
8
+ if (!args.recordedAssessment)
9
+ return undefined;
10
+ const consequence = args.step.assessmentConsequences[0];
11
+ if (!consequence)
12
+ return undefined;
13
+ const matchedDimension = args.recordedAssessment.dimensions.find(dimension => dimension.dimensionId === consequence.when.dimensionId &&
14
+ dimension.level === consequence.when.equalsLevel);
15
+ if (!matchedDimension)
16
+ return undefined;
17
+ return {
18
+ kind: 'require_followup',
19
+ assessmentId: args.recordedAssessment.assessmentId,
20
+ triggerDimensionId: consequence.when.dimensionId,
21
+ triggerLevel: consequence.when.equalsLevel,
22
+ guidance: consequence.effect.guidance,
23
+ };
24
+ }
25
+ function getDeclaredAssessmentConsequence(step) {
26
+ return step?.assessmentConsequences?.[0];
27
+ }
@@ -0,0 +1,16 @@
1
+ import type { AssessmentDefinition, WorkflowStepDefinition } from '../../../types/workflow-definition.js';
2
+ import type { ValidationResult } from '../../../types/validation.js';
3
+ import { ASSESSMENT_CONTRACT_REF, type AssessmentArtifactV1 } from '../../../v2/durable-core/schemas/artifacts/index.js';
4
+ import type { RecordedAssessmentV1 } from '../../../v2/durable-core/domain/assessment-record.js';
5
+ export interface AssessmentValidationOutcome {
6
+ readonly contractRef: typeof ASSESSMENT_CONTRACT_REF;
7
+ readonly validation: ValidationResult;
8
+ readonly acceptedArtifact: AssessmentArtifactV1 | undefined;
9
+ readonly acceptedArtifactIndex: number | undefined;
10
+ readonly recordedAssessment: RecordedAssessmentV1 | undefined;
11
+ }
12
+ export declare function validateAssessmentForStep(args: {
13
+ readonly step: WorkflowStepDefinition;
14
+ readonly assessments: readonly AssessmentDefinition[] | undefined;
15
+ readonly artifacts: readonly unknown[];
16
+ }): AssessmentValidationOutcome | undefined;
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateAssessmentForStep = validateAssessmentForStep;
4
+ const index_js_1 = require("../../../v2/durable-core/schemas/artifacts/index.js");
5
+ function normalizeLevel(level, allowedLevels) {
6
+ const exact = allowedLevels.find((candidate) => candidate === level);
7
+ if (exact)
8
+ return { kind: 'exact', value: exact };
9
+ const normalizedInput = level.trim().toLowerCase();
10
+ const normalizedMatches = allowedLevels.filter((candidate) => candidate.toLowerCase() === normalizedInput);
11
+ if (normalizedMatches.length === 1) {
12
+ return {
13
+ kind: 'normalized',
14
+ value: normalizedMatches[0],
15
+ note: `Normalized level "${level}" to canonical value "${normalizedMatches[0]}".`,
16
+ };
17
+ }
18
+ if (normalizedMatches.length > 1) {
19
+ return {
20
+ kind: 'ambiguous',
21
+ message: `Level "${level}" is ambiguous for this dimension. Allowed levels: ${allowedLevels.join(', ')}.`,
22
+ };
23
+ }
24
+ return {
25
+ kind: 'invalid',
26
+ message: `Level "${level}" is not allowed. Allowed levels: ${allowedLevels.join(', ')}.`,
27
+ };
28
+ }
29
+ function extractSubmittedLevel(value) {
30
+ if (typeof value === 'string') {
31
+ return { level: value, hasRationale: false };
32
+ }
33
+ return { level: value.level, hasRationale: typeof value.rationale === 'string' && value.rationale.trim().length > 0 };
34
+ }
35
+ function extractSubmittedRationale(value) {
36
+ if (typeof value === 'string')
37
+ return undefined;
38
+ const trimmed = value.rationale?.trim();
39
+ return trimmed && trimmed.length > 0 ? trimmed : undefined;
40
+ }
41
+ function buildDefinitionLookup(assessments, step) {
42
+ const refs = step.assessmentRefs ?? [];
43
+ if (refs.length === 0) {
44
+ return {
45
+ definition: undefined,
46
+ issues: [],
47
+ suggestions: [],
48
+ };
49
+ }
50
+ if (!assessments || assessments.length === 0) {
51
+ return {
52
+ definition: undefined,
53
+ issues: [`Step "${step.id}" expects assessment input, but the workflow declares no assessments.`],
54
+ suggestions: ['Update the workflow definition to declare the assessments referenced by this step.'],
55
+ };
56
+ }
57
+ if (refs.length > 1) {
58
+ return {
59
+ definition: undefined,
60
+ issues: [`Step "${step.id}" declares multiple assessmentRefs. Assessment boundary validation currently supports exactly one assessment per step.`],
61
+ suggestions: ['Reduce assessmentRefs to a single assessment for this step in v1.'],
62
+ };
63
+ }
64
+ const definition = assessments.find((assessment) => assessment.id === refs[0]);
65
+ if (!definition) {
66
+ return {
67
+ definition: undefined,
68
+ issues: [`Step "${step.id}" references undeclared assessment "${refs[0]}".`],
69
+ suggestions: [`Declare assessment "${refs[0]}" on the workflow or remove the step reference.`],
70
+ };
71
+ }
72
+ return { definition, issues: [], suggestions: [] };
73
+ }
74
+ function validateDimension(dimension, artifact, issues, suggestions, warnings, recordedDimensions) {
75
+ const submitted = artifact.dimensions[dimension.id];
76
+ if (submitted === undefined) {
77
+ if (dimension.required !== false) {
78
+ issues.push(`Missing assessment dimension "${dimension.id}".`);
79
+ suggestions.push(`Provide a value for "${dimension.id}". Allowed levels: ${dimension.levels.join(', ')}.`);
80
+ }
81
+ return;
82
+ }
83
+ const { level } = extractSubmittedLevel(submitted);
84
+ const normalized = normalizeLevel(level, dimension.levels);
85
+ switch (normalized.kind) {
86
+ case 'exact':
87
+ recordedDimensions.push({
88
+ dimensionId: dimension.id,
89
+ level: normalized.value,
90
+ rationale: extractSubmittedRationale(submitted),
91
+ normalization: 'exact',
92
+ });
93
+ return;
94
+ case 'normalized':
95
+ warnings.push(normalized.note);
96
+ recordedDimensions.push({
97
+ dimensionId: dimension.id,
98
+ level: normalized.value,
99
+ rationale: extractSubmittedRationale(submitted),
100
+ normalization: 'normalized',
101
+ });
102
+ return;
103
+ case 'ambiguous':
104
+ case 'invalid':
105
+ issues.push(`Dimension "${dimension.id}": ${normalized.message}`);
106
+ suggestions.push(`Use one of the canonical levels for "${dimension.id}": ${dimension.levels.join(', ')}.`);
107
+ return;
108
+ default: {
109
+ const _exhaustive = normalized;
110
+ return _exhaustive;
111
+ }
112
+ }
113
+ }
114
+ function validateAssessmentForStep(args) {
115
+ if (!args.step.assessmentRefs || args.step.assessmentRefs.length === 0)
116
+ return undefined;
117
+ const lookup = buildDefinitionLookup(args.assessments, args.step);
118
+ if (!lookup.definition) {
119
+ return {
120
+ contractRef: index_js_1.ASSESSMENT_CONTRACT_REF,
121
+ acceptedArtifact: undefined,
122
+ acceptedArtifactIndex: undefined,
123
+ recordedAssessment: undefined,
124
+ validation: {
125
+ valid: false,
126
+ issues: [...lookup.issues],
127
+ suggestions: [...lookup.suggestions],
128
+ },
129
+ };
130
+ }
131
+ const assessmentArtifacts = args.artifacts
132
+ .map((artifact, index) => ({ artifact, index }))
133
+ .filter(({ artifact }) => typeof artifact === 'object' && artifact !== null && artifact.kind === 'wr.assessment');
134
+ if (assessmentArtifacts.length === 0) {
135
+ return {
136
+ contractRef: index_js_1.ASSESSMENT_CONTRACT_REF,
137
+ acceptedArtifact: undefined,
138
+ acceptedArtifactIndex: undefined,
139
+ recordedAssessment: undefined,
140
+ validation: {
141
+ valid: false,
142
+ issues: [`This step requires an assessment submission for "${lookup.definition.id}".`],
143
+ suggestions: [
144
+ `Provide an artifact with kind "wr.assessment" for assessment "${lookup.definition.id}".`,
145
+ `Include dimension values for: ${lookup.definition.dimensions.map((dimension) => `${dimension.id} (${dimension.levels.join(' | ')})`).join(', ')}.`,
146
+ ],
147
+ },
148
+ };
149
+ }
150
+ const acceptedCandidate = assessmentArtifacts[0];
151
+ const parsed = (0, index_js_1.parseAssessmentArtifact)(acceptedCandidate.artifact);
152
+ if (!parsed) {
153
+ return {
154
+ contractRef: index_js_1.ASSESSMENT_CONTRACT_REF,
155
+ acceptedArtifact: undefined,
156
+ acceptedArtifactIndex: undefined,
157
+ recordedAssessment: undefined,
158
+ validation: {
159
+ valid: false,
160
+ issues: ['Assessment artifact is malformed or does not match the expected shape.'],
161
+ suggestions: [
162
+ `Use an artifact with kind "wr.assessment", a dimensions object, and canonical dimension values for assessment "${lookup.definition.id}".`,
163
+ ],
164
+ },
165
+ };
166
+ }
167
+ if (parsed.assessmentId && parsed.assessmentId !== lookup.definition.id) {
168
+ return {
169
+ contractRef: index_js_1.ASSESSMENT_CONTRACT_REF,
170
+ acceptedArtifact: undefined,
171
+ acceptedArtifactIndex: undefined,
172
+ recordedAssessment: undefined,
173
+ validation: {
174
+ valid: false,
175
+ issues: [`Assessment artifact targets "${parsed.assessmentId}", but this step expects "${lookup.definition.id}".`],
176
+ suggestions: [`Set assessmentId to "${lookup.definition.id}" or omit it and provide the correct dimensions.`],
177
+ },
178
+ };
179
+ }
180
+ const issues = [];
181
+ const suggestions = [];
182
+ const warnings = [];
183
+ const recordedDimensions = [];
184
+ for (const dimension of lookup.definition.dimensions) {
185
+ validateDimension(dimension, parsed, issues, suggestions, warnings, recordedDimensions);
186
+ }
187
+ const allowedDimensionIds = new Set(lookup.definition.dimensions.map((dimension) => dimension.id));
188
+ for (const submittedDimensionId of Object.keys(parsed.dimensions)) {
189
+ if (!allowedDimensionIds.has(submittedDimensionId)) {
190
+ issues.push(`Unknown assessment dimension "${submittedDimensionId}" for assessment "${lookup.definition.id}".`);
191
+ suggestions.push(`Remove "${submittedDimensionId}" and use only: ${lookup.definition.dimensions.map((dimension) => dimension.id).join(', ')}.`);
192
+ }
193
+ }
194
+ return {
195
+ contractRef: index_js_1.ASSESSMENT_CONTRACT_REF,
196
+ acceptedArtifact: issues.length === 0 ? parsed : undefined,
197
+ acceptedArtifactIndex: issues.length === 0 ? acceptedCandidate.index : undefined,
198
+ recordedAssessment: issues.length === 0
199
+ ? {
200
+ assessmentId: lookup.definition.id,
201
+ summary: parsed.summary,
202
+ dimensions: recordedDimensions,
203
+ normalizationNotes: warnings,
204
+ }
205
+ : undefined,
206
+ validation: {
207
+ valid: issues.length === 0,
208
+ issues,
209
+ suggestions,
210
+ warnings: warnings.length > 0 ? warnings : undefined,
211
+ },
212
+ };
213
+ }
@@ -25,6 +25,7 @@ type BuildAppendPlanArgs = {
25
25
  readonly kind: 'blocked';
26
26
  readonly blockers: import('../../../v2/durable-core/domain/reason-model.js').BlockerReportV1;
27
27
  readonly snapshotRef: import('../../../v2/durable-core/ids/index.js').SnapshotRef;
28
+ readonly outputsToAppend?: readonly OutputToAppend[];
28
29
  } | {
29
30
  readonly kind: 'advanced';
30
31
  readonly toNodeKind: 'step' | 'blocked_attempt' | undefined;
@@ -18,6 +18,7 @@ function buildAndAppendPlan(args) {
18
18
  const toNodeId = String(idFactory.mintNodeId());
19
19
  const evtNodeCreated = idFactory.mintEventId();
20
20
  const evtEdgeCreated = idFactory.mintEventId();
21
+ const outputEventIds = (args.outputsToAppend ?? []).map(() => idFactory.mintEventId());
21
22
  const hasChildren = truth.events.some((e) => e.kind === constants_js_1.EVENT_KIND.EDGE_CREATED && e.data.fromNodeId === String(currentNodeId));
22
23
  const causeKind = hasChildren ? constants_js_1.EDGE_CAUSE.NON_TIP_ADVANCE : constants_js_1.EDGE_CAUSE.INTENTIONAL_FORK;
23
24
  const planRes = (0, ack_advance_append_plan_js_1.buildAckAdvanceAppendPlanV1)({
@@ -37,9 +38,9 @@ function buildAndAppendPlan(args) {
37
38
  advanceRecordedEventId: evtAdvanceRecorded,
38
39
  nodeCreatedEventId: evtNodeCreated,
39
40
  edgeCreatedEventId: evtEdgeCreated,
40
- outputEventIds: [],
41
+ outputEventIds,
41
42
  },
42
- outputsToAppend: [],
43
+ outputsToAppend: args.outputsToAppend ?? [],
43
44
  });
44
45
  if (planRes.isErr())
45
46
  return (0, neverthrow_1.errAsync)({ kind: 'invariant_violation', message: planRes.error.message });
@@ -71,31 +71,46 @@ function executeAdvanceCore(args) {
71
71
  const ctx = { truth, sessionId, runId, currentNodeId, attemptId, workflowHash, inputOutput, pinnedWorkflow, engineState, pendingStep };
72
72
  const computed = { reasons: effectiveReasons, outputRequirement, validation: evalValidation };
73
73
  const portsLocal = { snapshotStore, sessionStore, sha256, idFactory };
74
- return (0, outcome_blocked_js_1.buildBlockedOutcome)({ mode, snap, ctx, computed, lock, ports: portsLocal });
74
+ return (0, outcome_blocked_js_1.buildBlockedOutcome)({ mode, snap, ctx, computed, v, lock, ports: portsLocal });
75
75
  }
76
76
  const validation = phase.validation;
77
+ const effectiveValidation = v.assessmentValidation && !v.assessmentValidation.validation.valid
78
+ ? v.assessmentValidation.validation
79
+ : validation;
77
80
  const outputRequirement = (0, validation_criteria_validator_js_1.getOutputRequirementStatusWithArtifactsV1)({
78
81
  outputContract: v.outputContract,
79
82
  artifacts: v.artifacts,
80
83
  validationCriteria: v.validationCriteria,
84
+ assessmentValidation: v.assessmentValidation?.validation,
81
85
  notesMarkdown: v.notesMarkdown,
82
- validation,
86
+ validation: effectiveValidation,
83
87
  });
84
88
  const missingNotes = !v.notesOptional && !v.notesMarkdown?.trim()
85
89
  ? { stepId: v.pendingStep.stepId }
86
90
  : undefined;
87
- const rawReasonsRes = (0, blocking_decision_js_1.detectBlockingReasonsV1)({ outputRequirement, missingNotes });
88
- if (rawReasonsRes.isErr()) {
89
- return errAsync({ kind: 'invariant_violation', message: rawReasonsRes.error.message });
91
+ const rawReasonsWithAssessmentRes = (0, blocking_decision_js_1.detectBlockingReasonsV1)({
92
+ outputRequirement,
93
+ missingNotes,
94
+ assessmentFollowupRequired: v.triggeredAssessmentConsequence
95
+ ? {
96
+ assessmentId: v.triggeredAssessmentConsequence.assessmentId,
97
+ dimensionId: v.triggeredAssessmentConsequence.triggerDimensionId,
98
+ level: v.triggeredAssessmentConsequence.triggerLevel,
99
+ guidance: v.triggeredAssessmentConsequence.guidance,
100
+ }
101
+ : undefined,
102
+ });
103
+ if (rawReasonsWithAssessmentRes.isErr()) {
104
+ return errAsync({ kind: 'invariant_violation', message: rawReasonsWithAssessmentRes.error.message });
90
105
  }
91
- const rawReasons = rawReasonsRes.value;
106
+ const rawReasons = rawReasonsWithAssessmentRes.value;
92
107
  const { blocking: reasons } = (0, risk_policy_guardrails_js_1.applyGuardrails)(v.riskPolicy, rawReasons);
93
108
  const shouldBlockNow = reasons.length > 0 && (0, reason_model_js_1.shouldBlock)(v.autonomy, reasons);
94
109
  const ctx = { truth, sessionId, runId, currentNodeId, attemptId, workflowHash, inputOutput, pinnedWorkflow, engineState, pendingStep };
95
- const computed = { reasons, outputRequirement, validation };
110
+ const computed = { reasons, outputRequirement, validation: effectiveValidation };
96
111
  const ports = { snapshotStore, sessionStore, sha256, idFactory };
97
112
  if (shouldBlockNow) {
98
- return (0, outcome_blocked_js_1.buildBlockedOutcome)({ mode, snap, ctx, computed, lock, ports });
113
+ return (0, outcome_blocked_js_1.buildBlockedOutcome)({ mode, snap, ctx, computed, v, lock, ports });
99
114
  }
100
115
  return (0, outcome_success_js_1.buildSuccessOutcome)({ mode, ctx, computed, v, lock, ports });
101
116
  });
@@ -3,8 +3,11 @@ import type { RunId, NodeId } from '../../../v2/durable-core/ids/index.js';
3
3
  import type { LoadedSessionTruthV2 } from '../../../v2/ports/session-event-log-store.port.js';
4
4
  import type { JsonValue, JsonObject } from '../../../v2/durable-core/canonical/json-types.js';
5
5
  import type { V2ContinueWorkflowInput } from '../../v2/tools.js';
6
- import type { OutputContract } from '../../../types/workflow-definition.js';
6
+ import type { AssessmentDefinition, OutputContract } from '../../../types/workflow-definition.js';
7
7
  import type { ValidationCriteria } from '../../../types/validation.js';
8
+ import type { AssessmentArtifactV1 } from '../../../v2/durable-core/schemas/artifacts/index.js';
9
+ import type { RecordedAssessmentV1 } from '../../../v2/durable-core/domain/assessment-record.js';
10
+ import type { TriggeredAssessmentConsequenceV1 } from './assessment-consequences.js';
8
11
  import type { InternalError } from '../v2-error-mapping.js';
9
12
  export interface ValidatedAdvanceInputs {
10
13
  readonly pendingStep: {
@@ -17,9 +20,14 @@ export interface ValidatedAdvanceInputs {
17
20
  readonly mergedContext: Record<string, unknown>;
18
21
  readonly inputContextObj: JsonObject | undefined;
19
22
  readonly validationCriteria: ValidationCriteria | undefined;
23
+ readonly assessmentValidation: import('./assessment-validation.js').AssessmentValidationOutcome | undefined;
20
24
  readonly outputContract: OutputContract | undefined;
21
25
  readonly notesMarkdown: string | undefined;
22
26
  readonly artifacts: readonly unknown[];
27
+ readonly assessmentArtifact: AssessmentArtifactV1 | undefined;
28
+ readonly recordedAssessment: RecordedAssessmentV1 | undefined;
29
+ readonly triggeredAssessmentConsequence: TriggeredAssessmentConsequenceV1 | undefined;
30
+ readonly stepAssessments: readonly AssessmentDefinition[];
23
31
  readonly autonomy: 'guided' | 'full_auto_stop_on_user_deps' | 'full_auto_never_stop';
24
32
  readonly riskPolicy: 'conservative' | 'balanced' | 'aggressive';
25
33
  readonly effectivePrefs: {
@@ -7,6 +7,8 @@ const run_context_js_1 = require("../../../v2/projections/run-context.js");
7
7
  const preferences_js_1 = require("../../../v2/projections/preferences.js");
8
8
  const context_merge_js_1 = require("../../../v2/durable-core/domain/context-merge.js");
9
9
  const constants_js_1 = require("../../../v2/durable-core/constants.js");
10
+ const assessment_validation_js_1 = require("./assessment-validation.js");
11
+ const assessment_consequences_js_1 = require("./assessment-consequences.js");
10
12
  function validateAdvanceInputs(args) {
11
13
  const { truth, runId, currentNodeId, inputContext, inputOutput, pinnedWorkflow, pendingStep } = args;
12
14
  const storedContextRes = (0, run_context_js_1.projectRunContextV2)(truth.events);
@@ -19,8 +21,21 @@ function validateAdvanceInputs(args) {
19
21
  return (0, neverthrow_1.err)({ kind: 'invariant_violation', message: `Context merge failed: ${mergedContextRes.error.message}` });
20
22
  }
21
23
  const step = (0, workflow_js_1.getStepById)(pinnedWorkflow, pendingStep.stepId);
22
- const validationCriteria = step?.validationCriteria;
23
- const outputContract = step?.outputContract;
24
+ const typedStep = step && !('type' in step && step.type === 'loop') ? step : undefined;
25
+ const validationCriteria = typedStep?.validationCriteria;
26
+ const outputContract = typedStep?.outputContract;
27
+ const stepAssessments = (pinnedWorkflow.definition.assessments ?? []).filter((assessment) => typedStep?.assessmentRefs?.includes(assessment.id));
28
+ const assessmentValidation = typedStep
29
+ ? (0, assessment_validation_js_1.validateAssessmentForStep)({
30
+ step: typedStep,
31
+ assessments: pinnedWorkflow.definition.assessments,
32
+ artifacts: inputOutput?.artifacts ?? [],
33
+ })
34
+ : undefined;
35
+ const triggeredAssessmentConsequence = (0, assessment_consequences_js_1.evaluateAssessmentConsequences)({
36
+ step: typedStep,
37
+ recordedAssessment: assessmentValidation?.recordedAssessment,
38
+ });
24
39
  const notesOptional = outputContract !== undefined ||
25
40
  (step !== null && step !== undefined && 'notesOptional' in step && step.notesOptional === true);
26
41
  const parentByNodeId = {};
@@ -50,9 +65,14 @@ function validateAdvanceInputs(args) {
50
65
  mergedContext: mergedContextRes.value,
51
66
  inputContextObj,
52
67
  validationCriteria,
68
+ assessmentValidation,
53
69
  outputContract,
54
70
  notesMarkdown: inputOutput?.notesMarkdown,
55
71
  artifacts: inputOutput?.artifacts ?? [],
72
+ assessmentArtifact: assessmentValidation?.acceptedArtifact,
73
+ recordedAssessment: assessmentValidation?.recordedAssessment,
74
+ triggeredAssessmentConsequence,
75
+ stepAssessments,
56
76
  autonomy,
57
77
  riskPolicy,
58
78
  effectivePrefs,
@@ -6,11 +6,13 @@ import type { WithHealthySessionLock } from '../../../v2/durable-core/ids/with-h
6
6
  import type { InternalError } from '../v2-error-mapping.js';
7
7
  import type { AdvanceMode } from './index.js';
8
8
  import type { AdvanceContext, ComputedAdvanceResults, AdvanceCorePorts } from './index.js';
9
+ import type { ValidatedAdvanceInputs } from './input-validation.js';
9
10
  export declare function buildBlockedOutcome(args: {
10
11
  readonly mode: AdvanceMode;
11
12
  readonly snap: ExecutionSnapshotFileV1;
12
13
  readonly ctx: AdvanceContext;
13
14
  readonly computed: ComputedAdvanceResults;
15
+ readonly v: ValidatedAdvanceInputs;
14
16
  readonly lock: WithHealthySessionLock;
15
17
  readonly ports: AdvanceCorePorts;
16
18
  }): RA<void, InternalError | SessionEventLogStoreError | SnapshotStoreError>;
@@ -5,6 +5,8 @@ const neverthrow_1 = require("neverthrow");
5
5
  const reason_model_js_1 = require("../../../v2/durable-core/domain/reason-model.js");
6
6
  const validation_event_builder_js_1 = require("../../../v2/durable-core/domain/validation-event-builder.js");
7
7
  const blocked_node_builder_js_1 = require("../../../v2/durable-core/domain/blocked-node-builder.js");
8
+ const assessment_recorded_event_builder_js_1 = require("../../../v2/durable-core/domain/assessment-recorded-event-builder.js");
9
+ const assessment_consequence_event_builder_js_1 = require("../../../v2/durable-core/domain/assessment-consequence-event-builder.js");
8
10
  const event_builders_js_1 = require("./event-builders.js");
9
11
  function buildBlockedOutcome(args) {
10
12
  const { mode, snap, lock, ports } = args;
@@ -15,26 +17,23 @@ function buildBlockedOutcome(args) {
15
17
  if (blockersRes.isErr()) {
16
18
  return errAsync({ kind: 'invariant_violation', message: blockersRes.error.message });
17
19
  }
18
- const validationEventId = idFactory.mintEventId();
19
- const validationId = `validation_${String(attemptId)}`;
20
- const contractRefForEvent = (outputRequirement.kind !== 'not_required' && outputRequirement.kind !== 'satisfied') ? outputRequirement.contractRef : 'none';
21
- const validationForEvent = validation ??
22
- (outputRequirement.kind === 'missing'
23
- ? { valid: false, issues: [`Missing required output for contractRef=${contractRefForEvent}`], suggestions: [], warnings: undefined }
24
- : { valid: false, issues: ['Validation result missing'], suggestions: [], warnings: undefined });
25
- const validationEventRes = (0, validation_event_builder_js_1.buildValidationPerformedEvent)({
26
- sessionId: String(sessionId),
27
- validationId,
28
- attemptId: String(attemptId),
29
- contractRef: contractRefForEvent,
30
- scope: { runId: String(runId), nodeId: String(currentNodeId) },
31
- minted: { eventId: validationEventId },
32
- result: validationForEvent,
33
- });
34
- if (validationEventRes.isErr()) {
35
- return errAsync({ kind: 'invariant_violation', message: validationEventRes.error.message });
20
+ const validationId = validation ? `validation_${String(attemptId)}` : undefined;
21
+ const extraEventsToAppend = [];
22
+ if (validation && outputRequirement.kind !== 'not_required' && outputRequirement.kind !== 'satisfied') {
23
+ const validationEventRes = (0, validation_event_builder_js_1.buildValidationPerformedEvent)({
24
+ sessionId: String(sessionId),
25
+ validationId: validationId,
26
+ attemptId: String(attemptId),
27
+ contractRef: outputRequirement.contractRef,
28
+ scope: { runId: String(runId), nodeId: String(currentNodeId) },
29
+ minted: { eventId: idFactory.mintEventId() },
30
+ result: validation,
31
+ });
32
+ if (validationEventRes.isErr()) {
33
+ return errAsync({ kind: 'invariant_violation', message: validationEventRes.error.message });
34
+ }
35
+ extraEventsToAppend.push(validationEventRes.value);
36
36
  }
37
- const extraEventsToAppend = [validationEventRes.value];
38
37
  const primaryReason = reasons[0];
39
38
  if (!primaryReason) {
40
39
  return errAsync({ kind: 'invariant_violation', message: 'shouldBlockNow=true requires at least one effective reason (post-guardrails)' });
@@ -50,11 +49,62 @@ function buildBlockedOutcome(args) {
50
49
  if (blockedSnapshotRes.isErr()) {
51
50
  return errAsync({ kind: 'invariant_violation', message: blockedSnapshotRes.error.message });
52
51
  }
52
+ const outputAppendResult = args.ctx.inputOutput?.artifacts
53
+ ? (() => {
54
+ const outputsRes = (0, event_builders_js_1.buildArtifactOutputs)(args.ctx.inputOutput.artifacts, attemptId, sha256);
55
+ if (outputsRes.isErr()) {
56
+ return outputsRes;
57
+ }
58
+ return outputsRes;
59
+ })()
60
+ : undefined;
61
+ if (outputAppendResult?.isErr()) {
62
+ return errAsync(outputAppendResult.error);
63
+ }
64
+ const outputsToAppend = outputAppendResult && outputAppendResult.isOk()
65
+ ? outputAppendResult.value
66
+ : [];
67
+ const validated = args.v;
68
+ if (validated?.recordedAssessment && validated.assessmentValidation?.acceptedArtifactIndex !== undefined) {
69
+ const assessmentOutput = outputsToAppend[validated.assessmentValidation.acceptedArtifactIndex];
70
+ if (!assessmentOutput || assessmentOutput.outputChannel !== 'artifact') {
71
+ return errAsync({ kind: 'invariant_violation', message: 'Accepted assessment artifact did not produce a matching artifact output on blocked path.' });
72
+ }
73
+ const assessmentEventRes = (0, assessment_recorded_event_builder_js_1.buildAssessmentRecordedEvent)({
74
+ sessionId: String(sessionId),
75
+ attemptId: String(attemptId),
76
+ artifactOutputId: String(assessmentOutput.outputId),
77
+ scope: { runId: String(runId), nodeId: String(currentNodeId) },
78
+ assessment: validated.recordedAssessment,
79
+ minted: { eventId: idFactory.mintEventId() },
80
+ });
81
+ if (assessmentEventRes.isErr()) {
82
+ return errAsync({ kind: 'invariant_violation', message: assessmentEventRes.error.message });
83
+ }
84
+ extraEventsToAppend.push(assessmentEventRes.value);
85
+ }
86
+ if (validated?.triggeredAssessmentConsequence) {
87
+ const consequenceEventRes = (0, assessment_consequence_event_builder_js_1.buildAssessmentConsequenceAppliedEvent)({
88
+ sessionId: String(sessionId),
89
+ attemptId: String(attemptId),
90
+ scope: { runId: String(runId), nodeId: String(currentNodeId) },
91
+ assessmentId: validated.triggeredAssessmentConsequence.assessmentId,
92
+ dimensionId: validated.triggeredAssessmentConsequence.triggerDimensionId,
93
+ level: validated.triggeredAssessmentConsequence.triggerLevel,
94
+ guidance: validated.triggeredAssessmentConsequence.guidance,
95
+ minted: { eventId: idFactory.mintEventId() },
96
+ });
97
+ if (consequenceEventRes.isErr()) {
98
+ return errAsync({ kind: 'invariant_violation', message: consequenceEventRes.error.message });
99
+ }
100
+ extraEventsToAppend.push(consequenceEventRes.value);
101
+ }
53
102
  return snapshotStore.putExecutionSnapshotV1(blockedSnapshotRes.value).andThen((blockedSnapshotRef) => {
54
103
  return (0, event_builders_js_1.buildAndAppendPlan)({
55
104
  kind: 'blocked',
56
105
  truth, sessionId, runId, currentNodeId, attemptId, workflowHash,
57
106
  extraEventsToAppend, blockers: blockersRes.value, snapshotRef: blockedSnapshotRef,
107
+ outputsToAppend,
58
108
  sessionStore, idFactory, lock,
59
109
  });
60
110
  });
@@ -9,6 +9,7 @@ const v2_state_conversion_js_1 = require("../v2-state-conversion.js");
9
9
  const v2_context_budget_js_1 = require("../v2-context-budget.js");
10
10
  const v2_advance_events_js_1 = require("../v2-advance-events.js");
11
11
  const event_builders_js_1 = require("./event-builders.js");
12
+ const assessment_recorded_event_builder_js_1 = require("../../../v2/durable-core/domain/assessment-recorded-event-builder.js");
12
13
  function successNodeKind(mode) {
13
14
  switch (mode.kind) {
14
15
  case 'fresh': return undefined;
@@ -124,6 +125,27 @@ function buildSuccessOutcome(args) {
124
125
  if (artifactOutputsRes.isErr()) {
125
126
  return errAsync(artifactOutputsRes.error);
126
127
  }
128
+ if (v.recordedAssessment && v.assessmentArtifact && v.assessmentValidation?.acceptedArtifactIndex !== undefined) {
129
+ const assessmentOutput = artifactOutputsRes.value[v.assessmentValidation.acceptedArtifactIndex];
130
+ if (!assessmentOutput || assessmentOutput.outputChannel !== 'artifact') {
131
+ return errAsync({
132
+ kind: 'invariant_violation',
133
+ message: 'Accepted assessment artifact did not produce a matching artifact output.',
134
+ });
135
+ }
136
+ const assessmentEventRes = (0, assessment_recorded_event_builder_js_1.buildAssessmentRecordedEvent)({
137
+ sessionId: String(sessionId),
138
+ attemptId: String(attemptId),
139
+ artifactOutputId: String(assessmentOutput.outputId),
140
+ scope: { runId: String(runId), nodeId: String(currentNodeId) },
141
+ assessment: v.recordedAssessment,
142
+ minted: { eventId: idFactory.mintEventId() },
143
+ });
144
+ if (assessmentEventRes.isErr()) {
145
+ return errAsync({ kind: 'invariant_violation', message: assessmentEventRes.error.message });
146
+ }
147
+ extraEventsToAppend.push(assessmentEventRes.value);
148
+ }
127
149
  const outputsToAppend = [...notesOutputs, ...artifactOutputsRes.value];
128
150
  return (0, event_builders_js_1.buildAndAppendPlan)({
129
151
  kind: 'advanced',
@@ -58,6 +58,12 @@ function buildAdvancedReplayResponse(args) {
58
58
  .mapErr((failure) => ({ kind: 'token_signing_failed', cause: failure }))
59
59
  : (0, neverthrow_1.okAsync)(undefined);
60
60
  const validation = (0, validation_loader_js_1.loadValidationResultV1)(truth.events, String(blocked.validationRef)).unwrapOr(null) ?? undefined;
61
+ const assessmentFollowup = blocked.reason.kind === 'assessment_followup_required'
62
+ ? {
63
+ title: `Assessment follow-up matched ${blocked.reason.assessmentId}.${blocked.reason.dimensionId} == ${blocked.reason.level}`,
64
+ guidance: blocked.reason.guidance,
65
+ }
66
+ : undefined;
61
67
  let blockedMeta = null;
62
68
  if (pending) {
63
69
  const result = (0, prompt_renderer_js_1.renderPendingPrompt)({
@@ -89,6 +95,7 @@ function buildAdvancedReplayResponse(args) {
89
95
  retryable,
90
96
  retryContinueToken,
91
97
  validation,
98
+ assessmentFollowup,
92
99
  }))));
93
100
  }
94
101
  let okMeta = null;