@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.
- package/dist/application/services/validation-engine.js +134 -0
- package/dist/application/services/workflow-compiler.js +54 -0
- package/dist/manifest.json +138 -74
- package/dist/mcp/handlers/v2-advance-core/assessment-consequences.d.ts +14 -0
- package/dist/mcp/handlers/v2-advance-core/assessment-consequences.js +27 -0
- package/dist/mcp/handlers/v2-advance-core/assessment-validation.d.ts +16 -0
- package/dist/mcp/handlers/v2-advance-core/assessment-validation.js +213 -0
- package/dist/mcp/handlers/v2-advance-core/event-builders.d.ts +1 -0
- package/dist/mcp/handlers/v2-advance-core/event-builders.js +3 -2
- package/dist/mcp/handlers/v2-advance-core/index.js +23 -8
- package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +9 -1
- package/dist/mcp/handlers/v2-advance-core/input-validation.js +22 -2
- package/dist/mcp/handlers/v2-advance-core/outcome-blocked.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +69 -19
- package/dist/mcp/handlers/v2-advance-core/outcome-success.js +22 -0
- package/dist/mcp/handlers/v2-execution/replay.js +7 -0
- package/dist/mcp/output-schemas.d.ts +156 -42
- package/dist/mcp/output-schemas.js +15 -0
- package/dist/mcp/v2-response-formatter.js +7 -1
- package/dist/types/workflow-definition.d.ts +26 -0
- package/dist/types/workflow-definition.js +16 -1
- package/dist/v2/durable-core/constants.d.ts +2 -0
- package/dist/v2/durable-core/constants.js +2 -0
- package/dist/v2/durable-core/domain/assessment-consequence-event-builder.d.ts +23 -0
- package/dist/v2/durable-core/domain/assessment-consequence-event-builder.js +36 -0
- package/dist/v2/durable-core/domain/assessment-record.d.ts +12 -0
- package/dist/v2/durable-core/domain/assessment-record.js +2 -0
- package/dist/v2/durable-core/domain/assessment-recorded-event-builder.d.ts +22 -0
- package/dist/v2/durable-core/domain/assessment-recorded-event-builder.js +38 -0
- package/dist/v2/durable-core/domain/blocked-node-builder.d.ts +1 -1
- package/dist/v2/durable-core/domain/blocked-node-builder.js +8 -0
- package/dist/v2/durable-core/domain/blocking-decision.d.ts +6 -0
- package/dist/v2/durable-core/domain/blocking-decision.js +15 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +25 -1
- package/dist/v2/durable-core/domain/reason-model.d.ts +12 -2
- package/dist/v2/durable-core/domain/reason-model.js +27 -2
- package/dist/v2/durable-core/domain/risk-policy-guardrails.js +1 -0
- package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +1 -0
- package/dist/v2/durable-core/domain/validation-criteria-validator.js +11 -0
- package/dist/v2/durable-core/schemas/artifacts/assessment.d.ts +55 -0
- package/dist/v2/durable-core/schemas/artifacts/assessment.js +29 -0
- package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
- package/dist/v2/durable-core/schemas/artifacts/index.js +8 -1
- package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +24 -24
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +141 -21
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.js +10 -1
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +729 -171
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +1442 -202
- package/dist/v2/durable-core/schemas/session/events.d.ts +231 -8
- package/dist/v2/durable-core/schemas/session/events.js +36 -0
- package/dist/v2/durable-core/schemas/session/gaps.d.ts +2 -2
- package/dist/v2/projections/assessment-consequences.d.ts +19 -0
- package/dist/v2/projections/assessment-consequences.js +33 -0
- package/dist/v2/projections/assessments.d.ts +21 -0
- package/dist/v2/projections/assessments.js +35 -0
- package/package.json +1 -1
- package/spec/workflow.schema.json +110 -0
- package/workflows/bug-investigation.agentic.v2.json +28 -2
- 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
|
|
88
|
-
|
|
89
|
-
|
|
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 =
|
|
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
|
|
23
|
-
const
|
|
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
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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;
|