intentdna 1.9.0 → 1.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +227 -87
- package/dist/cli/commands/run-lifecycle.d.ts +61 -11
- package/dist/cli/commands/run-lifecycle.js +184 -50
- package/dist/cli/commands/run-observer.d.ts +15 -0
- package/dist/cli/commands/run-observer.js +210 -0
- package/dist/cli/commands/run.d.ts +16 -20
- package/dist/cli/commands/run.js +461 -659
- package/dist/cli/commands/sync.js +16 -12
- package/dist/cli/index.js +14 -5
- package/dist/compiler/compile.d.ts +1 -0
- package/dist/compiler/compile.js +1 -1
- package/dist/compiler/controller.d.ts +16 -0
- package/dist/compiler/controller.js +693 -0
- package/dist/compiler/input-resolver.d.ts +2 -0
- package/dist/compiler/input-resolver.js +22 -6
- package/dist/hooks/cli.d.ts +45 -0
- package/dist/hooks/cli.js +263 -31
- package/dist/hooks/host-event.d.ts +57 -0
- package/dist/hooks/host-event.js +187 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/protocol.d.ts +7 -0
- package/dist/hooks/schema.d.ts +1 -0
- package/dist/hooks/schema.js +89 -1
- package/dist/hooks/state.d.ts +23 -1
- package/dist/hooks/state.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-run.d.ts +4 -0
- package/dist/mcp/tools-run.js +419 -0
- package/dist/mcp/tools-state.d.ts +1 -1
- package/dist/mcp/tools-state.js +13 -241
- package/dist/runtime/artifact-store.d.ts +146 -0
- package/dist/runtime/artifact-store.js +1436 -0
- package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
- package/dist/runtime/canonical-attempt-outcome.js +195 -0
- package/dist/runtime/canonical-json.d.ts +10 -0
- package/dist/runtime/canonical-json.js +105 -0
- package/dist/runtime/canonical-run-application.d.ts +93 -0
- package/dist/runtime/canonical-run-application.js +229 -0
- package/dist/runtime/canonical-run-service.d.ts +454 -0
- package/dist/runtime/canonical-run-service.js +4760 -0
- package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
- package/dist/runtime/canonical-runtime-composition.js +34 -0
- package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
- package/dist/runtime/canonical-runtime-projection.js +4 -0
- package/dist/runtime/canonical-target-compiler.d.ts +46 -0
- package/dist/runtime/canonical-target-compiler.js +514 -0
- package/dist/runtime/claude-agent-identity.d.ts +2 -0
- package/dist/runtime/claude-agent-identity.js +21 -0
- package/dist/runtime/executable-run-plan.d.ts +328 -0
- package/dist/runtime/executable-run-plan.js +1485 -0
- package/dist/runtime/execution-authority.d.ts +83 -0
- package/dist/runtime/execution-authority.js +98 -0
- package/dist/runtime/handoff-resolver.js +4 -1
- package/dist/runtime/harness-pull-adapter.d.ts +146 -0
- package/dist/runtime/harness-pull-adapter.js +315 -0
- package/dist/runtime/index.d.ts +39 -14
- package/dist/runtime/index.js +29 -7
- package/dist/runtime/local-execution-authority.d.ts +194 -0
- package/dist/runtime/local-execution-authority.js +2897 -0
- package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
- package/dist/runtime/local-execution-reconciliation.js +107 -0
- package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
- package/dist/runtime/local-execution-supervisor-script.js +793 -0
- package/dist/runtime/local-provider-sandbox.d.ts +42 -0
- package/dist/runtime/local-provider-sandbox.js +154 -0
- package/dist/runtime/local-verifier-execution.d.ts +24 -0
- package/dist/runtime/local-verifier-execution.js +290 -0
- package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
- package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
- package/dist/runtime/plan-store.d.ts +30 -0
- package/dist/runtime/plan-store.js +231 -0
- package/dist/runtime/process-tree.d.ts +9 -0
- package/dist/runtime/process-tree.js +97 -24
- package/dist/runtime/providers/codex.js +2 -2
- package/dist/runtime/push-driver.d.ts +184 -0
- package/dist/runtime/push-driver.js +1002 -0
- package/dist/runtime/result-store.d.ts +34 -3
- package/dist/runtime/result-store.js +1073 -2
- package/dist/runtime/run-binding.d.ts +68 -0
- package/dist/runtime/run-binding.js +278 -0
- package/dist/runtime/run-contracts.d.ts +575 -0
- package/dist/runtime/run-contracts.js +58 -7
- package/dist/runtime/run-controller.d.ts +1 -6
- package/dist/runtime/run-controller.js +0 -41
- package/dist/runtime/run-store.d.ts +85 -7
- package/dist/runtime/run-store.js +2528 -186
- package/dist/runtime/skill-adapter.d.ts +18 -7
- package/dist/runtime/skill-adapter.js +100 -742
- package/dist/runtime/structured-output-validator.d.ts +44 -0
- package/dist/runtime/structured-output-validator.js +171 -0
- package/dist/runtime/verifier-command-binding.d.ts +22 -0
- package/dist/runtime/verifier-command-binding.js +162 -0
- package/dist/runtime/verifier.d.ts +46 -6
- package/dist/runtime/verifier.js +355 -53
- package/dist/runtime/windows-job-keeper.d.ts +47 -0
- package/dist/runtime/windows-job-keeper.js +231 -0
- package/dist/runtime/worker-executor.d.ts +17 -1
- package/dist/runtime/worker-executor.js +26 -5
- package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
- package/dist/runtime/workflow-plan-adapter.js +502 -6
- package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
- package/dist/runtime/workflow-runtime-manifest.js +5 -0
- package/dist/runtime/workspace-isolation.d.ts +29 -2
- package/dist/runtime/workspace-isolation.js +488 -11
- package/dist/runtime/workspace-observation.d.ts +44 -0
- package/dist/runtime/workspace-observation.js +216 -0
- package/dist/schema/controller-registry.d.ts +0 -7
- package/dist/schema/controller-registry.js +0 -8
- package/dist/schema/types.d.ts +84 -10
- package/dist/schema/types.js +2 -0
- package/dist/schema/validate.js +151 -1
- package/dist/schema/validators/controllers.d.ts +1 -1
- package/dist/schema/validators/controllers.js +126 -41
- package/dist/schema/workflow-authoring-contract.js +2 -0
- package/dist/schema/yaml-parser.js +1 -1
- package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
- package/dist/templates/flutter-rewrite.dna.yaml +87 -60
- package/dist/templates/safe-refactoring.dna.yaml +1 -0
- package/native/windows-job-keeper/README.md +80 -0
- package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
- package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
- package/native/windows-job-keeper/keeper.c +979 -0
- package/native/windows-job-keeper/manifest.json +33 -0
- package/package.json +6 -2
- package/scripts/build-windows-job-keeper.mjs +172 -0
- package/scripts/verify-windows-job-keeper.mjs +184 -0
|
@@ -0,0 +1,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",
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -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" | "
|
|
231
|
-
export
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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 {
|
package/dist/schema/types.js
CHANGED
|
@@ -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"];
|
package/dist/schema/validate.js
CHANGED
|
@@ -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
|
|
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[];
|