intentdna 1.9.0 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +227 -87
  4. package/dist/cli/commands/run-lifecycle.d.ts +61 -11
  5. package/dist/cli/commands/run-lifecycle.js +184 -50
  6. package/dist/cli/commands/run-observer.d.ts +15 -0
  7. package/dist/cli/commands/run-observer.js +210 -0
  8. package/dist/cli/commands/run.d.ts +16 -20
  9. package/dist/cli/commands/run.js +461 -659
  10. package/dist/cli/commands/sync.js +16 -12
  11. package/dist/cli/index.js +14 -5
  12. package/dist/compiler/compile.d.ts +1 -0
  13. package/dist/compiler/compile.js +1 -1
  14. package/dist/compiler/controller.d.ts +16 -0
  15. package/dist/compiler/controller.js +693 -0
  16. package/dist/compiler/input-resolver.d.ts +2 -0
  17. package/dist/compiler/input-resolver.js +22 -6
  18. package/dist/hooks/cli.d.ts +45 -0
  19. package/dist/hooks/cli.js +263 -31
  20. package/dist/hooks/host-event.d.ts +57 -0
  21. package/dist/hooks/host-event.js +187 -0
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.js +1 -0
  24. package/dist/hooks/protocol.d.ts +7 -0
  25. package/dist/hooks/schema.d.ts +1 -0
  26. package/dist/hooks/schema.js +89 -1
  27. package/dist/hooks/state.d.ts +23 -1
  28. package/dist/hooks/state.js +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-run.d.ts +4 -0
  33. package/dist/mcp/tools-run.js +419 -0
  34. package/dist/mcp/tools-state.d.ts +1 -1
  35. package/dist/mcp/tools-state.js +13 -241
  36. package/dist/runtime/artifact-store.d.ts +146 -0
  37. package/dist/runtime/artifact-store.js +1436 -0
  38. package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
  39. package/dist/runtime/canonical-attempt-outcome.js +195 -0
  40. package/dist/runtime/canonical-json.d.ts +10 -0
  41. package/dist/runtime/canonical-json.js +105 -0
  42. package/dist/runtime/canonical-run-application.d.ts +93 -0
  43. package/dist/runtime/canonical-run-application.js +229 -0
  44. package/dist/runtime/canonical-run-service.d.ts +454 -0
  45. package/dist/runtime/canonical-run-service.js +4760 -0
  46. package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
  47. package/dist/runtime/canonical-runtime-composition.js +34 -0
  48. package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
  49. package/dist/runtime/canonical-runtime-projection.js +4 -0
  50. package/dist/runtime/canonical-target-compiler.d.ts +46 -0
  51. package/dist/runtime/canonical-target-compiler.js +514 -0
  52. package/dist/runtime/claude-agent-identity.d.ts +2 -0
  53. package/dist/runtime/claude-agent-identity.js +21 -0
  54. package/dist/runtime/executable-run-plan.d.ts +328 -0
  55. package/dist/runtime/executable-run-plan.js +1485 -0
  56. package/dist/runtime/execution-authority.d.ts +83 -0
  57. package/dist/runtime/execution-authority.js +98 -0
  58. package/dist/runtime/handoff-resolver.js +4 -1
  59. package/dist/runtime/harness-pull-adapter.d.ts +146 -0
  60. package/dist/runtime/harness-pull-adapter.js +315 -0
  61. package/dist/runtime/index.d.ts +39 -14
  62. package/dist/runtime/index.js +29 -7
  63. package/dist/runtime/local-execution-authority.d.ts +194 -0
  64. package/dist/runtime/local-execution-authority.js +2897 -0
  65. package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
  66. package/dist/runtime/local-execution-reconciliation.js +107 -0
  67. package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
  68. package/dist/runtime/local-execution-supervisor-script.js +793 -0
  69. package/dist/runtime/local-provider-sandbox.d.ts +42 -0
  70. package/dist/runtime/local-provider-sandbox.js +154 -0
  71. package/dist/runtime/local-verifier-execution.d.ts +24 -0
  72. package/dist/runtime/local-verifier-execution.js +290 -0
  73. package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
  74. package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
  75. package/dist/runtime/plan-store.d.ts +30 -0
  76. package/dist/runtime/plan-store.js +231 -0
  77. package/dist/runtime/process-tree.d.ts +9 -0
  78. package/dist/runtime/process-tree.js +97 -24
  79. package/dist/runtime/providers/codex.js +2 -2
  80. package/dist/runtime/push-driver.d.ts +184 -0
  81. package/dist/runtime/push-driver.js +1002 -0
  82. package/dist/runtime/result-store.d.ts +34 -3
  83. package/dist/runtime/result-store.js +1073 -2
  84. package/dist/runtime/run-binding.d.ts +68 -0
  85. package/dist/runtime/run-binding.js +278 -0
  86. package/dist/runtime/run-contracts.d.ts +575 -0
  87. package/dist/runtime/run-contracts.js +58 -7
  88. package/dist/runtime/run-controller.d.ts +1 -6
  89. package/dist/runtime/run-controller.js +0 -41
  90. package/dist/runtime/run-store.d.ts +85 -7
  91. package/dist/runtime/run-store.js +2528 -186
  92. package/dist/runtime/skill-adapter.d.ts +18 -7
  93. package/dist/runtime/skill-adapter.js +100 -742
  94. package/dist/runtime/structured-output-validator.d.ts +44 -0
  95. package/dist/runtime/structured-output-validator.js +171 -0
  96. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  97. package/dist/runtime/verifier-command-binding.js +162 -0
  98. package/dist/runtime/verifier.d.ts +46 -6
  99. package/dist/runtime/verifier.js +355 -53
  100. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  101. package/dist/runtime/windows-job-keeper.js +231 -0
  102. package/dist/runtime/worker-executor.d.ts +17 -1
  103. package/dist/runtime/worker-executor.js +26 -5
  104. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  105. package/dist/runtime/workflow-plan-adapter.js +502 -6
  106. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  107. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  108. package/dist/runtime/workspace-isolation.d.ts +29 -2
  109. package/dist/runtime/workspace-isolation.js +488 -11
  110. package/dist/runtime/workspace-observation.d.ts +44 -0
  111. package/dist/runtime/workspace-observation.js +216 -0
  112. package/dist/schema/controller-registry.d.ts +0 -7
  113. package/dist/schema/controller-registry.js +0 -8
  114. package/dist/schema/types.d.ts +84 -10
  115. package/dist/schema/types.js +2 -0
  116. package/dist/schema/validate.js +151 -1
  117. package/dist/schema/validators/controllers.d.ts +1 -1
  118. package/dist/schema/validators/controllers.js +126 -41
  119. package/dist/schema/workflow-authoring-contract.js +2 -0
  120. package/dist/schema/yaml-parser.js +1 -1
  121. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  122. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  123. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  124. package/native/windows-job-keeper/README.md +80 -0
  125. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  127. package/native/windows-job-keeper/keeper.c +979 -0
  128. package/native/windows-job-keeper/manifest.json +33 -0
  129. package/package.json +6 -2
  130. package/scripts/build-windows-job-keeper.mjs +172 -0
  131. package/scripts/verify-windows-job-keeper.mjs +184 -0
@@ -0,0 +1,216 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { constants, realpathSync } from "node:fs";
4
+ import { lstat, open, readdir, realpath, stat } from "node:fs/promises";
5
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
6
+ export class WorkspaceObservationError extends Error {
7
+ code;
8
+ constructor(code, message, options) {
9
+ super(message, options);
10
+ this.name = "WorkspaceObservationError";
11
+ this.code = code;
12
+ }
13
+ }
14
+ export const DEFAULT_WORKSPACE_OBSERVATION_LIMITS = {
15
+ max_entries: 100_000,
16
+ max_bytes: 1_073_741_824,
17
+ max_depth: 64,
18
+ max_duration_ms: 30_000,
19
+ };
20
+ export const WORKSPACE_OBSERVATION_EXCLUDED_PATHS = [
21
+ ".dna/runtime",
22
+ ".dna/worktrees/runtime",
23
+ ];
24
+ const GIT_EXCLUSION_PATHSPECS = [
25
+ ":(exclude).dna/runtime",
26
+ ":(exclude).dna/runtime/**",
27
+ ":(exclude).dna/worktrees/runtime",
28
+ ":(exclude).dna/worktrees/runtime/**",
29
+ ];
30
+ function metadata(value) {
31
+ const record = value;
32
+ return {
33
+ dev: String(record.dev),
34
+ ino: String(record.ino),
35
+ mode: String(record.mode),
36
+ size: String(record.size),
37
+ mtime_ns: String(record.mtimeNs),
38
+ ctime_ns: String(record.ctimeNs),
39
+ };
40
+ }
41
+ function sameMetadata(left, right) {
42
+ return JSON.stringify(left) === JSON.stringify(right);
43
+ }
44
+ function canonicalNameOrder(left, right) {
45
+ return Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8"));
46
+ }
47
+ function isExcluded(relativePath) {
48
+ return relativePath === ".git" || WORKSPACE_OBSERVATION_EXCLUDED_PATHS.some((excluded) => (relativePath === excluded || relativePath.startsWith(`${excluded}/`)));
49
+ }
50
+ function normalizedRelative(root, path) {
51
+ return relative(root, path).split(sep).join("/") || ".";
52
+ }
53
+ function git(cwd, args) {
54
+ const result = spawnSync("git", [...args], {
55
+ cwd,
56
+ encoding: "utf8",
57
+ stdio: ["ignore", "pipe", "pipe"],
58
+ windowsHide: true,
59
+ });
60
+ return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
61
+ }
62
+ function gitSnapshot(root) {
63
+ const top = git(root, ["rev-parse", "--show-toplevel"]);
64
+ if (top.status !== 0)
65
+ return null;
66
+ const common = git(root, ["rev-parse", "--git-common-dir"]);
67
+ const head = git(root, ["rev-parse", "--verify", "HEAD"]);
68
+ const branch = git(root, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
69
+ const status = git(root, [
70
+ "status", "--porcelain=v2", "--untracked-files=all", "--",
71
+ ".", ...GIT_EXCLUSION_PATHSPECS,
72
+ ]);
73
+ const localConfig = git(root, ["config", "--local", "--null", "--list"]);
74
+ if (common.status !== 0 || head.status !== 0 || status.status !== 0 || localConfig.status !== 0) {
75
+ throw new WorkspaceObservationError("verification_failed", status.stderr.trim() || head.stderr.trim() || common.stderr.trim()
76
+ || localConfig.stderr.trim() || "Git workspace inspection failed");
77
+ }
78
+ const commonPath = common.stdout.trim();
79
+ const identityPath = isAbsolute(commonPath) ? commonPath : resolve(root, commonPath);
80
+ return {
81
+ repository_identity_digest: `sha256:${createHash("sha256")
82
+ .update(realpathSyncForSnapshot(identityPath), "utf8")
83
+ .digest("hex")}`,
84
+ head: head.stdout.trim(),
85
+ symbolic_branch: branch.status === 0 ? branch.stdout.trim() : null,
86
+ porcelain_v2_digest: `sha256:${createHash("sha256").update(status.stdout, "utf8").digest("hex")}`,
87
+ local_config_digest: `sha256:${createHash("sha256").update(localConfig.stdout, "utf8").digest("hex")}`,
88
+ };
89
+ }
90
+ function realpathSyncForSnapshot(path) {
91
+ try {
92
+ return realpathSync(path);
93
+ }
94
+ catch {
95
+ throw new WorkspaceObservationError("verification_failed", `cannot resolve Git repository identity: ${path}`);
96
+ }
97
+ }
98
+ function validateLimits(input) {
99
+ const limits = { ...DEFAULT_WORKSPACE_OBSERVATION_LIMITS, ...input };
100
+ for (const [name, value] of Object.entries(limits)) {
101
+ if (!Number.isSafeInteger(value) || value < 0) {
102
+ throw new WorkspaceObservationError("verification_failed", `${name} must be a non-negative safe integer`);
103
+ }
104
+ }
105
+ return limits;
106
+ }
107
+ export function workspaceObservationPolicy(limits) {
108
+ return {
109
+ schema_version: "intentdna.workspace_observation_policy.v1",
110
+ limits: validateLimits(limits),
111
+ excluded_relative_paths: WORKSPACE_OBSERVATION_EXCLUDED_PATHS,
112
+ git_observation: "head_branch_status_local_config",
113
+ };
114
+ }
115
+ export async function observeWorkspace(request) {
116
+ const requestedRoot = resolve(request.workspace_root);
117
+ const requestedStatus = await lstat(requestedRoot, { bigint: true });
118
+ if (requestedStatus.isSymbolicLink()) {
119
+ throw new WorkspaceObservationError("verification_failed", "workspace_root must not be a symlink");
120
+ }
121
+ const root = await realpath(requestedRoot);
122
+ const rootStatus = await lstat(root, { bigint: true });
123
+ if (!rootStatus.isDirectory() || rootStatus.isSymbolicLink()) {
124
+ throw new WorkspaceObservationError("verification_failed", "workspace_root must be a real directory");
125
+ }
126
+ const limits = validateLimits(request.limits);
127
+ const now = request.now_ms ?? Date.now;
128
+ const startedAt = now();
129
+ const digest = createHash("sha256");
130
+ let entries = 0;
131
+ let bytes = 0;
132
+ const enforce = (depth) => {
133
+ if (depth > limits.max_depth)
134
+ throw new WorkspaceObservationError("observation_limit_exceeded", "workspace depth limit exceeded");
135
+ if (entries > limits.max_entries)
136
+ throw new WorkspaceObservationError("observation_limit_exceeded", "workspace entry limit exceeded");
137
+ if (bytes > limits.max_bytes)
138
+ throw new WorkspaceObservationError("observation_limit_exceeded", "workspace byte limit exceeded");
139
+ if (now() - startedAt > limits.max_duration_ms)
140
+ throw new WorkspaceObservationError("observation_limit_exceeded", "workspace duration limit exceeded");
141
+ };
142
+ const updateRecord = (kind, relativePath, meta) => {
143
+ digest.update(`${kind}\0${Buffer.byteLength(relativePath)}\0${relativePath}\0${JSON.stringify(meta)}\0`);
144
+ };
145
+ const visit = async (absolutePath, relativePath, depth) => {
146
+ enforce(depth);
147
+ const beforeStatus = await lstat(absolutePath, { bigint: true });
148
+ if (beforeStatus.isSymbolicLink()) {
149
+ throw new WorkspaceObservationError("verification_failed", `symlink is not allowed: ${relativePath}`);
150
+ }
151
+ entries += 1;
152
+ enforce(depth);
153
+ const before = metadata(beforeStatus);
154
+ if (beforeStatus.isDirectory()) {
155
+ updateRecord("directory", relativePath, before);
156
+ const names = (await readdir(absolutePath)).sort(canonicalNameOrder);
157
+ await request.hooks?.after_directory_read?.(relativePath);
158
+ for (const name of names) {
159
+ const childRelative = relativePath === "." ? name : `${relativePath}/${name}`;
160
+ if (isExcluded(childRelative))
161
+ continue;
162
+ await visit(join(absolutePath, name), childRelative, depth + 1);
163
+ }
164
+ const after = metadata(await stat(absolutePath, { bigint: true }));
165
+ if (!sameMetadata(before, after)) {
166
+ throw new WorkspaceObservationError("source_drift", `directory changed while observing: ${relativePath}`);
167
+ }
168
+ return;
169
+ }
170
+ if (!beforeStatus.isFile()) {
171
+ throw new WorkspaceObservationError("verification_failed", `special file is not allowed: ${relativePath}`);
172
+ }
173
+ // Node does not expose openat(2), so same-user namespace replacement cannot be
174
+ // eliminated completely. O_NOFOLLOW plus descriptor/path revalidation closes
175
+ // the practical symlink and replacement windows without native code.
176
+ const handle = await open(absolutePath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
177
+ try {
178
+ const descriptorBefore = metadata(await handle.stat({ bigint: true }));
179
+ if (!sameMetadata(before, descriptorBefore)) {
180
+ throw new WorkspaceObservationError("source_drift", `file changed before read: ${relativePath}`);
181
+ }
182
+ updateRecord("file", relativePath, before);
183
+ const stream = handle.createReadStream({ autoClose: false });
184
+ for await (const chunk of stream) {
185
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
186
+ bytes += buffer.length;
187
+ enforce(depth);
188
+ digest.update(buffer);
189
+ }
190
+ digest.update("\0");
191
+ const descriptorAfter = metadata(await handle.stat({ bigint: true }));
192
+ const pathAfter = await lstat(absolutePath, { bigint: true });
193
+ if (pathAfter.isSymbolicLink() || !sameMetadata(before, descriptorAfter) || !sameMetadata(before, metadata(pathAfter))) {
194
+ throw new WorkspaceObservationError("source_drift", `file changed while observing: ${relativePath}`);
195
+ }
196
+ }
197
+ finally {
198
+ await handle.close();
199
+ }
200
+ };
201
+ const gitBefore = gitSnapshot(root);
202
+ await visit(root, ".", 0);
203
+ const gitAfter = gitSnapshot(root);
204
+ if (JSON.stringify(gitBefore) !== JSON.stringify(gitAfter)) {
205
+ throw new WorkspaceObservationError("source_drift", "Git workspace state changed while observing");
206
+ }
207
+ digest.update(`git\0${JSON.stringify(gitAfter)}\0`);
208
+ return {
209
+ schema_version: "intentdna.workspace_observation.v2",
210
+ observed_digest: `sha256:${digest.digest("hex")}`,
211
+ entry_count: entries,
212
+ byte_count: bytes,
213
+ excluded_relative_paths: WORKSPACE_OBSERVATION_EXCLUDED_PATHS,
214
+ git: gitAfter,
215
+ };
216
+ }
@@ -16,13 +16,6 @@ export declare const CONTROLLER_KIND_REGISTRY: readonly [{
16
16
  readonly requiredRoleKeys: readonly ["analyzer", "analysis_reviewer", "surgeon", "fix_reviewer", "test_runner"];
17
17
  readonly diagnosisRoleKeys: readonly ["analyzer", "analysis_reviewer"];
18
18
  readonly fixRoleKeys: readonly ["surgeon", "fix_reviewer", "test_runner"];
19
- }, {
20
- readonly kind: "flutter_rewrite_fix_loop";
21
- readonly label: "Flutter rewrite fix loop";
22
- readonly supportsSkillGeneration: true;
23
- readonly requiredRoleKeys: readonly ["analyzer", "analysis_reviewer", "surgeon", "fix_reviewer", "test_runner"];
24
- readonly diagnosisRoleKeys: readonly ["analyzer", "analysis_reviewer"];
25
- readonly fixRoleKeys: readonly ["surgeon", "fix_reviewer", "test_runner"];
26
19
  }, {
27
20
  readonly kind: "sequential";
28
21
  readonly label: "Sequential workflow gate";
@@ -14,14 +14,6 @@ export const CONTROLLER_KIND_REGISTRY = [
14
14
  diagnosisRoleKeys: ["analyzer", "analysis_reviewer"],
15
15
  fixRoleKeys: ["surgeon", "fix_reviewer", "test_runner"],
16
16
  },
17
- {
18
- kind: "flutter_rewrite_fix_loop",
19
- label: "Flutter rewrite fix loop",
20
- supportsSkillGeneration: true,
21
- requiredRoleKeys: CONTROLLER_ROLE_KEYS,
22
- diagnosisRoleKeys: ["analyzer", "analysis_reviewer"],
23
- fixRoleKeys: ["surgeon", "fix_reviewer", "test_runner"],
24
- },
25
17
  {
26
18
  kind: "sequential",
27
19
  label: "Sequential workflow gate",
@@ -104,6 +104,7 @@ export interface HandoffArtifact {
104
104
  path?: string;
105
105
  name?: string;
106
106
  artifact_id?: string;
107
+ schema_ref?: string;
107
108
  from?: string;
108
109
  required?: boolean;
109
110
  description: string;
@@ -189,6 +190,20 @@ export interface CompletionCheck {
189
190
  command_success?: string;
190
191
  evaluator_result?: EvaluatorResultCompletion;
191
192
  }
193
+ export interface CanonicalRuntimeStepDef {
194
+ /** Explicit consumed artifact ids whose authored paths become immutable handoff paths. */
195
+ prompt_handoff_bindings?: string[];
196
+ /** Canonical-only producer step ids keyed by consumed artifact id. */
197
+ handoff_sources?: Record<string, string>;
198
+ /** Require the execution surface to prove an independent caller for submission. */
199
+ require_attested_independent_submit?: boolean;
200
+ /** Canonical-only completion checks; legacy Skill projection keeps `completion`. */
201
+ completion?: CompletionCheck[];
202
+ /** Canonical-only structured/reference outputs; legacy Skill projection is unchanged. */
203
+ outputs?: HandoffArtifact[];
204
+ /** Canonical-only output protocol clarification appended to the worker prompt. */
205
+ prompt_suffix?: string;
206
+ }
192
207
  /** Single step in a workflow */
193
208
  export interface WorkflowStepDef {
194
209
  id: string;
@@ -199,6 +214,7 @@ export interface WorkflowStepDef {
199
214
  run_if?: string;
200
215
  prompt?: string;
201
216
  completion?: CompletionCheck[];
217
+ canonical_runtime?: CanonicalRuntimeStepDef;
202
218
  checkpoints?: StepCheckpoint[];
203
219
  handoff?: StepHandoff;
204
220
  isolation?: WorkflowIsolation;
@@ -227,15 +243,66 @@ export interface WorkflowDef {
227
243
  default_isolation?: WorkflowIsolation;
228
244
  merge_strategy?: WorkflowMergeStrategy;
229
245
  }
230
- export type ControllerKind = "fix_loop" | "flutter_rewrite_fix_loop" | "sequential";
231
- export interface ControllerPolicyDef {
232
- diagnosis_reuse_criteria?: string[];
233
- progress_provenance_checks?: string[];
234
- progress_provenance_failure?: string;
235
- verdict_branches?: string[];
236
- verdict_mapping?: string[];
237
- stop_report?: string[];
238
- constraints?: string[];
246
+ export type ControllerKind = "fix_loop" | "sequential";
247
+ export declare const CONTROLLER_RUNTIME_PROGRAM_SCHEMA_VERSION: "intentdna.controller_program.v1";
248
+ export interface ControllerRuntimeDecisionDef {
249
+ step_id: string;
250
+ output_name: string;
251
+ schema_ref: string;
252
+ json_pointer: string;
253
+ }
254
+ export interface ControllerRuntimeReuseDef {
255
+ mode_input: string;
256
+ legacy: {
257
+ diagnosis_spec_reference_input: string;
258
+ diagnosis_spec_digest_input: string;
259
+ diagnosis_contract_reference_input: string;
260
+ diagnosis_contract_digest_input: string;
261
+ };
262
+ canonical: {
263
+ source_plan_input: string;
264
+ diagnosis_result_input: string;
265
+ review_result_input: string;
266
+ approval_reuse: {
267
+ enabled: boolean;
268
+ policy_id: string;
269
+ require_exact_artifact_ids: true;
270
+ require_exact_artifact_digests: true;
271
+ };
272
+ };
273
+ outputs: {
274
+ diagnosis_spec: string;
275
+ diagnosis_contract: string;
276
+ diagnosis_review: string;
277
+ diagnosis_review_verdict: string;
278
+ };
279
+ }
280
+ /**
281
+ * Canonical-only controller program. Runtime and drivers consume only the
282
+ * compiled ExecutableRunPlan; legacy kind/policy prose has no execution meaning.
283
+ */
284
+ export interface ControllerRuntimeProgramDef {
285
+ schema_version: typeof CONTROLLER_RUNTIME_PROGRAM_SCHEMA_VERSION;
286
+ diagnosis: {
287
+ analyzer_step_id: string;
288
+ reviewer_step_id: string;
289
+ decision: ControllerRuntimeDecisionDef;
290
+ approved_value: string;
291
+ request_changes_value: string;
292
+ };
293
+ fix: {
294
+ surgeon_step_id: string;
295
+ reviewer_step_id: string;
296
+ verifier_step_id: string;
297
+ decision: ControllerRuntimeDecisionDef;
298
+ pass_value: string;
299
+ continue_value: string;
300
+ request_changes_value: string;
301
+ blocked_value: string;
302
+ diagnosis_contract_invalid_value: string;
303
+ provenance_invalid_value: string;
304
+ };
305
+ reuse?: ControllerRuntimeReuseDef;
239
306
  }
240
307
  export interface ControllerDef {
241
308
  name: string;
@@ -251,7 +318,7 @@ export interface ControllerDef {
251
318
  review_artifact_path?: string;
252
319
  roles: Record<string, string>;
253
320
  stop_format: "configured_stop_report" | "zh_three_part";
254
- policy?: ControllerPolicyDef;
321
+ runtime_program?: ControllerRuntimeProgramDef;
255
322
  }
256
323
  /** Parallel block syntax sugar — expanded by compiler into steps with depends_on */
257
324
  export interface ParallelBlockDef {
@@ -328,7 +395,10 @@ export interface CompiledProvenanceManifest {
328
395
  export interface VariableDef {
329
396
  description: string;
330
397
  default: string;
398
+ runtime_type?: VariableRuntimeType;
331
399
  }
400
+ export declare const VARIABLE_RUNTIME_TYPE_VALUES: readonly ["trusted_verifier_command"];
401
+ export type VariableRuntimeType = typeof VARIABLE_RUNTIME_TYPE_VALUES[number];
332
402
  export interface DNAMetadata {
333
403
  created: string;
334
404
  updated: string;
@@ -580,6 +650,10 @@ export interface WorkflowStep {
580
650
  max_handoffs?: number;
581
651
  on_handoff_exhausted?: "skip" | "stop";
582
652
  blocked_items_path?: string;
653
+ /** Canonical compiler-only path substitutions derived from explicit handoff bindings. */
654
+ canonical_handoff_path_bindings?: Readonly<Record<string, string>>;
655
+ /** Canonical compiler-only independent submission requirement. */
656
+ canonical_requires_attested_independent_submit?: boolean;
583
657
  }
584
658
  /** A group of steps that can execute in parallel */
585
659
  export interface ParallelGroup {
@@ -17,3 +17,5 @@ export const WORKFLOW_ISOLATION_VALUES = ["none", "worktree", "auto"];
17
17
  export const WORKFLOW_STEP_FAILURE_VALUES = ["retry_with_feedback", "handoff", "skip"];
18
18
  export const WORKFLOW_HANDOFF_EXHAUSTED_VALUES = ["skip", "stop"];
19
19
  export const WORKFLOW_MERGE_STRATEGY_VALUES = ["escalate"];
20
+ export const CONTROLLER_RUNTIME_PROGRAM_SCHEMA_VERSION = "intentdna.controller_program.v1";
21
+ export const VARIABLE_RUNTIME_TYPE_VALUES = ["trusted_verifier_command"];
@@ -8,7 +8,7 @@
8
8
  * - No circular inheritance
9
9
  */
10
10
  import { isAbsolute } from "node:path";
11
- import { CHECKPOINT_ACTION_VALUES, HANDOFF_TYPE_VALUES, RETRY_BACKOFF_VALUES, TRANSITION_CONDITION_VALUES, WORKFLOW_HANDOFF_EXHAUSTED_VALUES, WORKFLOW_ISOLATION_VALUES, WORKFLOW_MERGE_STRATEGY_VALUES, WORKFLOW_STEP_FAILURE_VALUES, } from "./types.js";
11
+ import { CHECKPOINT_ACTION_VALUES, HANDOFF_TYPE_VALUES, RETRY_BACKOFF_VALUES, TRANSITION_CONDITION_VALUES, WORKFLOW_HANDOFF_EXHAUSTED_VALUES, WORKFLOW_ISOLATION_VALUES, WORKFLOW_MERGE_STRATEGY_VALUES, WORKFLOW_STEP_FAILURE_VALUES, VARIABLE_RUNTIME_TYPE_VALUES, } from "./types.js";
12
12
  import { validateController } from "./validators/controllers.js";
13
13
  const SAFE_GENERATED_IDENTIFIER = /^[A-Za-z0-9_-]+$/;
14
14
  const SAFE_GENERATED_DISPLAY_NAME = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/;
@@ -620,6 +620,131 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
620
620
  }
621
621
  }
622
622
  }
623
+ if (step.canonical_runtime !== undefined) {
624
+ const runtime = step.canonical_runtime;
625
+ if (typeof runtime !== "object" || runtime === null || Array.isArray(runtime)) {
626
+ errors.push({ path: `${stepPath}.canonical_runtime`, message: "canonical_runtime must be an object" });
627
+ }
628
+ else {
629
+ if (runtime.prompt_handoff_bindings !== undefined) {
630
+ if (!Array.isArray(runtime.prompt_handoff_bindings) || runtime.prompt_handoff_bindings.length === 0) {
631
+ errors.push({
632
+ path: `${stepPath}.canonical_runtime.prompt_handoff_bindings`,
633
+ message: "prompt_handoff_bindings must be a non-empty array",
634
+ });
635
+ }
636
+ else {
637
+ const seenBindings = new Set();
638
+ for (let j = 0; j < runtime.prompt_handoff_bindings.length; j++) {
639
+ const binding = runtime.prompt_handoff_bindings[j];
640
+ if (typeof binding !== "string" || !isSafeGeneratedIdentifier(binding)) {
641
+ errors.push({
642
+ path: `${stepPath}.canonical_runtime.prompt_handoff_bindings[${j}]`,
643
+ message: "binding id must contain only letters, digits, underscores, and hyphens",
644
+ });
645
+ }
646
+ else if (seenBindings.has(binding)) {
647
+ errors.push({
648
+ path: `${stepPath}.canonical_runtime.prompt_handoff_bindings[${j}]`,
649
+ message: `duplicate binding id '${binding}'`,
650
+ });
651
+ }
652
+ else {
653
+ seenBindings.add(binding);
654
+ }
655
+ }
656
+ }
657
+ }
658
+ if (runtime.handoff_sources !== undefined) {
659
+ if (typeof runtime.handoff_sources !== "object"
660
+ || runtime.handoff_sources === null
661
+ || Array.isArray(runtime.handoff_sources)
662
+ || Object.keys(runtime.handoff_sources).length === 0) {
663
+ errors.push({
664
+ path: `${stepPath}.canonical_runtime.handoff_sources`,
665
+ message: "handoff_sources must be a non-empty object",
666
+ });
667
+ }
668
+ else {
669
+ for (const [artifactId, sourceStepId] of Object.entries(runtime.handoff_sources)) {
670
+ if (!isSafeGeneratedIdentifier(artifactId)) {
671
+ errors.push({
672
+ path: `${stepPath}.canonical_runtime.handoff_sources.${artifactId}`,
673
+ message: "artifact id must contain only letters, digits, underscores, and hyphens",
674
+ });
675
+ }
676
+ if (typeof sourceStepId !== "string" || !isSafeGeneratedIdentifier(sourceStepId)) {
677
+ errors.push({
678
+ path: `${stepPath}.canonical_runtime.handoff_sources.${artifactId}`,
679
+ message: "source step id must contain only letters, digits, underscores, and hyphens",
680
+ });
681
+ }
682
+ }
683
+ }
684
+ }
685
+ if (runtime.require_attested_independent_submit !== undefined
686
+ && typeof runtime.require_attested_independent_submit !== "boolean") {
687
+ errors.push({
688
+ path: `${stepPath}.canonical_runtime.require_attested_independent_submit`,
689
+ message: "require_attested_independent_submit must be a boolean",
690
+ });
691
+ }
692
+ if (runtime.completion !== undefined) {
693
+ if (!Array.isArray(runtime.completion) || runtime.completion.length === 0) {
694
+ errors.push({
695
+ path: `${stepPath}.canonical_runtime.completion`,
696
+ message: "canonical completion must be a non-empty array",
697
+ });
698
+ }
699
+ else {
700
+ for (let j = 0; j < runtime.completion.length; j++) {
701
+ errors.push(...validateCompletionCheck(runtime.completion[j], `${stepPath}.canonical_runtime.completion[${j}]`));
702
+ }
703
+ }
704
+ }
705
+ if (runtime.outputs !== undefined) {
706
+ if (!Array.isArray(runtime.outputs) || runtime.outputs.length === 0) {
707
+ errors.push({
708
+ path: `${stepPath}.canonical_runtime.outputs`,
709
+ message: "canonical outputs must be a non-empty array",
710
+ });
711
+ }
712
+ else {
713
+ const outputIds = new Set();
714
+ for (let j = 0; j < runtime.outputs.length; j++) {
715
+ const artifact = runtime.outputs[j];
716
+ const artifactPath = `${stepPath}.canonical_runtime.outputs[${j}]`;
717
+ if (!HANDOFF_TYPE_VALUES.includes(artifact.type)) {
718
+ errors.push({ path: `${artifactPath}.type`, message: `must be one of: ${HANDOFF_TYPE_VALUES.join(", ")}` });
719
+ }
720
+ if (!artifact.description) {
721
+ errors.push({ path: `${artifactPath}.description`, message: "canonical output requires 'description'" });
722
+ }
723
+ const identity = artifact.artifact_id ?? artifact.name ?? artifact.path;
724
+ if (!identity) {
725
+ errors.push({ path: artifactPath, message: "canonical output must define path, name, or artifact_id" });
726
+ }
727
+ else if (outputIds.has(identity)) {
728
+ errors.push({ path: artifactPath, message: `duplicate canonical output '${identity}'` });
729
+ }
730
+ else {
731
+ outputIds.add(identity);
732
+ }
733
+ if (artifact.type === "state" && (typeof artifact.schema_ref !== "string" || artifact.schema_ref.trim() === "")) {
734
+ errors.push({ path: `${artifactPath}.schema_ref`, message: "structured state output requires schema_ref" });
735
+ }
736
+ }
737
+ }
738
+ }
739
+ if (runtime.prompt_suffix !== undefined
740
+ && (typeof runtime.prompt_suffix !== "string" || runtime.prompt_suffix.trim() === "")) {
741
+ errors.push({
742
+ path: `${stepPath}.canonical_runtime.prompt_suffix`,
743
+ message: "prompt_suffix must be a non-empty string",
744
+ });
745
+ }
746
+ }
747
+ }
623
748
  for (const direction of ["consumes", "produces"]) {
624
749
  const artifacts = step.handoff?.[direction] ?? [];
625
750
  for (let j = 0; j < artifacts.length; j++) {
@@ -631,6 +756,9 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
631
756
  if (!artifact.description) {
632
757
  errors.push({ path: `${artifactPath}.description`, message: "handoff artifact requires 'description'" });
633
758
  }
759
+ if (artifact.schema_ref !== undefined && (typeof artifact.schema_ref !== "string" || artifact.schema_ref.trim() === "")) {
760
+ errors.push({ path: `${artifactPath}.schema_ref`, message: "schema_ref must be a non-empty string" });
761
+ }
634
762
  if (artifact.required !== false && artifact.type !== "git_commit" && !artifact.path && !artifact.name && !artifact.artifact_id) {
635
763
  errors.push({ path: artifactPath, message: "required handoff artifacts must define path, name, or artifact_id" });
636
764
  }
@@ -805,6 +933,28 @@ export function validateDNA(dna) {
805
933
  for (const [name, role] of Object.entries(dna.roles ?? {})) {
806
934
  errors.push(...validateRole(name, role, geneNames));
807
935
  }
936
+ for (const [name, variable] of Object.entries(dna.variables ?? {})) {
937
+ if (typeof variable === "string")
938
+ continue;
939
+ const path = `variables.${name}`;
940
+ if (variable === null || typeof variable !== "object" || Array.isArray(variable)) {
941
+ errors.push({ path, message: "must be a string or variable definition" });
942
+ continue;
943
+ }
944
+ if (typeof variable.description !== "string" || !variable.description.trim()) {
945
+ errors.push({ path: `${path}.description`, message: "must be a non-empty string" });
946
+ }
947
+ if (typeof variable.default !== "string") {
948
+ errors.push({ path: `${path}.default`, message: "must be a string" });
949
+ }
950
+ if (variable.runtime_type !== undefined
951
+ && !VARIABLE_RUNTIME_TYPE_VALUES.includes(variable.runtime_type)) {
952
+ errors.push({
953
+ path: `${path}.runtime_type`,
954
+ message: `must be one of: ${VARIABLE_RUNTIME_TYPE_VALUES.join(", ")}`,
955
+ });
956
+ }
957
+ }
808
958
  // Validate workflow / workflows (mutually exclusive)
809
959
  if (dna.workflow !== undefined && dna.workflows !== undefined) {
810
960
  errors.push({
@@ -1,3 +1,3 @@
1
- import type { ControllerDef, WorkflowDef } from "../types.js";
1
+ import { type ControllerDef, type WorkflowDef } from "../types.js";
2
2
  import type { ValidationError } from "../validate.js";
3
3
  export declare function validateController(controller: ControllerDef, roleNames: Set<string>, workflows: Record<string, WorkflowDef>, path: string): ValidationError[];