@exaudeus/workrail 3.16.0 → 3.18.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 +7 -11
- package/dist/application/services/workflow-compiler.js +9 -11
- package/dist/application/use-cases/raw-workflow-file-scanner.js +10 -13
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +2 -1
- package/dist/cli/commands/init.d.ts +10 -0
- package/dist/cli/commands/init.js +72 -0
- package/dist/cli.js +13 -1
- package/dist/config/config-file.d.ts +8 -0
- package/dist/config/config-file.js +141 -0
- package/dist/config/feature-flags.js +8 -0
- package/dist/console/assets/index-BwJelCXK.js +28 -0
- package/dist/console/index.html +1 -1
- package/dist/di/container.d.ts +1 -0
- package/dist/di/container.js +24 -7
- package/dist/infrastructure/session/HttpServer.d.ts +0 -1
- package/dist/infrastructure/session/HttpServer.js +4 -46
- package/dist/manifest.json +120 -128
- package/dist/mcp/assert-output.js +2 -1
- package/dist/mcp/dev-mode.d.ts +1 -0
- package/dist/mcp/dev-mode.js +12 -0
- package/dist/mcp/handler-factory.d.ts +1 -1
- package/dist/mcp/handler-factory.js +8 -7
- package/dist/mcp/handlers/shared/request-workflow-reader.d.ts +1 -0
- package/dist/mcp/handlers/shared/request-workflow-reader.js +90 -20
- package/dist/mcp/handlers/v2-advance-core/assessment-consequences.d.ts +1 -1
- package/dist/mcp/handlers/v2-advance-core/assessment-consequences.js +14 -11
- package/dist/mcp/handlers/v2-advance-core/assessment-validation.d.ts +5 -3
- package/dist/mcp/handlers/v2-advance-core/assessment-validation.js +109 -87
- package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +0 -4
- package/dist/mcp/handlers/v2-advance-core/input-validation.js +1 -3
- package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +8 -3
- package/dist/mcp/handlers/v2-advance-core/outcome-success.js +8 -3
- package/dist/mcp/handlers/v2-execution/continue-advance.d.ts +1 -0
- package/dist/mcp/handlers/v2-execution/continue-advance.js +3 -1
- package/dist/mcp/handlers/v2-execution/continue-rehydrate.d.ts +1 -0
- package/dist/mcp/handlers/v2-execution/continue-rehydrate.js +2 -1
- package/dist/mcp/handlers/v2-execution/index.js +2 -0
- package/dist/mcp/handlers/v2-execution/replay.d.ts +2 -0
- package/dist/mcp/handlers/v2-execution/replay.js +7 -4
- package/dist/mcp/handlers/v2-execution/start.js +48 -20
- package/dist/mcp/handlers/v2-workflow.js +4 -2
- package/dist/mcp/output-schemas.d.ts +17 -12
- package/dist/mcp/output-schemas.js +12 -11
- package/dist/mcp/server.js +3 -2
- package/dist/mcp/v2-response-formatter.d.ts +1 -1
- package/dist/mcp/v2-response-formatter.js +2 -3
- package/dist/types/workflow-definition.d.ts +3 -1
- package/dist/types/workflow-definition.js +2 -0
- package/dist/v2/durable-core/domain/prompt-renderer.d.ts +1 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +5 -2
- package/dist/v2/durable-core/schemas/compiled-workflow/index.js +4 -3
- package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +2 -0
- package/dist/v2/infra/local/pinned-workflow-store/index.js +49 -0
- package/dist/v2/infra/local/remembered-roots-store/index.d.ts +3 -1
- package/dist/v2/infra/local/remembered-roots-store/index.js +6 -3
- package/dist/v2/infra/local/workspace-anchor/index.js +4 -2
- package/dist/v2/ports/pinned-workflow-store.port.d.ts +2 -0
- package/dist/v2/usecases/console-routes.js +3 -2
- package/package.json +1 -1
- package/spec/authoring-spec.json +3 -3
- package/spec/workflow.schema.json +1 -2
- package/workflows/workflow-for-workflows.json +558 -448
- package/dist/console/assets/index-BE5PAgPO.js +0 -28
- package/dist/env-flags.d.ts +0 -1
- package/dist/env-flags.js +0 -4
- package/dist/mcp/handlers/v2-resolve-refs-envelope.d.ts +0 -5
- package/dist/mcp/handlers/v2-resolve-refs-envelope.js +0 -17
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.clearWalkCacheForTesting = clearWalkCacheForTesting;
|
|
6
7
|
exports.hasRequestWorkspaceSignal = hasRequestWorkspaceSignal;
|
|
7
8
|
exports.resolveRequestWorkspaceDirectory = resolveRequestWorkspaceDirectory;
|
|
8
9
|
exports.toProjectWorkflowDirectory = toProjectWorkflowDirectory;
|
|
@@ -13,6 +14,27 @@ const path_1 = __importDefault(require("path"));
|
|
|
13
14
|
const url_1 = require("url");
|
|
14
15
|
const enhanced_multi_source_workflow_storage_js_1 = require("../../../infrastructure/storage/enhanced-multi-source-workflow-storage.js");
|
|
15
16
|
const schema_validating_workflow_storage_js_1 = require("../../../infrastructure/storage/schema-validating-workflow-storage.js");
|
|
17
|
+
const with_timeout_js_1 = require("./with-timeout.js");
|
|
18
|
+
const SKIP_DIRS = new Set([
|
|
19
|
+
'.git', 'node_modules',
|
|
20
|
+
'build', 'dist', 'out', 'target',
|
|
21
|
+
'.gradle', '.gradle-cache', '.cache',
|
|
22
|
+
'DerivedData', 'Pods',
|
|
23
|
+
'vendor',
|
|
24
|
+
'__pycache__', '.venv', 'venv',
|
|
25
|
+
'.next', '.nuxt', '.turbo', '.parcel-cache',
|
|
26
|
+
'.claude', '.claude-worktrees', '.firebender',
|
|
27
|
+
'coverage', '.nyc_output',
|
|
28
|
+
]);
|
|
29
|
+
const MAX_WALK_DEPTH = 5;
|
|
30
|
+
const WALK_CACHE_TTL_MS = 300000;
|
|
31
|
+
const walkCache = new Map();
|
|
32
|
+
const walkInFlight = new Map();
|
|
33
|
+
function clearWalkCacheForTesting() {
|
|
34
|
+
walkCache.clear();
|
|
35
|
+
walkInFlight.clear();
|
|
36
|
+
}
|
|
37
|
+
const DISCOVERY_TIMEOUT_MS = 10000;
|
|
16
38
|
function hasRequestWorkspaceSignal(options) {
|
|
17
39
|
return Boolean(options.workspacePath) || (options.resolvedRootUris?.length ?? 0) > 0;
|
|
18
40
|
}
|
|
@@ -34,18 +56,38 @@ function toProjectWorkflowDirectory(workspaceDirectory) {
|
|
|
34
56
|
? workspaceDirectory
|
|
35
57
|
: path_1.default.join(workspaceDirectory, 'workflows');
|
|
36
58
|
}
|
|
37
|
-
|
|
59
|
+
function discoverRootedWorkflowDirectories(roots) {
|
|
60
|
+
const cacheKey = roots.map((r) => path_1.default.resolve(r)).sort().join('\0');
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const cached = walkCache.get(cacheKey);
|
|
63
|
+
if (cached && cached.expiresAt > now) {
|
|
64
|
+
return Promise.resolve(cached.result);
|
|
65
|
+
}
|
|
66
|
+
const inFlight = walkInFlight.get(cacheKey);
|
|
67
|
+
if (inFlight)
|
|
68
|
+
return inFlight;
|
|
69
|
+
const promise = _doWalk(cacheKey, roots, now);
|
|
70
|
+
walkInFlight.set(cacheKey, promise);
|
|
71
|
+
promise.then(() => walkInFlight.delete(cacheKey), () => walkInFlight.delete(cacheKey));
|
|
72
|
+
return promise;
|
|
73
|
+
}
|
|
74
|
+
async function _doWalk(cacheKey, roots, now) {
|
|
38
75
|
const discoveredByPath = new Set();
|
|
39
76
|
const discoveredPaths = [];
|
|
40
77
|
const stalePaths = [];
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
78
|
+
const resolvedRoots = roots.map((r) => path_1.default.resolve(r));
|
|
79
|
+
const rootResults = await Promise.allSettled(resolvedRoots.map((rootPath) => discoverWorkflowDirectoriesUnderRoot(rootPath)));
|
|
80
|
+
for (let i = 0; i < resolvedRoots.length; i++) {
|
|
81
|
+
const rootPath = resolvedRoots[i];
|
|
82
|
+
const rootResult = rootResults[i];
|
|
83
|
+
if (rootResult.status === 'rejected') {
|
|
84
|
+
throw rootResult.reason;
|
|
85
|
+
}
|
|
86
|
+
if (rootResult.value.stale) {
|
|
45
87
|
stalePaths.push(rootPath);
|
|
46
88
|
continue;
|
|
47
89
|
}
|
|
48
|
-
for (const nextPath of
|
|
90
|
+
for (const nextPath of rootResult.value.discovered) {
|
|
49
91
|
const normalizedPath = path_1.default.resolve(nextPath);
|
|
50
92
|
if (discoveredByPath.has(normalizedPath))
|
|
51
93
|
continue;
|
|
@@ -53,13 +95,22 @@ async function discoverRootedWorkflowDirectories(roots) {
|
|
|
53
95
|
discoveredPaths.push(normalizedPath);
|
|
54
96
|
}
|
|
55
97
|
}
|
|
56
|
-
|
|
98
|
+
const result = { discovered: discoveredPaths, stale: stalePaths };
|
|
99
|
+
walkCache.set(cacheKey, { result, expiresAt: now + WALK_CACHE_TTL_MS });
|
|
100
|
+
return result;
|
|
57
101
|
}
|
|
58
102
|
async function createWorkflowReaderForRequest(options) {
|
|
59
103
|
const workspaceDirectory = resolveRequestWorkspaceDirectory(options);
|
|
60
104
|
const projectWorkflowDirectory = toProjectWorkflowDirectory(workspaceDirectory);
|
|
61
105
|
const rememberedRoots = await listRememberedRoots(options.rememberedRootsStore);
|
|
62
|
-
|
|
106
|
+
let discoveryResult;
|
|
107
|
+
try {
|
|
108
|
+
discoveryResult = await (0, with_timeout_js_1.withTimeout)(discoverRootedWorkflowDirectories(rememberedRoots), DISCOVERY_TIMEOUT_MS, 'workflow_root_discovery');
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
discoveryResult = { discovered: [], stale: [] };
|
|
112
|
+
}
|
|
113
|
+
const { discovered: rootedWorkflowDirectories, stale: stalePaths } = discoveryResult;
|
|
63
114
|
const rootedCustomPaths = rootedWorkflowDirectories.filter((directory) => directory !== projectWorkflowDirectory);
|
|
64
115
|
const { records: allManagedRecords, storeError: managedStoreError } = await listManagedSourceRecords(options.managedSourceStore);
|
|
65
116
|
const envCustomPaths = parseWorkflowStoragePathEnv();
|
|
@@ -70,17 +121,30 @@ async function createWorkflowReaderForRequest(options) {
|
|
|
70
121
|
const additionalManagedPaths = [];
|
|
71
122
|
const activeManagedRecords = [];
|
|
72
123
|
const staleManagedRecords = [];
|
|
124
|
+
const alreadyCovered = [];
|
|
125
|
+
const needsStatCheck = [];
|
|
73
126
|
for (const record of allManagedRecords) {
|
|
74
127
|
if (normalizedCustom.has(path_1.default.resolve(record.path))) {
|
|
75
|
-
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (await isDirectory(record.path)) {
|
|
79
|
-
additionalManagedPaths.push(record.path);
|
|
80
|
-
activeManagedRecords.push(record);
|
|
128
|
+
alreadyCovered.push(record);
|
|
81
129
|
}
|
|
82
130
|
else {
|
|
83
|
-
|
|
131
|
+
needsStatCheck.push(record);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
activeManagedRecords.push(...alreadyCovered);
|
|
135
|
+
if (needsStatCheck.length > 0) {
|
|
136
|
+
const statResults = await (0, with_timeout_js_1.withTimeout)(Promise.allSettled(needsStatCheck.map((record) => isDirectory(record.path))), DISCOVERY_TIMEOUT_MS, 'managed_source_stat').catch(() => null);
|
|
137
|
+
for (let i = 0; i < needsStatCheck.length; i++) {
|
|
138
|
+
const record = needsStatCheck[i];
|
|
139
|
+
const result = statResults?.[i];
|
|
140
|
+
const isDir = result?.status === 'fulfilled' && result.value === true;
|
|
141
|
+
if (isDir) {
|
|
142
|
+
additionalManagedPaths.push(record.path);
|
|
143
|
+
activeManagedRecords.push(record);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
staleManagedRecords.push(record);
|
|
147
|
+
}
|
|
84
148
|
}
|
|
85
149
|
}
|
|
86
150
|
const customPaths = [...rootedCustomPaths, ...additionalManagedPaths];
|
|
@@ -118,8 +182,8 @@ async function listRememberedRoots(rememberedRootsStore) {
|
|
|
118
182
|
return [];
|
|
119
183
|
const result = await rememberedRootsStore.listRoots();
|
|
120
184
|
if (result.isErr()) {
|
|
121
|
-
|
|
122
|
-
|
|
185
|
+
console.error(`[workrail] Failed to load remembered workflow roots: ${result.error.code}: ${result.error.message}`);
|
|
186
|
+
return [];
|
|
123
187
|
}
|
|
124
188
|
return result.value.map((root) => path_1.default.resolve(root));
|
|
125
189
|
}
|
|
@@ -136,7 +200,7 @@ async function discoverWorkflowDirectoriesUnderRoot(rootPath) {
|
|
|
136
200
|
}
|
|
137
201
|
return { discovered: discoveredPaths, stale: false };
|
|
138
202
|
}
|
|
139
|
-
async function walkForRootedWorkflowDirectories(currentDirectory, discoveredPaths) {
|
|
203
|
+
async function walkForRootedWorkflowDirectories(currentDirectory, discoveredPaths, depth = 0) {
|
|
140
204
|
const entries = await promises_1.default.readdir(currentDirectory, { withFileTypes: true });
|
|
141
205
|
const sortedEntries = [...entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
142
206
|
for (const entry of sortedEntries) {
|
|
@@ -152,14 +216,20 @@ async function walkForRootedWorkflowDirectories(currentDirectory, discoveredPath
|
|
|
152
216
|
}
|
|
153
217
|
continue;
|
|
154
218
|
}
|
|
155
|
-
|
|
219
|
+
if (depth >= MAX_WALK_DEPTH) {
|
|
220
|
+
if (process.env['WORKRAIL_DEV'] === '1') {
|
|
221
|
+
console.error(`[workrail] walk depth limit (${MAX_WALK_DEPTH}) reached at: ${entryPath}`);
|
|
222
|
+
}
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
await walkForRootedWorkflowDirectories(entryPath, discoveredPaths, depth + 1).catch((err) => {
|
|
156
226
|
if (err.code !== 'ENOENT')
|
|
157
227
|
throw err;
|
|
158
228
|
});
|
|
159
229
|
}
|
|
160
230
|
}
|
|
161
231
|
function shouldSkipDirectory(name) {
|
|
162
|
-
return
|
|
232
|
+
return SKIP_DIRS.has(name);
|
|
163
233
|
}
|
|
164
234
|
async function isDirectory(targetPath) {
|
|
165
235
|
try {
|
|
@@ -9,6 +9,6 @@ export interface TriggeredAssessmentConsequenceV1 {
|
|
|
9
9
|
}
|
|
10
10
|
export declare function evaluateAssessmentConsequences(args: {
|
|
11
11
|
readonly step: WorkflowStepDefinition | undefined;
|
|
12
|
-
readonly
|
|
12
|
+
readonly recordedAssessments: readonly RecordedAssessmentV1[];
|
|
13
13
|
}): TriggeredAssessmentConsequenceV1 | undefined;
|
|
14
14
|
export declare function getDeclaredAssessmentConsequence(step: WorkflowStepDefinition | undefined): AssessmentConsequenceDefinition | undefined;
|
|
@@ -5,21 +5,24 @@ exports.getDeclaredAssessmentConsequence = getDeclaredAssessmentConsequence;
|
|
|
5
5
|
function evaluateAssessmentConsequences(args) {
|
|
6
6
|
if (!args.step?.assessmentConsequences || args.step.assessmentConsequences.length === 0)
|
|
7
7
|
return undefined;
|
|
8
|
-
if (
|
|
8
|
+
if (args.recordedAssessments.length === 0)
|
|
9
9
|
return undefined;
|
|
10
10
|
const consequence = args.step.assessmentConsequences[0];
|
|
11
11
|
if (!consequence)
|
|
12
12
|
return undefined;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
for (const recorded of args.recordedAssessments) {
|
|
14
|
+
const matched = recorded.dimensions.find(d => d.level === consequence.when.anyEqualsLevel);
|
|
15
|
+
if (matched) {
|
|
16
|
+
return {
|
|
17
|
+
kind: 'require_followup',
|
|
18
|
+
assessmentId: recorded.assessmentId,
|
|
19
|
+
firstMatchedDimensionId: matched.dimensionId,
|
|
20
|
+
triggerLevel: consequence.when.anyEqualsLevel,
|
|
21
|
+
guidance: consequence.effect.guidance,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
23
26
|
}
|
|
24
27
|
function getDeclaredAssessmentConsequence(step) {
|
|
25
28
|
return step?.assessmentConsequences?.[0];
|
|
@@ -5,9 +5,11 @@ import type { RecordedAssessmentV1 } from '../../../v2/durable-core/domain/asses
|
|
|
5
5
|
export interface AssessmentValidationOutcome {
|
|
6
6
|
readonly contractRef: typeof ASSESSMENT_CONTRACT_REF;
|
|
7
7
|
readonly validation: ValidationResult;
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
10
|
-
|
|
8
|
+
readonly recordedAssessments: readonly RecordedAssessmentV1[];
|
|
9
|
+
readonly acceptedArtifacts: ReadonlyArray<{
|
|
10
|
+
readonly artifact: AssessmentArtifactV1;
|
|
11
|
+
readonly artifactIndex: number;
|
|
12
|
+
}>;
|
|
11
13
|
}
|
|
12
14
|
export declare function validateAssessmentForStep(args: {
|
|
13
15
|
readonly step: WorkflowStepDefinition;
|
|
@@ -38,39 +38,6 @@ function extractSubmittedRationale(value) {
|
|
|
38
38
|
const trimmed = value.rationale?.trim();
|
|
39
39
|
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
40
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
41
|
function validateDimension(dimension, artifact, issues, suggestions, warnings, recordedDimensions) {
|
|
75
42
|
const submitted = artifact.dimensions[dimension.id];
|
|
76
43
|
if (submitted === undefined) {
|
|
@@ -111,103 +78,158 @@ function validateDimension(dimension, artifact, issues, suggestions, warnings, r
|
|
|
111
78
|
}
|
|
112
79
|
}
|
|
113
80
|
}
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
81
|
+
function validateSingleAssessment(args) {
|
|
82
|
+
const assessmentArtifacts = args.artifacts
|
|
83
|
+
.map((artifact, index) => ({ artifact, index }))
|
|
84
|
+
.filter(({ artifact }) => typeof artifact === 'object' && artifact !== null && artifact.kind === 'wr.assessment');
|
|
85
|
+
if (assessmentArtifacts.length === 0) {
|
|
119
86
|
return {
|
|
120
|
-
|
|
87
|
+
issues: [`This step requires an assessment submission for "${args.definition.id}".`],
|
|
88
|
+
suggestions: [
|
|
89
|
+
`Provide an artifact with kind "wr.assessment" for assessment "${args.definition.id}".`,
|
|
90
|
+
`Include dimension values for: ${args.definition.dimensions.map((dimension) => `${dimension.id} (${dimension.levels.join(' | ')})`).join(', ')}.`,
|
|
91
|
+
],
|
|
92
|
+
warnings: [],
|
|
121
93
|
acceptedArtifact: undefined,
|
|
122
94
|
acceptedArtifactIndex: undefined,
|
|
123
95
|
recordedAssessment: undefined,
|
|
124
|
-
validation: {
|
|
125
|
-
valid: false,
|
|
126
|
-
issues: [...lookup.issues],
|
|
127
|
-
suggestions: [...lookup.suggestions],
|
|
128
|
-
},
|
|
129
96
|
};
|
|
130
97
|
}
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
.
|
|
134
|
-
|
|
98
|
+
const candidateEntry = args.isSingleRef
|
|
99
|
+
? assessmentArtifacts[0]
|
|
100
|
+
: assessmentArtifacts.find(({ artifact }) => {
|
|
101
|
+
const parsed = (0, index_js_1.parseAssessmentArtifact)(artifact);
|
|
102
|
+
return parsed?.assessmentId === args.definition.id;
|
|
103
|
+
});
|
|
104
|
+
if (!candidateEntry) {
|
|
135
105
|
return {
|
|
136
|
-
|
|
106
|
+
issues: [`Missing assessment artifact for "${args.definition.id}". Provide an artifact with kind "wr.assessment" and assessmentId "${args.definition.id}".`],
|
|
107
|
+
suggestions: [
|
|
108
|
+
`Include dimension values for: ${args.definition.dimensions.map((d) => `${d.id} (${d.levels.join(' | ')})`).join(', ')}.`,
|
|
109
|
+
],
|
|
110
|
+
warnings: [],
|
|
137
111
|
acceptedArtifact: undefined,
|
|
138
112
|
acceptedArtifactIndex: undefined,
|
|
139
113
|
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
114
|
};
|
|
149
115
|
}
|
|
150
|
-
const
|
|
151
|
-
const parsed = (0, index_js_1.parseAssessmentArtifact)(acceptedCandidate.artifact);
|
|
116
|
+
const parsed = (0, index_js_1.parseAssessmentArtifact)(candidateEntry.artifact);
|
|
152
117
|
if (!parsed) {
|
|
153
118
|
return {
|
|
154
|
-
|
|
119
|
+
issues: ['Assessment artifact is malformed or does not match the expected shape.'],
|
|
120
|
+
suggestions: [
|
|
121
|
+
`Use an artifact with kind "wr.assessment", a dimensions object, and canonical dimension values for assessment "${args.definition.id}".`,
|
|
122
|
+
],
|
|
123
|
+
warnings: [],
|
|
155
124
|
acceptedArtifact: undefined,
|
|
156
125
|
acceptedArtifactIndex: undefined,
|
|
157
126
|
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
127
|
};
|
|
166
128
|
}
|
|
167
|
-
if (parsed.assessmentId && parsed.assessmentId !==
|
|
129
|
+
if (parsed.assessmentId && parsed.assessmentId !== args.definition.id) {
|
|
168
130
|
return {
|
|
169
|
-
|
|
131
|
+
issues: [`Assessment artifact targets "${parsed.assessmentId}", but this step expects "${args.definition.id}".`],
|
|
132
|
+
suggestions: [`Set assessmentId to "${args.definition.id}" or omit it and provide the correct dimensions.`],
|
|
133
|
+
warnings: [],
|
|
170
134
|
acceptedArtifact: undefined,
|
|
171
135
|
acceptedArtifactIndex: undefined,
|
|
172
136
|
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
137
|
};
|
|
179
138
|
}
|
|
180
139
|
const issues = [];
|
|
181
140
|
const suggestions = [];
|
|
182
141
|
const warnings = [];
|
|
183
142
|
const recordedDimensions = [];
|
|
184
|
-
for (const dimension of
|
|
143
|
+
for (const dimension of args.definition.dimensions) {
|
|
185
144
|
validateDimension(dimension, parsed, issues, suggestions, warnings, recordedDimensions);
|
|
186
145
|
}
|
|
187
|
-
const allowedDimensionIds = new Set(
|
|
146
|
+
const allowedDimensionIds = new Set(args.definition.dimensions.map((d) => d.id));
|
|
188
147
|
for (const submittedDimensionId of Object.keys(parsed.dimensions)) {
|
|
189
148
|
if (!allowedDimensionIds.has(submittedDimensionId)) {
|
|
190
|
-
issues.push(`Unknown assessment dimension "${submittedDimensionId}" for assessment "${
|
|
191
|
-
suggestions.push(`Remove "${submittedDimensionId}" and use only: ${
|
|
149
|
+
issues.push(`Unknown assessment dimension "${submittedDimensionId}" for assessment "${args.definition.id}".`);
|
|
150
|
+
suggestions.push(`Remove "${submittedDimensionId}" and use only: ${args.definition.dimensions.map((d) => d.id).join(', ')}.`);
|
|
192
151
|
}
|
|
193
152
|
}
|
|
153
|
+
const valid = issues.length === 0;
|
|
194
154
|
return {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
155
|
+
issues,
|
|
156
|
+
suggestions,
|
|
157
|
+
warnings,
|
|
158
|
+
acceptedArtifact: valid ? parsed : undefined,
|
|
159
|
+
acceptedArtifactIndex: valid ? candidateEntry.index : undefined,
|
|
160
|
+
recordedAssessment: valid
|
|
199
161
|
? {
|
|
200
|
-
assessmentId:
|
|
162
|
+
assessmentId: args.definition.id,
|
|
201
163
|
summary: parsed.summary,
|
|
202
164
|
dimensions: recordedDimensions,
|
|
203
165
|
normalizationNotes: warnings,
|
|
204
166
|
}
|
|
205
167
|
: undefined,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function validateAssessmentForStep(args) {
|
|
171
|
+
if (!args.step.assessmentRefs || args.step.assessmentRefs.length === 0)
|
|
172
|
+
return undefined;
|
|
173
|
+
const refs = args.step.assessmentRefs;
|
|
174
|
+
if (!args.assessments || args.assessments.length === 0) {
|
|
175
|
+
return {
|
|
176
|
+
contractRef: index_js_1.ASSESSMENT_CONTRACT_REF,
|
|
177
|
+
recordedAssessments: [],
|
|
178
|
+
acceptedArtifacts: [],
|
|
179
|
+
validation: {
|
|
180
|
+
valid: false,
|
|
181
|
+
issues: refs.map((ref) => `Step expects assessment input for "${ref}", but the workflow declares no assessments.`),
|
|
182
|
+
suggestions: ['Update the workflow definition to declare the assessments referenced by this step.'],
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const allIssues = [];
|
|
187
|
+
const allSuggestions = [];
|
|
188
|
+
const definitions = [];
|
|
189
|
+
for (const ref of refs) {
|
|
190
|
+
const definition = args.assessments.find((a) => a.id === ref);
|
|
191
|
+
if (!definition) {
|
|
192
|
+
allIssues.push(`Step references undeclared assessment "${ref}".`);
|
|
193
|
+
allSuggestions.push(`Declare assessment "${ref}" on the workflow or remove the step reference.`);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
definitions.push(definition);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (allIssues.length > 0) {
|
|
200
|
+
return {
|
|
201
|
+
contractRef: index_js_1.ASSESSMENT_CONTRACT_REF,
|
|
202
|
+
recordedAssessments: [],
|
|
203
|
+
acceptedArtifacts: [],
|
|
204
|
+
validation: { valid: false, issues: allIssues, suggestions: allSuggestions },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const isSingleRef = refs.length === 1;
|
|
208
|
+
const perRefResults = definitions.map((definition) => validateSingleAssessment({ definition, artifacts: args.artifacts, isSingleRef }));
|
|
209
|
+
const combinedIssues = perRefResults.flatMap((r) => r.issues);
|
|
210
|
+
const combinedSuggestions = perRefResults.flatMap((r) => r.suggestions);
|
|
211
|
+
const combinedWarnings = perRefResults.flatMap((r) => r.warnings);
|
|
212
|
+
const allValid = combinedIssues.length === 0;
|
|
213
|
+
const recordedAssessments = [];
|
|
214
|
+
const acceptedArtifacts = [];
|
|
215
|
+
if (allValid) {
|
|
216
|
+
for (const result of perRefResults) {
|
|
217
|
+
if (result.recordedAssessment)
|
|
218
|
+
recordedAssessments.push(result.recordedAssessment);
|
|
219
|
+
if (result.acceptedArtifact !== undefined && result.acceptedArtifactIndex !== undefined) {
|
|
220
|
+
acceptedArtifacts.push({ artifact: result.acceptedArtifact, artifactIndex: result.acceptedArtifactIndex });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
contractRef: index_js_1.ASSESSMENT_CONTRACT_REF,
|
|
226
|
+
recordedAssessments,
|
|
227
|
+
acceptedArtifacts,
|
|
206
228
|
validation: {
|
|
207
|
-
valid:
|
|
208
|
-
issues,
|
|
209
|
-
suggestions,
|
|
210
|
-
|
|
229
|
+
valid: allValid,
|
|
230
|
+
issues: combinedIssues,
|
|
231
|
+
suggestions: combinedSuggestions,
|
|
232
|
+
...(combinedWarnings.length > 0 ? { warnings: combinedWarnings } : {}),
|
|
211
233
|
},
|
|
212
234
|
};
|
|
213
235
|
}
|
|
@@ -5,8 +5,6 @@ import type { JsonValue, JsonObject } from '../../../v2/durable-core/canonical/j
|
|
|
5
5
|
import type { V2ContinueWorkflowInput } from '../../v2/tools.js';
|
|
6
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
8
|
import type { TriggeredAssessmentConsequenceV1 } from './assessment-consequences.js';
|
|
11
9
|
import type { InternalError } from '../v2-error-mapping.js';
|
|
12
10
|
import type { SessionIndex } from '../../../v2/durable-core/session-index.js';
|
|
@@ -25,8 +23,6 @@ export interface ValidatedAdvanceInputs {
|
|
|
25
23
|
readonly outputContract: OutputContract | undefined;
|
|
26
24
|
readonly notesMarkdown: string | undefined;
|
|
27
25
|
readonly artifacts: readonly unknown[];
|
|
28
|
-
readonly assessmentArtifact: AssessmentArtifactV1 | undefined;
|
|
29
|
-
readonly recordedAssessment: RecordedAssessmentV1 | undefined;
|
|
30
26
|
readonly triggeredAssessmentConsequence: TriggeredAssessmentConsequenceV1 | undefined;
|
|
31
27
|
readonly stepAssessments: readonly AssessmentDefinition[];
|
|
32
28
|
readonly autonomy: 'guided' | 'full_auto_stop_on_user_deps' | 'full_auto_never_stop';
|
|
@@ -48,7 +48,7 @@ function validateAdvanceInputs(args) {
|
|
|
48
48
|
: undefined;
|
|
49
49
|
const triggeredAssessmentConsequence = (0, assessment_consequences_js_1.evaluateAssessmentConsequences)({
|
|
50
50
|
step: typedStep,
|
|
51
|
-
|
|
51
|
+
recordedAssessments: assessmentValidation?.recordedAssessments ?? [],
|
|
52
52
|
});
|
|
53
53
|
const notesOptional = outputContract !== undefined ||
|
|
54
54
|
(step !== null && step !== undefined && 'notesOptional' in step && step.notesOptional === true);
|
|
@@ -92,8 +92,6 @@ function validateAdvanceInputs(args) {
|
|
|
92
92
|
outputContract,
|
|
93
93
|
notesMarkdown: inputOutput?.notesMarkdown,
|
|
94
94
|
artifacts: inputOutput?.artifacts ?? [],
|
|
95
|
-
assessmentArtifact: assessmentValidation?.acceptedArtifact,
|
|
96
|
-
recordedAssessment: assessmentValidation?.recordedAssessment,
|
|
97
95
|
triggeredAssessmentConsequence,
|
|
98
96
|
stepAssessments,
|
|
99
97
|
autonomy,
|
|
@@ -65,8 +65,13 @@ function buildBlockedOutcome(args) {
|
|
|
65
65
|
? outputAppendResult.value
|
|
66
66
|
: [];
|
|
67
67
|
const validated = args.v;
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const acceptedArtifacts = validated?.assessmentValidation?.acceptedArtifacts ?? [];
|
|
69
|
+
for (let i = 0; i < acceptedArtifacts.length; i++) {
|
|
70
|
+
const { artifactIndex } = acceptedArtifacts[i];
|
|
71
|
+
const recordedAssessment = validated?.assessmentValidation?.recordedAssessments[i];
|
|
72
|
+
if (!recordedAssessment)
|
|
73
|
+
continue;
|
|
74
|
+
const assessmentOutput = outputsToAppend[artifactIndex];
|
|
70
75
|
if (!assessmentOutput || assessmentOutput.outputChannel !== 'artifact') {
|
|
71
76
|
return errAsync({ kind: 'invariant_violation', message: 'Accepted assessment artifact did not produce a matching artifact output on blocked path.' });
|
|
72
77
|
}
|
|
@@ -75,7 +80,7 @@ function buildBlockedOutcome(args) {
|
|
|
75
80
|
attemptId: String(attemptId),
|
|
76
81
|
artifactOutputId: String(assessmentOutput.outputId),
|
|
77
82
|
scope: { runId: String(runId), nodeId: String(currentNodeId) },
|
|
78
|
-
assessment:
|
|
83
|
+
assessment: recordedAssessment,
|
|
79
84
|
minted: { eventId: idFactory.mintEventId() },
|
|
80
85
|
});
|
|
81
86
|
if (assessmentEventRes.isErr()) {
|
|
@@ -125,8 +125,13 @@ function buildSuccessOutcome(args) {
|
|
|
125
125
|
if (artifactOutputsRes.isErr()) {
|
|
126
126
|
return errAsync(artifactOutputsRes.error);
|
|
127
127
|
}
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
const acceptedArtifacts = v.assessmentValidation?.acceptedArtifacts ?? [];
|
|
129
|
+
for (let i = 0; i < acceptedArtifacts.length; i++) {
|
|
130
|
+
const { artifactIndex } = acceptedArtifacts[i];
|
|
131
|
+
const recordedAssessment = v.assessmentValidation?.recordedAssessments[i];
|
|
132
|
+
if (!recordedAssessment)
|
|
133
|
+
continue;
|
|
134
|
+
const assessmentOutput = artifactOutputsRes.value[artifactIndex];
|
|
130
135
|
if (!assessmentOutput || assessmentOutput.outputChannel !== 'artifact') {
|
|
131
136
|
return errAsync({
|
|
132
137
|
kind: 'invariant_violation',
|
|
@@ -138,7 +143,7 @@ function buildSuccessOutcome(args) {
|
|
|
138
143
|
attemptId: String(attemptId),
|
|
139
144
|
artifactOutputId: String(assessmentOutput.outputId),
|
|
140
145
|
scope: { runId: String(runId), nodeId: String(currentNodeId) },
|
|
141
|
-
assessment:
|
|
146
|
+
assessment: recordedAssessment,
|
|
142
147
|
minted: { eventId: idFactory.mintEventId() },
|
|
143
148
|
});
|
|
144
149
|
if (assessmentEventRes.isErr()) {
|
|
@@ -28,4 +28,5 @@ export declare function handleAdvanceIntent(args: {
|
|
|
28
28
|
readonly sha256: Sha256PortV2;
|
|
29
29
|
readonly aliasStore: import('../../../v2/ports/token-alias-store.port.js').TokenAliasStorePortV2;
|
|
30
30
|
readonly entropy: import('../../../v2/ports/random-entropy.port.js').RandomEntropyPortV2;
|
|
31
|
+
readonly cleanResponseFormat?: boolean;
|
|
31
32
|
}): RA<z.infer<typeof V2ContinueWorkflowOutputSchema>, ContinueWorkflowError>;
|
|
@@ -11,7 +11,7 @@ const advance_js_1 = require("./advance.js");
|
|
|
11
11
|
const sorted_event_log_js_1 = require("../../../v2/durable-core/sorted-event-log.js");
|
|
12
12
|
const session_index_js_1 = require("../../../v2/durable-core/session-index.js");
|
|
13
13
|
function handleAdvanceIntent(args) {
|
|
14
|
-
const { input, sessionId, runId, nodeId, attemptId, workflowHashRef, truth, gate, sessionStore, snapshotStore, pinnedStore, tokenCodecPorts, idFactory, sha256, aliasStore, entropy } = args;
|
|
14
|
+
const { input, sessionId, runId, nodeId, attemptId, workflowHashRef, truth, gate, sessionStore, snapshotStore, pinnedStore, tokenCodecPorts, idFactory, sha256, aliasStore, entropy, cleanResponseFormat } = args;
|
|
15
15
|
const dedupeKey = `advance_recorded:${sessionId}:${nodeId}:${attemptId}`;
|
|
16
16
|
const preLockSortedResult = (0, sorted_event_log_js_1.asSortedEventLog)(truth.events);
|
|
17
17
|
if (preLockSortedResult.isErr()) {
|
|
@@ -101,6 +101,7 @@ function handleAdvanceIntent(args) {
|
|
|
101
101
|
tokenCodecPorts,
|
|
102
102
|
aliasStore,
|
|
103
103
|
entropy,
|
|
104
|
+
cleanResponseFormat,
|
|
104
105
|
});
|
|
105
106
|
}
|
|
106
107
|
return gate
|
|
@@ -198,6 +199,7 @@ function handleAdvanceIntent(args) {
|
|
|
198
199
|
tokenCodecPorts,
|
|
199
200
|
aliasStore,
|
|
200
201
|
entropy,
|
|
202
|
+
cleanResponseFormat,
|
|
201
203
|
});
|
|
202
204
|
});
|
|
203
205
|
});
|
|
@@ -27,4 +27,5 @@ export declare function handleRehydrateIntent(args: {
|
|
|
27
27
|
readonly aliasStore: import('../../../v2/ports/token-alias-store.port.js').TokenAliasStorePortV2;
|
|
28
28
|
readonly entropy: import('../../../v2/ports/random-entropy.port.js').RandomEntropyPortV2;
|
|
29
29
|
readonly resolvedRootUris?: readonly string[];
|
|
30
|
+
readonly cleanResponseFormat?: boolean;
|
|
30
31
|
}): RA<RehydrateResult, ContinueWorkflowError>;
|
|
@@ -20,7 +20,7 @@ const index_js_2 = require("./index.js");
|
|
|
20
20
|
const step_content_envelope_js_1 = require("../../step-content-envelope.js");
|
|
21
21
|
const assert_output_js_1 = require("../../assert-output.js");
|
|
22
22
|
function handleRehydrateIntent(args) {
|
|
23
|
-
const { input, sessionId, runId, nodeId, workflowHashRef, truth, tokenCodecPorts, pinnedStore, snapshotStore, idFactory, aliasStore, entropy, resolvedRootUris } = args;
|
|
23
|
+
const { input, sessionId, runId, nodeId, workflowHashRef, truth, tokenCodecPorts, pinnedStore, snapshotStore, idFactory, aliasStore, entropy, resolvedRootUris, cleanResponseFormat } = args;
|
|
24
24
|
const runStarted = truth.events.find((e) => e.kind === constants_js_1.EVENT_KIND.RUN_STARTED && e.scope.runId === String(runId));
|
|
25
25
|
const workflowId = runStarted?.data.workflowId;
|
|
26
26
|
if (!runStarted || typeof workflowId !== 'string' || workflowId.trim() === '') {
|
|
@@ -124,6 +124,7 @@ function handleRehydrateIntent(args) {
|
|
|
124
124
|
runId: (0, index_js_1.asRunId)(String(runId)),
|
|
125
125
|
nodeId: (0, index_js_1.asNodeId)(String(nodeId)),
|
|
126
126
|
rehydrateOnly: true,
|
|
127
|
+
cleanResponseFormat,
|
|
127
128
|
});
|
|
128
129
|
if (metaRes.isErr()) {
|
|
129
130
|
return (0, neverthrow_1.errAsync)({
|