intentdna 1.9.0 → 1.9.3
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +227 -87
- package/dist/cli/commands/run-lifecycle.d.ts +61 -11
- package/dist/cli/commands/run-lifecycle.js +184 -50
- package/dist/cli/commands/run-observer.d.ts +15 -0
- package/dist/cli/commands/run-observer.js +210 -0
- package/dist/cli/commands/run.d.ts +16 -20
- package/dist/cli/commands/run.js +461 -659
- package/dist/cli/commands/sync.js +16 -12
- package/dist/cli/index.js +14 -5
- package/dist/compiler/compile.d.ts +1 -0
- package/dist/compiler/compile.js +1 -1
- package/dist/compiler/controller.d.ts +16 -0
- package/dist/compiler/controller.js +693 -0
- package/dist/compiler/input-resolver.d.ts +2 -0
- package/dist/compiler/input-resolver.js +22 -6
- package/dist/hooks/cli.d.ts +45 -0
- package/dist/hooks/cli.js +263 -31
- package/dist/hooks/host-event.d.ts +57 -0
- package/dist/hooks/host-event.js +187 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/protocol.d.ts +7 -0
- package/dist/hooks/schema.d.ts +1 -0
- package/dist/hooks/schema.js +89 -1
- package/dist/hooks/state.d.ts +23 -1
- package/dist/hooks/state.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-run.d.ts +4 -0
- package/dist/mcp/tools-run.js +419 -0
- package/dist/mcp/tools-state.d.ts +1 -1
- package/dist/mcp/tools-state.js +13 -241
- package/dist/runtime/artifact-store.d.ts +146 -0
- package/dist/runtime/artifact-store.js +1436 -0
- package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
- package/dist/runtime/canonical-attempt-outcome.js +195 -0
- package/dist/runtime/canonical-json.d.ts +10 -0
- package/dist/runtime/canonical-json.js +105 -0
- package/dist/runtime/canonical-run-application.d.ts +93 -0
- package/dist/runtime/canonical-run-application.js +229 -0
- package/dist/runtime/canonical-run-service.d.ts +454 -0
- package/dist/runtime/canonical-run-service.js +4760 -0
- package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
- package/dist/runtime/canonical-runtime-composition.js +34 -0
- package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
- package/dist/runtime/canonical-runtime-projection.js +4 -0
- package/dist/runtime/canonical-target-compiler.d.ts +46 -0
- package/dist/runtime/canonical-target-compiler.js +514 -0
- package/dist/runtime/claude-agent-identity.d.ts +2 -0
- package/dist/runtime/claude-agent-identity.js +21 -0
- package/dist/runtime/executable-run-plan.d.ts +328 -0
- package/dist/runtime/executable-run-plan.js +1485 -0
- package/dist/runtime/execution-authority.d.ts +83 -0
- package/dist/runtime/execution-authority.js +98 -0
- package/dist/runtime/handoff-resolver.js +4 -1
- package/dist/runtime/harness-pull-adapter.d.ts +146 -0
- package/dist/runtime/harness-pull-adapter.js +315 -0
- package/dist/runtime/index.d.ts +39 -14
- package/dist/runtime/index.js +29 -7
- package/dist/runtime/local-execution-authority.d.ts +194 -0
- package/dist/runtime/local-execution-authority.js +2897 -0
- package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
- package/dist/runtime/local-execution-reconciliation.js +107 -0
- package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
- package/dist/runtime/local-execution-supervisor-script.js +793 -0
- package/dist/runtime/local-provider-sandbox.d.ts +42 -0
- package/dist/runtime/local-provider-sandbox.js +154 -0
- package/dist/runtime/local-verifier-execution.d.ts +24 -0
- package/dist/runtime/local-verifier-execution.js +290 -0
- package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
- package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
- package/dist/runtime/plan-store.d.ts +30 -0
- package/dist/runtime/plan-store.js +231 -0
- package/dist/runtime/process-tree.d.ts +9 -0
- package/dist/runtime/process-tree.js +97 -24
- package/dist/runtime/providers/codex.js +2 -2
- package/dist/runtime/push-driver.d.ts +184 -0
- package/dist/runtime/push-driver.js +1002 -0
- package/dist/runtime/result-store.d.ts +34 -3
- package/dist/runtime/result-store.js +1073 -2
- package/dist/runtime/run-binding.d.ts +68 -0
- package/dist/runtime/run-binding.js +278 -0
- package/dist/runtime/run-contracts.d.ts +575 -0
- package/dist/runtime/run-contracts.js +58 -7
- package/dist/runtime/run-controller.d.ts +1 -6
- package/dist/runtime/run-controller.js +0 -41
- package/dist/runtime/run-store.d.ts +85 -7
- package/dist/runtime/run-store.js +2528 -186
- package/dist/runtime/skill-adapter.d.ts +18 -7
- package/dist/runtime/skill-adapter.js +100 -742
- package/dist/runtime/structured-output-validator.d.ts +44 -0
- package/dist/runtime/structured-output-validator.js +171 -0
- package/dist/runtime/verifier-command-binding.d.ts +22 -0
- package/dist/runtime/verifier-command-binding.js +162 -0
- package/dist/runtime/verifier.d.ts +46 -6
- package/dist/runtime/verifier.js +355 -53
- package/dist/runtime/windows-job-keeper.d.ts +47 -0
- package/dist/runtime/windows-job-keeper.js +231 -0
- package/dist/runtime/worker-executor.d.ts +17 -1
- package/dist/runtime/worker-executor.js +26 -5
- package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
- package/dist/runtime/workflow-plan-adapter.js +502 -6
- package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
- package/dist/runtime/workflow-runtime-manifest.js +5 -0
- package/dist/runtime/workspace-isolation.d.ts +29 -2
- package/dist/runtime/workspace-isolation.js +488 -11
- package/dist/runtime/workspace-observation.d.ts +44 -0
- package/dist/runtime/workspace-observation.js +216 -0
- package/dist/schema/controller-registry.d.ts +0 -7
- package/dist/schema/controller-registry.js +0 -8
- package/dist/schema/types.d.ts +84 -10
- package/dist/schema/types.js +2 -0
- package/dist/schema/validate.js +151 -1
- package/dist/schema/validators/controllers.d.ts +1 -1
- package/dist/schema/validators/controllers.js +126 -41
- package/dist/schema/workflow-authoring-contract.js +2 -0
- package/dist/schema/yaml-parser.js +1 -1
- package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
- package/dist/templates/flutter-rewrite.dna.yaml +87 -60
- package/dist/templates/safe-refactoring.dna.yaml +1 -0
- package/native/windows-job-keeper/README.md +80 -0
- package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
- package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
- package/native/windows-job-keeper/keeper.c +979 -0
- package/native/windows-job-keeper/manifest.json +33 -0
- package/package.json +6 -2
- package/scripts/build-windows-job-keeper.mjs +172 -0
- package/scripts/verify-windows-job-keeper.mjs +184 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import { EXECUTABLE_RUN_PLAN_SCHEMA_VERSION, parseExecutableRunPlanPayload, } from "../runtime/executable-run-plan.js";
|
|
2
|
+
import { canonicalizeJson } from "../runtime/canonical-json.js";
|
|
3
|
+
export class ControllerPlanCompilerError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "ControllerPlanCompilerError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const TERMINAL_NODE_IDS = {
|
|
12
|
+
passed: "terminal:pass",
|
|
13
|
+
request_changes: "terminal:request-changes",
|
|
14
|
+
blocked: "terminal:blocked",
|
|
15
|
+
diagnosis_invalid: "terminal:diagnosis-contract-invalid",
|
|
16
|
+
provenance_invalid: "terminal:provenance-invalid",
|
|
17
|
+
max_rounds: "terminal:max-rounds",
|
|
18
|
+
cancelled: "terminal:cancelled",
|
|
19
|
+
};
|
|
20
|
+
const DIAGNOSIS_DECISION_NODE_ID = "decision:diagnosis-review";
|
|
21
|
+
const FIX_DECISION_NODE_ID = "decision:fix-verdict";
|
|
22
|
+
const DIAGNOSIS_SOURCE_NODE_ID = "system:resolve-diagnosis-source";
|
|
23
|
+
const DIAGNOSIS_SOURCE_DECISION_NODE_ID = "decision:diagnosis-source";
|
|
24
|
+
const ADOPT_DIAGNOSIS_NODE_ID = "system:adopt-diagnosis";
|
|
25
|
+
const IMPORT_DIAGNOSIS_NODE_ID = "system:import-diagnosis";
|
|
26
|
+
const SEAL_DIAGNOSIS_INPUTS_NODE_ID = "system:seal-diagnosis-inputs";
|
|
27
|
+
const DIAGNOSIS_APPROVAL_SOURCE_DECISION_NODE_ID = "decision:diagnosis-approval-source";
|
|
28
|
+
const IMPORT_DIAGNOSIS_REVIEW_NODE_ID = "system:import-diagnosis-review";
|
|
29
|
+
const SEAL_APPROVED_DIAGNOSIS_NODE_ID = "system:seal-approved-diagnosis";
|
|
30
|
+
const DIAGNOSIS_SOURCE_OUTPUT = "diagnosis-source";
|
|
31
|
+
const DIAGNOSIS_ORIGIN_OUTPUT = "diagnosis-origin";
|
|
32
|
+
const DIAGNOSIS_SOURCE_SCHEMA = "intentdna.controller.diagnosis-source.v1";
|
|
33
|
+
const DIAGNOSIS_ORIGIN_SCHEMA = "intentdna.controller.diagnosis-origin.v1";
|
|
34
|
+
function namespace(prefix, value) {
|
|
35
|
+
return `${prefix}/${value}`;
|
|
36
|
+
}
|
|
37
|
+
function rewriteHandoffTemplates(value, replacements) {
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
let rewritten = value;
|
|
40
|
+
for (const [source, target] of replacements) {
|
|
41
|
+
rewritten = rewritten.replaceAll(`{{handoff.${source}.`, `{{handoff.${target}.`);
|
|
42
|
+
}
|
|
43
|
+
return rewritten;
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value.map((item) => rewriteHandoffTemplates(item, replacements));
|
|
47
|
+
}
|
|
48
|
+
if (value !== null && typeof value === "object") {
|
|
49
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => ([key, rewriteHandoffTemplates(item, replacements)])));
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function componentTerminalStatus(plan, nodeId) {
|
|
54
|
+
const node = plan.nodes.find((candidate) => candidate.node_id === nodeId);
|
|
55
|
+
if (node?.kind !== "terminal")
|
|
56
|
+
return null;
|
|
57
|
+
return plan.terminals.find((terminal) => terminal.terminal_id === node.terminal_id)?.status ?? null;
|
|
58
|
+
}
|
|
59
|
+
function namespaceComponent(prefix, plan, successTargetNodeId) {
|
|
60
|
+
if (plan.target.kind !== "workflow") {
|
|
61
|
+
throw new ControllerPlanCompilerError("invalid_component_plan", `controller component '${prefix}' must be a workflow plan`);
|
|
62
|
+
}
|
|
63
|
+
if (plan.retry_segments.length > 0) {
|
|
64
|
+
throw new ControllerPlanCompilerError("invalid_component_plan", `controller component '${prefix}' cannot contain an independent workflow retry segment`);
|
|
65
|
+
}
|
|
66
|
+
const nodeId = (value) => namespace(prefix, value);
|
|
67
|
+
const routeId = (value) => namespace(prefix, value);
|
|
68
|
+
const verifierId = (value) => namespace(prefix, value);
|
|
69
|
+
const handoffId = (value) => namespace(prefix, value);
|
|
70
|
+
const handoffReplacements = new Map(plan.handoff_plan.map((handoff) => [handoff.handoff_id, handoffId(handoff.handoff_id)]));
|
|
71
|
+
const terminalIds = new Set(plan.nodes.filter((node) => node.kind === "terminal").map((node) => node.node_id));
|
|
72
|
+
const successTerminals = plan.nodes.filter((node) => (node.kind === "terminal"
|
|
73
|
+
&& componentTerminalStatus(plan, node.node_id) === "succeeded"));
|
|
74
|
+
const successDependencies = [...new Set(successTerminals.flatMap((node) => (node.depends_on.map(nodeId))))];
|
|
75
|
+
const nodes = plan.nodes.flatMap((originalNode) => {
|
|
76
|
+
const node = rewriteHandoffTemplates(originalNode, handoffReplacements);
|
|
77
|
+
if (node.kind === "terminal")
|
|
78
|
+
return [];
|
|
79
|
+
const depends_on = node.depends_on
|
|
80
|
+
.filter((dependency) => !terminalIds.has(dependency))
|
|
81
|
+
.map(nodeId);
|
|
82
|
+
if (node.kind === "worker") {
|
|
83
|
+
return [{
|
|
84
|
+
...node,
|
|
85
|
+
node_id: nodeId(node.node_id),
|
|
86
|
+
depends_on,
|
|
87
|
+
attempt_policy: { ...node.attempt_policy, retry_segment_id: null },
|
|
88
|
+
}];
|
|
89
|
+
}
|
|
90
|
+
if (node.kind === "system") {
|
|
91
|
+
return [{
|
|
92
|
+
...node,
|
|
93
|
+
node_id: nodeId(node.node_id),
|
|
94
|
+
depends_on,
|
|
95
|
+
operation: node.operation.kind === "materialize_handoff"
|
|
96
|
+
? { ...node.operation, handoff_id: handoffId(node.operation.handoff_id) }
|
|
97
|
+
: node.operation,
|
|
98
|
+
}];
|
|
99
|
+
}
|
|
100
|
+
if (node.kind === "gate") {
|
|
101
|
+
return [{
|
|
102
|
+
...node,
|
|
103
|
+
node_id: nodeId(node.node_id),
|
|
104
|
+
depends_on,
|
|
105
|
+
verifier_ids: node.verifier_ids.map(verifierId),
|
|
106
|
+
pass_route_ids: node.pass_route_ids.map(routeId),
|
|
107
|
+
fail_route_id: routeId(node.fail_route_id),
|
|
108
|
+
}];
|
|
109
|
+
}
|
|
110
|
+
if (node.kind === "decision") {
|
|
111
|
+
return [{
|
|
112
|
+
...node,
|
|
113
|
+
node_id: nodeId(node.node_id),
|
|
114
|
+
depends_on,
|
|
115
|
+
source: { ...node.source, node_id: nodeId(node.source.node_id) },
|
|
116
|
+
}];
|
|
117
|
+
}
|
|
118
|
+
return [{ ...node, node_id: nodeId(node.node_id), depends_on }];
|
|
119
|
+
});
|
|
120
|
+
const routes = plan.routes.flatMap((route) => {
|
|
121
|
+
const status = componentTerminalStatus(plan, route.to_node_id);
|
|
122
|
+
const toNodeId = status === "succeeded"
|
|
123
|
+
? successTargetNodeId
|
|
124
|
+
: status === "failed" || status === "unsupported"
|
|
125
|
+
? TERMINAL_NODE_IDS.blocked
|
|
126
|
+
: status === "cancelled"
|
|
127
|
+
? TERMINAL_NODE_IDS.cancelled
|
|
128
|
+
: nodeId(route.to_node_id);
|
|
129
|
+
const condition = route.condition.kind === "gate"
|
|
130
|
+
? { ...route.condition, gate_node_id: nodeId(route.condition.gate_node_id) }
|
|
131
|
+
: route.condition.kind === "decision"
|
|
132
|
+
? { ...route.condition, decision_node_id: nodeId(route.condition.decision_node_id) }
|
|
133
|
+
: route.condition.kind === "interaction"
|
|
134
|
+
? { ...route.condition, interaction_node_id: nodeId(route.condition.interaction_node_id) }
|
|
135
|
+
: route.condition;
|
|
136
|
+
return [{
|
|
137
|
+
...route,
|
|
138
|
+
route_id: routeId(route.route_id),
|
|
139
|
+
from_node_id: nodeId(route.from_node_id),
|
|
140
|
+
to_node_id: toNodeId,
|
|
141
|
+
condition,
|
|
142
|
+
}];
|
|
143
|
+
});
|
|
144
|
+
const incoming = new Set(routes.map((route) => route.to_node_id));
|
|
145
|
+
const roots = nodes.filter((node) => ((node.kind === "worker" || node.kind === "system" || node.kind === "interaction")
|
|
146
|
+
&& node.depends_on.length === 0
|
|
147
|
+
&& !incoming.has(node.node_id))).map((node) => node.node_id);
|
|
148
|
+
return {
|
|
149
|
+
nodes,
|
|
150
|
+
routes,
|
|
151
|
+
roots,
|
|
152
|
+
success_dependencies: successDependencies,
|
|
153
|
+
node_id: nodeId,
|
|
154
|
+
verifiers: plan.verifier_plan.map((originalVerifier) => {
|
|
155
|
+
const verifier = rewriteHandoffTemplates(originalVerifier, handoffReplacements);
|
|
156
|
+
return {
|
|
157
|
+
...verifier,
|
|
158
|
+
verifier_id: verifierId(verifier.verifier_id),
|
|
159
|
+
subject_node_id: nodeId(verifier.subject_node_id),
|
|
160
|
+
...(verifier.kind === "structured_output"
|
|
161
|
+
? { node_id: nodeId(verifier.node_id) }
|
|
162
|
+
: {}),
|
|
163
|
+
};
|
|
164
|
+
}),
|
|
165
|
+
handoffs: plan.handoff_plan.map((handoff) => ({
|
|
166
|
+
...handoff,
|
|
167
|
+
handoff_id: handoffId(handoff.handoff_id),
|
|
168
|
+
from_node_id: nodeId(handoff.from_node_id),
|
|
169
|
+
to_node_id: nodeId(handoff.to_node_id),
|
|
170
|
+
})),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function mergeInputs(plans) {
|
|
174
|
+
const inputs = new Map();
|
|
175
|
+
for (const plan of plans) {
|
|
176
|
+
for (const input of plan.input_contract.inputs) {
|
|
177
|
+
const existing = inputs.get(input.name);
|
|
178
|
+
if (existing && canonicalizeJson(existing) !== canonicalizeJson(input)) {
|
|
179
|
+
throw new ControllerPlanCompilerError("invalid_component_plan", `controller component input '${input.name}' has conflicting definitions`);
|
|
180
|
+
}
|
|
181
|
+
inputs.set(input.name, input);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return [...inputs.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
185
|
+
}
|
|
186
|
+
function assertDecisionOutput(component, decision, label) {
|
|
187
|
+
const nodeId = component.node_id(`worker:${decision.step_id}`);
|
|
188
|
+
const node = component.nodes.find((candidate) => candidate.node_id === nodeId);
|
|
189
|
+
if (node?.kind !== "worker") {
|
|
190
|
+
throw new ControllerPlanCompilerError("invalid_controller_program", `${label} decision step '${decision.step_id}' is not a worker node`);
|
|
191
|
+
}
|
|
192
|
+
const output = node.packet_template.output_contract.find((candidate) => candidate.name === decision.output_name);
|
|
193
|
+
if (output?.kind !== "structured" || output.schema_ref !== decision.schema_ref) {
|
|
194
|
+
throw new ControllerPlanCompilerError("invalid_controller_program", `${label} decision output '${decision.output_name}' is not a structured '${decision.schema_ref}' output`);
|
|
195
|
+
}
|
|
196
|
+
return nodeId;
|
|
197
|
+
}
|
|
198
|
+
function terminalNode(nodeId, terminalId) {
|
|
199
|
+
return { kind: "terminal", node_id: nodeId, depends_on: [], terminal_id: terminalId };
|
|
200
|
+
}
|
|
201
|
+
function decisionRoute(id, decisionNodeId, value, toNodeId) {
|
|
202
|
+
return {
|
|
203
|
+
route_id: id,
|
|
204
|
+
from_node_id: decisionNodeId,
|
|
205
|
+
to_node_id: toNodeId,
|
|
206
|
+
condition: { kind: "decision", decision_node_id: decisionNodeId, value },
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function alwaysRoute(id, fromNodeId, toNodeId) {
|
|
210
|
+
return {
|
|
211
|
+
route_id: id,
|
|
212
|
+
from_node_id: fromNodeId,
|
|
213
|
+
to_node_id: toNodeId,
|
|
214
|
+
condition: { kind: "always" },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function systemArtifactOutput(name, description) {
|
|
218
|
+
return {
|
|
219
|
+
name,
|
|
220
|
+
kind: "reference",
|
|
221
|
+
artifact_kind: "file",
|
|
222
|
+
required: true,
|
|
223
|
+
description,
|
|
224
|
+
relative_path_template: null,
|
|
225
|
+
schema_ref: null,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function systemStructuredOutput(name, schemaRef, description) {
|
|
229
|
+
return {
|
|
230
|
+
name,
|
|
231
|
+
kind: "structured",
|
|
232
|
+
artifact_kind: "state",
|
|
233
|
+
required: true,
|
|
234
|
+
description,
|
|
235
|
+
relative_path_template: null,
|
|
236
|
+
schema_ref: schemaRef,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function controllerReuseInputs(controller) {
|
|
240
|
+
const reuse = controller.runtime_program?.reuse;
|
|
241
|
+
if (!reuse)
|
|
242
|
+
return [];
|
|
243
|
+
const optional = (name, description) => ({
|
|
244
|
+
name,
|
|
245
|
+
type: "string",
|
|
246
|
+
required: false,
|
|
247
|
+
sensitive: false,
|
|
248
|
+
description,
|
|
249
|
+
});
|
|
250
|
+
return [
|
|
251
|
+
{
|
|
252
|
+
name: reuse.mode_input,
|
|
253
|
+
type: "string",
|
|
254
|
+
required: false,
|
|
255
|
+
sensitive: false,
|
|
256
|
+
description: "Typed diagnosis source mode: fresh, legacy, or canonical",
|
|
257
|
+
default_value: "fresh",
|
|
258
|
+
},
|
|
259
|
+
optional(reuse.legacy.diagnosis_spec_reference_input, "Project-relative legacy diagnosis spec path"),
|
|
260
|
+
optional(reuse.legacy.diagnosis_spec_digest_input, "Expected immutable digest for the legacy diagnosis spec"),
|
|
261
|
+
optional(reuse.legacy.diagnosis_contract_reference_input, "Project-relative legacy diagnosis contract path"),
|
|
262
|
+
optional(reuse.legacy.diagnosis_contract_digest_input, "Expected immutable digest for the legacy diagnosis contract"),
|
|
263
|
+
optional(reuse.canonical.source_plan_input, "Origin canonical plan id"),
|
|
264
|
+
optional(reuse.canonical.diagnosis_result_input, "Origin canonical diagnosis result id"),
|
|
265
|
+
optional(reuse.canonical.review_result_input, "Origin canonical diagnosis review result id"),
|
|
266
|
+
];
|
|
267
|
+
}
|
|
268
|
+
function replaceLiteralTemplates(value, replacements) {
|
|
269
|
+
if (typeof value === "string") {
|
|
270
|
+
let rewritten = value;
|
|
271
|
+
for (const [source, target] of replacements) {
|
|
272
|
+
rewritten = rewritten.replaceAll(source, target);
|
|
273
|
+
}
|
|
274
|
+
return rewritten;
|
|
275
|
+
}
|
|
276
|
+
if (Array.isArray(value)) {
|
|
277
|
+
return value.map((item) => replaceLiteralTemplates(item, replacements));
|
|
278
|
+
}
|
|
279
|
+
if (value !== null && typeof value === "object") {
|
|
280
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => ([key, replaceLiteralTemplates(item, replacements)])));
|
|
281
|
+
}
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
function controllerDiagnosisHandoffs(fix, sourceNodeId, outputs) {
|
|
285
|
+
const outputByPathSuffix = [
|
|
286
|
+
[".md", outputs.diagnosis_spec],
|
|
287
|
+
[".contract.json", outputs.diagnosis_contract],
|
|
288
|
+
[".review.json", outputs.diagnosis_review],
|
|
289
|
+
];
|
|
290
|
+
const addedHandoffs = [];
|
|
291
|
+
const verifierReplacements = new Map();
|
|
292
|
+
const nodes = fix.nodes.map((node) => {
|
|
293
|
+
if (node.kind !== "worker")
|
|
294
|
+
return node;
|
|
295
|
+
const literalReplacements = new Map();
|
|
296
|
+
const handoffInputs = node.packet_template.handoff_inputs.map((input) => {
|
|
297
|
+
if (input.source !== "external" || input.artifact_kind !== "file")
|
|
298
|
+
return input;
|
|
299
|
+
const output = outputByPathSuffix.find(([suffix]) => (input.relative_path_template?.includes("diagnosis-") === true
|
|
300
|
+
&& input.relative_path_template.endsWith(suffix)));
|
|
301
|
+
if (!output)
|
|
302
|
+
return input;
|
|
303
|
+
const handoffId = `controller:${node.node_id}:${output[1]}`;
|
|
304
|
+
addedHandoffs.push({
|
|
305
|
+
handoff_id: handoffId,
|
|
306
|
+
from_node_id: sourceNodeId,
|
|
307
|
+
output_name: output[1],
|
|
308
|
+
to_node_id: node.node_id,
|
|
309
|
+
input_name: input.name,
|
|
310
|
+
mode: "reference",
|
|
311
|
+
artifact_kind: "file",
|
|
312
|
+
required: true,
|
|
313
|
+
schema_ref: null,
|
|
314
|
+
});
|
|
315
|
+
if (input.relative_path_template) {
|
|
316
|
+
literalReplacements.set(input.relative_path_template, `{{handoff.${handoffId}.materialized_path}}`);
|
|
317
|
+
}
|
|
318
|
+
return { ...input, source: "handoff" };
|
|
319
|
+
});
|
|
320
|
+
if (literalReplacements.size === 0)
|
|
321
|
+
return node;
|
|
322
|
+
verifierReplacements.set(node.node_id, literalReplacements);
|
|
323
|
+
return {
|
|
324
|
+
...replaceLiteralTemplates(node, literalReplacements),
|
|
325
|
+
depends_on: [...new Set([...node.depends_on, sourceNodeId])],
|
|
326
|
+
packet_template: {
|
|
327
|
+
...replaceLiteralTemplates(node.packet_template, literalReplacements),
|
|
328
|
+
handoff_inputs: handoffInputs,
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
nodes,
|
|
334
|
+
handoffs: [...fix.handoffs, ...addedHandoffs],
|
|
335
|
+
verifiers: fix.verifiers.map((verifier) => {
|
|
336
|
+
const replacements = verifierReplacements.get(verifier.subject_node_id);
|
|
337
|
+
return replacements ? replaceLiteralTemplates(verifier, replacements) : verifier;
|
|
338
|
+
}),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
export function compileControllerPlan(options) {
|
|
342
|
+
const program = options.controller.runtime_program;
|
|
343
|
+
if (!program) {
|
|
344
|
+
throw new ControllerPlanCompilerError("invalid_controller_program", `controller '${options.controller_key}' has no typed runtime_program`);
|
|
345
|
+
}
|
|
346
|
+
const reuse = program.reuse;
|
|
347
|
+
const diagnosis = namespaceComponent("diagnosis", options.diagnosis_plan, DIAGNOSIS_DECISION_NODE_ID);
|
|
348
|
+
const fix = namespaceComponent("fix", options.fix_plan, FIX_DECISION_NODE_ID);
|
|
349
|
+
if (diagnosis.roots.length === 0 || fix.roots.length === 0) {
|
|
350
|
+
throw new ControllerPlanCompilerError("invalid_component_plan", "controller workflows must each expose at least one executable root");
|
|
351
|
+
}
|
|
352
|
+
const diagnosisSource = assertDecisionOutput(diagnosis, program.diagnosis.decision, "diagnosis");
|
|
353
|
+
const fixSource = assertDecisionOutput(fix, program.fix.decision, "fix");
|
|
354
|
+
const diagnosisDecisionId = DIAGNOSIS_DECISION_NODE_ID;
|
|
355
|
+
const fixDecisionId = FIX_DECISION_NODE_ID;
|
|
356
|
+
const diagnosisAllowed = [
|
|
357
|
+
program.diagnosis.approved_value,
|
|
358
|
+
program.diagnosis.request_changes_value,
|
|
359
|
+
];
|
|
360
|
+
const fixAllowed = [
|
|
361
|
+
program.fix.pass_value,
|
|
362
|
+
program.fix.continue_value,
|
|
363
|
+
program.fix.request_changes_value,
|
|
364
|
+
program.fix.blocked_value,
|
|
365
|
+
program.fix.diagnosis_contract_invalid_value,
|
|
366
|
+
program.fix.provenance_invalid_value,
|
|
367
|
+
];
|
|
368
|
+
if (new Set(diagnosisAllowed).size !== diagnosisAllowed.length) {
|
|
369
|
+
throw new ControllerPlanCompilerError("invalid_controller_program", "diagnosis verdict values must be unique");
|
|
370
|
+
}
|
|
371
|
+
if (new Set(fixAllowed).size !== fixAllowed.length) {
|
|
372
|
+
throw new ControllerPlanCompilerError("invalid_controller_program", "fix verdict values must be unique");
|
|
373
|
+
}
|
|
374
|
+
const diagnosisDecision = {
|
|
375
|
+
kind: "decision",
|
|
376
|
+
node_id: diagnosisDecisionId,
|
|
377
|
+
depends_on: reuse ? [SEAL_APPROVED_DIAGNOSIS_NODE_ID] : diagnosis.success_dependencies,
|
|
378
|
+
source: {
|
|
379
|
+
node_id: reuse ? SEAL_APPROVED_DIAGNOSIS_NODE_ID : diagnosisSource,
|
|
380
|
+
output_name: program.diagnosis.decision.output_name,
|
|
381
|
+
json_pointer: program.diagnosis.decision.json_pointer,
|
|
382
|
+
},
|
|
383
|
+
allowed_values: diagnosisAllowed,
|
|
384
|
+
};
|
|
385
|
+
const fixDecision = {
|
|
386
|
+
kind: "decision",
|
|
387
|
+
node_id: fixDecisionId,
|
|
388
|
+
depends_on: fix.success_dependencies,
|
|
389
|
+
source: {
|
|
390
|
+
node_id: fixSource,
|
|
391
|
+
output_name: program.fix.decision.output_name,
|
|
392
|
+
json_pointer: program.fix.decision.json_pointer,
|
|
393
|
+
},
|
|
394
|
+
allowed_values: fixAllowed,
|
|
395
|
+
};
|
|
396
|
+
const continueRouteId = "route:fix:continue";
|
|
397
|
+
const diagnosisAnalyzerId = diagnosis.node_id(`worker:${program.diagnosis.analyzer_step_id}`);
|
|
398
|
+
const diagnosisReviewerId = diagnosis.node_id(`worker:${program.diagnosis.reviewer_step_id}`);
|
|
399
|
+
const diagnosisReviewerGateId = diagnosis.nodes.find((node) => (node.kind === "gate" && node.depends_on.includes(diagnosisReviewerId)))?.node_id;
|
|
400
|
+
const diagnosisAnalyzerGateId = diagnosis.nodes.find((node) => (node.kind === "gate" && node.depends_on.includes(diagnosisAnalyzerId)))?.node_id;
|
|
401
|
+
if (reuse && (!diagnosisReviewerGateId || !diagnosisAnalyzerGateId)) {
|
|
402
|
+
throw new ControllerPlanCompilerError("invalid_component_plan", "diagnosis reuse requires typed analyzer and reviewer gates");
|
|
403
|
+
}
|
|
404
|
+
const diagnosisNodes = reuse
|
|
405
|
+
? diagnosis.nodes.map((node) => {
|
|
406
|
+
if (node.node_id !== diagnosisReviewerId || node.kind !== "worker")
|
|
407
|
+
return node;
|
|
408
|
+
return { ...node, depends_on: [SEAL_DIAGNOSIS_INPUTS_NODE_ID] };
|
|
409
|
+
})
|
|
410
|
+
: diagnosis.nodes;
|
|
411
|
+
const diagnosisHandoffs = reuse
|
|
412
|
+
? diagnosis.handoffs.map((handoff) => (handoff.to_node_id === diagnosisReviewerId
|
|
413
|
+
? { ...handoff, from_node_id: SEAL_DIAGNOSIS_INPUTS_NODE_ID }
|
|
414
|
+
: handoff))
|
|
415
|
+
: diagnosis.handoffs;
|
|
416
|
+
const fixWithDiagnosis = reuse
|
|
417
|
+
? controllerDiagnosisHandoffs(fix, SEAL_APPROVED_DIAGNOSIS_NODE_ID, reuse.outputs)
|
|
418
|
+
: { nodes: fix.nodes, verifiers: fix.verifiers, handoffs: fix.handoffs };
|
|
419
|
+
const baseDiagnosisRoutes = reuse
|
|
420
|
+
? diagnosis.routes.filter((route) => (route.from_node_id !== diagnosisAnalyzerGateId
|
|
421
|
+
&& route.from_node_id !== diagnosisReviewerGateId))
|
|
422
|
+
: diagnosis.routes;
|
|
423
|
+
const routes = [
|
|
424
|
+
...baseDiagnosisRoutes,
|
|
425
|
+
...fix.routes,
|
|
426
|
+
...(reuse ? [
|
|
427
|
+
decisionRoute("route:diagnosis-source:fresh", DIAGNOSIS_SOURCE_DECISION_NODE_ID, "fresh", diagnosisAnalyzerId),
|
|
428
|
+
decisionRoute("route:diagnosis-source:legacy", DIAGNOSIS_SOURCE_DECISION_NODE_ID, "legacy", ADOPT_DIAGNOSIS_NODE_ID),
|
|
429
|
+
decisionRoute("route:diagnosis-source:canonical", DIAGNOSIS_SOURCE_DECISION_NODE_ID, "canonical", IMPORT_DIAGNOSIS_NODE_ID),
|
|
430
|
+
{
|
|
431
|
+
...diagnosis.routes.find((route) => (route.from_node_id === diagnosisAnalyzerGateId && route.condition.kind === "gate" && route.condition.outcome === "pass")),
|
|
432
|
+
to_node_id: SEAL_DIAGNOSIS_INPUTS_NODE_ID,
|
|
433
|
+
},
|
|
434
|
+
...diagnosis.routes.filter((route) => (route.from_node_id === diagnosisAnalyzerGateId && route.condition.kind === "gate" && route.condition.outcome === "fail")),
|
|
435
|
+
alwaysRoute("route:diagnosis:legacy-sealed", ADOPT_DIAGNOSIS_NODE_ID, SEAL_DIAGNOSIS_INPUTS_NODE_ID),
|
|
436
|
+
alwaysRoute("route:diagnosis:canonical-sealed", IMPORT_DIAGNOSIS_NODE_ID, SEAL_DIAGNOSIS_INPUTS_NODE_ID),
|
|
437
|
+
decisionRoute("route:diagnosis-approval:fresh", DIAGNOSIS_APPROVAL_SOURCE_DECISION_NODE_ID, "fresh", diagnosisReviewerId),
|
|
438
|
+
decisionRoute("route:diagnosis-approval:legacy", DIAGNOSIS_APPROVAL_SOURCE_DECISION_NODE_ID, "legacy", diagnosisReviewerId),
|
|
439
|
+
decisionRoute("route:diagnosis-approval:canonical", DIAGNOSIS_APPROVAL_SOURCE_DECISION_NODE_ID, "canonical", IMPORT_DIAGNOSIS_REVIEW_NODE_ID),
|
|
440
|
+
{
|
|
441
|
+
...diagnosis.routes.find((route) => (route.from_node_id === diagnosisReviewerGateId && route.condition.kind === "gate" && route.condition.outcome === "pass")),
|
|
442
|
+
to_node_id: SEAL_APPROVED_DIAGNOSIS_NODE_ID,
|
|
443
|
+
},
|
|
444
|
+
...diagnosis.routes.filter((route) => (route.from_node_id === diagnosisReviewerGateId && route.condition.kind === "gate" && route.condition.outcome === "fail")),
|
|
445
|
+
alwaysRoute("route:diagnosis:imported-review-sealed", IMPORT_DIAGNOSIS_REVIEW_NODE_ID, SEAL_APPROVED_DIAGNOSIS_NODE_ID),
|
|
446
|
+
] : []),
|
|
447
|
+
...fix.roots.map((root, index) => decisionRoute(`route:diagnosis:approved:${index}`, diagnosisDecisionId, program.diagnosis.approved_value, root)),
|
|
448
|
+
decisionRoute("route:diagnosis:request-changes", diagnosisDecisionId, program.diagnosis.request_changes_value, TERMINAL_NODE_IDS.diagnosis_invalid),
|
|
449
|
+
decisionRoute("route:fix:pass", fixDecisionId, program.fix.pass_value, TERMINAL_NODE_IDS.passed),
|
|
450
|
+
decisionRoute(continueRouteId, fixDecisionId, program.fix.continue_value, TERMINAL_NODE_IDS.max_rounds),
|
|
451
|
+
decisionRoute("route:fix:request-changes", fixDecisionId, program.fix.request_changes_value, TERMINAL_NODE_IDS.request_changes),
|
|
452
|
+
decisionRoute("route:fix:blocked", fixDecisionId, program.fix.blocked_value, TERMINAL_NODE_IDS.blocked),
|
|
453
|
+
decisionRoute("route:fix:diagnosis-invalid", fixDecisionId, program.fix.diagnosis_contract_invalid_value, TERMINAL_NODE_IDS.diagnosis_invalid),
|
|
454
|
+
decisionRoute("route:fix:provenance-invalid", fixDecisionId, program.fix.provenance_invalid_value, TERMINAL_NODE_IDS.provenance_invalid),
|
|
455
|
+
];
|
|
456
|
+
const retrySegment = {
|
|
457
|
+
retry_segment_id: "retry:fix-rounds",
|
|
458
|
+
node_ids: fixWithDiagnosis.nodes.filter((node) => (node.kind === "worker" || node.kind === "system" || node.kind === "interaction")).map((node) => node.node_id),
|
|
459
|
+
retry_from_node_id: fix.roots[0],
|
|
460
|
+
max_rounds: options.controller.max_rounds,
|
|
461
|
+
backoff: "none",
|
|
462
|
+
initial_delay_ms: 0,
|
|
463
|
+
max_delay_ms: null,
|
|
464
|
+
retryable_outcomes: [],
|
|
465
|
+
continue_route_id: continueRouteId,
|
|
466
|
+
};
|
|
467
|
+
const reuseNodes = reuse ? [
|
|
468
|
+
{
|
|
469
|
+
kind: "system",
|
|
470
|
+
node_id: DIAGNOSIS_SOURCE_NODE_ID,
|
|
471
|
+
depends_on: [],
|
|
472
|
+
input_ports: [{ name: reuse.mode_input, source: "run_input" }],
|
|
473
|
+
output_contract: [systemStructuredOutput(DIAGNOSIS_SOURCE_OUTPUT, DIAGNOSIS_SOURCE_SCHEMA, "Typed diagnosis source selection")],
|
|
474
|
+
operation: { kind: "resolve_diagnosis_source", mode_input: reuse.mode_input, output_name: DIAGNOSIS_SOURCE_OUTPUT },
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
kind: "decision",
|
|
478
|
+
node_id: DIAGNOSIS_SOURCE_DECISION_NODE_ID,
|
|
479
|
+
depends_on: [DIAGNOSIS_SOURCE_NODE_ID],
|
|
480
|
+
source: { node_id: DIAGNOSIS_SOURCE_NODE_ID, output_name: DIAGNOSIS_SOURCE_OUTPUT, json_pointer: "/mode" },
|
|
481
|
+
allowed_values: ["fresh", "legacy", "canonical"],
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
kind: "system",
|
|
485
|
+
node_id: ADOPT_DIAGNOSIS_NODE_ID,
|
|
486
|
+
depends_on: [DIAGNOSIS_SOURCE_NODE_ID],
|
|
487
|
+
input_ports: [
|
|
488
|
+
{ name: reuse.legacy.diagnosis_spec_reference_input, source: "run_input" },
|
|
489
|
+
{ name: reuse.legacy.diagnosis_spec_digest_input, source: "run_input" },
|
|
490
|
+
{ name: reuse.legacy.diagnosis_contract_reference_input, source: "run_input" },
|
|
491
|
+
{ name: reuse.legacy.diagnosis_contract_digest_input, source: "run_input" },
|
|
492
|
+
],
|
|
493
|
+
output_contract: [
|
|
494
|
+
systemArtifactOutput(reuse.outputs.diagnosis_spec, "Adopted diagnosis spec snapshot"),
|
|
495
|
+
systemArtifactOutput(reuse.outputs.diagnosis_contract, "Adopted diagnosis contract snapshot"),
|
|
496
|
+
],
|
|
497
|
+
operation: {
|
|
498
|
+
kind: "adopt_artifact_bundle",
|
|
499
|
+
artifacts: [
|
|
500
|
+
{ source_reference_input: reuse.legacy.diagnosis_spec_reference_input, expected_digest_input: reuse.legacy.diagnosis_spec_digest_input, output_name: reuse.outputs.diagnosis_spec, artifact_kind: "file" },
|
|
501
|
+
{ source_reference_input: reuse.legacy.diagnosis_contract_reference_input, expected_digest_input: reuse.legacy.diagnosis_contract_digest_input, output_name: reuse.outputs.diagnosis_contract, artifact_kind: "file" },
|
|
502
|
+
],
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
kind: "system",
|
|
507
|
+
node_id: IMPORT_DIAGNOSIS_NODE_ID,
|
|
508
|
+
depends_on: [DIAGNOSIS_SOURCE_NODE_ID],
|
|
509
|
+
input_ports: [
|
|
510
|
+
{ name: reuse.canonical.source_plan_input, source: "run_input" },
|
|
511
|
+
{ name: reuse.canonical.diagnosis_result_input, source: "run_input" },
|
|
512
|
+
],
|
|
513
|
+
output_contract: [
|
|
514
|
+
systemArtifactOutput(reuse.outputs.diagnosis_spec, "Imported canonical diagnosis spec"),
|
|
515
|
+
systemArtifactOutput(reuse.outputs.diagnosis_contract, "Imported canonical diagnosis contract"),
|
|
516
|
+
],
|
|
517
|
+
operation: {
|
|
518
|
+
kind: "import_result_bundle",
|
|
519
|
+
source_plan_input: reuse.canonical.source_plan_input,
|
|
520
|
+
source_result_input: reuse.canonical.diagnosis_result_input,
|
|
521
|
+
required_outputs: [
|
|
522
|
+
{ source_output_name: reuse.outputs.diagnosis_spec, output_name: reuse.outputs.diagnosis_spec },
|
|
523
|
+
{ source_output_name: reuse.outputs.diagnosis_contract, output_name: reuse.outputs.diagnosis_contract },
|
|
524
|
+
],
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
kind: "system",
|
|
529
|
+
node_id: SEAL_DIAGNOSIS_INPUTS_NODE_ID,
|
|
530
|
+
depends_on: [],
|
|
531
|
+
input_ports: [],
|
|
532
|
+
output_contract: [
|
|
533
|
+
systemArtifactOutput(reuse.outputs.diagnosis_spec, "Current-run diagnosis spec"),
|
|
534
|
+
systemArtifactOutput(reuse.outputs.diagnosis_contract, "Current-run diagnosis contract"),
|
|
535
|
+
systemStructuredOutput(DIAGNOSIS_ORIGIN_OUTPUT, DIAGNOSIS_ORIGIN_SCHEMA, "Current-run diagnosis origin"),
|
|
536
|
+
],
|
|
537
|
+
operation: {
|
|
538
|
+
kind: "seal_result_bundle",
|
|
539
|
+
origin_output_name: DIAGNOSIS_ORIGIN_OUTPUT,
|
|
540
|
+
candidates: [
|
|
541
|
+
{ required_node_ids: [diagnosisAnalyzerId, diagnosisAnalyzerGateId], outputs: [
|
|
542
|
+
{ source_node_id: diagnosisAnalyzerId, source_output_name: reuse.outputs.diagnosis_spec, output_name: reuse.outputs.diagnosis_spec },
|
|
543
|
+
{ source_node_id: diagnosisAnalyzerId, source_output_name: reuse.outputs.diagnosis_contract, output_name: reuse.outputs.diagnosis_contract },
|
|
544
|
+
] },
|
|
545
|
+
{ required_node_ids: [ADOPT_DIAGNOSIS_NODE_ID], outputs: [
|
|
546
|
+
{ source_node_id: ADOPT_DIAGNOSIS_NODE_ID, source_output_name: reuse.outputs.diagnosis_spec, output_name: reuse.outputs.diagnosis_spec },
|
|
547
|
+
{ source_node_id: ADOPT_DIAGNOSIS_NODE_ID, source_output_name: reuse.outputs.diagnosis_contract, output_name: reuse.outputs.diagnosis_contract },
|
|
548
|
+
] },
|
|
549
|
+
{ required_node_ids: [IMPORT_DIAGNOSIS_NODE_ID], outputs: [
|
|
550
|
+
{ source_node_id: IMPORT_DIAGNOSIS_NODE_ID, source_output_name: reuse.outputs.diagnosis_spec, output_name: reuse.outputs.diagnosis_spec },
|
|
551
|
+
{ source_node_id: IMPORT_DIAGNOSIS_NODE_ID, source_output_name: reuse.outputs.diagnosis_contract, output_name: reuse.outputs.diagnosis_contract },
|
|
552
|
+
] },
|
|
553
|
+
],
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
kind: "decision",
|
|
558
|
+
node_id: DIAGNOSIS_APPROVAL_SOURCE_DECISION_NODE_ID,
|
|
559
|
+
depends_on: [SEAL_DIAGNOSIS_INPUTS_NODE_ID],
|
|
560
|
+
source: { node_id: SEAL_DIAGNOSIS_INPUTS_NODE_ID, output_name: DIAGNOSIS_ORIGIN_OUTPUT, json_pointer: "/mode" },
|
|
561
|
+
allowed_values: ["fresh", "legacy", "canonical"],
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
kind: "system",
|
|
565
|
+
node_id: IMPORT_DIAGNOSIS_REVIEW_NODE_ID,
|
|
566
|
+
depends_on: [SEAL_DIAGNOSIS_INPUTS_NODE_ID],
|
|
567
|
+
input_ports: [
|
|
568
|
+
{ name: reuse.canonical.source_plan_input, source: "run_input" },
|
|
569
|
+
{ name: reuse.canonical.diagnosis_result_input, source: "run_input" },
|
|
570
|
+
{ name: reuse.canonical.review_result_input, source: "run_input" },
|
|
571
|
+
],
|
|
572
|
+
output_contract: [
|
|
573
|
+
systemArtifactOutput(reuse.outputs.diagnosis_review, "Imported canonical diagnosis review"),
|
|
574
|
+
systemStructuredOutput(reuse.outputs.diagnosis_review_verdict, program.diagnosis.decision.schema_ref, "Imported canonical diagnosis review verdict"),
|
|
575
|
+
],
|
|
576
|
+
operation: {
|
|
577
|
+
kind: "import_review",
|
|
578
|
+
source_plan_input: reuse.canonical.source_plan_input,
|
|
579
|
+
source_diagnosis_result_input: reuse.canonical.diagnosis_result_input,
|
|
580
|
+
source_review_result_input: reuse.canonical.review_result_input,
|
|
581
|
+
policy_id: reuse.canonical.approval_reuse.policy_id,
|
|
582
|
+
required_outputs: [
|
|
583
|
+
{ source_output_name: reuse.outputs.diagnosis_review, output_name: reuse.outputs.diagnosis_review },
|
|
584
|
+
{ source_output_name: reuse.outputs.diagnosis_review_verdict, output_name: reuse.outputs.diagnosis_review_verdict },
|
|
585
|
+
],
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
kind: "system",
|
|
590
|
+
node_id: SEAL_APPROVED_DIAGNOSIS_NODE_ID,
|
|
591
|
+
depends_on: [],
|
|
592
|
+
input_ports: [],
|
|
593
|
+
output_contract: [
|
|
594
|
+
systemArtifactOutput(reuse.outputs.diagnosis_spec, "Approved diagnosis spec"),
|
|
595
|
+
systemArtifactOutput(reuse.outputs.diagnosis_contract, "Approved diagnosis contract"),
|
|
596
|
+
systemArtifactOutput(reuse.outputs.diagnosis_review, "Approved diagnosis review"),
|
|
597
|
+
systemStructuredOutput(reuse.outputs.diagnosis_review_verdict, program.diagnosis.decision.schema_ref, "Approved diagnosis review verdict"),
|
|
598
|
+
],
|
|
599
|
+
operation: {
|
|
600
|
+
kind: "seal_result_bundle",
|
|
601
|
+
candidates: [
|
|
602
|
+
{ required_node_ids: [SEAL_DIAGNOSIS_INPUTS_NODE_ID, diagnosisReviewerId, diagnosisReviewerGateId], outputs: [
|
|
603
|
+
{ source_node_id: SEAL_DIAGNOSIS_INPUTS_NODE_ID, source_output_name: reuse.outputs.diagnosis_spec, output_name: reuse.outputs.diagnosis_spec },
|
|
604
|
+
{ source_node_id: SEAL_DIAGNOSIS_INPUTS_NODE_ID, source_output_name: reuse.outputs.diagnosis_contract, output_name: reuse.outputs.diagnosis_contract },
|
|
605
|
+
{ source_node_id: diagnosisReviewerId, source_output_name: reuse.outputs.diagnosis_review, output_name: reuse.outputs.diagnosis_review },
|
|
606
|
+
{ source_node_id: diagnosisReviewerId, source_output_name: reuse.outputs.diagnosis_review_verdict, output_name: reuse.outputs.diagnosis_review_verdict },
|
|
607
|
+
] },
|
|
608
|
+
{ required_node_ids: [SEAL_DIAGNOSIS_INPUTS_NODE_ID, IMPORT_DIAGNOSIS_REVIEW_NODE_ID], outputs: [
|
|
609
|
+
{ source_node_id: SEAL_DIAGNOSIS_INPUTS_NODE_ID, source_output_name: reuse.outputs.diagnosis_spec, output_name: reuse.outputs.diagnosis_spec },
|
|
610
|
+
{ source_node_id: SEAL_DIAGNOSIS_INPUTS_NODE_ID, source_output_name: reuse.outputs.diagnosis_contract, output_name: reuse.outputs.diagnosis_contract },
|
|
611
|
+
{ source_node_id: IMPORT_DIAGNOSIS_REVIEW_NODE_ID, source_output_name: reuse.outputs.diagnosis_review, output_name: reuse.outputs.diagnosis_review },
|
|
612
|
+
{ source_node_id: IMPORT_DIAGNOSIS_REVIEW_NODE_ID, source_output_name: reuse.outputs.diagnosis_review_verdict, output_name: reuse.outputs.diagnosis_review_verdict },
|
|
613
|
+
] },
|
|
614
|
+
],
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
] : [];
|
|
618
|
+
const nodes = [
|
|
619
|
+
...reuseNodes.slice(0, 2),
|
|
620
|
+
...diagnosisNodes,
|
|
621
|
+
...reuseNodes.slice(2),
|
|
622
|
+
diagnosisDecision,
|
|
623
|
+
...fixWithDiagnosis.nodes,
|
|
624
|
+
fixDecision,
|
|
625
|
+
terminalNode(TERMINAL_NODE_IDS.passed, "pass"),
|
|
626
|
+
terminalNode(TERMINAL_NODE_IDS.request_changes, "request_changes"),
|
|
627
|
+
terminalNode(TERMINAL_NODE_IDS.blocked, "blocked"),
|
|
628
|
+
terminalNode(TERMINAL_NODE_IDS.diagnosis_invalid, "diagnosis_contract_invalid"),
|
|
629
|
+
terminalNode(TERMINAL_NODE_IDS.provenance_invalid, "provenance_invalid"),
|
|
630
|
+
terminalNode(TERMINAL_NODE_IDS.max_rounds, "max_rounds"),
|
|
631
|
+
terminalNode(TERMINAL_NODE_IDS.cancelled, "cancelled"),
|
|
632
|
+
];
|
|
633
|
+
const readable = new Set([
|
|
634
|
+
...options.diagnosis_plan.workspace_policy.readable_path_templates,
|
|
635
|
+
...options.fix_plan.workspace_policy.readable_path_templates,
|
|
636
|
+
]);
|
|
637
|
+
const writable = new Set([
|
|
638
|
+
...options.diagnosis_plan.workspace_policy.writable_path_templates,
|
|
639
|
+
...options.fix_plan.workspace_policy.writable_path_templates,
|
|
640
|
+
]);
|
|
641
|
+
const payload = {
|
|
642
|
+
schema_version: EXECUTABLE_RUN_PLAN_SCHEMA_VERSION,
|
|
643
|
+
target: { kind: "controller", key: options.controller_key },
|
|
644
|
+
input_contract: {
|
|
645
|
+
inputs: [...mergeInputs([options.diagnosis_plan, options.fix_plan]), ...controllerReuseInputs(options.controller)]
|
|
646
|
+
.sort((left, right) => left.name.localeCompare(right.name)),
|
|
647
|
+
allow_unknown_inputs: false,
|
|
648
|
+
},
|
|
649
|
+
nodes,
|
|
650
|
+
routes,
|
|
651
|
+
retry_segments: [retrySegment],
|
|
652
|
+
terminals: [
|
|
653
|
+
{ terminal_id: "pass", status: "succeeded", reason_code: "PASS" },
|
|
654
|
+
{ terminal_id: "request_changes", status: "failed", reason_code: "REQUEST_CHANGES" },
|
|
655
|
+
{ terminal_id: "blocked", status: "failed", reason_code: "BLOCKED" },
|
|
656
|
+
{ terminal_id: "diagnosis_contract_invalid", status: "failed", reason_code: "DIAGNOSIS_CONTRACT_INVALID" },
|
|
657
|
+
{ terminal_id: "provenance_invalid", status: "failed", reason_code: "PROVENANCE_INVALID" },
|
|
658
|
+
{ terminal_id: "max_rounds", status: "failed", reason_code: "MAX_ROUNDS" },
|
|
659
|
+
{ terminal_id: "cancelled", status: "cancelled", reason_code: "run_cancelled" },
|
|
660
|
+
],
|
|
661
|
+
verifier_plan: [...diagnosis.verifiers, ...fixWithDiagnosis.verifiers],
|
|
662
|
+
handoff_plan: [...diagnosisHandoffs, ...fixWithDiagnosis.handoffs],
|
|
663
|
+
workspace_policy: {
|
|
664
|
+
default_isolation: options.diagnosis_plan.workspace_policy.default_isolation === "worktree"
|
|
665
|
+
|| options.fix_plan.workspace_policy.default_isolation === "worktree"
|
|
666
|
+
? "worktree"
|
|
667
|
+
: "none",
|
|
668
|
+
relative_workspace_root: ".",
|
|
669
|
+
allow_network: options.diagnosis_plan.workspace_policy.allow_network
|
|
670
|
+
&& options.fix_plan.workspace_policy.allow_network,
|
|
671
|
+
readable_path_templates: [...readable],
|
|
672
|
+
writable_path_templates: [...writable],
|
|
673
|
+
},
|
|
674
|
+
required_capabilities: {
|
|
675
|
+
worktree_isolation: options.diagnosis_plan.required_capabilities.worktree_isolation
|
|
676
|
+
|| options.fix_plan.required_capabilities.worktree_isolation,
|
|
677
|
+
parallelism: options.diagnosis_plan.required_capabilities.parallelism
|
|
678
|
+
|| options.fix_plan.required_capabilities.parallelism,
|
|
679
|
+
cancellation: false,
|
|
680
|
+
hook_identity: options.diagnosis_plan.required_capabilities.hook_identity
|
|
681
|
+
|| options.fix_plan.required_capabilities.hook_identity,
|
|
682
|
+
caller_attestation: options.diagnosis_plan.required_capabilities.caller_attestation
|
|
683
|
+
|| options.fix_plan.required_capabilities.caller_attestation,
|
|
684
|
+
human_interaction: options.diagnosis_plan.required_capabilities.human_interaction
|
|
685
|
+
|| options.fix_plan.required_capabilities.human_interaction,
|
|
686
|
+
direct_structured_submission: true,
|
|
687
|
+
network_policy_enforcement: options.diagnosis_plan.required_capabilities.network_policy_enforcement
|
|
688
|
+
|| options.fix_plan.required_capabilities.network_policy_enforcement,
|
|
689
|
+
},
|
|
690
|
+
source_provenance: options.source_provenance,
|
|
691
|
+
};
|
|
692
|
+
return parseExecutableRunPlanPayload(payload);
|
|
693
|
+
}
|