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
@@ -1,3 +1,5 @@
1
+ import { resolvePostCheckCommand } from "../compiler/compile.js";
2
+ import { EXECUTABLE_RUN_PLAN_SCHEMA_VERSION, deriveWorkerRuntimeInputNames, parseExecutableRunPlanPayload, } from "./executable-run-plan.js";
1
3
  const DEFAULT_RETRYABLE_OUTCOMES = [
2
4
  "non_zero_exit",
3
5
  "malformed_result",
@@ -114,15 +116,20 @@ function collectConstraints(step, role, variables) {
114
116
  for (const checkpoint of step.checkpoints ?? []) {
115
117
  constraints.push(checkpointText(checkpoint, variables));
116
118
  }
117
- return constraints;
119
+ const bindings = Object.entries(step.canonical_handoff_path_bindings ?? {});
120
+ return constraints.map((constraint) => bindings.reduce((rendered, [path, replacement]) => rendered.replaceAll(path, replacement), constraint));
118
121
  }
119
122
  function createOutputContract(step, variables) {
120
123
  const outputs = (step.handoff?.produces ?? []).map((artifact) => ({
121
124
  name: artifactIdentity(artifact, variables, step.id),
122
125
  kind: outputKind(artifact.type),
126
+ artifact_kind: artifact.type,
123
127
  required: artifact.required !== false,
124
128
  description: substitute(artifact.description, variables),
125
- schema_ref: null,
129
+ relative_path_template: artifact.path === undefined
130
+ ? null
131
+ : substitute(artifact.path, variables),
132
+ schema_ref: artifact.schema_ref ?? null,
126
133
  }));
127
134
  const names = new Set();
128
135
  for (const output of outputs) {
@@ -133,6 +140,34 @@ function createOutputContract(step, variables) {
133
140
  }
134
141
  return outputs;
135
142
  }
143
+ function createHandoffInputContract(step, variables) {
144
+ return (step.handoff?.consumes ?? []).map((artifact) => ({
145
+ name: artifactIdentity(artifact, variables, step.id),
146
+ source: artifact.from ? "handoff" : "external",
147
+ mode: handoffMode(artifact.type),
148
+ artifact_kind: artifact.type,
149
+ required: artifact.required !== false,
150
+ description: substitute(artifact.description, variables),
151
+ relative_path_template: artifact.path === undefined
152
+ ? null
153
+ : substitute(artifact.path, variables),
154
+ schema_ref: artifact.schema_ref ?? null,
155
+ }));
156
+ }
157
+ function workerResponseContract(role) {
158
+ if (role.output_schema === undefined)
159
+ return { kind: "none" };
160
+ if (typeof role.output_schema === "string") {
161
+ return { kind: "named", schema_ref: role.output_schema };
162
+ }
163
+ return {
164
+ kind: "document",
165
+ relative_path_template: role.output_schema.path ?? null,
166
+ required_sections: role.output_schema.required_sections ?? [],
167
+ format: (role.output_schema.format ?? null),
168
+ converter_command: role.output_schema.converter ?? null,
169
+ };
170
+ }
136
171
  function findSourceOutput(sourceStep, outputName, variables) {
137
172
  const matches = (sourceStep.handoff?.produces ?? []).filter((artifact) => artifactIdentity(artifact, variables, sourceStep.id) === outputName);
138
173
  if (matches.length !== 1) {
@@ -155,8 +190,8 @@ function createHandoffs(step, stepById, variables) {
155
190
  const inputName = artifactIdentity(artifact, variables, step.id);
156
191
  const sourceArtifact = findSourceOutput(sourceStep, inputName, variables);
157
192
  const mode = handoffMode(artifact.type);
158
- if (handoffMode(sourceArtifact.type) !== mode) {
159
- throw new WorkflowPlanAdapterError("invalid_handoff", `handoff '${inputName}' changes mode between '${sourceStep.id}' and '${step.id}'`, step.id);
193
+ if (sourceArtifact.type !== artifact.type) {
194
+ throw new WorkflowPlanAdapterError("invalid_handoff", `handoff '${inputName}' changes artifact kind between '${sourceStep.id}' and '${step.id}'`, step.id);
160
195
  }
161
196
  const base = {
162
197
  binding_id: `${step.id}:${inputName}`,
@@ -172,10 +207,14 @@ function createHandoffs(step, stepById, variables) {
172
207
  declarations.push({ ...base, mode, selection: null });
173
208
  }
174
209
  else if (mode === "structured") {
175
- declarations.push({ ...base, mode, schema_ref: null });
210
+ declarations.push({ ...base, mode, schema_ref: artifact.schema_ref ?? null });
176
211
  }
177
212
  else {
178
- declarations.push({ ...base, mode });
213
+ declarations.push({
214
+ ...base,
215
+ mode,
216
+ artifact_kind: artifact.type,
217
+ });
179
218
  }
180
219
  }
181
220
  const bindingIds = new Set();
@@ -225,6 +264,7 @@ function renderBinding(binding) {
225
264
  prefix,
226
265
  ` Reference kind: ${binding.reference.kind}`,
227
266
  ` Reference value: ${binding.reference.value}`,
267
+ ` Artifact id: ${binding.reference.artifact_id ?? "legacy-unavailable"}`,
228
268
  ];
229
269
  }
230
270
  if (binding.mode === "quote") {
@@ -434,7 +474,10 @@ export function adaptWorkflowPlan(plan, options) {
434
474
  constraints: collectConstraints(step, role, variables),
435
475
  handoffs,
436
476
  external_inputs,
477
+ handoff_inputs: createHandoffInputContract(step, variables),
478
+ runtime_input_names: [],
437
479
  outputs: createOutputContract(step, variables),
480
+ response_contract: workerResponseContract(role),
438
481
  isolation: isolationForStep(plan.parallel_groups, step.id),
439
482
  retry: retryPolicy(plan, step, options, workflowLoop !== null),
440
483
  };
@@ -481,3 +524,456 @@ export function adaptWorkflowPlan(plan, options) {
481
524
  },
482
525
  }));
483
526
  }
527
+ const EXECUTABLE_WORKFLOW_RETRY_SEGMENT_ID = "retry:workflow";
528
+ const SUCCEEDED_TERMINAL_NODE_ID = "terminal:succeeded";
529
+ const FAILED_TERMINAL_NODE_ID = "terminal:failed";
530
+ const CANCELLED_TERMINAL_NODE_ID = "terminal:cancelled";
531
+ function workerNodeId(stepId) {
532
+ return `worker:${stepId}`;
533
+ }
534
+ function gateNodeId(stepId) {
535
+ return `gate:${stepId}`;
536
+ }
537
+ function gatePassRouteId(stepId) {
538
+ return `route:gate:${stepId}:pass`;
539
+ }
540
+ function gateFailRouteId(stepId) {
541
+ return `route:gate:${stepId}:fail`;
542
+ }
543
+ function completionVerifier(stepId, completion, index) {
544
+ const verifier_id = `verifier:${stepId}:completion:${index}`;
545
+ if (completion.file_exists !== undefined) {
546
+ return {
547
+ verifier_id,
548
+ subject_node_id: workerNodeId(stepId),
549
+ kind: "artifact_exists",
550
+ relative_path_template: completion.file_exists,
551
+ };
552
+ }
553
+ if (completion.file_not_empty !== undefined) {
554
+ return {
555
+ verifier_id,
556
+ subject_node_id: workerNodeId(stepId),
557
+ kind: "artifact_non_empty",
558
+ relative_path_template: completion.file_not_empty,
559
+ };
560
+ }
561
+ if (completion.file_contains !== undefined) {
562
+ return {
563
+ verifier_id,
564
+ subject_node_id: workerNodeId(stepId),
565
+ kind: "artifact_contains",
566
+ relative_path_template: completion.file_contains.path,
567
+ pattern: completion.file_contains.pattern,
568
+ };
569
+ }
570
+ if (completion.evaluator_result !== undefined) {
571
+ return {
572
+ verifier_id,
573
+ subject_node_id: workerNodeId(stepId),
574
+ kind: "evaluator_result",
575
+ relative_path_template: completion.evaluator_result.path,
576
+ require_score: completion.evaluator_result.require_score === true,
577
+ };
578
+ }
579
+ return {
580
+ verifier_id,
581
+ subject_node_id: workerNodeId(stepId),
582
+ kind: "command",
583
+ command: completion.command_success ?? "",
584
+ relative_working_directory: ".",
585
+ };
586
+ }
587
+ function checkpointVerifier(stepId, checkpoint, index) {
588
+ return {
589
+ verifier_id: `verifier:${stepId}:checkpoint:${index}`,
590
+ subject_node_id: workerNodeId(stepId),
591
+ kind: "checkpoint",
592
+ assertion: checkpoint.assert,
593
+ command: checkpoint.command ?? null,
594
+ message: checkpoint.message,
595
+ action: checkpoint.action ?? "block",
596
+ };
597
+ }
598
+ function rolePostCheckVerifier(stepId, postCheck, index) {
599
+ return {
600
+ verifier_id: `verifier:${stepId}:post-check:${index}`,
601
+ subject_node_id: workerNodeId(stepId),
602
+ kind: "checkpoint",
603
+ assertion: `role_post_check_${index}`,
604
+ command: resolvePostCheckCommand(postCheck),
605
+ message: `Post-check failed: ${postCheck}`,
606
+ action: "warn",
607
+ };
608
+ }
609
+ function executableVerifiersForStep(step, role) {
610
+ return [
611
+ ...(step.completion ?? []).map((completion, index) => (completionVerifier(step.id, completion, index))),
612
+ ...(step.checkpoints ?? []).map((checkpoint, index) => (checkpointVerifier(step.id, checkpoint, index))),
613
+ ...(role.post_checks ?? []).map((postCheck, index) => (rolePostCheckVerifier(step.id, postCheck, index))),
614
+ ];
615
+ }
616
+ function renderExecutableHandoffInput(handoff) {
617
+ const prefix = `- ${handoff.input_name} (${handoff.mode}, ${handoff.required ? "required" : "optional"}): ${handoff.description}`;
618
+ if (handoff.mode === "reference") {
619
+ if (handoff.artifact_kind === "git_commit") {
620
+ return [
621
+ prefix,
622
+ ` Commit id: {{handoff.${handoff.binding_id}.commit_oid}}`,
623
+ ` Artifact id: {{handoff.${handoff.binding_id}.artifact_id}}`,
624
+ ];
625
+ }
626
+ return [
627
+ prefix,
628
+ ` Materialized path: {{handoff.${handoff.binding_id}.materialized_path}}`,
629
+ ` Artifact id: {{handoff.${handoff.binding_id}.artifact_id}}`,
630
+ ];
631
+ }
632
+ if (handoff.mode === "quote") {
633
+ return [prefix, ` Exact text: {{handoff.${handoff.binding_id}.exact_text}}`];
634
+ }
635
+ if (handoff.mode === "structured") {
636
+ return [prefix, ` JSON value: {{handoff.${handoff.binding_id}.json}}`];
637
+ }
638
+ return [prefix];
639
+ }
640
+ function renderResponseContract(contract) {
641
+ if (contract.kind === "none")
642
+ return [];
643
+ if (contract.kind === "named") {
644
+ return ["## Response Contract", `- Schema: ${contract.schema_ref}`, ""];
645
+ }
646
+ const lines = ["## Response Contract"];
647
+ if (contract.relative_path_template !== null) {
648
+ lines.push(`- Output path: ${contract.relative_path_template}`);
649
+ }
650
+ if (contract.format !== null)
651
+ lines.push(`- Final format: ${contract.format}`);
652
+ for (const section of contract.required_sections) {
653
+ lines.push(`- Required section: ## ${section}`);
654
+ }
655
+ return [...lines, ""];
656
+ }
657
+ function executableStandalonePrompt(plan, adapted) {
658
+ const { step, role, constraints, handoffs, external_inputs: externalInputs, outputs } = adapted;
659
+ const lines = [
660
+ `# ${plan.name}: ${step.id}`,
661
+ "",
662
+ "You are executing one standalone workflow Step in an independent session.",
663
+ "Use only the task, constraints, and declared inputs below. Do not rely on or request previous conversation history.",
664
+ "Complete only this Step and stop after returning its declared result.",
665
+ "",
666
+ "## Role",
667
+ `${step.role}: ${role.description}`,
668
+ "",
669
+ "## Task",
670
+ step.prompt ?? step.description,
671
+ "",
672
+ "## Constraints",
673
+ ...(constraints.length > 0
674
+ ? constraints.map((constraint) => `- ${constraint}`)
675
+ : ["- No additional current-Step constraints are declared."]),
676
+ "",
677
+ "## Inputs",
678
+ ];
679
+ if (handoffs.length === 0 && externalInputs.length === 0) {
680
+ lines.push("- None. This Step has no declared upstream delivery.");
681
+ }
682
+ else {
683
+ for (const handoff of handoffs)
684
+ lines.push(...renderExecutableHandoffInput(handoff));
685
+ for (const artifact of externalInputs) {
686
+ lines.push(...renderExternalInput(artifact, {}, step.id));
687
+ }
688
+ }
689
+ lines.push("", "## Outputs", ...renderOutputProtocol(outputs), "", ...renderResponseContract(adapted.response_contract));
690
+ if (role.success_criteria?.length) {
691
+ lines.push("## Success Criteria", ...role.success_criteria.map((criterion) => `- ${criterion}`), "");
692
+ }
693
+ if (role.failure_modes?.length) {
694
+ lines.push("## Failure Modes To Avoid", ...role.failure_modes.map((failure) => `- ${failure}`), "");
695
+ }
696
+ return lines.join("\n");
697
+ }
698
+ function executableHandoffs(adapted) {
699
+ return adapted.flatMap((entry) => entry.handoffs.flatMap((handoff) => {
700
+ if (handoff.mode === "none")
701
+ return [];
702
+ const input = entry.handoff_inputs.find((candidate) => candidate.name === handoff.input_name);
703
+ if (!input || input.source !== "handoff") {
704
+ throw new WorkflowPlanAdapterError("invalid_handoff", `step '${entry.step.id}' is missing typed input '${handoff.input_name}'`, entry.step.id);
705
+ }
706
+ return [{
707
+ handoff_id: handoff.binding_id,
708
+ from_node_id: workerNodeId(handoff.source.step_id),
709
+ output_name: handoff.source.output_name,
710
+ to_node_id: workerNodeId(entry.step.id),
711
+ input_name: handoff.input_name,
712
+ mode: handoff.mode,
713
+ artifact_kind: input.artifact_kind,
714
+ required: handoff.required,
715
+ schema_ref: handoff.mode === "structured" ? handoff.schema_ref : null,
716
+ }];
717
+ }));
718
+ }
719
+ /**
720
+ * Compile a WorkflowPlan into the immutable, provider-neutral plan payload.
721
+ * The legacy closure-based adapter above remains unchanged until the S8 cutover.
722
+ */
723
+ export function adaptWorkflowPlanToExecutableRunPlan(plan, options) {
724
+ if (!options.target_key) {
725
+ throw new WorkflowPlanAdapterError("invalid_plan", "target_key must be non-empty");
726
+ }
727
+ const cancellationGrace = options.cancellation_grace_ms ?? 5_000;
728
+ requireNonNegativeInteger(cancellationGrace, "cancellation_grace_ms");
729
+ if (options.timeout_ms !== undefined && options.timeout_ms !== null) {
730
+ requireNonNegativeInteger(options.timeout_ms, "timeout_ms");
731
+ }
732
+ const adapterOptions = {
733
+ roles: options.roles,
734
+ variables: {},
735
+ working_directory: ".",
736
+ timeout_ms: options.timeout_ms,
737
+ cancellation_grace_ms: cancellationGrace,
738
+ retry_initial_delay_ms: options.retry_initial_delay_ms,
739
+ retry_max_delay_ms: options.retry_max_delay_ms,
740
+ retryable_outcomes: options.retryable_outcomes,
741
+ };
742
+ const workflowLoop = validatePlanRouting(plan, adapterOptions);
743
+ for (const step of plan.steps) {
744
+ if (workflowLoop === null && step.run_if !== null) {
745
+ throw new WorkflowPlanAdapterError("unsupported_routing", `step '${step.id}' uses run_if without a typed retry segment`, step.id);
746
+ }
747
+ if (step.blocked_items_path !== undefined) {
748
+ throw new WorkflowPlanAdapterError("unsupported_routing", `step '${step.id}' uses failure routing not represented by the executable plan`, step.id);
749
+ }
750
+ if (step.on_fail === "retry_with_feedback"
751
+ && (step.max_attempts === undefined || step.max_attempts < 2)) {
752
+ throw new WorkflowPlanAdapterError("invalid_plan", `step '${step.id}' retry_with_feedback requires max_attempts of at least 2`, step.id);
753
+ }
754
+ }
755
+ const stepById = new Map(plan.steps.map((step) => [step.id, step]));
756
+ const adapted = plan.steps.map((step) => {
757
+ const role = options.roles[step.role];
758
+ if (!role) {
759
+ throw new WorkflowPlanAdapterError("missing_role", `step '${step.id}' references missing role '${step.role}'`, step.id);
760
+ }
761
+ const { handoffs, external_inputs } = createHandoffs(step, stepById, {});
762
+ return {
763
+ step,
764
+ role,
765
+ constraints: [
766
+ ...(options.global_constraints ?? []),
767
+ ...collectConstraints(step, role, {}),
768
+ ],
769
+ handoffs,
770
+ external_inputs,
771
+ handoff_inputs: createHandoffInputContract(step, {}),
772
+ runtime_input_names: [],
773
+ outputs: createOutputContract(step, {}),
774
+ response_contract: workerResponseContract(role),
775
+ isolation: isolationForStep(plan.parallel_groups, step.id),
776
+ retry: retryPolicy(plan, step, adapterOptions, workflowLoop !== null),
777
+ };
778
+ });
779
+ const verifiersByStep = new Map(adapted.map((entry) => [
780
+ entry.step.id,
781
+ executableVerifiersForStep(entry.step, entry.role),
782
+ ]));
783
+ const handoffPlan = executableHandoffs(adapted);
784
+ const effectiveNodeId = (stepId) => ((verifiersByStep.get(stepId)?.length ?? 0) > 0
785
+ ? gateNodeId(stepId)
786
+ : workerNodeId(stepId));
787
+ const dependenciesByStep = new Map(adapted.map((entry) => [
788
+ entry.step.id,
789
+ addHandoffDependencies(entry.step, entry.handoffs).map(effectiveNodeId),
790
+ ]));
791
+ const dependentSteps = new Map();
792
+ for (const entry of adapted) {
793
+ for (const dependency of addHandoffDependencies(entry.step, entry.handoffs)) {
794
+ const dependents = dependentSteps.get(dependency) ?? [];
795
+ dependents.push(entry.step.id);
796
+ dependentSteps.set(dependency, dependents);
797
+ }
798
+ }
799
+ const workerNodes = adapted.map((entry) => {
800
+ const worker = {
801
+ kind: "worker",
802
+ node_id: workerNodeId(entry.step.id),
803
+ depends_on: dependenciesByStep.get(entry.step.id) ?? [],
804
+ role: entry.step.role,
805
+ packet_template: {
806
+ standalone_prompt: executableStandalonePrompt(plan, entry),
807
+ constraints: entry.constraints,
808
+ runtime_input_names: [],
809
+ handoff_inputs: entry.handoff_inputs,
810
+ output_contract: entry.outputs,
811
+ response_contract: entry.response_contract,
812
+ },
813
+ workspace: {
814
+ isolation: entry.isolation,
815
+ relative_working_directory: ".",
816
+ read_only: entry.step.enforce?.read_only === true,
817
+ readable_path_templates: entry.role.scope?.read ?? ["**/*"],
818
+ writable_path_templates: entry.step.enforce?.read_only === true
819
+ ? []
820
+ : [
821
+ ...(entry.role.scope?.write ?? []),
822
+ ...(entry.step.enforce?.additional_write_paths ?? []),
823
+ ],
824
+ scope_relaxation: entry.step.enforce?.relax_after_iteration === undefined
825
+ ? null
826
+ : {
827
+ from_iteration: entry.step.enforce.relax_after_iteration,
828
+ effect: "disable_path_scope",
829
+ },
830
+ },
831
+ tool_policy: {
832
+ allow: entry.role.tool_permissions?.allow ?? null,
833
+ deny: entry.role.tool_permissions?.deny ?? [],
834
+ },
835
+ attempt_policy: {
836
+ timeout_ms: options.timeout_ms ?? null,
837
+ cancellation_grace_ms: cancellationGrace,
838
+ max_attempts: entry.retry.max_attempts,
839
+ backoff: entry.retry.backoff,
840
+ initial_delay_ms: entry.retry.initial_delay_ms,
841
+ max_delay_ms: entry.retry.max_delay_ms,
842
+ retryable_outcomes: entry.retry.retryable_outcomes.filter((outcome) => outcome !== "success"),
843
+ retry_segment_id: workflowLoop?.step_ids.includes(entry.step.id)
844
+ ? EXECUTABLE_WORKFLOW_RETRY_SEGMENT_ID
845
+ : null,
846
+ },
847
+ };
848
+ return {
849
+ ...worker,
850
+ packet_template: {
851
+ ...worker.packet_template,
852
+ runtime_input_names: deriveWorkerRuntimeInputNames(worker, verifiersByStep.get(entry.step.id) ?? [], handoffPlan),
853
+ },
854
+ };
855
+ });
856
+ const routes = [];
857
+ const gateNodes = adapted.flatMap((entry) => {
858
+ const verifiers = verifiersByStep.get(entry.step.id) ?? [];
859
+ if (verifiers.length === 0)
860
+ return [];
861
+ const passTargets = dependentSteps.get(entry.step.id)?.map(workerNodeId)
862
+ ?? [SUCCEEDED_TERMINAL_NODE_ID];
863
+ routes.push(...passTargets.map((toNodeId, index) => ({
864
+ route_id: index === 0
865
+ ? gatePassRouteId(entry.step.id)
866
+ : `${gatePassRouteId(entry.step.id)}:${index}`,
867
+ from_node_id: gateNodeId(entry.step.id),
868
+ to_node_id: toNodeId,
869
+ condition: {
870
+ kind: "gate",
871
+ gate_node_id: gateNodeId(entry.step.id),
872
+ outcome: "pass",
873
+ },
874
+ })), {
875
+ route_id: gateFailRouteId(entry.step.id),
876
+ from_node_id: gateNodeId(entry.step.id),
877
+ to_node_id: FAILED_TERMINAL_NODE_ID,
878
+ condition: {
879
+ kind: "gate",
880
+ gate_node_id: gateNodeId(entry.step.id),
881
+ outcome: "fail",
882
+ },
883
+ });
884
+ return [{
885
+ kind: "gate",
886
+ node_id: gateNodeId(entry.step.id),
887
+ depends_on: [workerNodeId(entry.step.id)],
888
+ verifier_ids: verifiers.map((verifier) => verifier.verifier_id),
889
+ pass_route_ids: passTargets.map((_target, index) => (index === 0
890
+ ? gatePassRouteId(entry.step.id)
891
+ : `${gatePassRouteId(entry.step.id)}:${index}`)),
892
+ fail_route_id: gateFailRouteId(entry.step.id),
893
+ }];
894
+ });
895
+ const leafNodeIds = adapted
896
+ .filter((entry) => (dependentSteps.get(entry.step.id)?.length ?? 0) === 0)
897
+ .map((entry) => effectiveNodeId(entry.step.id));
898
+ const retrySegments = workflowLoop === null ? [] : [{
899
+ retry_segment_id: EXECUTABLE_WORKFLOW_RETRY_SEGMENT_ID,
900
+ node_ids: workflowLoop.step_ids.flatMap((stepId) => [
901
+ workerNodeId(stepId),
902
+ ...((verifiersByStep.get(stepId)?.length ?? 0) > 0 ? [gateNodeId(stepId)] : []),
903
+ ]),
904
+ retry_from_node_id: workerNodeId(workflowLoop.retry_from),
905
+ max_rounds: workflowLoop.max_retries + 1,
906
+ backoff: workflowLoop.backoff,
907
+ initial_delay_ms: workflowLoop.initial_delay_ms,
908
+ max_delay_ms: workflowLoop.max_delay_ms,
909
+ retryable_outcomes: DEFAULT_RETRYABLE_OUTCOMES,
910
+ }];
911
+ const writablePaths = adapted.flatMap((entry) => (entry.step.enforce?.read_only === true
912
+ ? []
913
+ : [
914
+ ...(entry.role.scope?.write ?? []),
915
+ ...(entry.step.enforce?.additional_write_paths ?? []),
916
+ ]));
917
+ const readablePaths = adapted.flatMap((entry) => entry.role.scope?.read ?? ["**/*"]);
918
+ const allowNetwork = options.allow_network ?? true;
919
+ const payload = {
920
+ schema_version: EXECUTABLE_RUN_PLAN_SCHEMA_VERSION,
921
+ target: { kind: "workflow", key: options.target_key },
922
+ input_contract: {
923
+ ...options.input_contract,
924
+ inputs: options.input_contract.inputs.filter((input) => workerNodes.some((worker) => worker.packet_template.runtime_input_names.includes(input.name))),
925
+ },
926
+ nodes: [
927
+ ...workerNodes,
928
+ ...gateNodes,
929
+ {
930
+ kind: "terminal",
931
+ node_id: SUCCEEDED_TERMINAL_NODE_ID,
932
+ depends_on: leafNodeIds,
933
+ terminal_id: "succeeded",
934
+ },
935
+ {
936
+ kind: "terminal",
937
+ node_id: FAILED_TERMINAL_NODE_ID,
938
+ depends_on: [],
939
+ terminal_id: "failed",
940
+ },
941
+ {
942
+ kind: "terminal",
943
+ node_id: CANCELLED_TERMINAL_NODE_ID,
944
+ depends_on: [],
945
+ terminal_id: "cancelled",
946
+ },
947
+ ],
948
+ routes,
949
+ retry_segments: retrySegments,
950
+ terminals: [
951
+ { terminal_id: "succeeded", status: "succeeded", reason_code: "workflow_completed" },
952
+ { terminal_id: "failed", status: "failed", reason_code: "workflow_failed" },
953
+ { terminal_id: "cancelled", status: "cancelled", reason_code: "run_cancelled" },
954
+ ],
955
+ verifier_plan: [...verifiersByStep.values()].flat(),
956
+ handoff_plan: handoffPlan,
957
+ workspace_policy: {
958
+ default_isolation: adapted.some((entry) => entry.isolation === "worktree")
959
+ ? "worktree"
960
+ : "none",
961
+ relative_workspace_root: ".",
962
+ allow_network: allowNetwork,
963
+ readable_path_templates: [...new Set(readablePaths)],
964
+ writable_path_templates: [...new Set(writablePaths)],
965
+ },
966
+ required_capabilities: {
967
+ worktree_isolation: adapted.some((entry) => entry.isolation === "worktree"),
968
+ parallelism: plan.parallel_groups.some((group) => group.step_ids.length > 1),
969
+ cancellation: false,
970
+ hook_identity: false,
971
+ caller_attestation: plan.steps.some((step) => step.canonical_requires_attested_independent_submit === true),
972
+ human_interaction: false,
973
+ direct_structured_submission: true,
974
+ network_policy_enforcement: !allowNetwork,
975
+ },
976
+ source_provenance: options.source_provenance,
977
+ };
978
+ return parseExecutableRunPlanPayload(payload);
979
+ }
@@ -39,6 +39,7 @@ export declare function createWorkflowRuntimeManifest(options: {
39
39
  resolvedVariables: Record<string, string>;
40
40
  workflowAsset?: string;
41
41
  additionalTemplateVariableRefs?: string[];
42
+ runtimeInputRefs?: string[];
42
43
  additionalRuntimeValues?: unknown;
43
44
  }>;
44
45
  compiledAt: string;
@@ -116,12 +116,16 @@ export function createWorkflowRuntimeManifest(options) {
116
116
  .filter((name) => !WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name))
117
117
  .filter((name) => Object.prototype.hasOwnProperty.call(planVariables, name))
118
118
  .map((name) => [name, planVariables[name]]));
119
+ const explicitRuntimeInputRefs = item.runtimeInputRefs ?? [];
120
+ const explicitRuntimeInputs = new Set(explicitRuntimeInputRefs);
119
121
  const undefinedVariableRefs = templateVariableRefs.filter((name) => !WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name) &&
122
+ !explicitRuntimeInputs.has(name) &&
120
123
  !Object.prototype.hasOwnProperty.call(planVariables, name));
121
124
  if (undefinedVariableRefs.length > 0) {
122
125
  throw new Error(`Workflow ${workflow} references undefined template variable(s): ${undefinedVariableRefs.join(", ")}. Declare them in DNA variables or use $ARGUMENTS/task_id for runtime input.`);
123
126
  }
124
127
  const runtimeInputRefs = templateVariableRefs.filter((name) => WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name) ||
128
+ explicitRuntimeInputs.has(name) ||
125
129
  !Object.prototype.hasOwnProperty.call(planVariables, name));
126
130
  const requiresArguments = workflowUsesArguments(plan, planVariables) ||
127
131
  valueUsesArguments(item.additionalRuntimeValues, planVariables) ||
@@ -132,6 +136,7 @@ export function createWorkflowRuntimeManifest(options) {
132
136
  template_variable_refs: templateVariableRefs,
133
137
  runtime_input_refs: [
134
138
  ...(requiresArguments ? ["ARGUMENTS"] : []),
139
+ ...explicitRuntimeInputRefs,
135
140
  ...runtimeInputRefs.filter((name) => name !== "ARGUMENTS"),
136
141
  ].filter((name, index, names) => names.indexOf(name) === index).sort(),
137
142
  requires_arguments: requiresArguments,
@@ -5,9 +5,10 @@
5
5
  * commit, and leaves preservation or cleanup to an explicit Controller decision.
6
6
  * Team naming, CLI parsing, generated shell, and automatic rollback are excluded.
7
7
  */
8
- import type { AttemptId, RunId, StepId } from "./run-contracts.js";
8
+ import type { AttemptId, CanonicalWorkspaceAllocation, CanonicalWorkspacePlan, RunId, StepId } from "./run-contracts.js";
9
+ import type { AllocateAttemptWorkspaceRequest, AttemptWorkspaceAuthorityPort, CommittedWorkspaceAuthorityRequest, WorkspaceMutationAuthority } from "./canonical-run-service.js";
9
10
  export type WorkspaceIsolation = "none" | "worktree";
10
- export type WorkspaceIsolationErrorCode = "invalid_request" | "not_git_repository" | "git_command_failed" | "path_conflict" | "branch_conflict" | "workspace_mismatch" | "unsafe_cleanup_target";
11
+ export type WorkspaceIsolationErrorCode = "invalid_request" | "not_git_repository" | "git_command_failed" | "path_conflict" | "branch_conflict" | "workspace_mismatch" | "workspace_conflict" | "unsafe_cleanup_target";
11
12
  export declare class WorkspaceIsolationError extends Error {
12
13
  readonly code: WorkspaceIsolationErrorCode;
13
14
  constructor(code: WorkspaceIsolationErrorCode, message: string);
@@ -95,8 +96,34 @@ export type WorkspaceLifecycleResult = {
95
96
  readonly working_directory: string;
96
97
  readonly branch_deleted: boolean;
97
98
  };
99
+ /**
100
+ * Resolve an existing directory below a committed workspace without following
101
+ * symbolic-link components. This is suitable for verifier/process cwd values.
102
+ */
103
+ export declare function canonicalWorkspaceDirectory(workspaceRoot: string, relativeDirectory: string, label?: string): Promise<string>;
98
104
  export declare function planAttemptWorkspace(request: AttemptWorkspaceRequest): AttemptWorkspacePlan;
99
105
  export declare function allocateAttemptWorkspace(plan: AttemptWorkspacePlan): AttemptWorkspaceAllocation;
106
+ export interface LocalAttemptWorkspaceAuthorityOptions {
107
+ readonly lock_root?: string;
108
+ readonly lock_wait_ms?: number;
109
+ readonly lock_stale_ms?: number;
110
+ readonly lock_retry_ms?: number;
111
+ readonly lock_heartbeat_ms?: number;
112
+ }
113
+ export declare class LocalAttemptWorkspaceAuthority implements AttemptWorkspaceAuthorityPort {
114
+ private readonly lockRoot;
115
+ private readonly lockWaitMs;
116
+ private readonly lockStaleMs;
117
+ private readonly lockRetryMs;
118
+ private readonly lockHeartbeatMs;
119
+ constructor(options?: LocalAttemptWorkspaceAuthorityOptions);
120
+ plan(request: AllocateAttemptWorkspaceRequest): Promise<CanonicalWorkspacePlan>;
121
+ ensureWithExclusiveAuthority<T>(plan: CanonicalWorkspacePlan, operation: (allocation: CanonicalWorkspaceAllocation) => Promise<T>): Promise<T>;
122
+ withExclusiveMutationAuthority<T>(request: CommittedWorkspaceAuthorityRequest, operation: (authority: WorkspaceMutationAuthority) => Promise<T>): Promise<T>;
123
+ private nextFencingEpoch;
124
+ private assertCommittedAllocation;
125
+ private withLock;
126
+ }
100
127
  export declare function inspectAttemptWorkspace(workspace: AttemptWorkspaceAllocation): WorkspaceInspection;
101
128
  export declare function applyWorkspaceLifecycleDecision(workspace: AttemptWorkspaceAllocation, decision: WorkspaceLifecycleDecision): WorkspaceLifecycleResult;
102
129
  export declare function defaultWorktreeRoot(repositoryRoot: string): string;