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.
Files changed (131) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +227 -87
  4. package/dist/cli/commands/run-lifecycle.d.ts +61 -11
  5. package/dist/cli/commands/run-lifecycle.js +184 -50
  6. package/dist/cli/commands/run-observer.d.ts +15 -0
  7. package/dist/cli/commands/run-observer.js +210 -0
  8. package/dist/cli/commands/run.d.ts +16 -20
  9. package/dist/cli/commands/run.js +461 -659
  10. package/dist/cli/commands/sync.js +16 -12
  11. package/dist/cli/index.js +14 -5
  12. package/dist/compiler/compile.d.ts +1 -0
  13. package/dist/compiler/compile.js +1 -1
  14. package/dist/compiler/controller.d.ts +16 -0
  15. package/dist/compiler/controller.js +693 -0
  16. package/dist/compiler/input-resolver.d.ts +2 -0
  17. package/dist/compiler/input-resolver.js +22 -6
  18. package/dist/hooks/cli.d.ts +45 -0
  19. package/dist/hooks/cli.js +263 -31
  20. package/dist/hooks/host-event.d.ts +57 -0
  21. package/dist/hooks/host-event.js +187 -0
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.js +1 -0
  24. package/dist/hooks/protocol.d.ts +7 -0
  25. package/dist/hooks/schema.d.ts +1 -0
  26. package/dist/hooks/schema.js +89 -1
  27. package/dist/hooks/state.d.ts +23 -1
  28. package/dist/hooks/state.js +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-run.d.ts +4 -0
  33. package/dist/mcp/tools-run.js +419 -0
  34. package/dist/mcp/tools-state.d.ts +1 -1
  35. package/dist/mcp/tools-state.js +13 -241
  36. package/dist/runtime/artifact-store.d.ts +146 -0
  37. package/dist/runtime/artifact-store.js +1436 -0
  38. package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
  39. package/dist/runtime/canonical-attempt-outcome.js +195 -0
  40. package/dist/runtime/canonical-json.d.ts +10 -0
  41. package/dist/runtime/canonical-json.js +105 -0
  42. package/dist/runtime/canonical-run-application.d.ts +93 -0
  43. package/dist/runtime/canonical-run-application.js +229 -0
  44. package/dist/runtime/canonical-run-service.d.ts +454 -0
  45. package/dist/runtime/canonical-run-service.js +4760 -0
  46. package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
  47. package/dist/runtime/canonical-runtime-composition.js +34 -0
  48. package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
  49. package/dist/runtime/canonical-runtime-projection.js +4 -0
  50. package/dist/runtime/canonical-target-compiler.d.ts +46 -0
  51. package/dist/runtime/canonical-target-compiler.js +514 -0
  52. package/dist/runtime/claude-agent-identity.d.ts +2 -0
  53. package/dist/runtime/claude-agent-identity.js +21 -0
  54. package/dist/runtime/executable-run-plan.d.ts +328 -0
  55. package/dist/runtime/executable-run-plan.js +1485 -0
  56. package/dist/runtime/execution-authority.d.ts +83 -0
  57. package/dist/runtime/execution-authority.js +98 -0
  58. package/dist/runtime/handoff-resolver.js +4 -1
  59. package/dist/runtime/harness-pull-adapter.d.ts +146 -0
  60. package/dist/runtime/harness-pull-adapter.js +315 -0
  61. package/dist/runtime/index.d.ts +39 -14
  62. package/dist/runtime/index.js +29 -7
  63. package/dist/runtime/local-execution-authority.d.ts +194 -0
  64. package/dist/runtime/local-execution-authority.js +2897 -0
  65. package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
  66. package/dist/runtime/local-execution-reconciliation.js +107 -0
  67. package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
  68. package/dist/runtime/local-execution-supervisor-script.js +793 -0
  69. package/dist/runtime/local-provider-sandbox.d.ts +42 -0
  70. package/dist/runtime/local-provider-sandbox.js +154 -0
  71. package/dist/runtime/local-verifier-execution.d.ts +24 -0
  72. package/dist/runtime/local-verifier-execution.js +290 -0
  73. package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
  74. package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
  75. package/dist/runtime/plan-store.d.ts +30 -0
  76. package/dist/runtime/plan-store.js +231 -0
  77. package/dist/runtime/process-tree.d.ts +9 -0
  78. package/dist/runtime/process-tree.js +97 -24
  79. package/dist/runtime/providers/codex.js +2 -2
  80. package/dist/runtime/push-driver.d.ts +184 -0
  81. package/dist/runtime/push-driver.js +1002 -0
  82. package/dist/runtime/result-store.d.ts +34 -3
  83. package/dist/runtime/result-store.js +1073 -2
  84. package/dist/runtime/run-binding.d.ts +68 -0
  85. package/dist/runtime/run-binding.js +278 -0
  86. package/dist/runtime/run-contracts.d.ts +575 -0
  87. package/dist/runtime/run-contracts.js +58 -7
  88. package/dist/runtime/run-controller.d.ts +1 -6
  89. package/dist/runtime/run-controller.js +0 -41
  90. package/dist/runtime/run-store.d.ts +85 -7
  91. package/dist/runtime/run-store.js +2528 -186
  92. package/dist/runtime/skill-adapter.d.ts +18 -7
  93. package/dist/runtime/skill-adapter.js +100 -742
  94. package/dist/runtime/structured-output-validator.d.ts +44 -0
  95. package/dist/runtime/structured-output-validator.js +171 -0
  96. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  97. package/dist/runtime/verifier-command-binding.js +162 -0
  98. package/dist/runtime/verifier.d.ts +46 -6
  99. package/dist/runtime/verifier.js +355 -53
  100. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  101. package/dist/runtime/windows-job-keeper.js +231 -0
  102. package/dist/runtime/worker-executor.d.ts +17 -1
  103. package/dist/runtime/worker-executor.js +26 -5
  104. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  105. package/dist/runtime/workflow-plan-adapter.js +502 -6
  106. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  107. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  108. package/dist/runtime/workspace-isolation.d.ts +29 -2
  109. package/dist/runtime/workspace-isolation.js +488 -11
  110. package/dist/runtime/workspace-observation.d.ts +44 -0
  111. package/dist/runtime/workspace-observation.js +216 -0
  112. package/dist/schema/controller-registry.d.ts +0 -7
  113. package/dist/schema/controller-registry.js +0 -8
  114. package/dist/schema/types.d.ts +84 -10
  115. package/dist/schema/types.js +2 -0
  116. package/dist/schema/validate.js +151 -1
  117. package/dist/schema/validators/controllers.d.ts +1 -1
  118. package/dist/schema/validators/controllers.js +126 -41
  119. package/dist/schema/workflow-authoring-contract.js +2 -0
  120. package/dist/schema/yaml-parser.js +1 -1
  121. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  122. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  123. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  124. package/native/windows-job-keeper/README.md +80 -0
  125. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  127. package/native/windows-job-keeper/keeper.c +979 -0
  128. package/native/windows-job-keeper/manifest.json +33 -0
  129. package/package.json +6 -2
  130. package/scripts/build-windows-job-keeper.mjs +172 -0
  131. 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
+ }