@abelxiaoxing/cadence 1.0.10 → 1.0.12

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.
@@ -0,0 +1,481 @@
1
+ import { lstatSync, readFileSync, realpathSync } from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ type AtomicVerificationContract,
5
+ isValidRelativePath,
6
+ type VerificationAdapterCode,
7
+ validateVerificationContract,
8
+ verificationInputPaths,
9
+ verificationSteps,
10
+ } from "./contracts.ts";
11
+
12
+ export type VerificationCapabilityDiagnostic =
13
+ | {
14
+ kind: "design-readiness";
15
+ code: "verification-contract-unsupported";
16
+ verificationId?: string;
17
+ message: string;
18
+ }
19
+ | {
20
+ kind: "verification-adapter";
21
+ code: VerificationAdapterCode;
22
+ verificationId: string;
23
+ message: string;
24
+ };
25
+
26
+ export type VerificationCapabilityResult =
27
+ | {
28
+ ok: true;
29
+ verificationId: string;
30
+ runnerBindings: VerificationRunnerBinding[];
31
+ }
32
+ | { ok: false; diagnostic: VerificationCapabilityDiagnostic };
33
+
34
+ export interface VerificationRunnerBinding {
35
+ command: string;
36
+ executablePath: string;
37
+ mountSource?: string;
38
+ }
39
+
40
+ export interface VerificationReadiness {
41
+ taskContractsExecutable: boolean;
42
+ diagnostics: VerificationCapabilityDiagnostic[];
43
+ }
44
+
45
+ const SHELL_OPERATOR = /[;&|`$<>\n\r\0]/u;
46
+ const SIMPLE_TOKEN =
47
+ /^(?:[a-z0-9][a-z0-9._:@/=-]*|--?[a-z0-9][a-z0-9._:@/=-]*)$/iu;
48
+ const NETWORKING_RUNNERS = new Set(["bunx", "pnpx"]);
49
+ const SYSTEM_MOUNT_ROOTS = ["/usr", "/bin", "/lib", "/lib64", "/etc"];
50
+ const BROAD_MOUNT_ROOTS = new Set([
51
+ "/",
52
+ "/home",
53
+ "/root",
54
+ "/tmp",
55
+ "/var",
56
+ "/var/tmp",
57
+ ]);
58
+
59
+ function capabilitySuccess(
60
+ verificationId: string,
61
+ runnerBindings: VerificationRunnerBinding[] = [],
62
+ ): VerificationCapabilityResult {
63
+ const seen = new Set<string>();
64
+ return {
65
+ ok: true,
66
+ verificationId,
67
+ runnerBindings: runnerBindings.filter((binding) => {
68
+ const key = `${binding.command}\0${binding.executablePath}\0${binding.mountSource ?? ""}`;
69
+ if (seen.has(key)) return false;
70
+ seen.add(key);
71
+ return true;
72
+ }),
73
+ };
74
+ }
75
+
76
+ function adapterFailure(
77
+ verificationId: string,
78
+ code: VerificationAdapterCode,
79
+ message: string,
80
+ ): VerificationCapabilityResult {
81
+ return {
82
+ ok: false,
83
+ diagnostic: { kind: "verification-adapter", code, verificationId, message },
84
+ };
85
+ }
86
+
87
+ function fileStatus(
88
+ root: string,
89
+ relative: string,
90
+ ): "regular" | "absent" | "unsafe" {
91
+ if (!isValidRelativePath(relative)) return "unsafe";
92
+ const resolvedRoot = path.resolve(root);
93
+ const absolute = path.resolve(resolvedRoot, relative);
94
+ if (
95
+ absolute === resolvedRoot ||
96
+ !absolute.startsWith(`${resolvedRoot}${path.sep}`)
97
+ ) {
98
+ return "unsafe";
99
+ }
100
+ const stat = lstatSync(absolute, { throwIfNoEntry: false });
101
+ if (!stat) return "absent";
102
+ return stat.isFile() && !stat.isSymbolicLink() ? "regular" : "unsafe";
103
+ }
104
+
105
+ function regularFile(root: string, relative: string): boolean {
106
+ return fileStatus(root, relative) === "regular";
107
+ }
108
+
109
+ function localExecutable(
110
+ root: string,
111
+ executable: string,
112
+ dependencyOwner = root,
113
+ ): boolean {
114
+ const dependencyRoot = path.resolve(dependencyOwner, "node_modules");
115
+ const unresolved = path.resolve(dependencyRoot, ".bin", executable);
116
+ try {
117
+ const canonicalDependencyRoot = realpathSync(dependencyRoot);
118
+ const canonical = realpathSync(unresolved);
119
+ const stat = lstatSync(canonical);
120
+ return (
121
+ canonical !== canonicalDependencyRoot &&
122
+ canonical.startsWith(`${canonicalDependencyRoot}${path.sep}`) &&
123
+ stat.isFile() &&
124
+ (stat.mode & 0o111) !== 0
125
+ );
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ function within(root: string, candidate: string): boolean {
132
+ return candidate === root || candidate.startsWith(`${root}${path.sep}`);
133
+ }
134
+
135
+ function commonDirectory(left: string, right: string): string {
136
+ const leftParts = path.resolve(left).split(path.sep).filter(Boolean);
137
+ const rightParts = path.resolve(right).split(path.sep).filter(Boolean);
138
+ const common: string[] = [];
139
+ for (
140
+ let index = 0;
141
+ index < Math.min(leftParts.length, rightParts.length);
142
+ index++
143
+ ) {
144
+ if (leftParts[index] !== rightParts[index]) break;
145
+ common.push(leftParts[index] as string);
146
+ }
147
+ return path.join(path.parse(path.resolve(left)).root, ...common);
148
+ }
149
+
150
+ function unsafeMountSource(source: string): boolean {
151
+ if (BROAD_MOUNT_ROOTS.has(source)) return true;
152
+ const parts = source.split(path.sep).filter(Boolean);
153
+ return (
154
+ (parts[0] === "home" && parts.length <= 2) ||
155
+ (parts[0] === "Users" && parts.length <= 2) ||
156
+ (parts[0] === "root" && parts.length <= 1)
157
+ );
158
+ }
159
+
160
+ export function resolveVerificationRunner(
161
+ command: string,
162
+ ): VerificationRunnerBinding | null {
163
+ const candidates = [
164
+ ...(command === "bun" && process.versions.bun ? [process.execPath] : []),
165
+ ...(process.env.PATH ?? "")
166
+ .split(path.delimiter)
167
+ .filter(Boolean)
168
+ .map((directory) => path.join(directory, command)),
169
+ ];
170
+ for (const candidate of candidates) {
171
+ try {
172
+ const executablePath = path.resolve(candidate);
173
+ const canonical = realpathSync(executablePath);
174
+ const stat = lstatSync(canonical);
175
+ if (!stat.isFile() || (stat.mode & 0o111) === 0) continue;
176
+ const systemVisible = SYSTEM_MOUNT_ROOTS.some(
177
+ (root) => within(root, executablePath) && within(root, canonical),
178
+ );
179
+ if (systemVisible) return { command, executablePath };
180
+ const mountSource = commonDirectory(
181
+ path.dirname(executablePath),
182
+ path.dirname(canonical),
183
+ );
184
+ if (unsafeMountSource(mountSource)) continue;
185
+ const sourceStat = lstatSync(mountSource);
186
+ if (!sourceStat.isDirectory() || sourceStat.isSymbolicLink()) continue;
187
+ return { command, executablePath, mountSource };
188
+ } catch {}
189
+ }
190
+ return null;
191
+ }
192
+
193
+ function packageManifest(root: string): Record<string, unknown> | null {
194
+ if (!regularFile(root, "package.json")) return null;
195
+ try {
196
+ const value = JSON.parse(
197
+ readFileSync(path.join(root, "package.json"), "utf8"),
198
+ );
199
+ return value && typeof value === "object" && !Array.isArray(value)
200
+ ? (value as Record<string, unknown>)
201
+ : null;
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+
207
+ function validateScriptCommand(
208
+ root: string,
209
+ verificationId: string,
210
+ command: string,
211
+ dependencyOwner: string,
212
+ ): VerificationCapabilityResult {
213
+ if (
214
+ command.length === 0 ||
215
+ command.trim() !== command ||
216
+ SHELL_OPERATOR.test(command)
217
+ ) {
218
+ return adapterFailure(
219
+ verificationId,
220
+ "script-unsafe",
221
+ "approved package script contains unsupported shell syntax",
222
+ );
223
+ }
224
+ const tokens = command.split(/\s+/u);
225
+ if (!SIMPLE_TOKEN.test(tokens[0] ?? "")) {
226
+ return adapterFailure(
227
+ verificationId,
228
+ "script-unsafe",
229
+ "approved package script cannot be represented as safe tokens",
230
+ );
231
+ }
232
+ const executable = tokens[0] ?? "";
233
+ const subcommands = new Set(tokens.slice(1));
234
+ if (
235
+ NETWORKING_RUNNERS.has(executable) ||
236
+ (executable === "bun" && subcommands.has("x")) ||
237
+ (executable === "npm" &&
238
+ (subcommands.has("exec") || subcommands.has("x"))) ||
239
+ (executable === "pnpm" && subcommands.has("dlx")) ||
240
+ (executable === "yarn" && subcommands.has("dlx"))
241
+ ) {
242
+ return adapterFailure(
243
+ verificationId,
244
+ "script-unsafe",
245
+ "package script uses a runner that can download implicitly",
246
+ );
247
+ }
248
+ if (executable === "npx") {
249
+ const local = tokens[1] === "--no-install" ? tokens[2] : undefined;
250
+ if (!local || !localExecutable(root, local, dependencyOwner)) {
251
+ return adapterFailure(
252
+ verificationId,
253
+ "local-executable-missing",
254
+ "npx requires --no-install and an installed local executable",
255
+ );
256
+ }
257
+ const runner = resolveVerificationRunner("npx");
258
+ return runner
259
+ ? capabilitySuccess(verificationId, [runner])
260
+ : adapterFailure(
261
+ verificationId,
262
+ "runner-missing",
263
+ "npx is unavailable or cannot be mounted safely",
264
+ );
265
+ }
266
+ if (["node", "bun"].includes(executable)) {
267
+ const runner = resolveVerificationRunner(executable);
268
+ return runner
269
+ ? capabilitySuccess(verificationId, [runner])
270
+ : adapterFailure(
271
+ verificationId,
272
+ "runner-missing",
273
+ `approved runner ${executable} is unavailable or cannot be mounted safely`,
274
+ );
275
+ }
276
+ return localExecutable(root, executable, dependencyOwner)
277
+ ? capabilitySuccess(verificationId)
278
+ : adapterFailure(
279
+ verificationId,
280
+ "local-executable-missing",
281
+ `local executable ${executable} is unavailable`,
282
+ );
283
+ }
284
+
285
+ function validatePackageScript(
286
+ root: string,
287
+ verificationId: string,
288
+ packageManager: string,
289
+ script: string,
290
+ approvedCommand?: string,
291
+ dependencyOwner = root,
292
+ ): VerificationCapabilityResult {
293
+ const packageRunner = resolveVerificationRunner(packageManager);
294
+ if (!packageRunner) {
295
+ return adapterFailure(
296
+ verificationId,
297
+ "runner-missing",
298
+ `approved package manager ${packageManager} is unavailable or cannot be mounted safely`,
299
+ );
300
+ }
301
+ const manifest = packageManifest(root);
302
+ const scripts = manifest?.scripts;
303
+ const actual =
304
+ scripts && typeof scripts === "object" && !Array.isArray(scripts)
305
+ ? (scripts as Record<string, unknown>)[script]
306
+ : undefined;
307
+ if (typeof actual !== "string") {
308
+ return adapterFailure(
309
+ verificationId,
310
+ "script-missing",
311
+ `package.json script ${script} is missing`,
312
+ );
313
+ }
314
+ if (approvedCommand !== undefined && actual !== approvedCommand) {
315
+ return adapterFailure(
316
+ verificationId,
317
+ "script-command-mismatch",
318
+ `package.json script ${script} differs from the Gate B contract`,
319
+ );
320
+ }
321
+ const scriptCapability = validateScriptCommand(
322
+ root,
323
+ verificationId,
324
+ actual,
325
+ dependencyOwner,
326
+ );
327
+ return scriptCapability.ok
328
+ ? capabilitySuccess(verificationId, [
329
+ packageRunner,
330
+ ...scriptCapability.runnerBindings,
331
+ ])
332
+ : scriptCapability;
333
+ }
334
+
335
+ function validateAtomicCapability(
336
+ root: string,
337
+ step: AtomicVerificationContract,
338
+ dependencyOwner: string,
339
+ allowedMissingInputs: ReadonlySet<string>,
340
+ ): VerificationCapabilityResult {
341
+ for (const input of verificationInputPaths(step)) {
342
+ const status = fileStatus(root, input);
343
+ if (
344
+ status !== "regular" &&
345
+ !(status === "absent" && allowedMissingInputs.has(input))
346
+ ) {
347
+ return adapterFailure(
348
+ step.id,
349
+ "input-missing",
350
+ `verification input ${input} is missing or unsafe`,
351
+ );
352
+ }
353
+ }
354
+ if (step.kind === "package-script") {
355
+ return validatePackageScript(
356
+ root,
357
+ step.id,
358
+ step.packageManager,
359
+ step.script,
360
+ step.command,
361
+ dependencyOwner,
362
+ );
363
+ }
364
+ const runner = step.runner;
365
+ if (runner.kind === "package-script") {
366
+ const script = validatePackageScript(
367
+ root,
368
+ step.id,
369
+ runner.packageManager,
370
+ runner.script,
371
+ runner.command,
372
+ dependencyOwner,
373
+ );
374
+ if (!script.ok) return script;
375
+ if (
376
+ step.kind === "vitest" &&
377
+ !localExecutable(root, "vitest", dependencyOwner)
378
+ ) {
379
+ return adapterFailure(
380
+ step.id,
381
+ "local-executable-missing",
382
+ "Vitest is not installed in consumer node_modules",
383
+ );
384
+ }
385
+ return script;
386
+ }
387
+ if (runner.kind === "node") {
388
+ const node = resolveVerificationRunner("node");
389
+ return node
390
+ ? capabilitySuccess(step.id, [node])
391
+ : adapterFailure(
392
+ step.id,
393
+ "runner-missing",
394
+ "Node is unavailable or cannot be mounted safely",
395
+ );
396
+ }
397
+ if (!localExecutable(root, runner.executable, dependencyOwner)) {
398
+ return adapterFailure(
399
+ step.id,
400
+ "local-executable-missing",
401
+ `local executable ${runner.executable} is unavailable`,
402
+ );
403
+ }
404
+ if (runner.kind === "npx") {
405
+ const npx = resolveVerificationRunner("npx");
406
+ return npx
407
+ ? capabilitySuccess(step.id, [npx])
408
+ : adapterFailure(
409
+ step.id,
410
+ "runner-missing",
411
+ "npx is unavailable or cannot be mounted safely",
412
+ );
413
+ }
414
+ return capabilitySuccess(step.id);
415
+ }
416
+
417
+ export function validateVerificationCapability(
418
+ root: string,
419
+ value: unknown,
420
+ options: {
421
+ dependencyOwner?: string;
422
+ allowedMissingInputs?: readonly string[];
423
+ } = {},
424
+ ): VerificationCapabilityResult {
425
+ const validation = validateVerificationContract(value);
426
+ if (!validation.ok) {
427
+ const id =
428
+ value &&
429
+ typeof value === "object" &&
430
+ !Array.isArray(value) &&
431
+ typeof (value as Record<string, unknown>).id === "string"
432
+ ? String((value as Record<string, unknown>).id)
433
+ : undefined;
434
+ return {
435
+ ok: false,
436
+ diagnostic: {
437
+ kind: "design-readiness",
438
+ code: "verification-contract-unsupported",
439
+ ...(id ? { verificationId: id } : {}),
440
+ message: validation.reason,
441
+ },
442
+ };
443
+ }
444
+ const dependencyOwner = options.dependencyOwner ?? root;
445
+ const allowedMissingInputs = new Set(options.allowedMissingInputs ?? []);
446
+ const runnerBindings: VerificationRunnerBinding[] = [];
447
+ for (const step of verificationSteps(validation.value)) {
448
+ const capability = validateAtomicCapability(
449
+ root,
450
+ step,
451
+ dependencyOwner,
452
+ allowedMissingInputs,
453
+ );
454
+ if (!capability.ok) return capability;
455
+ runnerBindings.push(...capability.runnerBindings);
456
+ }
457
+ return capabilitySuccess(validation.value.id, runnerBindings);
458
+ }
459
+
460
+ export function assessVerificationReadiness(
461
+ root: string,
462
+ contracts: readonly unknown[],
463
+ options: {
464
+ dependencyOwner?: string;
465
+ allowedMissingInputs?: readonly string[];
466
+ } = {},
467
+ ): VerificationReadiness {
468
+ const diagnostics = contracts
469
+ .map((contract) => validateVerificationCapability(root, contract, options))
470
+ .filter(
471
+ (
472
+ result,
473
+ ): result is Extract<VerificationCapabilityResult, { ok: false }> =>
474
+ !result.ok,
475
+ )
476
+ .map((result) => result.diagnostic);
477
+ return {
478
+ taskContractsExecutable: diagnostics.length === 0,
479
+ diagnostics,
480
+ };
481
+ }