intentdna 1.9.0 → 1.9.1

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 (130) 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 +2877 -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 +771 -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/push-driver.d.ts +184 -0
  80. package/dist/runtime/push-driver.js +1002 -0
  81. package/dist/runtime/result-store.d.ts +34 -3
  82. package/dist/runtime/result-store.js +1073 -2
  83. package/dist/runtime/run-binding.d.ts +68 -0
  84. package/dist/runtime/run-binding.js +278 -0
  85. package/dist/runtime/run-contracts.d.ts +575 -0
  86. package/dist/runtime/run-contracts.js +58 -7
  87. package/dist/runtime/run-controller.d.ts +1 -6
  88. package/dist/runtime/run-controller.js +0 -41
  89. package/dist/runtime/run-store.d.ts +85 -7
  90. package/dist/runtime/run-store.js +2528 -186
  91. package/dist/runtime/skill-adapter.d.ts +18 -7
  92. package/dist/runtime/skill-adapter.js +100 -742
  93. package/dist/runtime/structured-output-validator.d.ts +44 -0
  94. package/dist/runtime/structured-output-validator.js +171 -0
  95. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  96. package/dist/runtime/verifier-command-binding.js +162 -0
  97. package/dist/runtime/verifier.d.ts +46 -6
  98. package/dist/runtime/verifier.js +355 -53
  99. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  100. package/dist/runtime/windows-job-keeper.js +231 -0
  101. package/dist/runtime/worker-executor.d.ts +17 -1
  102. package/dist/runtime/worker-executor.js +26 -5
  103. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  104. package/dist/runtime/workflow-plan-adapter.js +502 -6
  105. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  106. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  107. package/dist/runtime/workspace-isolation.d.ts +29 -2
  108. package/dist/runtime/workspace-isolation.js +488 -11
  109. package/dist/runtime/workspace-observation.d.ts +44 -0
  110. package/dist/runtime/workspace-observation.js +216 -0
  111. package/dist/schema/controller-registry.d.ts +0 -7
  112. package/dist/schema/controller-registry.js +0 -8
  113. package/dist/schema/types.d.ts +84 -10
  114. package/dist/schema/types.js +2 -0
  115. package/dist/schema/validate.js +151 -1
  116. package/dist/schema/validators/controllers.d.ts +1 -1
  117. package/dist/schema/validators/controllers.js +126 -41
  118. package/dist/schema/workflow-authoring-contract.js +2 -0
  119. package/dist/schema/yaml-parser.js +1 -1
  120. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  121. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  122. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  123. package/native/windows-job-keeper/README.md +80 -0
  124. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  125. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/keeper.c +979 -0
  127. package/native/windows-job-keeper/manifest.json +33 -0
  128. package/package.json +5 -1
  129. package/scripts/build-windows-job-keeper.mjs +172 -0
  130. package/scripts/verify-windows-job-keeper.mjs +184 -0
@@ -1,68 +1,90 @@
1
- /**
2
- * Intent DNA - canonical dna run command.
3
- *
4
- * The CLI compiles one workflow into Controller definitions, persists one
5
- * durable run ledger, and launches one disposable provider session per Step
6
- * attempt. Cross-Step inputs come only from committed results.
7
- */
8
- import { createHash, randomUUID } from "node:crypto";
9
- import { isAbsolute, join, relative, resolve, sep, } from "node:path";
10
- import { cascadeDNA } from "../../compiler/cascade.js";
11
- import { compileFromFiles, expandDNAInputFiles, loadDNA, } from "../../compiler/index.js";
12
- import { compileWorkflow } from "../../compiler/workflow.js";
13
- import { DNAStateManager } from "../../hooks/state-manager.js";
14
- import { appendEvidenceCaptureEvent, EVIDENCE_CAPTURE_SCHEMA_VERSION, } from "../../hooks/state.js";
15
- import { compileAllRolesToAgentMD, toKebabCase, writeAgentMDFiles, } from "../../runtime/agent-md.js";
16
- import { HandoffResolver } from "../../runtime/handoff-resolver.js";
17
- import { createCompiledIR, writeCompiledIR } from "../../runtime/plugin-adapter.js";
18
- import { createWorkflowRuntimeManifest, WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES, } from "../../runtime/workflow-runtime-manifest.js";
19
- import { defaultResolvedVariableScopes, indexWorkflowVariableScopes, resolvedVariablesForNamespace, } from "../../runtime/workflow-variable-scopes.js";
20
- import { ImmutableResultStore } from "../../runtime/result-store.js";
21
- import { RunController, } from "../../runtime/run-controller.js";
22
- import { DurableRunStore, RunStoreError, } from "../../runtime/run-store.js";
1
+ /** Intent DNA - canonical dna run CLI transport. */
2
+ import { createHash } from "node:crypto";
3
+ import { join, resolve } from "node:path";
4
+ import { toKebabCase } from "../../runtime/agent-md.js";
5
+ import { CanonicalRunApplication, } from "../../runtime/canonical-run-application.js";
6
+ import { canonicalRuntimeRoot as runtimeRoot, createCanonicalRuntimeComposition, } from "../../runtime/canonical-runtime-composition.js";
7
+ import { LocalAttemptExecutionAuthority, } from "../../runtime/local-execution-authority.js";
8
+ import { reconcileLocalExecutionPreparations } from "../../runtime/local-execution-reconciliation.js";
9
+ import { LocalProviderNetworkSandbox } from "../../runtime/local-provider-sandbox.js";
10
+ import { ProductionLocalVerifierExecutionPort } from "../../runtime/local-verifier-execution.js";
11
+ import { canonicalizeJson } from "../../runtime/canonical-json.js";
23
12
  import { createClaudeExecutionProvider } from "../../runtime/providers/claude.js";
24
13
  import { createCodexExecutionProvider } from "../../runtime/providers/codex.js";
25
- import { runCheckpointVerifier, runCompletionVerifier, } from "../../runtime/verifier.js";
26
- import { adaptWorkflowPlan, adaptWorkflowRetryLoop, } from "../../runtime/workflow-plan-adapter.js";
27
- import { executeWorkerAttempt, } from "../../runtime/worker-executor.js";
28
- import { allocateAttemptWorkspace, applyWorkspaceLifecycleDecision, planAttemptWorkspace, } from "../../runtime/workspace-isolation.js";
29
- import { runLifecycleCancel, runLifecycleInspect, runLifecycleResume, runLifecycleStart, runLifecycleStatus, } from "./run-lifecycle.js";
14
+ import { PushDriver, } from "../../runtime/push-driver.js";
15
+ import { CALLER_ATTESTATION_SCHEMA_VERSION, } from "../../runtime/run-contracts.js";
16
+ import { validateWorkflowRuntimeManifest, WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES, } from "../../runtime/workflow-runtime-manifest.js";
17
+ import { DurableRunStore } from "../../runtime/run-store.js";
18
+ import { LocalAttemptWorkspaceAuthority } from "../../runtime/workspace-isolation.js";
19
+ import { createRunAttemptObservers, durableRuntimeProjection, syncRuntimeProjection, writeTerminalDriverFailure, } from "./run-observer.js";
20
+ import { runLifecycleCancel, runLifecycleClose, runLifecycleInspect, runLifecycleResume, runLifecycleStatus, writeCanonicalOperation, } from "./run-lifecycle.js";
21
+ function canonicalInputs(metadata) {
22
+ const { workflow: _workflow, ...inputs } = metadata.variables;
23
+ return inputs;
24
+ }
25
+ function metadataForBinding(metadata, binding) {
26
+ const variables = {
27
+ workflow: metadata.target.key,
28
+ };
29
+ for (const [name, value] of Object.entries(binding.binding.validated_inputs)) {
30
+ if (typeof value === "string")
31
+ variables[name] = value;
32
+ }
33
+ return { ...metadata, variables };
34
+ }
35
+ function callerAttestationDigest(payload) {
36
+ return `sha256:${createHash("sha256")
37
+ .update(canonicalizeJson(payload), "utf8")
38
+ .digest("hex")}`;
39
+ }
40
+ function createCallerAttestationAuthority(authorityId) {
41
+ if (authorityId.length === 0) {
42
+ throw new Error("caller attestation authority id must not be empty");
43
+ }
44
+ return {
45
+ authority_id: authorityId,
46
+ attest: (request) => {
47
+ const payload = {
48
+ schema_version: CALLER_ATTESTATION_SCHEMA_VERSION,
49
+ authority_id: authorityId,
50
+ agent_id: request.worker_session_id,
51
+ session_id: request.provider_session_id ?? request.worker_session_id,
52
+ run_id: request.run_id,
53
+ step_id: request.step_id,
54
+ activation_id: request.activation_id,
55
+ attempt_id: request.attempt_id,
56
+ claim_epoch: request.claim_epoch,
57
+ worker_session_id: request.worker_session_id,
58
+ submission_key_digest: request.submission_key_digest,
59
+ execution_digest: request.execution_digest,
60
+ };
61
+ return {
62
+ kind: "caller_attestation",
63
+ attestation_ref: [
64
+ "attestation:/local-cli",
65
+ encodeURIComponent(authorityId),
66
+ encodeURIComponent(request.run_id),
67
+ encodeURIComponent(request.attempt_id),
68
+ ].join("/"),
69
+ attestation_digest: callerAttestationDigest(payload),
70
+ payload,
71
+ };
72
+ },
73
+ };
74
+ }
30
75
  class RunUsageError extends Error {
31
76
  constructor(message) {
32
77
  super(message);
33
78
  this.name = "RunUsageError";
34
79
  }
35
80
  }
36
- // ── Compatibility-only pure helpers ───────────────────────
37
81
  export function substituteTemplate(template, vars) {
38
- return template.replace(/\{\{(\w+)\}\}/g, (match, key) => (key in vars ? vars[key] : match));
82
+ const argumentsValue = vars.ARGUMENTS ?? vars.arguments ?? vars.task_id;
83
+ const resolved = argumentsValue === undefined
84
+ ? template
85
+ : template.replace(/(^|[^\\])\$ARGUMENTS/g, (_match, prefix) => `${prefix}${argumentsValue}`);
86
+ return resolved.replace(/\{\{(\w+)\}\}/g, (match, key) => key in vars ? vars[key] : match);
39
87
  }
40
- export function evaluateRunIf(condition, round) {
41
- if (condition === null)
42
- return true;
43
- const eqMatch = condition.match(/^round\s*==\s*(\d+)$/);
44
- if (eqMatch)
45
- return round === parseInt(eqMatch[1], 10);
46
- const gtMatch = condition.match(/^round\s*>\s*(\d+)$/);
47
- if (gtMatch)
48
- return round > parseInt(gtMatch[1], 10);
49
- const geMatch = condition.match(/^round\s*>=\s*(\d+)$/);
50
- if (geMatch)
51
- return round >= parseInt(geMatch[1], 10);
52
- if (condition === "retry")
53
- return round > 1;
54
- return true;
55
- }
56
- export function checkRetryNeeded(stepResults, transitions) {
57
- for (const transition of transitions) {
58
- if (transition.condition === "fail"
59
- && stepResults.get(transition.from)?.status === "fail") {
60
- return true;
61
- }
62
- }
63
- return false;
64
- }
65
- // ── Validation and durable metadata ───────────────────────
66
88
  function requireText(value, label) {
67
89
  const trimmed = value?.trim();
68
90
  if (!trimmed)
@@ -70,21 +92,18 @@ function requireText(value, label) {
70
92
  return trimmed;
71
93
  }
72
94
  function requirePositiveNumber(value, label) {
73
- if (!Number.isFinite(value) || value <= 0) {
95
+ if (!Number.isFinite(value) || value <= 0)
74
96
  throw new RunUsageError(`${label} must be a positive number`);
75
- }
76
97
  return value;
77
98
  }
78
99
  function requirePositiveInteger(value, label) {
79
- if (!Number.isSafeInteger(value) || value <= 0) {
100
+ if (!Number.isSafeInteger(value) || value <= 0)
80
101
  throw new RunUsageError(`${label} must be a positive integer`);
81
- }
82
102
  return value;
83
103
  }
84
104
  function requireNonNegativeInteger(value, label) {
85
- if (!Number.isSafeInteger(value) || value < 0) {
105
+ if (!Number.isSafeInteger(value) || value < 0)
86
106
  throw new RunUsageError(`${label} must be a non-negative integer`);
87
- }
88
107
  return value;
89
108
  }
90
109
  function asRunId(value) {
@@ -93,679 +112,471 @@ function asRunId(value) {
93
112
  function isRecord(value) {
94
113
  return typeof value === "object" && value !== null && !Array.isArray(value);
95
114
  }
96
- function readStringRecord(value, label) {
97
- if (!isRecord(value))
98
- throw new Error(`${label} must be an object`);
99
- const result = {};
100
- for (const [key, entry] of Object.entries(value)) {
101
- if (typeof entry !== "string") {
102
- throw new Error(`${label}.${key} must be a string`);
103
- }
104
- result[key] = entry;
105
- }
106
- return result;
115
+ function hasExactKeys(value, keys) {
116
+ const expected = new Set(keys);
117
+ return Object.keys(value).length === expected.size
118
+ && Object.keys(value).every((key) => expected.has(key));
119
+ }
120
+ function metadataError(detail) {
121
+ throw new Error(`run metadata is invalid: ${detail}`);
107
122
  }
108
123
  function readCanonicalMetadata(value) {
109
- if (!isRecord(value) || value.schema_version !== "intentdna.canonical_run.v1") {
110
- throw new Error("run metadata is not intentdna.canonical_run.v1");
124
+ if (isRecord(value)
125
+ && value.schema_version === "intentdna.canonical_run.v2"
126
+ && typeof value.workflow_name === "string") {
127
+ const { workflow_name: workflowName, ...legacy } = value;
128
+ return readCanonicalMetadata({
129
+ ...legacy,
130
+ schema_version: "intentdna.canonical_run.v3",
131
+ target: { kind: "workflow", key: workflowName },
132
+ });
111
133
  }
112
- const dnaFiles = value.dna_files;
113
- const context = value.context;
114
- const provider = value.provider;
115
- const timeoutMs = value.timeout_ms;
116
- const workflowAsset = value.workflow_asset;
117
- if (typeof value.project_directory !== "string"
118
- || !Array.isArray(dnaFiles)
119
- || !dnaFiles.every((entry) => typeof entry === "string")
120
- || typeof value.workflow_name !== "string"
134
+ if (!isRecord(value)
135
+ || value.schema_version !== "intentdna.canonical_run.v3"
136
+ || !hasExactKeys(value, [
137
+ "schema_version",
138
+ "project_directory",
139
+ "dna_files",
140
+ "target",
141
+ "task_id",
142
+ "context",
143
+ "variables",
144
+ "agents_directory",
145
+ "provider",
146
+ "max_concurrency",
147
+ "timeout_ms",
148
+ "cancellation_grace_ms",
149
+ "workflow_asset",
150
+ "runtime_projection",
151
+ ])
152
+ || typeof value.project_directory !== "string"
153
+ || value.project_directory.length === 0
154
+ || !Array.isArray(value.dna_files)
155
+ || !value.dna_files.every((file) => typeof file === "string" && file.length > 0)
156
+ || !isRecord(value.target)
157
+ || !hasExactKeys(value.target, ["kind", "key"])
158
+ || (value.target.kind !== "workflow" && value.target.kind !== "controller")
159
+ || typeof value.target.key !== "string"
160
+ || value.target.key.length === 0
121
161
  || typeof value.task_id !== "string"
122
- || (context !== null && typeof context !== "string")
162
+ || value.task_id.length === 0
163
+ || (value.context !== null && typeof value.context !== "string")
164
+ || !isRecord(value.variables)
165
+ || !Object.values(value.variables).every((item) => typeof item === "string")
123
166
  || typeof value.agents_directory !== "string"
124
- || !isRecord(provider)
125
- || !["claude", "codex"].includes(String(provider.kind))
126
- || (provider.executable !== null && typeof provider.executable !== "string")
127
- || typeof provider.max_budget_usd !== "number"
128
- || typeof provider.permission_mode !== "string"
167
+ || value.agents_directory.length === 0
168
+ || !isRecord(value.provider)
169
+ || !hasExactKeys(value.provider, [
170
+ "kind",
171
+ "executable",
172
+ "max_budget_usd",
173
+ "permission_mode",
174
+ ])
175
+ || (value.provider.kind !== "claude" && value.provider.kind !== "codex")
176
+ || (value.provider.executable !== null && (typeof value.provider.executable !== "string"
177
+ || value.provider.executable.length === 0))
178
+ || typeof value.provider.max_budget_usd !== "number"
179
+ || !Number.isFinite(value.provider.max_budget_usd)
180
+ || value.provider.max_budget_usd <= 0
181
+ || typeof value.provider.permission_mode !== "string"
182
+ || value.provider.permission_mode.length === 0
129
183
  || typeof value.max_concurrency !== "number"
130
- || (timeoutMs !== null && typeof timeoutMs !== "number")
184
+ || !Number.isSafeInteger(value.max_concurrency)
185
+ || value.max_concurrency <= 0
186
+ || (value.timeout_ms !== null && (typeof value.timeout_ms !== "number"
187
+ ||
188
+ !Number.isSafeInteger(value.timeout_ms)
189
+ || value.timeout_ms < 0))
131
190
  || typeof value.cancellation_grace_ms !== "number"
132
- || (workflowAsset !== null && typeof workflowAsset !== "string")) {
133
- throw new Error("run metadata is malformed");
191
+ || !Number.isSafeInteger(value.cancellation_grace_ms)
192
+ || value.cancellation_grace_ms < 0
193
+ || (value.workflow_asset !== null && typeof value.workflow_asset !== "string")
194
+ || !isRecord(value.runtime_projection)
195
+ || value.runtime_projection.ir_version !== 1
196
+ || !isRecord(value.runtime_projection.ir)
197
+ || !isRecord(value.runtime_projection.workflow_runtime)) {
198
+ metadataError("expected strict intentdna.canonical_run.v3 data");
199
+ }
200
+ const compiledHash = value.runtime_projection.compiled_ir_hash;
201
+ if (typeof compiledHash !== "string"
202
+ || value.runtime_projection.ir.compiled_ir_hash !== compiledHash) {
203
+ metadataError("runtime projection digest is missing or inconsistent");
204
+ }
205
+ const manifest = validateWorkflowRuntimeManifest(value.runtime_projection.workflow_runtime, compiledHash);
206
+ if (!manifest.valid) {
207
+ metadataError(manifest.reason);
208
+ }
209
+ if (value.target.kind === "workflow" && !manifest.manifest.workflows[value.target.key]) {
210
+ metadataError(`runtime projection does not contain workflow ${value.target.key}`);
134
211
  }
135
- const metadata = {
136
- schema_version: "intentdna.canonical_run.v1",
137
- project_directory: value.project_directory,
138
- dna_files: dnaFiles,
139
- workflow_name: value.workflow_name,
140
- task_id: value.task_id,
141
- context,
142
- variables: readStringRecord(value.variables, "run metadata variables"),
143
- agents_directory: value.agents_directory,
144
- provider: {
145
- kind: provider.kind,
146
- executable: provider.executable === null
147
- ? null
148
- : requireText(provider.executable, "run metadata provider.executable"),
149
- max_budget_usd: requirePositiveNumber(provider.max_budget_usd, "run metadata provider.max_budget_usd"),
150
- permission_mode: requireText(provider.permission_mode, "run metadata provider.permission_mode"),
212
+ return value;
213
+ }
214
+ function providerConfiguration(metadata) {
215
+ return {
216
+ provider: metadata.provider.kind,
217
+ model: null,
218
+ options: {
219
+ ...(metadata.provider.executable === null
220
+ ? {}
221
+ : { executable: metadata.provider.executable }),
222
+ max_budget_usd: metadata.provider.max_budget_usd,
223
+ permission_mode: metadata.provider.permission_mode,
151
224
  },
152
- max_concurrency: requirePositiveInteger(value.max_concurrency, "run metadata max_concurrency"),
153
- timeout_ms: timeoutMs === null
154
- ? null
155
- : requireNonNegativeInteger(timeoutMs, "run metadata timeout_ms"),
156
- cancellation_grace_ms: requireNonNegativeInteger(value.cancellation_grace_ms, "run metadata cancellation_grace_ms"),
157
- workflow_asset: workflowAsset,
225
+ credential_references: [],
158
226
  };
159
- requireText(metadata.project_directory, "run metadata project_directory");
160
- requireText(metadata.workflow_name, "run metadata workflow_name");
161
- requireText(metadata.task_id, "run metadata task_id");
162
- requireText(metadata.agents_directory, "run metadata agents_directory");
163
- if (metadata.dna_files.length === 0) {
164
- throw new Error("run metadata dna_files must not be empty");
165
- }
166
- return metadata;
167
- }
168
- function stableJson(value) {
169
- if (Array.isArray(value)) {
170
- return `[${value.map(stableJson).join(",")}]`;
171
- }
172
- if (value !== null && typeof value === "object") {
173
- const entries = Object.entries(value)
174
- .filter(([, entry]) => entry !== undefined)
175
- .sort(([left], [right]) => left.localeCompare(right));
176
- return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
177
- }
178
- return JSON.stringify(value) ?? "null";
179
- }
180
- function planDigest(plan, roles) {
181
- const stablePlan = { ...plan, compiled_at: null };
182
- return createHash("sha256")
183
- .update(stableJson({ plan: stablePlan, roles }), "utf-8")
184
- .digest("hex");
185
227
  }
186
- function runtimeRoot(projectDirectory) {
187
- return join(projectDirectory, ".dna", "runtime");
188
- }
189
- // ── Compilation and provider construction ─────────────────
190
- async function compileRunAssets(metadata) {
191
- const dnas = [];
192
- for (const file of metadata.dna_files)
193
- dnas.push(await loadDNA(file));
194
- const cascaded = cascadeDNA(dnas);
195
- const variableScopeIndex = indexWorkflowVariableScopes(dnas);
196
- const defaultVariableScopes = defaultResolvedVariableScopes(variableScopeIndex);
197
- const ir = await compileFromFiles([...metadata.dna_files], {
198
- context: metadata.context ?? undefined,
199
- });
200
- const definition = cascaded.workflows[metadata.workflow_name];
201
- if (!definition) {
202
- throw new Error(`workflow '${metadata.workflow_name}' not found; available: `
203
- + `${Object.keys(cascaded.workflows).join(", ") || "(none)"}`);
204
- }
205
- const runtimePlans = [];
206
- for (const [workflowKey, workflowDefinition] of Object.entries(cascaded.workflows)) {
207
- const result = compileWorkflow(workflowDefinition, { workflow_key: workflowKey });
208
- if (!result.ok || !result.plan) {
209
- throw new Error(`workflow '${workflowKey}' compilation failed:\n`
210
- + result.errors.map((error) => ` ${error.path}: ${error.message}`).join("\n"));
228
+ function assertRunComposition(view, metadata, projectDirectory) {
229
+ const binding = view.run.run_binding.binding;
230
+ if (resolve(binding.project_root) !== projectDirectory
231
+ || resolve(binding.workspace_root) !== projectDirectory
232
+ || resolve(metadata.project_directory) !== projectDirectory) {
233
+ throw new Error(`run ${view.run.run_id} belongs to project ${binding.project_root}, not ${projectDirectory}`);
234
+ }
235
+ if (view.run.target.kind !== metadata.target.kind
236
+ || view.run.target.key !== metadata.target.key) {
237
+ throw new Error("run metadata target does not match the immutable run target");
238
+ }
239
+ const expectedProvider = providerConfiguration(metadata);
240
+ if (canonicalizeJson(binding.provider_config)
241
+ !== canonicalizeJson(expectedProvider)) {
242
+ throw new Error("run metadata provider projection does not match the immutable run binding");
243
+ }
244
+ for (const name of WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES) {
245
+ const pinned = binding.validated_inputs[name];
246
+ if (pinned !== undefined && pinned !== metadata.task_id) {
247
+ throw new Error(`run metadata task_id does not match pinned input ${name}`);
211
248
  }
212
- const namespace = variableScopeIndex.workflowNamespaces.get(workflowKey) ?? "";
213
- runtimePlans.push({
214
- plan: result.plan,
215
- workflow_asset: variableScopeIndex.workflowAssets.get(workflowKey) ?? null,
216
- resolved_variables: resolvedVariablesForNamespace(defaultVariableScopes, namespace, workflowKey === metadata.workflow_name ? { ...metadata.variables } : {}),
217
- });
218
249
  }
219
- const plan = runtimePlans.find((item) => item.plan.workflow_key === metadata.workflow_name)?.plan;
220
- if (!plan)
221
- throw new Error(`compiled workflow '${metadata.workflow_name}' is missing from runtime plans`);
222
- const workflowAsset = runtimePlans.find((item) => item.plan.workflow_key === metadata.workflow_name)?.workflow_asset ?? null;
223
- if (metadata.workflow_asset !== null
224
- && workflowAsset !== metadata.workflow_asset) {
225
- throw new Error(`workflow asset changed from '${metadata.workflow_asset}' to '${workflowAsset ?? "unknown"}'`);
226
- }
227
- return {
228
- plan,
229
- runtime_plans: runtimePlans,
230
- roles: cascaded.roles,
231
- ir,
232
- plan_digest: planDigest(plan, cascaded.roles),
233
- workflow_asset: workflowAsset,
234
- };
235
250
  }
236
- async function compileCanonicalRun(metadata) {
237
- const assets = await compileRunAssets(metadata);
238
- if (!assets.ir.compiled_ir_hash) {
239
- throw new Error("Compiled IR hash is required for canonical workflow runtime bindings");
240
- }
241
- const workflowRuntime = createWorkflowRuntimeManifest({
242
- plans: assets.runtime_plans.map((runtimePlan) => ({
243
- plan: runtimePlan.plan,
244
- resolvedVariables: runtimePlan.resolved_variables,
245
- workflowAsset: runtimePlan.workflow_asset ?? undefined,
246
- additionalRuntimeValues: assets.ir.verifier_specs?.filter((spec) => spec.workflow_name === runtimePlan.plan.workflow_key) ?? [],
247
- })),
248
- compiledAt: assets.ir.compiled_at,
249
- compiledIrHash: assets.ir.compiled_ir_hash,
250
- sourceDnaIds: assets.ir.source_dna_ids,
251
+ function buildRuntime(projectDirectory, composition, runStore = new DurableRunStore({ root_directory: runtimeRoot(projectDirectory) })) {
252
+ const root = runtimeRoot(projectDirectory);
253
+ const configuredExecutionAuthority = composition.execution_authority_factory?.(join(root, "execution-authority"));
254
+ const localExecutionAuthority = configuredExecutionAuthority === undefined
255
+ ? new LocalAttemptExecutionAuthority({
256
+ root_directory: join(root, "execution-authority"),
257
+ })
258
+ : null;
259
+ const executionAuthority = configuredExecutionAuthority
260
+ ?? localExecutionAuthority;
261
+ const composed = createCanonicalRuntimeComposition({
262
+ project_directory: projectDirectory,
263
+ run_store: runStore,
264
+ workspace_authority: new LocalAttemptWorkspaceAuthority({
265
+ lock_root: join(root, "workspace-locks"),
266
+ }),
267
+ execution_authority: executionAuthority,
268
+ verifier_execution_port: new ProductionLocalVerifierExecutionPort({
269
+ root_directory: join(root, "verifier-execution-authority"),
270
+ }),
251
271
  });
252
- const adapterOptions = {
253
- roles: assets.roles,
254
- variables: metadata.variables,
255
- working_directory: metadata.project_directory,
256
- timeout_ms: metadata.timeout_ms,
257
- cancellation_grace_ms: metadata.cancellation_grace_ms,
258
- };
259
272
  return {
260
- ...assets,
261
- workflow_runtime: workflowRuntime,
262
- definitions: adaptWorkflowPlan(assets.plan, adapterOptions),
263
- retry_loop: adaptWorkflowRetryLoop(assets.plan, adapterOptions),
273
+ run_store: runStore,
274
+ execution_authority: executionAuthority,
275
+ local_execution_authority: localExecutionAuthority,
276
+ service: composed.service,
264
277
  };
265
278
  }
266
- async function syncRuntimeArtifacts(compiled, metadata) {
267
- const agents = compileAllRolesToAgentMD(compiled.roles, compiled.ir);
268
- await writeAgentMDFiles(agents, metadata.agents_directory);
269
- await writeCompiledIR(metadata.project_directory, createCompiledIR(compiled.ir, compiled.roles, compiled.workflow_runtime));
270
- }
271
- function projectPathInWorkspace(metadata, workspaceDirectory, projectPath) {
272
- const projectRelative = relative(metadata.project_directory, projectPath);
273
- const outsideProject = (projectRelative === ".."
274
- || projectRelative.startsWith(`..${sep}`)
275
- || isAbsolute(projectRelative));
276
- return outsideProject
277
- ? projectPath
278
- : resolve(workspaceDirectory, projectRelative);
279
+ function buildInspectionService(projectDirectory, runStore) {
280
+ return createCanonicalRuntimeComposition({
281
+ project_directory: projectDirectory,
282
+ run_store: runStore,
283
+ }).service;
279
284
  }
280
- async function syncAttemptRuntimeArtifacts(compiled, metadata, workspace) {
281
- if (workspace.isolation !== "worktree")
285
+ async function reconcileLocalPreparations(runtime, view) {
286
+ if (runtime.local_execution_authority === null)
282
287
  return;
283
- const agentsDirectory = projectPathInWorkspace(metadata, workspace.working_directory, metadata.agents_directory);
284
- if (agentsDirectory !== metadata.agents_directory) {
285
- const agents = compileAllRolesToAgentMD(compiled.roles, compiled.ir);
286
- await writeAgentMDFiles(agents, agentsDirectory);
287
- }
288
- await writeCompiledIR(workspace.working_directory, createCompiledIR(compiled.ir, compiled.roles, compiled.workflow_runtime));
288
+ await reconcileLocalExecutionPreparations(runtime.local_execution_authority, view);
289
289
  }
290
- function createProvider(metadata) {
291
- if (metadata.provider.kind === "codex") {
292
- return createCodexExecutionProvider({
293
- executable: metadata.provider.executable ?? undefined,
294
- });
290
+ function createProvider(binding) {
291
+ const configuration = binding.provider_config;
292
+ const executable = configuration.options.executable;
293
+ if (configuration.provider === "codex") {
294
+ return createCodexExecutionProvider({ executable });
295
+ }
296
+ if (configuration.provider !== "claude") {
297
+ throw new Error(`unsupported provider in immutable run binding: ${configuration.provider}`);
298
+ }
299
+ const maxBudget = configuration.options.max_budget_usd;
300
+ const permissionMode = configuration.options.permission_mode;
301
+ if (maxBudget === undefined || permissionMode === undefined) {
302
+ throw new Error("Claude run binding is missing launch budget or permission mode");
295
303
  }
296
304
  return createClaudeExecutionProvider({
297
- executable: metadata.provider.executable ?? undefined,
305
+ executable,
298
306
  agentName: (packet) => `dna-${toKebabCase(packet.role)}`,
299
307
  extraArgs: [
300
308
  "--max-budget-usd",
301
- String(metadata.provider.max_budget_usd),
309
+ String(maxBudget),
302
310
  "--permission-mode",
303
- metadata.provider.permission_mode,
311
+ permissionMode,
304
312
  ],
305
313
  });
306
314
  }
307
- function preservationReason(packet) {
308
- return packet.workspace.isolation === "worktree"
309
- ? "integration_pending"
310
- : "controller_policy";
311
- }
312
- function preserveWorkspace(workspace, reason, detail) {
313
- const result = applyWorkspaceLifecycleDecision(workspace, {
314
- action: "preserve",
315
- reason,
316
- detail,
317
- });
318
- if (result.status === "preserved") {
319
- log(`Preserved worktree ${result.working_directory}: `
320
- + `${result.reason}${result.detail ? ` (${result.detail})` : ""}`);
321
- }
322
- }
323
- async function recordAttemptProcess(runStore, packet, processId) {
324
- if (!Number.isSafeInteger(processId) || processId <= 0) {
325
- throw new Error(`provider returned invalid process id ${processId}`);
326
- }
327
- while (true) {
328
- const snapshot = await runStore.requireRun(packet.run_id);
329
- const attempt = snapshot.attempts.find((entry) => entry.attempt_id === packet.attempt_id);
330
- if (!attempt
331
- || attempt.step_id !== packet.step_id
332
- || attempt.worker_session_id !== packet.worker_session_id) {
333
- throw new Error(`attempt ${packet.attempt_id} disappeared before process registration`);
334
- }
335
- const recordedProcessId = attempt.process_id ?? null;
336
- if (recordedProcessId === processId || attempt.phase === "terminal")
337
- return;
338
- if (recordedProcessId !== null) {
339
- throw new Error(`attempt ${packet.attempt_id} already owns process ${recordedProcessId}`);
340
- }
341
- try {
342
- await runStore.updateRun({
343
- run_id: packet.run_id,
344
- expected_record_version: snapshot.record_version,
345
- attempts: [{ ...attempt, process_id: processId }],
346
- });
347
- return;
348
- }
349
- catch (error) {
350
- if (error instanceof RunStoreError && error.code === "version_conflict") {
351
- continue;
352
- }
353
- throw error;
354
- }
355
- }
356
- }
357
- const reconcileLocalProcess = async (attempt) => {
358
- const processId = attempt.process_id ?? null;
359
- if (processId === null
360
- || !Number.isSafeInteger(processId)
361
- || processId <= 0) {
362
- return "unknown";
363
- }
364
- try {
365
- process.kill(processId, 0);
366
- return "live";
367
- }
368
- catch (error) {
369
- const code = error instanceof Error
370
- ? error.code
371
- : undefined;
372
- if (code === "EPERM")
373
- return "live";
374
- if (code === "ESRCH")
375
- return "dead";
376
- return "unknown";
377
- }
378
- };
379
- function verifierFailureMessage(prefix, stepId, result) {
380
- const exitCode = result.exit_code === undefined
381
- ? ""
382
- : ` exit_code=${result.exit_code}`;
383
- const target = result.target ? ` target=${result.target}` : "";
384
- const message = result.message ? ` - ${result.message}` : "";
385
- const detail = `${prefix}${target}${exitCode}${message}`;
386
- log(`${detail} (step: ${stepId})`);
387
- return detail;
388
- }
389
- async function applyStepValidation(metadata, compiled, packet, execution) {
390
- if (execution.result.outcome.kind !== "success")
391
- return execution;
392
- const step = compiled.plan.steps.find((entry) => entry.id === packet.step_id);
393
- if (!step) {
394
- throw new Error(`compiled Step ${packet.step_id} disappeared before validation`);
395
- }
396
- const variables = {
397
- ...metadata.variables,
398
- round: String(packet.workflow_round),
399
- };
400
- let failure = null;
401
- for (const completion of step.completion ?? []) {
402
- const result = await runCompletionVerifier(completion, compiled.ir, {
403
- projectDir: packet.workspace.working_directory,
404
- variables,
405
- });
406
- if (!result.passed) {
407
- failure = verifierFailureMessage("Completion failed", step.id, result);
408
- break;
409
- }
410
- }
411
- if (!failure) {
412
- for (const checkpoint of step.checkpoints ?? []) {
413
- const result = await runCheckpointVerifier(checkpoint, compiled.ir, {
414
- projectDir: packet.workspace.working_directory,
415
- variables,
416
- });
417
- if (result.passed)
418
- continue;
419
- const detail = verifierFailureMessage(`Checkpoint failed: ${checkpoint.assert}`, step.id, result);
420
- if ((checkpoint.action ?? "block") === "block") {
421
- failure = detail;
422
- break;
423
- }
424
- }
425
- }
426
- if (!failure)
427
- return execution;
428
- return {
429
- ...execution,
430
- result: {
431
- ...execution.result,
432
- outputs: [],
433
- outcome: {
434
- kind: "malformed_result",
435
- exit_code: 0,
436
- signal: null,
437
- error: failure,
438
- },
315
+ function createDriver(runtime, view, metadata, projectDirectory) {
316
+ assertRunComposition(view, metadata, projectDirectory);
317
+ runtime.execution_authority.assertExecutionSupported();
318
+ const networkSandbox = new LocalProviderNetworkSandbox();
319
+ return new PushDriver({
320
+ service: runtime.service,
321
+ provider_factory: ({ run, binding }) => {
322
+ assertRunComposition(run, readCanonicalMetadata(run.run.metadata), projectDirectory);
323
+ return run.plan.plan.workspace_policy.allow_network
324
+ ? createProvider(binding)
325
+ : networkSandbox.wrap(createProvider(binding));
326
+ },
327
+ capabilities: {
328
+ network_policy_enforcement: networkSandbox.network_policy_enforcement,
439
329
  },
440
- };
441
- }
442
- async function recordStepResultEvidence(metadata, packet, execution) {
443
- const outcome = execution.result.outcome;
444
- const succeeded = outcome.kind === "success";
445
- const startedAt = Date.parse(execution.result.started_at);
446
- const completedAt = Date.parse(execution.result.completed_at);
447
- await appendEvidenceCaptureEvent(metadata.project_directory, {
448
- schema_version: EVIDENCE_CAPTURE_SCHEMA_VERSION,
449
- event_id: `c5:${randomUUID()}`,
450
- captured_at: execution.result.completed_at,
451
- project_root: metadata.project_directory,
452
- source: "dna_cli",
453
- workflow_asset: metadata.workflow_asset ?? "unknown",
454
- workflow_id: metadata.workflow_name,
455
- run_id: packet.run_id,
456
- step_id: packet.step_id,
457
- fact: "result",
458
- summary: `dna run recorded ${succeeded ? "success" : "failure"} for workflow step ${packet.step_id}`,
459
- result: succeeded ? "success" : "failure",
460
- exit_code: "exit_code" in outcome
461
- ? outcome.exit_code ?? undefined
462
- : undefined,
463
- duration_ms: Number.isFinite(startedAt) && Number.isFinite(completedAt)
464
- ? Math.max(0, completedAt - startedAt)
465
- : undefined,
466
- });
467
- }
468
- function createWorkspaceAwareExecutor(metadata, compiled, runStore) {
469
- return async (packet, provider, options) => {
470
- const workspace = allocateAttemptWorkspace(planAttemptWorkspace({
471
- isolation: packet.workspace.isolation,
472
- repository_directory: metadata.project_directory,
473
- run_id: packet.run_id,
474
- step_id: packet.step_id,
475
- attempt_id: packet.attempt_id,
476
- }));
477
- const workerPacket = {
478
- ...packet,
479
- workspace: {
480
- isolation: workspace.isolation,
481
- working_directory: workspace.working_directory,
482
- },
483
- };
484
- const state = new DNAStateManager(workspace.working_directory, packet.worker_session_id);
485
- let lifecycleRecorded = false;
486
- try {
487
- await syncAttemptRuntimeArtifacts(compiled, metadata, workspace);
488
- const runtimeBinding = compiled.workflow_runtime.workflows[metadata.workflow_name];
489
- if (!runtimeBinding)
490
- throw new Error(`Missing runtime binding for ${metadata.workflow_name}`);
491
- const canonicalArguments = metadata.variables.ARGUMENTS ??
492
- metadata.variables.arguments ?? metadata.variables.task_id;
493
- const runtimeInputs = {};
494
- for (const name of runtimeBinding.runtime_input_refs) {
495
- const value = WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name)
496
- ? canonicalArguments
497
- : metadata.variables[name];
498
- if (value === undefined)
499
- throw new Error(`Missing runtime input ${name} for ${metadata.workflow_name}`);
500
- runtimeInputs[name] = value;
501
- }
502
- await state.writeWorkflowState({
503
- active: true,
504
- workflow_asset: runtimeBinding.workflow_asset,
505
- workflow: metadata.workflow_name,
506
- current_step: packet.step_id,
507
- current_role: packet.role,
508
- iteration: packet.workflow_round,
509
- session_id: packet.worker_session_id,
510
- worker_session_id: packet.worker_session_id,
511
- run_id: packet.run_id,
512
- step_id: packet.step_id,
513
- attempt_id: packet.attempt_id,
514
- started_at: packet.created_at,
515
- inputs: Object.keys(runtimeInputs).length > 0 ? runtimeInputs : undefined,
516
- resolved_variables: runtimeBinding.resolved_variables,
517
- resolved_variables_source: "compiled_manifest",
518
- workflow_runtime_manifest_hash: compiled.workflow_runtime.manifest_hash,
519
- workflow_runtime_compiled_ir_hash: compiled.workflow_runtime.compiled_ir_hash,
520
- workflow_inputs_pinned: true,
521
- });
522
- const inheritedProcessObserver = options?.onProcessStart;
523
- let execution = await executeWorkerAttempt(workerPacket, provider, {
524
- ...options,
525
- onProcessStart: async (processId) => {
526
- await recordAttemptProcess(runStore, workerPacket, processId);
527
- await inheritedProcessObserver?.(processId);
528
- },
529
- });
530
- execution = await applyStepValidation(metadata, compiled, workerPacket, execution);
531
- await recordStepResultEvidence(metadata, workerPacket, execution);
532
- const reason = execution.result.outcome.kind === "cancelled"
533
- ? "attempt_cancelled"
534
- : execution.result.outcome.kind === "success"
535
- ? preservationReason(workerPacket)
536
- : "attempt_failed";
537
- preserveWorkspace(workspace, reason, `attempt outcome: ${execution.result.outcome.kind}`);
538
- lifecycleRecorded = true;
539
- return execution;
540
- }
541
- catch (error) {
542
- if (!lifecycleRecorded) {
543
- preserveWorkspace(workspace, "controller_policy", `attempt launch failed: ${error instanceof Error ? error.message : String(error)}`);
544
- }
545
- throw error;
546
- }
547
- finally {
548
- try {
549
- await state.cleanup();
550
- }
551
- catch (error) {
552
- log(`Worker Hook state cleanup failed for ${packet.worker_session_id}: `
553
- + `${error instanceof Error ? error.message : String(error)}`);
554
- }
555
- }
556
- };
557
- }
558
- function buildRuntime(metadata, compiled) {
559
- const runStore = new DurableRunStore({
560
- root_directory: runtimeRoot(metadata.project_directory),
561
- });
562
- const resultStore = new ImmutableResultStore(runStore);
563
- const resolver = new HandoffResolver(resultStore);
564
- const provider = createProvider(metadata);
565
- const controller = new RunController({
566
- run_store: runStore,
567
- handoff_resolver: resolver,
568
- steps: compiled.definitions,
569
- workflow_retry_loop: compiled.retry_loop,
570
- provider_for_step: () => provider,
571
- reconcile_attempt: reconcileLocalProcess,
572
- execute_attempt: createWorkspaceAwareExecutor(metadata, compiled, runStore),
573
330
  max_concurrency: metadata.max_concurrency,
574
- lease_duration_ms: 15_000,
575
- lease_heartbeat_ms: 3_000,
576
- scheduler_poll_ms: 50,
577
- });
578
- return { run_store: runStore, controller };
579
- }
580
- function buildCancellationController(runStore) {
581
- return new RunController({
582
- run_store: runStore,
583
- steps: [],
584
- provider_for_step: () => {
585
- throw new Error("cancellation-only Controller cannot launch a Step");
331
+ caller_attestation_authority: createCallerAttestationAuthority(runtime.execution_authority.authority_instance_id),
332
+ execution_authority: {
333
+ prepare: async (context) => {
334
+ await reconcileLocalPreparations(runtime, await runtime.service.inspect(context.lease.run_id));
335
+ return runtime.execution_authority.prepareExecution({
336
+ run_id: context.lease.run_id,
337
+ step_id: context.lease.step_id,
338
+ activation_id: context.lease.activation_id,
339
+ attempt_id: context.lease.attempt_id,
340
+ execution_claim_epoch: context.execution_claim_epoch,
341
+ worker_session_id: context.worker_session_id,
342
+ });
343
+ },
344
+ abandon: (_context, preparation) => runtime.execution_authority.abandonPreparedExecution(preparation),
345
+ bind: (context, preparation) => runtime.execution_authority.bindExecution(preparation, { started_at: context.started_at }),
346
+ recover: (context) => {
347
+ const preparationRequest = {
348
+ run_id: context.lease.run_id,
349
+ step_id: context.lease.step_id,
350
+ activation_id: context.lease.activation_id,
351
+ attempt_id: context.lease.attempt_id,
352
+ execution_claim_epoch: context.execution_claim_epoch,
353
+ worker_session_id: context.worker_session_id,
354
+ };
355
+ const prepared = runtime.execution_authority.recoverPreparedExecution(preparationRequest);
356
+ if (prepared !== null) {
357
+ return {
358
+ kind: "launch",
359
+ binding: runtime.execution_authority.bindExecution(prepared, { started_at: context.started_at }),
360
+ };
361
+ }
362
+ return runtime.execution_authority.recoverExecution({
363
+ run_id: context.lease.run_id,
364
+ step_id: context.lease.step_id,
365
+ activation_id: context.lease.activation_id,
366
+ attempt_id: context.lease.attempt_id,
367
+ execution_claim_epoch: context.execution_claim_epoch,
368
+ executor: { kind: "worker", worker_session_id: context.worker_session_id },
369
+ started_at: context.started_at,
370
+ });
371
+ },
586
372
  },
587
- reconcile_attempt: reconcileLocalProcess,
588
- max_concurrency: 1,
589
- lease_duration_ms: 15_000,
590
- lease_heartbeat_ms: 3_000,
591
- scheduler_poll_ms: 50,
373
+ lifecycle_hooks: createRunAttemptObservers((context) => (readCanonicalMetadata(context.run.run.metadata))),
592
374
  });
593
375
  }
594
- async function startMetadata(opts, projectDirectory) {
595
- if (opts.dnaFiles.length === 0) {
376
+ function startMetadata(opts, projectDirectory) {
377
+ if (opts.dnaFiles.length === 0)
596
378
  throw new RunUsageError("--dna requires at least one DNA file");
597
- }
598
- const workflowName = requireText(opts.workflowName, "--workflow");
379
+ const selectedTargets = [opts.workflowName, opts.controllerName]
380
+ .filter((value) => value?.trim()).length;
381
+ if (selectedTargets !== 1) {
382
+ throw new RunUsageError("exactly one of --workflow or --controller is required");
383
+ }
384
+ const target = opts.controllerName?.trim()
385
+ ? { kind: "controller", key: requireText(opts.controllerName, "--controller") }
386
+ : { kind: "workflow", key: requireText(opts.workflowName, "--workflow") };
599
387
  const taskId = requireText(opts.taskId, "--task");
600
- const expanded = await expandDNAInputFiles(opts.dnaFiles, {
601
- cwd: projectDirectory,
602
- });
603
- if (expanded.length === 0) {
604
- throw new RunUsageError("--dna did not resolve any DNA files");
605
- }
606
- const variables = {
607
- ...(opts.vars ?? {}),
608
- task_id: taskId,
609
- ARGUMENTS: taskId,
610
- workflow: workflowName,
611
- };
612
- const maxBudget = requirePositiveNumber(opts.maxBudget ?? 5, "--max-budget");
613
- const maxConcurrency = requirePositiveInteger(opts.maxConcurrency ?? 1, "--max-concurrency");
614
- const timeoutMs = opts.timeoutMs === null || opts.timeoutMs === undefined
615
- ? null
616
- : requireNonNegativeInteger(opts.timeoutMs, "--timeout-ms");
617
- const cancellationGraceMs = requireNonNegativeInteger(opts.cancellationGraceMs ?? 5_000, "--cancellation-grace-ms");
618
- const permissionMode = requireText(opts.permissionMode ?? "default", "--permission-mode");
388
+ const timeoutMs = opts.timeoutMs == null ? null : requireNonNegativeInteger(opts.timeoutMs, "--timeout-ms");
619
389
  return {
620
- schema_version: "intentdna.canonical_run.v1",
390
+ schema_version: "intentdna.canonical_run.v3",
621
391
  project_directory: projectDirectory,
622
- dna_files: expanded.map((file) => resolve(projectDirectory, file)),
623
- workflow_name: workflowName,
392
+ dna_files: opts.dnaFiles.map((file) => resolve(projectDirectory, file)),
393
+ target,
624
394
  task_id: taskId,
625
395
  context: opts.context ?? null,
626
- variables,
627
- agents_directory: opts.agentsDir
628
- ? resolve(projectDirectory, opts.agentsDir)
629
- : join(projectDirectory, ".claude", "agents"),
396
+ variables: {
397
+ ...(opts.vars ?? {}),
398
+ ARGUMENTS: taskId,
399
+ arguments: taskId,
400
+ task_id: taskId,
401
+ workflow: target.key,
402
+ },
403
+ agents_directory: opts.agentsDir ? resolve(projectDirectory, opts.agentsDir) : join(projectDirectory, ".claude", "agents"),
630
404
  provider: {
631
405
  kind: opts.provider ?? "claude",
632
- executable: opts.providerExecutable === undefined
633
- ? null
634
- : requireText(opts.providerExecutable, "--provider-executable"),
635
- max_budget_usd: maxBudget,
636
- permission_mode: permissionMode,
406
+ executable: opts.providerExecutable === undefined ? null : requireText(opts.providerExecutable, "--provider-executable"),
407
+ max_budget_usd: requirePositiveNumber(opts.maxBudget ?? 5, "--max-budget"),
408
+ permission_mode: requireText(opts.permissionMode ?? "default", "--permission-mode"),
637
409
  },
638
- max_concurrency: maxConcurrency,
410
+ max_concurrency: requirePositiveInteger(opts.maxConcurrency ?? 1, "--max-concurrency"),
639
411
  timeout_ms: timeoutMs,
640
- cancellation_grace_ms: cancellationGraceMs,
412
+ cancellation_grace_ms: requireNonNegativeInteger(opts.cancellationGraceMs ?? 5_000, "--cancellation-grace-ms"),
641
413
  workflow_asset: null,
414
+ runtime_projection: null,
642
415
  };
643
416
  }
644
- async function runtimeForExistingRun(runStore, runId, projectDirectory) {
645
- const snapshot = await runStore.requireRun(runId);
646
- const metadata = readCanonicalMetadata(snapshot.run.metadata);
647
- if (resolve(metadata.project_directory) !== projectDirectory) {
648
- throw new Error(`run ${runId} belongs to project ${metadata.project_directory}, not ${projectDirectory}`);
649
- }
650
- const compiled = await compileCanonicalRun(metadata);
651
- if (compiled.plan_digest !== snapshot.run.plan_digest) {
652
- throw new Error(`run ${runId} plan digest changed; resume requires the original DNA and role definitions`);
653
- }
654
- await syncRuntimeArtifacts(compiled, metadata);
655
- return buildRuntime(metadata, compiled);
417
+ async function previewRun(metadata) {
418
+ const app = new CanonicalRunApplication({});
419
+ const compiled = await app.compileTarget({
420
+ dna_sources: metadata.dna_files,
421
+ target: metadata.target,
422
+ inputs: canonicalInputs(metadata),
423
+ input_policy: "declared_only",
424
+ environment: { project_root: metadata.project_directory },
425
+ provider: providerConfiguration(metadata),
426
+ max_concurrency: metadata.max_concurrency,
427
+ context: metadata.context,
428
+ attempt_policy: { timeout_ms: metadata.timeout_ms, cancellation_grace_ms: metadata.cancellation_grace_ms },
429
+ });
430
+ if (!compiled.projection)
431
+ throw new Error("canonical compiler did not produce a runtime projection");
432
+ const pinnedMetadata = metadataForBinding(metadata, compiled.run_binding);
433
+ return printDryRun(compiled.projection.workflow_plan, pinnedMetadata, compiled.run_binding.binding.plan_id);
656
434
  }
657
- // ── CLI entry ──────────────────────────────────────────────
658
- export async function runRun(opts) {
435
+ export async function runRun(opts, composition = {}) {
659
436
  const action = opts.action ?? "start";
660
437
  const projectDirectory = resolve(opts.projectDir ?? process.cwd());
661
- const runStore = new DurableRunStore({
662
- root_directory: runtimeRoot(projectDirectory),
663
- });
664
438
  try {
665
- if (action !== "start"
666
- && action !== "status"
667
- && action !== "inspect"
668
- && action !== "resume"
669
- && action !== "cancel") {
439
+ if (!["start", "status", "inspect", "resume", "cancel", "close"].includes(action)) {
670
440
  throw new RunUsageError(`unsupported run action '${String(action)}'`);
671
441
  }
442
+ const runStore = new DurableRunStore({ root_directory: runtimeRoot(projectDirectory) });
443
+ const existingRunId = () => asRunId(requireText(opts.runId, "run_id"));
672
444
  if (action === "status") {
673
445
  return runLifecycleStatus({
674
446
  run_store: runStore,
675
- run_id: asRunId(requireText(opts.runId, "run_id")),
447
+ service_factory: async () => buildInspectionService(projectDirectory, runStore),
448
+ run_id: existingRunId(),
676
449
  json: opts.json,
677
450
  });
678
451
  }
679
452
  if (action === "inspect") {
680
453
  return runLifecycleInspect({
681
454
  run_store: runStore,
682
- run_id: asRunId(requireText(opts.runId, "run_id")),
455
+ service_factory: async () => buildInspectionService(projectDirectory, runStore),
456
+ run_id: existingRunId(),
683
457
  json: opts.json,
684
458
  });
685
459
  }
686
- if (action === "resume") {
687
- const runId = asRunId(requireText(opts.runId, "run_id"));
688
- const runtime = await runtimeForExistingRun(runStore, runId, projectDirectory);
689
- return runLifecycleResume({
690
- controller: runtime.controller,
691
- run_id: runId,
460
+ if (action === "close") {
461
+ return runLifecycleClose({
462
+ run_store: runStore,
463
+ run_id: existingRunId(),
464
+ reason: opts.reason,
692
465
  json: opts.json,
693
466
  });
694
467
  }
468
+ if (action === "start") {
469
+ const initialMetadata = startMetadata(opts, projectDirectory);
470
+ if (opts.dryRun)
471
+ return await previewRun(initialMetadata);
472
+ }
473
+ let runtimeComposition = null;
474
+ const requireRuntime = () => {
475
+ runtimeComposition ??= buildRuntime(projectDirectory, composition, runStore);
476
+ return runtimeComposition;
477
+ };
695
478
  if (action === "cancel") {
696
- const runId = asRunId(requireText(opts.runId, "run_id"));
697
479
  return runLifecycleCancel({
698
- controller: buildCancellationController(runStore),
699
- run_id: runId,
480
+ run_store: runStore,
481
+ service_factory: async () => {
482
+ const composed = requireRuntime();
483
+ return {
484
+ inspect: (runId) => composed.service.inspect(runId),
485
+ inspectAudit: (runId) => composed.service.inspectAudit(runId),
486
+ resume: (runId) => composed.service.resume(runId),
487
+ cancel: async (runId, reason) => {
488
+ await reconcileLocalPreparations(composed, await composed.service.inspect(runId));
489
+ const cancelled = await composed.service.cancel(runId, reason);
490
+ await reconcileLocalPreparations(composed, cancelled);
491
+ return cancelled;
492
+ },
493
+ };
494
+ },
495
+ run_id: existingRunId(),
700
496
  reason: opts.reason,
701
497
  json: opts.json,
702
498
  });
703
499
  }
704
- const initialMetadata = await startMetadata(opts, projectDirectory);
705
- if (opts.dryRun) {
706
- const firstPreview = await compileRunAssets(initialMetadata);
707
- const previewMetadata = {
708
- ...initialMetadata,
709
- workflow_asset: firstPreview.workflow_asset,
710
- };
711
- const preview = previewMetadata.workflow_asset
712
- === initialMetadata.workflow_asset
713
- ? firstPreview
714
- : await compileRunAssets(previewMetadata);
715
- return printDryRun(preview.plan, previewMetadata, preview.plan_digest);
500
+ if (action === "resume") {
501
+ const runId = existingRunId();
502
+ return runLifecycleResume({
503
+ run_store: runStore,
504
+ service_factory: async () => requireRuntime().service,
505
+ run_id: runId,
506
+ drive: async (service, id) => {
507
+ const composed = requireRuntime();
508
+ const view = await service.inspect(id);
509
+ await reconcileLocalPreparations(composed, view);
510
+ const metadata = readCanonicalMetadata(view.run.metadata);
511
+ const resumed = await createDriver(composed, view, metadata, projectDirectory).run(id, { resume: true });
512
+ await reconcileLocalPreparations(composed, resumed);
513
+ writeTerminalDriverFailure(resumed);
514
+ return resumed;
515
+ },
516
+ json: opts.json,
517
+ });
716
518
  }
717
- const firstCompilation = await compileCanonicalRun(initialMetadata);
718
- const metadata = {
719
- ...initialMetadata,
720
- workflow_asset: firstCompilation.workflow_asset,
721
- };
722
- const compiled = metadata.workflow_asset === initialMetadata.workflow_asset
723
- ? firstCompilation
724
- : await compileCanonicalRun(metadata);
725
- await syncRuntimeArtifacts(compiled, metadata);
726
- const runtime = buildRuntime(metadata, compiled);
727
- const now = new Date().toISOString();
728
- const runId = opts.runId?.trim()
729
- ? asRunId(opts.runId)
730
- : `run_${randomUUID()}`;
731
- const run = {
732
- run_id: runId,
733
- workflow_id: metadata.workflow_name,
734
- workflow_version: null,
735
- plan_digest: compiled.plan_digest,
736
- status: "active",
737
- cancellation: null,
738
- terminal: null,
739
- metadata: metadata,
740
- created_at: now,
741
- updated_at: now,
742
- };
743
- return runLifecycleStart({
744
- controller: runtime.controller,
745
- run,
746
- json: opts.json,
519
+ const initialMetadata = startMetadata(opts, projectDirectory);
520
+ const runtime = requireRuntime();
521
+ const app = new CanonicalRunApplication({
522
+ starter: runtime.service,
523
+ metadata_factory: ({ compiled }) => {
524
+ if (!compiled.projection) {
525
+ throw new Error("canonical compiler did not produce a runtime projection");
526
+ }
527
+ return metadataForBinding({
528
+ ...initialMetadata,
529
+ workflow_asset: compiled.projection.workflow_asset,
530
+ runtime_projection: durableRuntimeProjection(compiled.projection),
531
+ }, compiled.run_binding);
532
+ },
533
+ projection_sink: async ({ phase, projection }) => {
534
+ if (phase !== "before_start")
535
+ return;
536
+ await syncRuntimeProjection(projectDirectory, initialMetadata.agents_directory, projection, durableRuntimeProjection(projection));
537
+ },
538
+ });
539
+ const started = await app.startTarget({
540
+ dna_sources: initialMetadata.dna_files,
541
+ target: initialMetadata.target,
542
+ inputs: canonicalInputs(initialMetadata),
543
+ input_policy: "declared_only",
544
+ environment: { project_root: projectDirectory, workspace_root: projectDirectory },
545
+ provider: providerConfiguration(initialMetadata),
546
+ max_concurrency: initialMetadata.max_concurrency,
547
+ context: initialMetadata.context,
548
+ attempt_policy: { timeout_ms: initialMetadata.timeout_ms, cancellation_grace_ms: initialMetadata.cancellation_grace_ms },
549
+ metadata: initialMetadata,
550
+ run_id: opts.runId?.trim() ? asRunId(opts.runId) : undefined,
747
551
  });
552
+ const storedMetadata = readCanonicalMetadata(started.view.run.metadata);
553
+ const view = await createDriver(runtime, started.view, storedMetadata, projectDirectory).run(started.run_id, { initial_view: started.view });
554
+ await reconcileLocalPreparations(runtime, view);
555
+ writeTerminalDriverFailure(view);
556
+ writeCanonicalOperation("start", view, opts.json === true);
557
+ return view.run.status === "failed" ? 1 : 0;
748
558
  }
749
559
  catch (error) {
750
560
  const usage = error instanceof RunUsageError;
751
- process.stderr.write(`${usage ? "Usage error" : "Error"}: `
752
- + `${error instanceof Error ? error.message : String(error)}\n`);
561
+ const typedCode = error instanceof Error
562
+ && "code" in error
563
+ && typeof error.code === "string"
564
+ ? error.code
565
+ : null;
566
+ const detail = error instanceof Error ? error.message : String(error);
567
+ process.stderr.write(`${usage ? "Usage error" : "Error"}: ${typedCode === null ? "" : `${typedCode}: `}${detail}\n`);
753
568
  return usage ? 2 : 1;
754
569
  }
755
570
  }
756
571
  function printDryRun(plan, metadata, digest) {
757
572
  process.stderr.write("\n=== DRY RUN - Canonical Controller Plan ===\n\n");
758
- process.stderr.write(`Workflow: ${plan.name}\n`);
759
- process.stderr.write(`Plan digest: ${digest}\n`);
760
- process.stderr.write(`Provider: ${metadata.provider.kind}\n`);
761
- process.stderr.write(`Max concurrency: ${metadata.max_concurrency}\n`);
762
- process.stderr.write(`Durable root: ${runtimeRoot(metadata.project_directory)}\n`);
763
- process.stderr.write(`Steps: ${plan.steps.length}\n\n`);
764
- process.stderr.write(`Groups: ${plan.parallel_groups.length}\n`);
573
+ process.stderr.write(`Target: ${metadata.target.kind}:${metadata.target.key}\n`);
574
+ process.stderr.write(`Projection workflow: ${plan.name}\nPlan digest: ${digest}\nProvider: ${metadata.provider.kind}\n`);
575
+ process.stderr.write(`Max concurrency: ${metadata.max_concurrency}\nDurable root: ${runtimeRoot(metadata.project_directory)}\n`);
576
+ process.stderr.write(`Steps: ${plan.steps.length}\n\nGroups: ${plan.parallel_groups.length}\n`);
765
577
  process.stderr.write(`Max retries: ${plan.retry.max_retries}\n`);
766
- if (plan.retry.retry_from) {
578
+ if (plan.retry.retry_from)
767
579
  process.stderr.write(`Retry from: ${plan.retry.retry_from}\n`);
768
- }
769
580
  process.stderr.write(`Variables: ${JSON.stringify(metadata.variables)}\n\n`);
770
581
  for (const group of plan.parallel_groups) {
771
582
  process.stderr.write(`Group ${group.group_index + 1} (${group.isolation}):\n`);
@@ -774,24 +585,15 @@ function printDryRun(plan, metadata, digest) {
774
585
  if (!step)
775
586
  continue;
776
587
  const prompt = substituteTemplate(step.prompt ?? step.description, metadata.variables);
777
- const runIf = step.run_if ? ` [run_if: ${step.run_if}]` : "";
778
- const optional = step.optional ? " (optional)" : "";
779
- process.stderr.write(` - ${step.id} -> agent: dna-${toKebabCase(step.role)}`
780
- + `${runIf}${optional}; independent session\n`);
588
+ process.stderr.write(` - ${step.id} -> agent: dna-${toKebabCase(step.role)}${step.run_if ? ` [run_if: ${step.run_if}]` : ""}${step.optional ? " (optional)" : ""}; independent session\n`);
781
589
  process.stderr.write(` prompt: ${prompt.slice(0, 120)}${prompt.length > 120 ? "..." : ""}\n`);
782
590
  }
783
591
  }
784
592
  if (plan.transitions.length > 0) {
785
593
  process.stderr.write("\nTransitions:\n");
786
- for (const transition of plan.transitions) {
787
- process.stderr.write(` ${transition.from} -> ${transition.to} `
788
- + `(on ${transition.condition})\n`);
789
- }
594
+ for (const transition of plan.transitions)
595
+ process.stderr.write(` ${transition.from} -> ${transition.to} (on ${transition.condition})\n`);
790
596
  }
791
597
  process.stderr.write("\n");
792
598
  return 0;
793
599
  }
794
- function log(message) {
795
- const timestamp = new Date().toLocaleTimeString("en-US", { hour12: false });
796
- process.stderr.write(`[${timestamp}] ${message}\n`);
797
- }