@abelxiaoxing/cadence 1.0.14 → 1.1.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.
- package/README.md +39 -13
- package/agents/contract-reviewer.md +2 -2
- package/agents/design-explorer.md +1 -1
- package/agents/diagnosis-worker.md +6 -3
- package/agents/implementation-worker.md +14 -9
- package/config/.env.example +5 -17
- package/config/routes.example.json +42 -0
- package/package.json +3 -3
- package/prompts/abel-design.md +206 -228
- package/prompts/abel-diagnose.md +39 -18
- package/prompts/abel-implement.md +122 -178
- package/prompts/abel-init.md +36 -33
- package/skills/abel-workflow/SKILL.md +190 -168
- package/src/apply-transaction.ts +1427 -0
- package/src/artifact-store.ts +204 -0
- package/src/child-session.ts +136 -17
- package/src/contracts.ts +121 -696
- package/src/control-contracts.ts +246 -0
- package/src/delivery-compiler.ts +1118 -0
- package/src/design-control.ts +846 -0
- package/src/design-journal.ts +1569 -0
- package/src/file-snapshot.ts +5 -0
- package/src/implement-graph.ts +11 -4
- package/src/index.ts +1833 -118
- package/src/isolation-backend.ts +269 -0
- package/src/packet-runtime.ts +533 -0
- package/src/parent-provider.ts +23 -4
- package/src/route-policy.ts +483 -0
- package/src/run-state.ts +430 -0
- package/src/run-store.ts +990 -0
- package/src/safe-path.ts +11 -0
- package/src/scoped-tools.ts +36 -0
- package/src/state-root.ts +215 -0
- package/src/subagent-activity.ts +526 -27
- package/src/submit-tool.ts +274 -0
- package/src/task-ledger.ts +1380 -0
- package/src/verification-capability.ts +560 -45
- package/src/worker-broker.ts +703 -0
- package/src/workflow-engine.ts +7706 -0
- package/src/workspace-store.ts +544 -0
- package/src/candidate-preflight.ts +0 -1618
- package/src/drain.ts +0 -20
- package/src/patch.ts +0 -505
- package/src/result-store.ts +0 -312
- package/src/runtime.ts +0 -2436
- package/src/scheduler.ts +0 -384
- package/src/subagent-endpoint.ts +0 -158
- package/src/worker.ts +0 -204
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
lstatSync,
|
|
3
|
+
readdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
realpathSync,
|
|
6
|
+
type Stats,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import path from "node:path";
|
|
3
9
|
import {
|
|
4
10
|
type AtomicVerificationContract,
|
|
@@ -8,6 +14,7 @@ import {
|
|
|
8
14
|
verificationInputPaths,
|
|
9
15
|
verificationSteps,
|
|
10
16
|
} from "./contracts.ts";
|
|
17
|
+
import { type Bound, isCurrent, snapshotFiles } from "./file-snapshot.ts";
|
|
11
18
|
import { isSafeRegularFile, observeSafePath } from "./safe-path.ts";
|
|
12
19
|
|
|
13
20
|
export type VerificationCapabilityDiagnostic =
|
|
@@ -32,16 +39,41 @@ export type VerificationCapabilityResult =
|
|
|
32
39
|
}
|
|
33
40
|
| { ok: false; diagnostic: VerificationCapabilityDiagnostic };
|
|
34
41
|
|
|
42
|
+
export interface CurrentVerificationCapability {
|
|
43
|
+
verificationId: string;
|
|
44
|
+
runnerBindings: VerificationRunnerBinding[];
|
|
45
|
+
inputSnapshot: Bound;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type CurrentVerificationCapabilityResult =
|
|
49
|
+
| { ok: true; value: CurrentVerificationCapability }
|
|
50
|
+
| { ok: false; diagnostic: VerificationCapabilityDiagnostic };
|
|
51
|
+
|
|
35
52
|
export interface VerificationRunnerBinding {
|
|
36
53
|
command: string;
|
|
37
54
|
executablePath: string;
|
|
55
|
+
fixedArgs?: string[];
|
|
38
56
|
mountSource?: string;
|
|
39
57
|
}
|
|
40
58
|
|
|
59
|
+
export interface VerificationRunnerEnvironment {
|
|
60
|
+
platform?: NodeJS.Platform;
|
|
61
|
+
path?: string;
|
|
62
|
+
pathExt?: string;
|
|
63
|
+
pathDelimiter?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface VerificationCapabilityOptions {
|
|
67
|
+
dependencyOwner?: string;
|
|
68
|
+
runnerEnvironment?: VerificationRunnerEnvironment;
|
|
69
|
+
}
|
|
70
|
+
|
|
41
71
|
const SHELL_OPERATOR = /[;&|`$<>\n\r\0]/u;
|
|
42
72
|
const SIMPLE_TOKEN =
|
|
43
73
|
/^(?:[a-z0-9][a-z0-9._:@/=-]*|--?[a-z0-9][a-z0-9._:@/=-]*)$/iu;
|
|
44
74
|
const NETWORKING_RUNNERS = new Set(["bunx", "pnpx"]);
|
|
75
|
+
const WINDOWS_EXECUTABLE_EXTENSIONS = new Set([".exe", ".cmd"]);
|
|
76
|
+
const DEFAULT_WINDOWS_PATH_EXT = ".COM;.EXE;.BAT;.CMD";
|
|
45
77
|
const SYSTEM_MOUNT_ROOTS = ["/usr", "/bin", "/lib", "/lib64", "/etc"];
|
|
46
78
|
const BROAD_MOUNT_ROOTS = new Set([
|
|
47
79
|
"/",
|
|
@@ -60,12 +92,17 @@ function capabilitySuccess(
|
|
|
60
92
|
return {
|
|
61
93
|
ok: true,
|
|
62
94
|
verificationId,
|
|
63
|
-
runnerBindings: runnerBindings
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
95
|
+
runnerBindings: runnerBindings
|
|
96
|
+
.filter((binding) => {
|
|
97
|
+
const key = `${binding.command}\0${binding.executablePath}\0${JSON.stringify(binding.fixedArgs ?? [])}\0${binding.mountSource ?? ""}`;
|
|
98
|
+
if (seen.has(key)) return false;
|
|
99
|
+
seen.add(key);
|
|
100
|
+
return true;
|
|
101
|
+
})
|
|
102
|
+
.map((binding) => ({
|
|
103
|
+
...binding,
|
|
104
|
+
...(binding.fixedArgs ? { fixedArgs: [...binding.fixedArgs] } : {}),
|
|
105
|
+
})),
|
|
69
106
|
};
|
|
70
107
|
}
|
|
71
108
|
|
|
@@ -97,69 +134,473 @@ function localExecutable(
|
|
|
97
134
|
root: string,
|
|
98
135
|
executable: string,
|
|
99
136
|
dependencyOwner = root,
|
|
137
|
+
environment: VerificationRunnerEnvironment = {},
|
|
100
138
|
): boolean {
|
|
101
139
|
const dependencyRoot = path.resolve(dependencyOwner, "node_modules");
|
|
102
|
-
const unresolved = path.resolve(dependencyRoot, ".bin", executable);
|
|
103
140
|
try {
|
|
104
141
|
const canonicalDependencyRoot = realpathSync(dependencyRoot);
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
(
|
|
112
|
-
|
|
142
|
+
for (const name of localExecutableNames(executable, environment)) {
|
|
143
|
+
const unresolved = resolveCandidateName(
|
|
144
|
+
path.resolve(dependencyRoot, ".bin"),
|
|
145
|
+
name,
|
|
146
|
+
environment.platform ?? process.platform,
|
|
147
|
+
);
|
|
148
|
+
if (!unresolved) continue;
|
|
149
|
+
const canonical = realpathSync(unresolved);
|
|
150
|
+
const stat = lstatSync(canonical);
|
|
151
|
+
if (
|
|
152
|
+
canonical !== canonicalDependencyRoot &&
|
|
153
|
+
pathWithin(
|
|
154
|
+
canonicalDependencyRoot,
|
|
155
|
+
canonical,
|
|
156
|
+
environment.platform ?? process.platform,
|
|
157
|
+
) &&
|
|
158
|
+
regularExecutableFile(stat, environment.platform ?? process.platform)
|
|
159
|
+
) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
113
164
|
} catch {
|
|
114
165
|
return false;
|
|
115
166
|
}
|
|
116
167
|
}
|
|
117
168
|
|
|
169
|
+
function pathApi(
|
|
170
|
+
platform: NodeJS.Platform,
|
|
171
|
+
...values: readonly string[]
|
|
172
|
+
): typeof path.posix {
|
|
173
|
+
if (platform !== "win32") return path.posix;
|
|
174
|
+
if (
|
|
175
|
+
process.platform === "win32" ||
|
|
176
|
+
values.some(
|
|
177
|
+
(value) => /^[a-z]:[\\/]/iu.test(value) || value.startsWith("\\\\"),
|
|
178
|
+
)
|
|
179
|
+
) {
|
|
180
|
+
return path.win32;
|
|
181
|
+
}
|
|
182
|
+
// Tests may model Windows executable semantics over a POSIX fixture tree.
|
|
183
|
+
return path.posix;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function pathWithin(
|
|
187
|
+
root: string,
|
|
188
|
+
candidate: string,
|
|
189
|
+
platform: NodeJS.Platform,
|
|
190
|
+
): boolean {
|
|
191
|
+
const platformPath = pathApi(platform, root, candidate);
|
|
192
|
+
const normalizedRoot = platformPath.resolve(root);
|
|
193
|
+
const relative = platformPath.relative(
|
|
194
|
+
normalizedRoot,
|
|
195
|
+
platformPath.resolve(candidate),
|
|
196
|
+
);
|
|
197
|
+
return (
|
|
198
|
+
relative === "" ||
|
|
199
|
+
(relative !== ".." &&
|
|
200
|
+
!relative.startsWith(`..${platformPath.sep}`) &&
|
|
201
|
+
!platformPath.isAbsolute(relative))
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function regularExecutableFile(
|
|
206
|
+
stat: Stats,
|
|
207
|
+
platform: NodeJS.Platform,
|
|
208
|
+
): boolean {
|
|
209
|
+
return stat.isFile() && (platform === "win32" || (stat.mode & 0o111) !== 0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function windowsPathExtensions(pathExt: string | undefined): string[] {
|
|
213
|
+
const seen = new Set<string>();
|
|
214
|
+
const extensions: string[] = [];
|
|
215
|
+
for (const entry of (pathExt ?? DEFAULT_WINDOWS_PATH_EXT).split(";")) {
|
|
216
|
+
const extension = entry.trim().toLowerCase();
|
|
217
|
+
if (!WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) || seen.has(extension)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
seen.add(extension);
|
|
221
|
+
extensions.push(extension);
|
|
222
|
+
}
|
|
223
|
+
return extensions;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function runnerNames(
|
|
227
|
+
command: string,
|
|
228
|
+
environment: VerificationRunnerEnvironment,
|
|
229
|
+
): string[] {
|
|
230
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(command)) return [];
|
|
231
|
+
if ((environment.platform ?? process.platform) !== "win32") {
|
|
232
|
+
return [command];
|
|
233
|
+
}
|
|
234
|
+
const extension = path.extname(command).toLowerCase();
|
|
235
|
+
if (extension) {
|
|
236
|
+
return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [command] : [];
|
|
237
|
+
}
|
|
238
|
+
return windowsPathExtensions(environment.pathExt).map(
|
|
239
|
+
(candidateExtension) => `${command}${candidateExtension}`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function localExecutableNames(
|
|
244
|
+
executable: string,
|
|
245
|
+
environment: VerificationRunnerEnvironment,
|
|
246
|
+
): string[] {
|
|
247
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(executable)) return [];
|
|
248
|
+
if ((environment.platform ?? process.platform) !== "win32") {
|
|
249
|
+
return [executable];
|
|
250
|
+
}
|
|
251
|
+
const extension = path.extname(executable).toLowerCase();
|
|
252
|
+
if (extension) {
|
|
253
|
+
return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [executable] : [];
|
|
254
|
+
}
|
|
255
|
+
return [
|
|
256
|
+
executable,
|
|
257
|
+
...windowsPathExtensions(environment.pathExt).map(
|
|
258
|
+
(candidateExtension) => `${executable}${candidateExtension}`,
|
|
259
|
+
),
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function resolveCandidateName(
|
|
264
|
+
directory: string,
|
|
265
|
+
name: string,
|
|
266
|
+
platform: NodeJS.Platform,
|
|
267
|
+
): string | null {
|
|
268
|
+
const exact = path.join(directory, name);
|
|
269
|
+
if (platform !== "win32") return exact;
|
|
270
|
+
try {
|
|
271
|
+
const matches = readdirSync(directory).filter(
|
|
272
|
+
(entry) => entry.toLowerCase() === name.toLowerCase(),
|
|
273
|
+
);
|
|
274
|
+
return matches.length === 1
|
|
275
|
+
? path.join(directory, matches[0] as string)
|
|
276
|
+
: null;
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
118
282
|
function within(root: string, candidate: string): boolean {
|
|
119
283
|
return candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
|
120
284
|
}
|
|
121
285
|
|
|
122
|
-
function commonDirectory(
|
|
123
|
-
|
|
124
|
-
|
|
286
|
+
export function commonDirectory(
|
|
287
|
+
left: string,
|
|
288
|
+
right: string,
|
|
289
|
+
platform: NodeJS.Platform = process.platform,
|
|
290
|
+
): string {
|
|
291
|
+
const platformPath = pathApi(platform, left, right);
|
|
292
|
+
const leftResolved = platformPath.resolve(left);
|
|
293
|
+
const rightResolved = platformPath.resolve(right);
|
|
294
|
+
const leftRoot = platformPath.parse(leftResolved).root;
|
|
295
|
+
const rightRoot = platformPath.parse(rightResolved).root;
|
|
296
|
+
const comparable = (value: string) =>
|
|
297
|
+
platform === "win32" ? value.toLowerCase() : value;
|
|
298
|
+
if (comparable(leftRoot) !== comparable(rightRoot)) return leftRoot;
|
|
299
|
+
|
|
300
|
+
const parts = (value: string, root: string) =>
|
|
301
|
+
platformPath.relative(root, value).split(platformPath.sep).filter(Boolean);
|
|
302
|
+
const leftParts = parts(leftResolved, leftRoot);
|
|
303
|
+
const rightParts = parts(rightResolved, rightRoot);
|
|
125
304
|
const common: string[] = [];
|
|
126
305
|
for (
|
|
127
306
|
let index = 0;
|
|
128
307
|
index < Math.min(leftParts.length, rightParts.length);
|
|
129
308
|
index++
|
|
130
309
|
) {
|
|
131
|
-
|
|
132
|
-
|
|
310
|
+
const leftPart = leftParts[index] as string;
|
|
311
|
+
const rightPart = rightParts[index] as string;
|
|
312
|
+
if (comparable(leftPart) !== comparable(rightPart)) break;
|
|
313
|
+
common.push(leftPart);
|
|
133
314
|
}
|
|
134
|
-
return
|
|
315
|
+
return platformPath.join(leftRoot, ...common);
|
|
135
316
|
}
|
|
136
317
|
|
|
137
318
|
function unsafeMountSource(source: string): boolean {
|
|
138
319
|
if (BROAD_MOUNT_ROOTS.has(source)) return true;
|
|
139
|
-
|
|
320
|
+
if (path.resolve(source) === path.parse(path.resolve(source)).root)
|
|
321
|
+
return true;
|
|
322
|
+
const parts = path
|
|
323
|
+
.relative(path.parse(path.resolve(source)).root, path.resolve(source))
|
|
324
|
+
.split(path.sep)
|
|
325
|
+
.filter(Boolean)
|
|
326
|
+
.map((part) => part.toLowerCase());
|
|
140
327
|
return (
|
|
141
328
|
(parts[0] === "home" && parts.length <= 2) ||
|
|
142
|
-
(parts[0] === "
|
|
329
|
+
(parts[0] === "users" && parts.length <= 2) ||
|
|
143
330
|
(parts[0] === "root" && parts.length <= 1)
|
|
144
331
|
);
|
|
145
332
|
}
|
|
146
333
|
|
|
334
|
+
function safeRunnerSource(
|
|
335
|
+
paths: readonly string[],
|
|
336
|
+
platform: NodeJS.Platform,
|
|
337
|
+
): string | null {
|
|
338
|
+
let source = path.dirname(paths[0] as string);
|
|
339
|
+
for (const candidate of paths.slice(1)) {
|
|
340
|
+
source = commonDirectory(source, path.dirname(candidate), platform);
|
|
341
|
+
}
|
|
342
|
+
if (unsafeMountSource(source)) return null;
|
|
343
|
+
try {
|
|
344
|
+
const stat = lstatSync(source);
|
|
345
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return null;
|
|
346
|
+
} catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
return paths.every((candidate) => pathWithin(source, candidate, platform))
|
|
350
|
+
? source
|
|
351
|
+
: null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function resolvedRunnerEnvironment(
|
|
355
|
+
environment: VerificationRunnerEnvironment,
|
|
356
|
+
): Required<VerificationRunnerEnvironment> {
|
|
357
|
+
return {
|
|
358
|
+
platform: environment.platform ?? process.platform,
|
|
359
|
+
path: environment.path ?? process.env.PATH ?? "",
|
|
360
|
+
pathExt:
|
|
361
|
+
environment.pathExt ?? process.env.PATHEXT ?? DEFAULT_WINDOWS_PATH_EXT,
|
|
362
|
+
pathDelimiter: environment.pathDelimiter ?? path.delimiter,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function windowsShimBinding(
|
|
367
|
+
command: string,
|
|
368
|
+
launcherPath: string,
|
|
369
|
+
environment: Required<VerificationRunnerEnvironment>,
|
|
370
|
+
): VerificationRunnerBinding | null {
|
|
371
|
+
const name = path.basename(command, path.extname(command)).toLowerCase();
|
|
372
|
+
if (name !== "npm" && name !== "npx") return null;
|
|
373
|
+
const node = resolveVerificationRunner("node", environment);
|
|
374
|
+
if (
|
|
375
|
+
!node ||
|
|
376
|
+
node.fixedArgs ||
|
|
377
|
+
path.extname(node.executablePath).toLowerCase() !== ".exe"
|
|
378
|
+
) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const cli = path.join(
|
|
382
|
+
path.dirname(launcherPath),
|
|
383
|
+
"node_modules",
|
|
384
|
+
"npm",
|
|
385
|
+
"bin",
|
|
386
|
+
`${name}-cli.js`,
|
|
387
|
+
);
|
|
388
|
+
try {
|
|
389
|
+
const canonicalLauncher = realpathSync(launcherPath);
|
|
390
|
+
const canonicalCli = realpathSync(cli);
|
|
391
|
+
const cliStat = lstatSync(canonicalCli);
|
|
392
|
+
if (!cliStat.isFile()) return null;
|
|
393
|
+
const source = safeRunnerSource(
|
|
394
|
+
[node.executablePath, canonicalLauncher, canonicalCli],
|
|
395
|
+
environment.platform,
|
|
396
|
+
);
|
|
397
|
+
if (!source) return null;
|
|
398
|
+
return {
|
|
399
|
+
command,
|
|
400
|
+
executablePath: node.executablePath,
|
|
401
|
+
fixedArgs: [canonicalCli],
|
|
402
|
+
};
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function localPackageDirectories(dependencyRoot: string): string[] {
|
|
409
|
+
const packages: string[] = [];
|
|
410
|
+
try {
|
|
411
|
+
for (const entry of readdirSync(dependencyRoot, { withFileTypes: true })) {
|
|
412
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
413
|
+
const candidate = path.join(dependencyRoot, entry.name);
|
|
414
|
+
if (!entry.name.startsWith("@")) {
|
|
415
|
+
packages.push(candidate);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
for (const scoped of readdirSync(candidate, { withFileTypes: true })) {
|
|
419
|
+
if (scoped.isDirectory() && !scoped.name.startsWith(".")) {
|
|
420
|
+
packages.push(path.join(candidate, scoped.name));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
return packages;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function localPackageBinTargets(
|
|
431
|
+
dependencyOwner: string,
|
|
432
|
+
executable: string,
|
|
433
|
+
platform: NodeJS.Platform,
|
|
434
|
+
): string[] {
|
|
435
|
+
const dependencyRoot = path.resolve(dependencyOwner, "node_modules");
|
|
436
|
+
let canonicalDependencyRoot: string;
|
|
437
|
+
try {
|
|
438
|
+
canonicalDependencyRoot = realpathSync(dependencyRoot);
|
|
439
|
+
} catch {
|
|
440
|
+
return [];
|
|
441
|
+
}
|
|
442
|
+
const targets: string[] = [];
|
|
443
|
+
for (const packageDirectory of localPackageDirectories(dependencyRoot)) {
|
|
444
|
+
try {
|
|
445
|
+
const directoryStat = lstatSync(packageDirectory);
|
|
446
|
+
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const canonicalPackage = realpathSync(packageDirectory);
|
|
450
|
+
if (
|
|
451
|
+
!pathWithin(canonicalDependencyRoot, canonicalPackage, platform) ||
|
|
452
|
+
canonicalPackage === canonicalDependencyRoot
|
|
453
|
+
) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const manifestPath = path.join(canonicalPackage, "package.json");
|
|
457
|
+
const manifestStat = lstatSync(manifestPath);
|
|
458
|
+
if (!manifestStat.isFile() || manifestStat.isSymbolicLink()) continue;
|
|
459
|
+
const manifest = JSON.parse(
|
|
460
|
+
readFileSync(manifestPath, "utf8"),
|
|
461
|
+
) as unknown;
|
|
462
|
+
if (
|
|
463
|
+
!manifest ||
|
|
464
|
+
typeof manifest !== "object" ||
|
|
465
|
+
Array.isArray(manifest)
|
|
466
|
+
) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const record = manifest as Record<string, unknown>;
|
|
470
|
+
const packageName =
|
|
471
|
+
typeof record.name === "string"
|
|
472
|
+
? (record.name.split("/").pop() ?? "")
|
|
473
|
+
: "";
|
|
474
|
+
const bin = record.bin;
|
|
475
|
+
let relative: unknown;
|
|
476
|
+
if (
|
|
477
|
+
typeof bin === "string" &&
|
|
478
|
+
(platform === "win32"
|
|
479
|
+
? packageName.toLowerCase() === executable.toLowerCase()
|
|
480
|
+
: packageName === executable)
|
|
481
|
+
) {
|
|
482
|
+
relative = bin;
|
|
483
|
+
} else if (bin && typeof bin === "object" && !Array.isArray(bin)) {
|
|
484
|
+
const entries = Object.entries(bin as Record<string, unknown>).filter(
|
|
485
|
+
([name]) =>
|
|
486
|
+
platform === "win32"
|
|
487
|
+
? name.toLowerCase() === executable.toLowerCase()
|
|
488
|
+
: name === executable,
|
|
489
|
+
);
|
|
490
|
+
if (entries.length === 1) relative = entries[0]?.[1];
|
|
491
|
+
}
|
|
492
|
+
if (
|
|
493
|
+
typeof relative !== "string" ||
|
|
494
|
+
relative.length === 0 ||
|
|
495
|
+
relative.includes("\0")
|
|
496
|
+
) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const unresolved = path.resolve(canonicalPackage, relative);
|
|
500
|
+
if (!pathWithin(canonicalPackage, unresolved, platform)) continue;
|
|
501
|
+
const canonical = realpathSync(unresolved);
|
|
502
|
+
const stat = lstatSync(canonical);
|
|
503
|
+
if (
|
|
504
|
+
stat.isFile() &&
|
|
505
|
+
pathWithin(canonicalPackage, canonical, platform) &&
|
|
506
|
+
pathWithin(canonicalDependencyRoot, canonical, platform)
|
|
507
|
+
) {
|
|
508
|
+
targets.push(canonical);
|
|
509
|
+
}
|
|
510
|
+
} catch {}
|
|
511
|
+
}
|
|
512
|
+
return [...new Set(targets)];
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function resolveLocalVerificationRunner(
|
|
516
|
+
dependencyOwner: string,
|
|
517
|
+
executable: string,
|
|
518
|
+
runnerEnvironment: VerificationRunnerEnvironment,
|
|
519
|
+
): VerificationRunnerBinding | null {
|
|
520
|
+
const environment = resolvedRunnerEnvironment(runnerEnvironment);
|
|
521
|
+
if (environment.platform !== "win32") return null;
|
|
522
|
+
const targets = localPackageBinTargets(
|
|
523
|
+
dependencyOwner,
|
|
524
|
+
executable,
|
|
525
|
+
environment.platform,
|
|
526
|
+
);
|
|
527
|
+
if (targets.length !== 1) return null;
|
|
528
|
+
const target = targets[0] as string;
|
|
529
|
+
const extension = path.extname(target).toLowerCase();
|
|
530
|
+
if (extension === ".exe") {
|
|
531
|
+
return { command: executable, executablePath: target };
|
|
532
|
+
}
|
|
533
|
+
if (![".js", ".cjs", ".mjs"].includes(extension)) return null;
|
|
534
|
+
const node = resolveVerificationRunner("node", environment);
|
|
535
|
+
if (!node || node.fixedArgs) return null;
|
|
536
|
+
return {
|
|
537
|
+
command: executable,
|
|
538
|
+
executablePath: node.executablePath,
|
|
539
|
+
fixedArgs: [target],
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
147
543
|
export function resolveVerificationRunner(
|
|
148
544
|
command: string,
|
|
545
|
+
runnerEnvironment: VerificationRunnerEnvironment = {},
|
|
149
546
|
): VerificationRunnerBinding | null {
|
|
547
|
+
const environment = resolvedRunnerEnvironment(runnerEnvironment);
|
|
548
|
+
const directories = environment.path
|
|
549
|
+
.split(environment.pathDelimiter)
|
|
550
|
+
.filter(Boolean);
|
|
551
|
+
const seenDirectories = new Set<string>();
|
|
150
552
|
const candidates = [
|
|
151
|
-
...(command === "bun" &&
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
553
|
+
...(command === "bun" &&
|
|
554
|
+
process.versions.bun &&
|
|
555
|
+
environment.platform === process.platform
|
|
556
|
+
? [process.execPath]
|
|
557
|
+
: []),
|
|
558
|
+
...directories.flatMap((directory) => {
|
|
559
|
+
const resolvedDirectory = path.resolve(directory);
|
|
560
|
+
const key =
|
|
561
|
+
environment.platform === "win32"
|
|
562
|
+
? resolvedDirectory.toLowerCase()
|
|
563
|
+
: resolvedDirectory;
|
|
564
|
+
if (seenDirectories.has(key)) return [];
|
|
565
|
+
seenDirectories.add(key);
|
|
566
|
+
return runnerNames(command, environment).flatMap((name) => {
|
|
567
|
+
const candidate = resolveCandidateName(
|
|
568
|
+
resolvedDirectory,
|
|
569
|
+
name,
|
|
570
|
+
environment.platform,
|
|
571
|
+
);
|
|
572
|
+
return candidate ? [candidate] : [];
|
|
573
|
+
});
|
|
574
|
+
}),
|
|
156
575
|
];
|
|
576
|
+
const seenCandidates = new Set<string>();
|
|
157
577
|
for (const candidate of candidates) {
|
|
158
578
|
try {
|
|
159
579
|
const executablePath = path.resolve(candidate);
|
|
580
|
+
const candidateKey =
|
|
581
|
+
environment.platform === "win32"
|
|
582
|
+
? executablePath.toLowerCase()
|
|
583
|
+
: executablePath;
|
|
584
|
+
if (seenCandidates.has(candidateKey)) continue;
|
|
585
|
+
seenCandidates.add(candidateKey);
|
|
160
586
|
const canonical = realpathSync(executablePath);
|
|
161
587
|
const stat = lstatSync(canonical);
|
|
162
|
-
if (!stat
|
|
588
|
+
if (!regularExecutableFile(stat, environment.platform)) continue;
|
|
589
|
+
if (environment.platform === "win32") {
|
|
590
|
+
const extension = path.extname(executablePath).toLowerCase();
|
|
591
|
+
if (extension === ".cmd") {
|
|
592
|
+
const shim = windowsShimBinding(command, executablePath, environment);
|
|
593
|
+
if (shim) return shim;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (extension !== ".exe") continue;
|
|
597
|
+
if (
|
|
598
|
+
!safeRunnerSource([executablePath, canonical], environment.platform)
|
|
599
|
+
) {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
return { command, executablePath };
|
|
603
|
+
}
|
|
163
604
|
const systemVisible = SYSTEM_MOUNT_ROOTS.some(
|
|
164
605
|
(root) => within(root, executablePath) && within(root, canonical),
|
|
165
606
|
);
|
|
@@ -167,6 +608,7 @@ export function resolveVerificationRunner(
|
|
|
167
608
|
const mountSource = commonDirectory(
|
|
168
609
|
path.dirname(executablePath),
|
|
169
610
|
path.dirname(canonical),
|
|
611
|
+
environment.platform,
|
|
170
612
|
);
|
|
171
613
|
if (unsafeMountSource(mountSource)) continue;
|
|
172
614
|
const sourceStat = lstatSync(mountSource);
|
|
@@ -196,6 +638,7 @@ function validateScriptCommand(
|
|
|
196
638
|
verificationId: string,
|
|
197
639
|
command: string,
|
|
198
640
|
dependencyOwner: string,
|
|
641
|
+
runnerEnvironment: VerificationRunnerEnvironment,
|
|
199
642
|
): VerificationCapabilityResult {
|
|
200
643
|
if (
|
|
201
644
|
command.length === 0 ||
|
|
@@ -234,14 +677,17 @@ function validateScriptCommand(
|
|
|
234
677
|
}
|
|
235
678
|
if (executable === "npx") {
|
|
236
679
|
const local = tokens[1] === "--no-install" ? tokens[2] : undefined;
|
|
237
|
-
if (
|
|
680
|
+
if (
|
|
681
|
+
!local ||
|
|
682
|
+
!localExecutable(root, local, dependencyOwner, runnerEnvironment)
|
|
683
|
+
) {
|
|
238
684
|
return adapterFailure(
|
|
239
685
|
verificationId,
|
|
240
686
|
"local-executable-missing",
|
|
241
687
|
"npx requires --no-install and an installed local executable",
|
|
242
688
|
);
|
|
243
689
|
}
|
|
244
|
-
const runner = resolveVerificationRunner("npx");
|
|
690
|
+
const runner = resolveVerificationRunner("npx", runnerEnvironment);
|
|
245
691
|
return runner
|
|
246
692
|
? capabilitySuccess(verificationId, [runner])
|
|
247
693
|
: adapterFailure(
|
|
@@ -251,7 +697,7 @@ function validateScriptCommand(
|
|
|
251
697
|
);
|
|
252
698
|
}
|
|
253
699
|
if (["node", "bun"].includes(executable)) {
|
|
254
|
-
const runner = resolveVerificationRunner(executable);
|
|
700
|
+
const runner = resolveVerificationRunner(executable, runnerEnvironment);
|
|
255
701
|
return runner
|
|
256
702
|
? capabilitySuccess(verificationId, [runner])
|
|
257
703
|
: adapterFailure(
|
|
@@ -260,7 +706,7 @@ function validateScriptCommand(
|
|
|
260
706
|
`approved runner ${executable} is unavailable or cannot be mounted safely`,
|
|
261
707
|
);
|
|
262
708
|
}
|
|
263
|
-
return localExecutable(root, executable, dependencyOwner)
|
|
709
|
+
return localExecutable(root, executable, dependencyOwner, runnerEnvironment)
|
|
264
710
|
? capabilitySuccess(verificationId)
|
|
265
711
|
: adapterFailure(
|
|
266
712
|
verificationId,
|
|
@@ -276,8 +722,12 @@ function validatePackageScript(
|
|
|
276
722
|
script: string,
|
|
277
723
|
approvedCommand: string,
|
|
278
724
|
dependencyOwner = root,
|
|
725
|
+
runnerEnvironment: VerificationRunnerEnvironment = {},
|
|
279
726
|
): VerificationCapabilityResult {
|
|
280
|
-
const packageRunner = resolveVerificationRunner(
|
|
727
|
+
const packageRunner = resolveVerificationRunner(
|
|
728
|
+
packageManager,
|
|
729
|
+
runnerEnvironment,
|
|
730
|
+
);
|
|
281
731
|
if (!packageRunner) {
|
|
282
732
|
return adapterFailure(
|
|
283
733
|
verificationId,
|
|
@@ -310,6 +760,7 @@ function validatePackageScript(
|
|
|
310
760
|
verificationId,
|
|
311
761
|
actual,
|
|
312
762
|
dependencyOwner,
|
|
763
|
+
runnerEnvironment,
|
|
313
764
|
);
|
|
314
765
|
return scriptCapability.ok
|
|
315
766
|
? capabilitySuccess(verificationId, [
|
|
@@ -323,6 +774,7 @@ function validateAtomicCapability(
|
|
|
323
774
|
root: string,
|
|
324
775
|
step: AtomicVerificationContract,
|
|
325
776
|
dependencyOwner: string,
|
|
777
|
+
runnerEnvironment: VerificationRunnerEnvironment,
|
|
326
778
|
): VerificationCapabilityResult {
|
|
327
779
|
if (step.kind === "package-script") {
|
|
328
780
|
return validatePackageScript(
|
|
@@ -332,6 +784,7 @@ function validateAtomicCapability(
|
|
|
332
784
|
step.script,
|
|
333
785
|
step.command,
|
|
334
786
|
dependencyOwner,
|
|
787
|
+
runnerEnvironment,
|
|
335
788
|
);
|
|
336
789
|
}
|
|
337
790
|
const runner = step.runner;
|
|
@@ -343,11 +796,12 @@ function validateAtomicCapability(
|
|
|
343
796
|
runner.script,
|
|
344
797
|
runner.command,
|
|
345
798
|
dependencyOwner,
|
|
799
|
+
runnerEnvironment,
|
|
346
800
|
);
|
|
347
801
|
if (!script.ok) return script;
|
|
348
802
|
if (
|
|
349
803
|
step.kind === "vitest" &&
|
|
350
|
-
!localExecutable(root, "vitest", dependencyOwner)
|
|
804
|
+
!localExecutable(root, "vitest", dependencyOwner, runnerEnvironment)
|
|
351
805
|
) {
|
|
352
806
|
return adapterFailure(
|
|
353
807
|
step.id,
|
|
@@ -358,7 +812,7 @@ function validateAtomicCapability(
|
|
|
358
812
|
return script;
|
|
359
813
|
}
|
|
360
814
|
if (runner.kind === "node") {
|
|
361
|
-
const node = resolveVerificationRunner("node");
|
|
815
|
+
const node = resolveVerificationRunner("node", runnerEnvironment);
|
|
362
816
|
return node
|
|
363
817
|
? capabilitySuccess(step.id, [node])
|
|
364
818
|
: adapterFailure(
|
|
@@ -367,7 +821,14 @@ function validateAtomicCapability(
|
|
|
367
821
|
"Node is unavailable or cannot be mounted safely",
|
|
368
822
|
);
|
|
369
823
|
}
|
|
370
|
-
if (
|
|
824
|
+
if (
|
|
825
|
+
!localExecutable(
|
|
826
|
+
root,
|
|
827
|
+
runner.executable,
|
|
828
|
+
dependencyOwner,
|
|
829
|
+
runnerEnvironment,
|
|
830
|
+
)
|
|
831
|
+
) {
|
|
371
832
|
return adapterFailure(
|
|
372
833
|
step.id,
|
|
373
834
|
"local-executable-missing",
|
|
@@ -375,7 +836,7 @@ function validateAtomicCapability(
|
|
|
375
836
|
);
|
|
376
837
|
}
|
|
377
838
|
if (runner.kind === "npx") {
|
|
378
|
-
const npx = resolveVerificationRunner("npx");
|
|
839
|
+
const npx = resolveVerificationRunner("npx", runnerEnvironment);
|
|
379
840
|
return npx
|
|
380
841
|
? capabilitySuccess(step.id, [npx])
|
|
381
842
|
: adapterFailure(
|
|
@@ -384,23 +845,70 @@ function validateAtomicCapability(
|
|
|
384
845
|
"npx is unavailable or cannot be mounted safely",
|
|
385
846
|
);
|
|
386
847
|
}
|
|
848
|
+
if ((runnerEnvironment.platform ?? process.platform) === "win32") {
|
|
849
|
+
const binding = resolveLocalVerificationRunner(
|
|
850
|
+
dependencyOwner,
|
|
851
|
+
runner.executable,
|
|
852
|
+
runnerEnvironment,
|
|
853
|
+
);
|
|
854
|
+
return binding
|
|
855
|
+
? capabilitySuccess(step.id, [binding])
|
|
856
|
+
: adapterFailure(
|
|
857
|
+
step.id,
|
|
858
|
+
"local-executable-missing",
|
|
859
|
+
`local executable ${runner.executable} has no safe direct Windows binding`,
|
|
860
|
+
);
|
|
861
|
+
}
|
|
387
862
|
return capabilitySuccess(step.id);
|
|
388
863
|
}
|
|
389
864
|
|
|
390
865
|
export function validateVerificationCapability(
|
|
391
866
|
root: string,
|
|
392
867
|
value: unknown,
|
|
393
|
-
options: {
|
|
394
|
-
dependencyOwner?: string;
|
|
395
|
-
} = {},
|
|
868
|
+
options: VerificationCapabilityOptions = {},
|
|
396
869
|
): VerificationCapabilityResult {
|
|
397
870
|
return validateCapability(root, value, options, true);
|
|
398
871
|
}
|
|
399
872
|
|
|
873
|
+
/** Validate one exact adapter and bind all of its current file inputs. */
|
|
874
|
+
export function bindCurrentVerificationCapability(
|
|
875
|
+
root: string,
|
|
876
|
+
value: unknown,
|
|
877
|
+
options: VerificationCapabilityOptions = {},
|
|
878
|
+
): CurrentVerificationCapabilityResult {
|
|
879
|
+
const capability = validateVerificationCapability(root, value, options);
|
|
880
|
+
if (!capability.ok) return capability;
|
|
881
|
+
const validation = validateVerificationContract(value);
|
|
882
|
+
if (!validation.ok) {
|
|
883
|
+
throw new Error("verification-capability-invariant");
|
|
884
|
+
}
|
|
885
|
+
return {
|
|
886
|
+
ok: true,
|
|
887
|
+
value: {
|
|
888
|
+
verificationId: capability.verificationId,
|
|
889
|
+
runnerBindings: capability.runnerBindings.map((binding) => ({
|
|
890
|
+
...binding,
|
|
891
|
+
...(binding.fixedArgs ? { fixedArgs: [...binding.fixedArgs] } : {}),
|
|
892
|
+
})),
|
|
893
|
+
inputSnapshot: snapshotFiles(
|
|
894
|
+
root,
|
|
895
|
+
verificationInputPaths(validation.value),
|
|
896
|
+
),
|
|
897
|
+
},
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
export function isVerificationCapabilityCurrent(
|
|
902
|
+
root: string,
|
|
903
|
+
capability: CurrentVerificationCapability,
|
|
904
|
+
): boolean {
|
|
905
|
+
return isCurrent(root, capability.inputSnapshot);
|
|
906
|
+
}
|
|
907
|
+
|
|
400
908
|
export function validateVerificationAdapterCapability(
|
|
401
909
|
root: string,
|
|
402
910
|
value: unknown,
|
|
403
|
-
options:
|
|
911
|
+
options: VerificationCapabilityOptions = {},
|
|
404
912
|
): VerificationCapabilityResult {
|
|
405
913
|
return validateCapability(root, value, options, false);
|
|
406
914
|
}
|
|
@@ -408,7 +916,7 @@ export function validateVerificationAdapterCapability(
|
|
|
408
916
|
function validateCapability(
|
|
409
917
|
root: string,
|
|
410
918
|
value: unknown,
|
|
411
|
-
options:
|
|
919
|
+
options: VerificationCapabilityOptions,
|
|
412
920
|
requireInputAvailability: boolean,
|
|
413
921
|
): VerificationCapabilityResult {
|
|
414
922
|
const validation = validateVerificationContract(value);
|
|
@@ -435,6 +943,7 @@ function validateCapability(
|
|
|
435
943
|
root,
|
|
436
944
|
validation.value,
|
|
437
945
|
dependencyOwner,
|
|
946
|
+
options.runnerEnvironment ?? {},
|
|
438
947
|
requireInputAvailability,
|
|
439
948
|
);
|
|
440
949
|
}
|
|
@@ -443,6 +952,7 @@ function validateAcceptedVerificationCapability(
|
|
|
443
952
|
root: string,
|
|
444
953
|
verification: StructuredVerificationContract,
|
|
445
954
|
dependencyOwner: string,
|
|
955
|
+
runnerEnvironment: VerificationRunnerEnvironment,
|
|
446
956
|
requireInputAvailability: boolean,
|
|
447
957
|
): VerificationCapabilityResult {
|
|
448
958
|
if (requireInputAvailability) {
|
|
@@ -458,7 +968,12 @@ function validateAcceptedVerificationCapability(
|
|
|
458
968
|
}
|
|
459
969
|
const runnerBindings: VerificationRunnerBinding[] = [];
|
|
460
970
|
for (const step of verificationSteps(verification)) {
|
|
461
|
-
const capability = validateAtomicCapability(
|
|
971
|
+
const capability = validateAtomicCapability(
|
|
972
|
+
root,
|
|
973
|
+
step,
|
|
974
|
+
dependencyOwner,
|
|
975
|
+
runnerEnvironment,
|
|
976
|
+
);
|
|
462
977
|
if (!capability.ok) return capability;
|
|
463
978
|
runnerBindings.push(...capability.runnerBindings);
|
|
464
979
|
}
|