@abelxiaoxing/cadence 1.0.13 → 1.1.0
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 +29 -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 +150 -228
- package/prompts/abel-diagnose.md +37 -18
- package/prompts/abel-implement.md +111 -178
- package/prompts/abel-init.md +36 -33
- package/skills/abel-workflow/SKILL.md +156 -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 +105 -696
- package/src/control-contracts.ts +259 -0
- package/src/delivery-compiler.ts +1088 -0
- package/src/file-snapshot.ts +5 -0
- package/src/implement-graph.ts +11 -4
- package/src/index.ts +1465 -133
- 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 +393 -0
- package/src/run-store.ts +918 -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 +529 -42
- package/src/worker-broker.ts +703 -0
- package/src/workflow-engine.ts +7391 -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,29 +134,138 @@ 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 pathWithin(
|
|
170
|
+
root: string,
|
|
171
|
+
candidate: string,
|
|
172
|
+
platform: NodeJS.Platform,
|
|
173
|
+
): boolean {
|
|
174
|
+
const normalizedRoot = platform === "win32" ? root.toLowerCase() : root;
|
|
175
|
+
const normalizedCandidate =
|
|
176
|
+
platform === "win32" ? candidate.toLowerCase() : candidate;
|
|
177
|
+
return (
|
|
178
|
+
normalizedCandidate === normalizedRoot ||
|
|
179
|
+
normalizedCandidate.startsWith(`${normalizedRoot}${path.sep}`)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function regularExecutableFile(
|
|
184
|
+
stat: Stats,
|
|
185
|
+
platform: NodeJS.Platform,
|
|
186
|
+
): boolean {
|
|
187
|
+
return stat.isFile() && (platform === "win32" || (stat.mode & 0o111) !== 0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function windowsPathExtensions(pathExt: string | undefined): string[] {
|
|
191
|
+
const seen = new Set<string>();
|
|
192
|
+
const extensions: string[] = [];
|
|
193
|
+
for (const entry of (pathExt ?? DEFAULT_WINDOWS_PATH_EXT).split(";")) {
|
|
194
|
+
const extension = entry.trim().toLowerCase();
|
|
195
|
+
if (!WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) || seen.has(extension)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
seen.add(extension);
|
|
199
|
+
extensions.push(extension);
|
|
200
|
+
}
|
|
201
|
+
return extensions;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function runnerNames(
|
|
205
|
+
command: string,
|
|
206
|
+
environment: VerificationRunnerEnvironment,
|
|
207
|
+
): string[] {
|
|
208
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(command)) return [];
|
|
209
|
+
if ((environment.platform ?? process.platform) !== "win32") {
|
|
210
|
+
return [command];
|
|
211
|
+
}
|
|
212
|
+
const extension = path.extname(command).toLowerCase();
|
|
213
|
+
if (extension) {
|
|
214
|
+
return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [command] : [];
|
|
215
|
+
}
|
|
216
|
+
return windowsPathExtensions(environment.pathExt).map(
|
|
217
|
+
(candidateExtension) => `${command}${candidateExtension}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function localExecutableNames(
|
|
222
|
+
executable: string,
|
|
223
|
+
environment: VerificationRunnerEnvironment,
|
|
224
|
+
): string[] {
|
|
225
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(executable)) return [];
|
|
226
|
+
if ((environment.platform ?? process.platform) !== "win32") {
|
|
227
|
+
return [executable];
|
|
228
|
+
}
|
|
229
|
+
const extension = path.extname(executable).toLowerCase();
|
|
230
|
+
if (extension) {
|
|
231
|
+
return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [executable] : [];
|
|
232
|
+
}
|
|
233
|
+
return [
|
|
234
|
+
executable,
|
|
235
|
+
...windowsPathExtensions(environment.pathExt).map(
|
|
236
|
+
(candidateExtension) => `${executable}${candidateExtension}`,
|
|
237
|
+
),
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function resolveCandidateName(
|
|
242
|
+
directory: string,
|
|
243
|
+
name: string,
|
|
244
|
+
platform: NodeJS.Platform,
|
|
245
|
+
): string | null {
|
|
246
|
+
const exact = path.join(directory, name);
|
|
247
|
+
if (platform !== "win32") return exact;
|
|
248
|
+
try {
|
|
249
|
+
const matches = readdirSync(directory).filter(
|
|
250
|
+
(entry) => entry.toLowerCase() === name.toLowerCase(),
|
|
251
|
+
);
|
|
252
|
+
return matches.length === 1
|
|
253
|
+
? path.join(directory, matches[0] as string)
|
|
254
|
+
: null;
|
|
255
|
+
} catch {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
118
260
|
function within(root: string, candidate: string): boolean {
|
|
119
261
|
return candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
|
120
262
|
}
|
|
121
263
|
|
|
122
|
-
function commonDirectory(
|
|
264
|
+
function commonDirectory(
|
|
265
|
+
left: string,
|
|
266
|
+
right: string,
|
|
267
|
+
platform: NodeJS.Platform = process.platform,
|
|
268
|
+
): string {
|
|
123
269
|
const leftParts = path.resolve(left).split(path.sep).filter(Boolean);
|
|
124
270
|
const rightParts = path.resolve(right).split(path.sep).filter(Boolean);
|
|
125
271
|
const common: string[] = [];
|
|
@@ -128,38 +274,305 @@ function commonDirectory(left: string, right: string): string {
|
|
|
128
274
|
index < Math.min(leftParts.length, rightParts.length);
|
|
129
275
|
index++
|
|
130
276
|
) {
|
|
131
|
-
|
|
132
|
-
|
|
277
|
+
const leftPart = leftParts[index] as string;
|
|
278
|
+
const rightPart = rightParts[index] as string;
|
|
279
|
+
if (
|
|
280
|
+
(platform === "win32" ? leftPart.toLowerCase() : leftPart) !==
|
|
281
|
+
(platform === "win32" ? rightPart.toLowerCase() : rightPart)
|
|
282
|
+
) {
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
common.push(leftPart);
|
|
133
286
|
}
|
|
134
287
|
return path.join(path.parse(path.resolve(left)).root, ...common);
|
|
135
288
|
}
|
|
136
289
|
|
|
137
290
|
function unsafeMountSource(source: string): boolean {
|
|
138
291
|
if (BROAD_MOUNT_ROOTS.has(source)) return true;
|
|
139
|
-
|
|
292
|
+
if (path.resolve(source) === path.parse(path.resolve(source)).root)
|
|
293
|
+
return true;
|
|
294
|
+
const parts = path
|
|
295
|
+
.relative(path.parse(path.resolve(source)).root, path.resolve(source))
|
|
296
|
+
.split(path.sep)
|
|
297
|
+
.filter(Boolean)
|
|
298
|
+
.map((part) => part.toLowerCase());
|
|
140
299
|
return (
|
|
141
300
|
(parts[0] === "home" && parts.length <= 2) ||
|
|
142
|
-
(parts[0] === "
|
|
301
|
+
(parts[0] === "users" && parts.length <= 2) ||
|
|
143
302
|
(parts[0] === "root" && parts.length <= 1)
|
|
144
303
|
);
|
|
145
304
|
}
|
|
146
305
|
|
|
306
|
+
function safeRunnerSource(
|
|
307
|
+
paths: readonly string[],
|
|
308
|
+
platform: NodeJS.Platform,
|
|
309
|
+
): string | null {
|
|
310
|
+
let source = path.dirname(paths[0] as string);
|
|
311
|
+
for (const candidate of paths.slice(1)) {
|
|
312
|
+
source = commonDirectory(source, path.dirname(candidate), platform);
|
|
313
|
+
}
|
|
314
|
+
if (unsafeMountSource(source)) return null;
|
|
315
|
+
try {
|
|
316
|
+
const stat = lstatSync(source);
|
|
317
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return null;
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return paths.every((candidate) => pathWithin(source, candidate, platform))
|
|
322
|
+
? source
|
|
323
|
+
: null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function resolvedRunnerEnvironment(
|
|
327
|
+
environment: VerificationRunnerEnvironment,
|
|
328
|
+
): Required<VerificationRunnerEnvironment> {
|
|
329
|
+
return {
|
|
330
|
+
platform: environment.platform ?? process.platform,
|
|
331
|
+
path: environment.path ?? process.env.PATH ?? "",
|
|
332
|
+
pathExt:
|
|
333
|
+
environment.pathExt ?? process.env.PATHEXT ?? DEFAULT_WINDOWS_PATH_EXT,
|
|
334
|
+
pathDelimiter: environment.pathDelimiter ?? path.delimiter,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function windowsShimBinding(
|
|
339
|
+
command: string,
|
|
340
|
+
launcherPath: string,
|
|
341
|
+
environment: Required<VerificationRunnerEnvironment>,
|
|
342
|
+
): VerificationRunnerBinding | null {
|
|
343
|
+
const name = path.basename(command, path.extname(command)).toLowerCase();
|
|
344
|
+
if (name !== "npm" && name !== "npx") return null;
|
|
345
|
+
const node = resolveVerificationRunner("node", environment);
|
|
346
|
+
if (
|
|
347
|
+
!node ||
|
|
348
|
+
node.fixedArgs ||
|
|
349
|
+
path.extname(node.executablePath).toLowerCase() !== ".exe"
|
|
350
|
+
) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const cli = path.join(
|
|
354
|
+
path.dirname(launcherPath),
|
|
355
|
+
"node_modules",
|
|
356
|
+
"npm",
|
|
357
|
+
"bin",
|
|
358
|
+
`${name}-cli.js`,
|
|
359
|
+
);
|
|
360
|
+
try {
|
|
361
|
+
const canonicalLauncher = realpathSync(launcherPath);
|
|
362
|
+
const canonicalCli = realpathSync(cli);
|
|
363
|
+
const cliStat = lstatSync(canonicalCli);
|
|
364
|
+
if (!cliStat.isFile()) return null;
|
|
365
|
+
const source = safeRunnerSource(
|
|
366
|
+
[node.executablePath, canonicalLauncher, canonicalCli],
|
|
367
|
+
environment.platform,
|
|
368
|
+
);
|
|
369
|
+
if (!source) return null;
|
|
370
|
+
return {
|
|
371
|
+
command,
|
|
372
|
+
executablePath: node.executablePath,
|
|
373
|
+
fixedArgs: [canonicalCli],
|
|
374
|
+
};
|
|
375
|
+
} catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function localPackageDirectories(dependencyRoot: string): string[] {
|
|
381
|
+
const packages: string[] = [];
|
|
382
|
+
try {
|
|
383
|
+
for (const entry of readdirSync(dependencyRoot, { withFileTypes: true })) {
|
|
384
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
385
|
+
const candidate = path.join(dependencyRoot, entry.name);
|
|
386
|
+
if (!entry.name.startsWith("@")) {
|
|
387
|
+
packages.push(candidate);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
for (const scoped of readdirSync(candidate, { withFileTypes: true })) {
|
|
391
|
+
if (scoped.isDirectory() && !scoped.name.startsWith(".")) {
|
|
392
|
+
packages.push(path.join(candidate, scoped.name));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
return packages;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function localPackageBinTargets(
|
|
403
|
+
dependencyOwner: string,
|
|
404
|
+
executable: string,
|
|
405
|
+
platform: NodeJS.Platform,
|
|
406
|
+
): string[] {
|
|
407
|
+
const dependencyRoot = path.resolve(dependencyOwner, "node_modules");
|
|
408
|
+
let canonicalDependencyRoot: string;
|
|
409
|
+
try {
|
|
410
|
+
canonicalDependencyRoot = realpathSync(dependencyRoot);
|
|
411
|
+
} catch {
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
const targets: string[] = [];
|
|
415
|
+
for (const packageDirectory of localPackageDirectories(dependencyRoot)) {
|
|
416
|
+
try {
|
|
417
|
+
const directoryStat = lstatSync(packageDirectory);
|
|
418
|
+
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const canonicalPackage = realpathSync(packageDirectory);
|
|
422
|
+
if (
|
|
423
|
+
!pathWithin(canonicalDependencyRoot, canonicalPackage, platform) ||
|
|
424
|
+
canonicalPackage === canonicalDependencyRoot
|
|
425
|
+
) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const manifestPath = path.join(canonicalPackage, "package.json");
|
|
429
|
+
const manifestStat = lstatSync(manifestPath);
|
|
430
|
+
if (!manifestStat.isFile() || manifestStat.isSymbolicLink()) continue;
|
|
431
|
+
const manifest = JSON.parse(
|
|
432
|
+
readFileSync(manifestPath, "utf8"),
|
|
433
|
+
) as unknown;
|
|
434
|
+
if (
|
|
435
|
+
!manifest ||
|
|
436
|
+
typeof manifest !== "object" ||
|
|
437
|
+
Array.isArray(manifest)
|
|
438
|
+
) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const record = manifest as Record<string, unknown>;
|
|
442
|
+
const packageName =
|
|
443
|
+
typeof record.name === "string"
|
|
444
|
+
? (record.name.split("/").pop() ?? "")
|
|
445
|
+
: "";
|
|
446
|
+
const bin = record.bin;
|
|
447
|
+
let relative: unknown;
|
|
448
|
+
if (
|
|
449
|
+
typeof bin === "string" &&
|
|
450
|
+
(platform === "win32"
|
|
451
|
+
? packageName.toLowerCase() === executable.toLowerCase()
|
|
452
|
+
: packageName === executable)
|
|
453
|
+
) {
|
|
454
|
+
relative = bin;
|
|
455
|
+
} else if (bin && typeof bin === "object" && !Array.isArray(bin)) {
|
|
456
|
+
const entries = Object.entries(bin as Record<string, unknown>).filter(
|
|
457
|
+
([name]) =>
|
|
458
|
+
platform === "win32"
|
|
459
|
+
? name.toLowerCase() === executable.toLowerCase()
|
|
460
|
+
: name === executable,
|
|
461
|
+
);
|
|
462
|
+
if (entries.length === 1) relative = entries[0]?.[1];
|
|
463
|
+
}
|
|
464
|
+
if (
|
|
465
|
+
typeof relative !== "string" ||
|
|
466
|
+
relative.length === 0 ||
|
|
467
|
+
relative.includes("\0")
|
|
468
|
+
) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const unresolved = path.resolve(canonicalPackage, relative);
|
|
472
|
+
if (!pathWithin(canonicalPackage, unresolved, platform)) continue;
|
|
473
|
+
const canonical = realpathSync(unresolved);
|
|
474
|
+
const stat = lstatSync(canonical);
|
|
475
|
+
if (
|
|
476
|
+
stat.isFile() &&
|
|
477
|
+
pathWithin(canonicalPackage, canonical, platform) &&
|
|
478
|
+
pathWithin(canonicalDependencyRoot, canonical, platform)
|
|
479
|
+
) {
|
|
480
|
+
targets.push(canonical);
|
|
481
|
+
}
|
|
482
|
+
} catch {}
|
|
483
|
+
}
|
|
484
|
+
return [...new Set(targets)];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function resolveLocalVerificationRunner(
|
|
488
|
+
dependencyOwner: string,
|
|
489
|
+
executable: string,
|
|
490
|
+
runnerEnvironment: VerificationRunnerEnvironment,
|
|
491
|
+
): VerificationRunnerBinding | null {
|
|
492
|
+
const environment = resolvedRunnerEnvironment(runnerEnvironment);
|
|
493
|
+
if (environment.platform !== "win32") return null;
|
|
494
|
+
const targets = localPackageBinTargets(
|
|
495
|
+
dependencyOwner,
|
|
496
|
+
executable,
|
|
497
|
+
environment.platform,
|
|
498
|
+
);
|
|
499
|
+
if (targets.length !== 1) return null;
|
|
500
|
+
const target = targets[0] as string;
|
|
501
|
+
const extension = path.extname(target).toLowerCase();
|
|
502
|
+
if (extension === ".exe") {
|
|
503
|
+
return { command: executable, executablePath: target };
|
|
504
|
+
}
|
|
505
|
+
if (![".js", ".cjs", ".mjs"].includes(extension)) return null;
|
|
506
|
+
const node = resolveVerificationRunner("node", environment);
|
|
507
|
+
if (!node || node.fixedArgs) return null;
|
|
508
|
+
return {
|
|
509
|
+
command: executable,
|
|
510
|
+
executablePath: node.executablePath,
|
|
511
|
+
fixedArgs: [target],
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
147
515
|
export function resolveVerificationRunner(
|
|
148
516
|
command: string,
|
|
517
|
+
runnerEnvironment: VerificationRunnerEnvironment = {},
|
|
149
518
|
): VerificationRunnerBinding | null {
|
|
519
|
+
const environment = resolvedRunnerEnvironment(runnerEnvironment);
|
|
520
|
+
const directories = environment.path
|
|
521
|
+
.split(environment.pathDelimiter)
|
|
522
|
+
.filter(Boolean);
|
|
523
|
+
const seenDirectories = new Set<string>();
|
|
150
524
|
const candidates = [
|
|
151
|
-
...(command === "bun" &&
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
525
|
+
...(command === "bun" &&
|
|
526
|
+
process.versions.bun &&
|
|
527
|
+
environment.platform === process.platform
|
|
528
|
+
? [process.execPath]
|
|
529
|
+
: []),
|
|
530
|
+
...directories.flatMap((directory) => {
|
|
531
|
+
const resolvedDirectory = path.resolve(directory);
|
|
532
|
+
const key =
|
|
533
|
+
environment.platform === "win32"
|
|
534
|
+
? resolvedDirectory.toLowerCase()
|
|
535
|
+
: resolvedDirectory;
|
|
536
|
+
if (seenDirectories.has(key)) return [];
|
|
537
|
+
seenDirectories.add(key);
|
|
538
|
+
return runnerNames(command, environment).flatMap((name) => {
|
|
539
|
+
const candidate = resolveCandidateName(
|
|
540
|
+
resolvedDirectory,
|
|
541
|
+
name,
|
|
542
|
+
environment.platform,
|
|
543
|
+
);
|
|
544
|
+
return candidate ? [candidate] : [];
|
|
545
|
+
});
|
|
546
|
+
}),
|
|
156
547
|
];
|
|
548
|
+
const seenCandidates = new Set<string>();
|
|
157
549
|
for (const candidate of candidates) {
|
|
158
550
|
try {
|
|
159
551
|
const executablePath = path.resolve(candidate);
|
|
552
|
+
const candidateKey =
|
|
553
|
+
environment.platform === "win32"
|
|
554
|
+
? executablePath.toLowerCase()
|
|
555
|
+
: executablePath;
|
|
556
|
+
if (seenCandidates.has(candidateKey)) continue;
|
|
557
|
+
seenCandidates.add(candidateKey);
|
|
160
558
|
const canonical = realpathSync(executablePath);
|
|
161
559
|
const stat = lstatSync(canonical);
|
|
162
|
-
if (!stat
|
|
560
|
+
if (!regularExecutableFile(stat, environment.platform)) continue;
|
|
561
|
+
if (environment.platform === "win32") {
|
|
562
|
+
const extension = path.extname(executablePath).toLowerCase();
|
|
563
|
+
if (extension === ".cmd") {
|
|
564
|
+
const shim = windowsShimBinding(command, executablePath, environment);
|
|
565
|
+
if (shim) return shim;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (extension !== ".exe") continue;
|
|
569
|
+
if (
|
|
570
|
+
!safeRunnerSource([executablePath, canonical], environment.platform)
|
|
571
|
+
) {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
return { command, executablePath };
|
|
575
|
+
}
|
|
163
576
|
const systemVisible = SYSTEM_MOUNT_ROOTS.some(
|
|
164
577
|
(root) => within(root, executablePath) && within(root, canonical),
|
|
165
578
|
);
|
|
@@ -167,6 +580,7 @@ export function resolveVerificationRunner(
|
|
|
167
580
|
const mountSource = commonDirectory(
|
|
168
581
|
path.dirname(executablePath),
|
|
169
582
|
path.dirname(canonical),
|
|
583
|
+
environment.platform,
|
|
170
584
|
);
|
|
171
585
|
if (unsafeMountSource(mountSource)) continue;
|
|
172
586
|
const sourceStat = lstatSync(mountSource);
|
|
@@ -196,6 +610,7 @@ function validateScriptCommand(
|
|
|
196
610
|
verificationId: string,
|
|
197
611
|
command: string,
|
|
198
612
|
dependencyOwner: string,
|
|
613
|
+
runnerEnvironment: VerificationRunnerEnvironment,
|
|
199
614
|
): VerificationCapabilityResult {
|
|
200
615
|
if (
|
|
201
616
|
command.length === 0 ||
|
|
@@ -234,14 +649,17 @@ function validateScriptCommand(
|
|
|
234
649
|
}
|
|
235
650
|
if (executable === "npx") {
|
|
236
651
|
const local = tokens[1] === "--no-install" ? tokens[2] : undefined;
|
|
237
|
-
if (
|
|
652
|
+
if (
|
|
653
|
+
!local ||
|
|
654
|
+
!localExecutable(root, local, dependencyOwner, runnerEnvironment)
|
|
655
|
+
) {
|
|
238
656
|
return adapterFailure(
|
|
239
657
|
verificationId,
|
|
240
658
|
"local-executable-missing",
|
|
241
659
|
"npx requires --no-install and an installed local executable",
|
|
242
660
|
);
|
|
243
661
|
}
|
|
244
|
-
const runner = resolveVerificationRunner("npx");
|
|
662
|
+
const runner = resolveVerificationRunner("npx", runnerEnvironment);
|
|
245
663
|
return runner
|
|
246
664
|
? capabilitySuccess(verificationId, [runner])
|
|
247
665
|
: adapterFailure(
|
|
@@ -251,7 +669,7 @@ function validateScriptCommand(
|
|
|
251
669
|
);
|
|
252
670
|
}
|
|
253
671
|
if (["node", "bun"].includes(executable)) {
|
|
254
|
-
const runner = resolveVerificationRunner(executable);
|
|
672
|
+
const runner = resolveVerificationRunner(executable, runnerEnvironment);
|
|
255
673
|
return runner
|
|
256
674
|
? capabilitySuccess(verificationId, [runner])
|
|
257
675
|
: adapterFailure(
|
|
@@ -260,7 +678,7 @@ function validateScriptCommand(
|
|
|
260
678
|
`approved runner ${executable} is unavailable or cannot be mounted safely`,
|
|
261
679
|
);
|
|
262
680
|
}
|
|
263
|
-
return localExecutable(root, executable, dependencyOwner)
|
|
681
|
+
return localExecutable(root, executable, dependencyOwner, runnerEnvironment)
|
|
264
682
|
? capabilitySuccess(verificationId)
|
|
265
683
|
: adapterFailure(
|
|
266
684
|
verificationId,
|
|
@@ -276,8 +694,12 @@ function validatePackageScript(
|
|
|
276
694
|
script: string,
|
|
277
695
|
approvedCommand: string,
|
|
278
696
|
dependencyOwner = root,
|
|
697
|
+
runnerEnvironment: VerificationRunnerEnvironment = {},
|
|
279
698
|
): VerificationCapabilityResult {
|
|
280
|
-
const packageRunner = resolveVerificationRunner(
|
|
699
|
+
const packageRunner = resolveVerificationRunner(
|
|
700
|
+
packageManager,
|
|
701
|
+
runnerEnvironment,
|
|
702
|
+
);
|
|
281
703
|
if (!packageRunner) {
|
|
282
704
|
return adapterFailure(
|
|
283
705
|
verificationId,
|
|
@@ -310,6 +732,7 @@ function validatePackageScript(
|
|
|
310
732
|
verificationId,
|
|
311
733
|
actual,
|
|
312
734
|
dependencyOwner,
|
|
735
|
+
runnerEnvironment,
|
|
313
736
|
);
|
|
314
737
|
return scriptCapability.ok
|
|
315
738
|
? capabilitySuccess(verificationId, [
|
|
@@ -323,6 +746,7 @@ function validateAtomicCapability(
|
|
|
323
746
|
root: string,
|
|
324
747
|
step: AtomicVerificationContract,
|
|
325
748
|
dependencyOwner: string,
|
|
749
|
+
runnerEnvironment: VerificationRunnerEnvironment,
|
|
326
750
|
): VerificationCapabilityResult {
|
|
327
751
|
if (step.kind === "package-script") {
|
|
328
752
|
return validatePackageScript(
|
|
@@ -332,6 +756,7 @@ function validateAtomicCapability(
|
|
|
332
756
|
step.script,
|
|
333
757
|
step.command,
|
|
334
758
|
dependencyOwner,
|
|
759
|
+
runnerEnvironment,
|
|
335
760
|
);
|
|
336
761
|
}
|
|
337
762
|
const runner = step.runner;
|
|
@@ -343,11 +768,12 @@ function validateAtomicCapability(
|
|
|
343
768
|
runner.script,
|
|
344
769
|
runner.command,
|
|
345
770
|
dependencyOwner,
|
|
771
|
+
runnerEnvironment,
|
|
346
772
|
);
|
|
347
773
|
if (!script.ok) return script;
|
|
348
774
|
if (
|
|
349
775
|
step.kind === "vitest" &&
|
|
350
|
-
!localExecutable(root, "vitest", dependencyOwner)
|
|
776
|
+
!localExecutable(root, "vitest", dependencyOwner, runnerEnvironment)
|
|
351
777
|
) {
|
|
352
778
|
return adapterFailure(
|
|
353
779
|
step.id,
|
|
@@ -358,7 +784,7 @@ function validateAtomicCapability(
|
|
|
358
784
|
return script;
|
|
359
785
|
}
|
|
360
786
|
if (runner.kind === "node") {
|
|
361
|
-
const node = resolveVerificationRunner("node");
|
|
787
|
+
const node = resolveVerificationRunner("node", runnerEnvironment);
|
|
362
788
|
return node
|
|
363
789
|
? capabilitySuccess(step.id, [node])
|
|
364
790
|
: adapterFailure(
|
|
@@ -367,7 +793,14 @@ function validateAtomicCapability(
|
|
|
367
793
|
"Node is unavailable or cannot be mounted safely",
|
|
368
794
|
);
|
|
369
795
|
}
|
|
370
|
-
if (
|
|
796
|
+
if (
|
|
797
|
+
!localExecutable(
|
|
798
|
+
root,
|
|
799
|
+
runner.executable,
|
|
800
|
+
dependencyOwner,
|
|
801
|
+
runnerEnvironment,
|
|
802
|
+
)
|
|
803
|
+
) {
|
|
371
804
|
return adapterFailure(
|
|
372
805
|
step.id,
|
|
373
806
|
"local-executable-missing",
|
|
@@ -375,7 +808,7 @@ function validateAtomicCapability(
|
|
|
375
808
|
);
|
|
376
809
|
}
|
|
377
810
|
if (runner.kind === "npx") {
|
|
378
|
-
const npx = resolveVerificationRunner("npx");
|
|
811
|
+
const npx = resolveVerificationRunner("npx", runnerEnvironment);
|
|
379
812
|
return npx
|
|
380
813
|
? capabilitySuccess(step.id, [npx])
|
|
381
814
|
: adapterFailure(
|
|
@@ -384,23 +817,70 @@ function validateAtomicCapability(
|
|
|
384
817
|
"npx is unavailable or cannot be mounted safely",
|
|
385
818
|
);
|
|
386
819
|
}
|
|
820
|
+
if ((runnerEnvironment.platform ?? process.platform) === "win32") {
|
|
821
|
+
const binding = resolveLocalVerificationRunner(
|
|
822
|
+
dependencyOwner,
|
|
823
|
+
runner.executable,
|
|
824
|
+
runnerEnvironment,
|
|
825
|
+
);
|
|
826
|
+
return binding
|
|
827
|
+
? capabilitySuccess(step.id, [binding])
|
|
828
|
+
: adapterFailure(
|
|
829
|
+
step.id,
|
|
830
|
+
"local-executable-missing",
|
|
831
|
+
`local executable ${runner.executable} has no safe direct Windows binding`,
|
|
832
|
+
);
|
|
833
|
+
}
|
|
387
834
|
return capabilitySuccess(step.id);
|
|
388
835
|
}
|
|
389
836
|
|
|
390
837
|
export function validateVerificationCapability(
|
|
391
838
|
root: string,
|
|
392
839
|
value: unknown,
|
|
393
|
-
options: {
|
|
394
|
-
dependencyOwner?: string;
|
|
395
|
-
} = {},
|
|
840
|
+
options: VerificationCapabilityOptions = {},
|
|
396
841
|
): VerificationCapabilityResult {
|
|
397
842
|
return validateCapability(root, value, options, true);
|
|
398
843
|
}
|
|
399
844
|
|
|
845
|
+
/** Validate one exact adapter and bind all of its current file inputs. */
|
|
846
|
+
export function bindCurrentVerificationCapability(
|
|
847
|
+
root: string,
|
|
848
|
+
value: unknown,
|
|
849
|
+
options: VerificationCapabilityOptions = {},
|
|
850
|
+
): CurrentVerificationCapabilityResult {
|
|
851
|
+
const capability = validateVerificationCapability(root, value, options);
|
|
852
|
+
if (!capability.ok) return capability;
|
|
853
|
+
const validation = validateVerificationContract(value);
|
|
854
|
+
if (!validation.ok) {
|
|
855
|
+
throw new Error("verification-capability-invariant");
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
ok: true,
|
|
859
|
+
value: {
|
|
860
|
+
verificationId: capability.verificationId,
|
|
861
|
+
runnerBindings: capability.runnerBindings.map((binding) => ({
|
|
862
|
+
...binding,
|
|
863
|
+
...(binding.fixedArgs ? { fixedArgs: [...binding.fixedArgs] } : {}),
|
|
864
|
+
})),
|
|
865
|
+
inputSnapshot: snapshotFiles(
|
|
866
|
+
root,
|
|
867
|
+
verificationInputPaths(validation.value),
|
|
868
|
+
),
|
|
869
|
+
},
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export function isVerificationCapabilityCurrent(
|
|
874
|
+
root: string,
|
|
875
|
+
capability: CurrentVerificationCapability,
|
|
876
|
+
): boolean {
|
|
877
|
+
return isCurrent(root, capability.inputSnapshot);
|
|
878
|
+
}
|
|
879
|
+
|
|
400
880
|
export function validateVerificationAdapterCapability(
|
|
401
881
|
root: string,
|
|
402
882
|
value: unknown,
|
|
403
|
-
options:
|
|
883
|
+
options: VerificationCapabilityOptions = {},
|
|
404
884
|
): VerificationCapabilityResult {
|
|
405
885
|
return validateCapability(root, value, options, false);
|
|
406
886
|
}
|
|
@@ -408,7 +888,7 @@ export function validateVerificationAdapterCapability(
|
|
|
408
888
|
function validateCapability(
|
|
409
889
|
root: string,
|
|
410
890
|
value: unknown,
|
|
411
|
-
options:
|
|
891
|
+
options: VerificationCapabilityOptions,
|
|
412
892
|
requireInputAvailability: boolean,
|
|
413
893
|
): VerificationCapabilityResult {
|
|
414
894
|
const validation = validateVerificationContract(value);
|
|
@@ -435,6 +915,7 @@ function validateCapability(
|
|
|
435
915
|
root,
|
|
436
916
|
validation.value,
|
|
437
917
|
dependencyOwner,
|
|
918
|
+
options.runnerEnvironment ?? {},
|
|
438
919
|
requireInputAvailability,
|
|
439
920
|
);
|
|
440
921
|
}
|
|
@@ -443,6 +924,7 @@ function validateAcceptedVerificationCapability(
|
|
|
443
924
|
root: string,
|
|
444
925
|
verification: StructuredVerificationContract,
|
|
445
926
|
dependencyOwner: string,
|
|
927
|
+
runnerEnvironment: VerificationRunnerEnvironment,
|
|
446
928
|
requireInputAvailability: boolean,
|
|
447
929
|
): VerificationCapabilityResult {
|
|
448
930
|
if (requireInputAvailability) {
|
|
@@ -458,7 +940,12 @@ function validateAcceptedVerificationCapability(
|
|
|
458
940
|
}
|
|
459
941
|
const runnerBindings: VerificationRunnerBinding[] = [];
|
|
460
942
|
for (const step of verificationSteps(verification)) {
|
|
461
|
-
const capability = validateAtomicCapability(
|
|
943
|
+
const capability = validateAtomicCapability(
|
|
944
|
+
root,
|
|
945
|
+
step,
|
|
946
|
+
dependencyOwner,
|
|
947
|
+
runnerEnvironment,
|
|
948
|
+
);
|
|
462
949
|
if (!capability.ok) return capability;
|
|
463
950
|
runnerBindings.push(...capability.runnerBindings);
|
|
464
951
|
}
|