@abelxiaoxing/cadence 1.8.4 → 2.0.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.
Files changed (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -1,1191 +0,0 @@
1
- import {
2
- lstatSync,
3
- readdirSync,
4
- readFileSync,
5
- realpathSync,
6
- type Stats,
7
- } from "node:fs";
8
- import path from "node:path";
9
- import {
10
- type AtomicVerificationContract,
11
- type StructuredVerificationContract,
12
- VERIFICATION_CONFIGURATION_PATHS,
13
- type VerificationAdapterCode,
14
- type VerificationInputObservation,
15
- validateVerificationContract,
16
- verificationBoundInputPaths,
17
- verificationInputPaths,
18
- verificationSteps,
19
- } from "./contracts.ts";
20
- import { executionProfile } from "./execution-profile.ts";
21
- import {
22
- type Bound,
23
- isCurrent,
24
- snapshotFile,
25
- snapshotFiles,
26
- } from "./file-snapshot.ts";
27
- import { observeSafePath } from "./safe-path.ts";
28
- import { verificationEnvironmentDigest } from "./verification-identity.ts";
29
-
30
- export type VerificationCapabilityDiagnostic =
31
- | {
32
- kind: "design-readiness";
33
- code: "verification-contract-unsupported";
34
- verificationId?: string;
35
- message: string;
36
- }
37
- | {
38
- kind: "verification-adapter";
39
- code: VerificationAdapterCode;
40
- verificationId: string;
41
- message: string;
42
- inputObservation?: VerificationInputObservation;
43
- };
44
-
45
- export type VerificationCapabilityResult =
46
- | {
47
- ok: true;
48
- verificationId: string;
49
- runnerBindings: VerificationRunnerBinding[];
50
- }
51
- | { ok: false; diagnostic: VerificationCapabilityDiagnostic };
52
-
53
- export interface CurrentVerificationCapability {
54
- verificationId: string;
55
- runnerBindings: VerificationRunnerBinding[];
56
- inputSnapshot: Bound;
57
- runnerSnapshot: { paths: string[]; digest: string };
58
- }
59
-
60
- export type CurrentVerificationCapabilityResult =
61
- | { ok: true; value: CurrentVerificationCapability }
62
- | { ok: false; diagnostic: VerificationCapabilityDiagnostic };
63
-
64
- export interface VerificationRunnerBinding {
65
- command: string;
66
- executablePath: string;
67
- fixedArgs?: string[];
68
- mountSource?: string;
69
- }
70
-
71
- export interface VerificationRunnerEnvironment {
72
- platform?: NodeJS.Platform;
73
- path?: string;
74
- pathExt?: string;
75
- pathDelimiter?: string;
76
- }
77
-
78
- export interface VerificationCapabilityOptions {
79
- /** Trusted plan write/delete authority, supplied only for isolated execution. */
80
- executionWritePaths?: readonly string[];
81
- dependencyOwner?: string;
82
- runnerEnvironment?: VerificationRunnerEnvironment;
83
- }
84
-
85
- const WINDOWS_EXECUTABLE_EXTENSIONS = new Set([".exe", ".cmd"]);
86
- const DEFAULT_WINDOWS_PATH_EXT = ".COM;.EXE;.BAT;.CMD";
87
- const SYSTEM_MOUNT_ROOTS = ["/usr", "/bin", "/lib", "/lib64", "/etc"];
88
- const BROAD_MOUNT_ROOTS = new Set([
89
- "/",
90
- "/home",
91
- "/root",
92
- "/tmp",
93
- "/var",
94
- "/var/tmp",
95
- ]);
96
-
97
- function capabilitySuccess(
98
- verificationId: string,
99
- runnerBindings: VerificationRunnerBinding[] = [],
100
- ): VerificationCapabilityResult {
101
- const seen = new Set<string>();
102
- return {
103
- ok: true,
104
- verificationId,
105
- runnerBindings: runnerBindings
106
- .filter((binding) => {
107
- const key = `${binding.command}\0${binding.executablePath}\0${JSON.stringify(binding.fixedArgs ?? [])}\0${binding.mountSource ?? ""}`;
108
- if (seen.has(key)) return false;
109
- seen.add(key);
110
- return true;
111
- })
112
- .map((binding) => ({
113
- ...binding,
114
- ...(binding.fixedArgs ? { fixedArgs: [...binding.fixedArgs] } : {}),
115
- })),
116
- };
117
- }
118
-
119
- function adapterFailure(
120
- verificationId: string,
121
- code: VerificationAdapterCode,
122
- message: string,
123
- inputObservation?: VerificationInputObservation,
124
- ): VerificationCapabilityResult {
125
- return {
126
- ok: false,
127
- diagnostic: {
128
- kind: "verification-adapter",
129
- code,
130
- verificationId,
131
- message,
132
- ...(inputObservation ? { inputObservation } : {}),
133
- },
134
- };
135
- }
136
-
137
- function fileStatus(
138
- root: string,
139
- relative: string,
140
- ): "regular" | "absent" | "unsafe" {
141
- const observation = observeSafePath(root, relative);
142
- if (observation.kind === "file") return "regular";
143
- return observation.kind === "absent" ? "absent" : "unsafe";
144
- }
145
-
146
- function regularFile(root: string, relative: string): boolean {
147
- return fileStatus(root, relative) === "regular";
148
- }
149
-
150
- function localExecutable(
151
- root: string,
152
- executable: string,
153
- dependencyOwner = root,
154
- environment: VerificationRunnerEnvironment = {},
155
- ): boolean {
156
- const dependencyRoot = path.resolve(dependencyOwner, "node_modules");
157
- try {
158
- const canonicalDependencyRoot = realpathSync(dependencyRoot);
159
- for (const name of localExecutableNames(executable, environment)) {
160
- const unresolved = resolveCandidateName(
161
- path.resolve(dependencyRoot, ".bin"),
162
- name,
163
- environment.platform ?? process.platform,
164
- );
165
- if (!unresolved) continue;
166
- const canonical = realpathSync(unresolved);
167
- const stat = lstatSync(canonical);
168
- if (
169
- canonical !== canonicalDependencyRoot &&
170
- pathWithin(
171
- canonicalDependencyRoot,
172
- canonical,
173
- environment.platform ?? process.platform,
174
- ) &&
175
- regularExecutableFile(stat, environment.platform ?? process.platform)
176
- ) {
177
- return true;
178
- }
179
- }
180
- return false;
181
- } catch {
182
- return false;
183
- }
184
- }
185
-
186
- function pathApi(
187
- platform: NodeJS.Platform,
188
- ...values: readonly string[]
189
- ): typeof path.posix {
190
- if (platform !== "win32") return path.posix;
191
- if (
192
- process.platform === "win32" ||
193
- values.some(
194
- (value) => /^[a-z]:[\\/]/iu.test(value) || value.startsWith("\\\\"),
195
- )
196
- ) {
197
- return path.win32;
198
- }
199
- // Tests may model Windows executable semantics over a POSIX fixture tree.
200
- return path.posix;
201
- }
202
-
203
- function pathWithin(
204
- root: string,
205
- candidate: string,
206
- platform: NodeJS.Platform,
207
- ): boolean {
208
- const platformPath = pathApi(platform, root, candidate);
209
- const normalizedRoot = platformPath.resolve(root);
210
- const relative = platformPath.relative(
211
- normalizedRoot,
212
- platformPath.resolve(candidate),
213
- );
214
- return (
215
- relative === "" ||
216
- (relative !== ".." &&
217
- !relative.startsWith(`..${platformPath.sep}`) &&
218
- !platformPath.isAbsolute(relative))
219
- );
220
- }
221
-
222
- function regularExecutableFile(
223
- stat: Stats,
224
- platform: NodeJS.Platform,
225
- ): boolean {
226
- return stat.isFile() && (platform === "win32" || (stat.mode & 0o111) !== 0);
227
- }
228
-
229
- function windowsPathExtensions(pathExt: string | undefined): string[] {
230
- const seen = new Set<string>();
231
- const extensions: string[] = [];
232
- for (const entry of (pathExt ?? DEFAULT_WINDOWS_PATH_EXT).split(";")) {
233
- const extension = entry.trim().toLowerCase();
234
- if (!WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) || seen.has(extension)) {
235
- continue;
236
- }
237
- seen.add(extension);
238
- extensions.push(extension);
239
- }
240
- return extensions;
241
- }
242
-
243
- function runnerNames(
244
- command: string,
245
- environment: VerificationRunnerEnvironment,
246
- ): string[] {
247
- if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(command)) return [];
248
- if ((environment.platform ?? process.platform) !== "win32") {
249
- return [command];
250
- }
251
- const extension = path.extname(command).toLowerCase();
252
- if (extension) {
253
- return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [command] : [];
254
- }
255
- return windowsPathExtensions(environment.pathExt).map(
256
- (candidateExtension) => `${command}${candidateExtension}`,
257
- );
258
- }
259
-
260
- function localExecutableNames(
261
- executable: string,
262
- environment: VerificationRunnerEnvironment,
263
- ): string[] {
264
- if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(executable)) return [];
265
- if ((environment.platform ?? process.platform) !== "win32") {
266
- return [executable];
267
- }
268
- const extension = path.extname(executable).toLowerCase();
269
- if (extension) {
270
- return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [executable] : [];
271
- }
272
- return [
273
- executable,
274
- ...windowsPathExtensions(environment.pathExt).map(
275
- (candidateExtension) => `${executable}${candidateExtension}`,
276
- ),
277
- ];
278
- }
279
-
280
- function resolveCandidateName(
281
- directory: string,
282
- name: string,
283
- platform: NodeJS.Platform,
284
- ): string | null {
285
- const exact = path.join(directory, name);
286
- if (platform !== "win32") return exact;
287
- try {
288
- const matches = readdirSync(directory).filter(
289
- (entry) => entry.toLowerCase() === name.toLowerCase(),
290
- );
291
- return matches.length === 1
292
- ? path.join(directory, matches[0] as string)
293
- : null;
294
- } catch {
295
- return null;
296
- }
297
- }
298
-
299
- function within(root: string, candidate: string): boolean {
300
- return candidate === root || candidate.startsWith(`${root}${path.sep}`);
301
- }
302
-
303
- export function commonDirectory(
304
- left: string,
305
- right: string,
306
- platform: NodeJS.Platform = process.platform,
307
- ): string {
308
- const platformPath = pathApi(platform, left, right);
309
- const leftResolved = platformPath.resolve(left);
310
- const rightResolved = platformPath.resolve(right);
311
- const leftRoot = platformPath.parse(leftResolved).root;
312
- const rightRoot = platformPath.parse(rightResolved).root;
313
- const comparable = (value: string) =>
314
- platform === "win32" ? value.toLowerCase() : value;
315
- if (comparable(leftRoot) !== comparable(rightRoot)) return leftRoot;
316
-
317
- const parts = (value: string, root: string) =>
318
- platformPath.relative(root, value).split(platformPath.sep).filter(Boolean);
319
- const leftParts = parts(leftResolved, leftRoot);
320
- const rightParts = parts(rightResolved, rightRoot);
321
- const common: string[] = [];
322
- for (
323
- let index = 0;
324
- index < Math.min(leftParts.length, rightParts.length);
325
- index++
326
- ) {
327
- const leftPart = leftParts[index] as string;
328
- const rightPart = rightParts[index] as string;
329
- if (comparable(leftPart) !== comparable(rightPart)) break;
330
- common.push(leftPart);
331
- }
332
- return platformPath.join(leftRoot, ...common);
333
- }
334
-
335
- function unsafeMountSource(source: string): boolean {
336
- if (BROAD_MOUNT_ROOTS.has(source)) return true;
337
- if (path.resolve(source) === path.parse(path.resolve(source)).root)
338
- return true;
339
- const parts = path
340
- .relative(path.parse(path.resolve(source)).root, path.resolve(source))
341
- .split(path.sep)
342
- .filter(Boolean)
343
- .map((part) => part.toLowerCase());
344
- return (
345
- (parts[0] === "home" && parts.length <= 2) ||
346
- (parts[0] === "users" && parts.length <= 2) ||
347
- (parts[0] === "root" && parts.length <= 1)
348
- );
349
- }
350
-
351
- function safeRunnerSource(
352
- paths: readonly string[],
353
- platform: NodeJS.Platform,
354
- ): string | null {
355
- let source = path.dirname(paths[0] as string);
356
- for (const candidate of paths.slice(1)) {
357
- source = commonDirectory(source, path.dirname(candidate), platform);
358
- }
359
- if (unsafeMountSource(source)) return null;
360
- try {
361
- const stat = lstatSync(source);
362
- if (!stat.isDirectory() || stat.isSymbolicLink()) return null;
363
- } catch {
364
- return null;
365
- }
366
- return paths.every((candidate) => pathWithin(source, candidate, platform))
367
- ? source
368
- : null;
369
- }
370
-
371
- function resolvedRunnerEnvironment(
372
- environment: VerificationRunnerEnvironment,
373
- ): Required<VerificationRunnerEnvironment> {
374
- return {
375
- platform: environment.platform ?? process.platform,
376
- path: environment.path ?? process.env.PATH ?? "",
377
- pathExt:
378
- environment.pathExt ?? process.env.PATHEXT ?? DEFAULT_WINDOWS_PATH_EXT,
379
- pathDelimiter: environment.pathDelimiter ?? path.delimiter,
380
- };
381
- }
382
-
383
- function windowsShimBinding(
384
- command: string,
385
- launcherPath: string,
386
- environment: Required<VerificationRunnerEnvironment>,
387
- ): VerificationRunnerBinding | null {
388
- const name = path.basename(command, path.extname(command)).toLowerCase();
389
- if (name !== "npm" && name !== "npx") return null;
390
- const node = resolveVerificationRunner("node", environment);
391
- if (
392
- !node ||
393
- node.fixedArgs ||
394
- path.extname(node.executablePath).toLowerCase() !== ".exe"
395
- ) {
396
- return null;
397
- }
398
- const cli = path.join(
399
- path.dirname(launcherPath),
400
- "node_modules",
401
- "npm",
402
- "bin",
403
- `${name}-cli.js`,
404
- );
405
- try {
406
- const canonicalLauncher = realpathSync(launcherPath);
407
- const canonicalCli = realpathSync(cli);
408
- const cliStat = lstatSync(canonicalCli);
409
- if (!cliStat.isFile()) return null;
410
- const source = safeRunnerSource(
411
- [node.executablePath, canonicalLauncher, canonicalCli],
412
- environment.platform,
413
- );
414
- if (!source) return null;
415
- return {
416
- command,
417
- executablePath: node.executablePath,
418
- fixedArgs: [canonicalCli],
419
- };
420
- } catch {
421
- return null;
422
- }
423
- }
424
-
425
- function localPackageDirectories(dependencyRoot: string): string[] {
426
- const packages: string[] = [];
427
- try {
428
- for (const entry of readdirSync(dependencyRoot, { withFileTypes: true })) {
429
- if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
430
- const candidate = path.join(dependencyRoot, entry.name);
431
- if (!entry.name.startsWith("@")) {
432
- packages.push(candidate);
433
- continue;
434
- }
435
- for (const scoped of readdirSync(candidate, { withFileTypes: true })) {
436
- if (scoped.isDirectory() && !scoped.name.startsWith(".")) {
437
- packages.push(path.join(candidate, scoped.name));
438
- }
439
- }
440
- }
441
- } catch {
442
- return [];
443
- }
444
- return packages;
445
- }
446
-
447
- function localPackageBinTargets(
448
- dependencyOwner: string,
449
- executable: string,
450
- platform: NodeJS.Platform,
451
- ): string[] {
452
- const dependencyRoot = path.resolve(dependencyOwner, "node_modules");
453
- let canonicalDependencyRoot: string;
454
- try {
455
- canonicalDependencyRoot = realpathSync(dependencyRoot);
456
- } catch {
457
- return [];
458
- }
459
- const targets: string[] = [];
460
- for (const packageDirectory of localPackageDirectories(dependencyRoot)) {
461
- try {
462
- const directoryStat = lstatSync(packageDirectory);
463
- if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {
464
- continue;
465
- }
466
- const canonicalPackage = realpathSync(packageDirectory);
467
- if (
468
- !pathWithin(canonicalDependencyRoot, canonicalPackage, platform) ||
469
- canonicalPackage === canonicalDependencyRoot
470
- ) {
471
- continue;
472
- }
473
- const manifestPath = path.join(canonicalPackage, "package.json");
474
- const manifestStat = lstatSync(manifestPath);
475
- if (!manifestStat.isFile() || manifestStat.isSymbolicLink()) continue;
476
- const manifest = JSON.parse(
477
- readFileSync(manifestPath, "utf8"),
478
- ) as unknown;
479
- if (
480
- !manifest ||
481
- typeof manifest !== "object" ||
482
- Array.isArray(manifest)
483
- ) {
484
- continue;
485
- }
486
- const record = manifest as Record<string, unknown>;
487
- const packageName =
488
- typeof record.name === "string"
489
- ? (record.name.split("/").pop() ?? "")
490
- : "";
491
- const bin = record.bin;
492
- let relative: unknown;
493
- if (
494
- typeof bin === "string" &&
495
- (platform === "win32"
496
- ? packageName.toLowerCase() === executable.toLowerCase()
497
- : packageName === executable)
498
- ) {
499
- relative = bin;
500
- } else if (bin && typeof bin === "object" && !Array.isArray(bin)) {
501
- const entries = Object.entries(bin as Record<string, unknown>).filter(
502
- ([name]) =>
503
- platform === "win32"
504
- ? name.toLowerCase() === executable.toLowerCase()
505
- : name === executable,
506
- );
507
- if (entries.length === 1) relative = entries[0]?.[1];
508
- }
509
- if (
510
- typeof relative !== "string" ||
511
- relative.length === 0 ||
512
- relative.includes("\0")
513
- ) {
514
- continue;
515
- }
516
- const unresolved = path.resolve(canonicalPackage, relative);
517
- if (!pathWithin(canonicalPackage, unresolved, platform)) continue;
518
- const canonical = realpathSync(unresolved);
519
- const stat = lstatSync(canonical);
520
- if (
521
- stat.isFile() &&
522
- pathWithin(canonicalPackage, canonical, platform) &&
523
- pathWithin(canonicalDependencyRoot, canonical, platform)
524
- ) {
525
- targets.push(canonical);
526
- }
527
- } catch {}
528
- }
529
- return [...new Set(targets)];
530
- }
531
-
532
- function resolveLocalVerificationRunner(
533
- dependencyOwner: string,
534
- executable: string,
535
- runnerEnvironment: VerificationRunnerEnvironment,
536
- ): VerificationRunnerBinding | null {
537
- const environment = resolvedRunnerEnvironment(runnerEnvironment);
538
- if (environment.platform !== "win32") return null;
539
- const targets = localPackageBinTargets(
540
- dependencyOwner,
541
- executable,
542
- environment.platform,
543
- );
544
- if (targets.length !== 1) return null;
545
- const target = targets[0] as string;
546
- const extension = path.extname(target).toLowerCase();
547
- if (extension === ".exe") {
548
- return { command: executable, executablePath: target };
549
- }
550
- if (![".js", ".cjs", ".mjs"].includes(extension)) return null;
551
- const node = resolveVerificationRunner("node", environment);
552
- if (!node || node.fixedArgs) return null;
553
- return {
554
- command: executable,
555
- executablePath: node.executablePath,
556
- fixedArgs: [target],
557
- };
558
- }
559
-
560
- export function resolveVerificationRunner(
561
- command: string,
562
- runnerEnvironment: VerificationRunnerEnvironment = {},
563
- ): VerificationRunnerBinding | null {
564
- const environment = resolvedRunnerEnvironment(runnerEnvironment);
565
- const directories = environment.path
566
- .split(environment.pathDelimiter)
567
- .filter(Boolean);
568
- const seenDirectories = new Set<string>();
569
- const candidates = [
570
- ...(command === "bun" &&
571
- process.versions.bun &&
572
- environment.platform === process.platform
573
- ? [process.execPath]
574
- : []),
575
- ...directories.flatMap((directory) => {
576
- const resolvedDirectory = path.resolve(directory);
577
- const key =
578
- environment.platform === "win32"
579
- ? resolvedDirectory.toLowerCase()
580
- : resolvedDirectory;
581
- if (seenDirectories.has(key)) return [];
582
- seenDirectories.add(key);
583
- return runnerNames(command, environment).flatMap((name) => {
584
- const candidate = resolveCandidateName(
585
- resolvedDirectory,
586
- name,
587
- environment.platform,
588
- );
589
- return candidate ? [candidate] : [];
590
- });
591
- }),
592
- ];
593
- const seenCandidates = new Set<string>();
594
- for (const candidate of candidates) {
595
- try {
596
- const executablePath = path.resolve(candidate);
597
- const candidateKey =
598
- environment.platform === "win32"
599
- ? executablePath.toLowerCase()
600
- : executablePath;
601
- if (seenCandidates.has(candidateKey)) continue;
602
- seenCandidates.add(candidateKey);
603
- const canonical = realpathSync(executablePath);
604
- const stat = lstatSync(canonical);
605
- if (!regularExecutableFile(stat, environment.platform)) continue;
606
- if (environment.platform === "win32") {
607
- const extension = path.extname(executablePath).toLowerCase();
608
- if (extension === ".cmd") {
609
- const shim = windowsShimBinding(command, executablePath, environment);
610
- if (shim) return shim;
611
- continue;
612
- }
613
- if (extension !== ".exe") continue;
614
- if (
615
- !safeRunnerSource([executablePath, canonical], environment.platform)
616
- ) {
617
- continue;
618
- }
619
- return { command, executablePath };
620
- }
621
- const systemVisible = SYSTEM_MOUNT_ROOTS.some(
622
- (root) => within(root, executablePath) && within(root, canonical),
623
- );
624
- if (systemVisible) return { command, executablePath };
625
- const mountSource = commonDirectory(
626
- path.dirname(executablePath),
627
- path.dirname(canonical),
628
- environment.platform,
629
- );
630
- if (unsafeMountSource(mountSource)) continue;
631
- const sourceStat = lstatSync(mountSource);
632
- if (!sourceStat.isDirectory() || sourceStat.isSymbolicLink()) continue;
633
- return { command, executablePath, mountSource };
634
- } catch {}
635
- }
636
- return null;
637
- }
638
-
639
- function packageManifest(root: string): Record<string, unknown> | null {
640
- if (!regularFile(root, "package.json")) return null;
641
- try {
642
- const value = JSON.parse(
643
- readFileSync(path.join(root, "package.json"), "utf8"),
644
- );
645
- return value && typeof value === "object" && !Array.isArray(value)
646
- ? (value as Record<string, unknown>)
647
- : null;
648
- } catch {
649
- return null;
650
- }
651
- }
652
-
653
- function validatePackageScript(
654
- root: string,
655
- verificationId: string,
656
- packageManager: string,
657
- script: string,
658
- approvedCommand: string,
659
- _dependencyOwner = root,
660
- runnerEnvironment: VerificationRunnerEnvironment = {},
661
- ): VerificationCapabilityResult {
662
- const packageRunner = resolveVerificationRunner(
663
- packageManager,
664
- runnerEnvironment,
665
- );
666
- if (!packageRunner) {
667
- return adapterFailure(
668
- verificationId,
669
- "runner-missing",
670
- `approved package manager ${packageManager} is unavailable or cannot be mounted safely`,
671
- );
672
- }
673
- const manifest = packageManifest(root);
674
- const scripts = manifest?.scripts;
675
- const actual =
676
- scripts && typeof scripts === "object" && !Array.isArray(scripts)
677
- ? (scripts as Record<string, unknown>)[script]
678
- : undefined;
679
- if (typeof actual !== "string") {
680
- return adapterFailure(
681
- verificationId,
682
- "script-missing",
683
- `package.json script ${script} is missing`,
684
- );
685
- }
686
- if (actual !== approvedCommand) {
687
- return adapterFailure(
688
- verificationId,
689
- "script-command-mismatch",
690
- `package.json script ${script} differs from the Gate B contract`,
691
- );
692
- }
693
- // Approved scripts are opaque shell programs and may invoke nested scripts
694
- // or hooks. Supply the available supported runtimes without parsing shell
695
- // syntax or rejecting composition; isolation still owns their execution.
696
- const runtimes = ["node", "bun", "npm", "npx", "pnpm", "yarn"].flatMap(
697
- (command) => {
698
- const binding = resolveVerificationRunner(command, runnerEnvironment);
699
- return binding ? [binding] : [];
700
- },
701
- );
702
- return capabilitySuccess(verificationId, [packageRunner, ...runtimes]);
703
- }
704
-
705
- /** Inspect active directives, never comments or arbitrary words in values. */
706
- function unsupportedExecutionConfiguration(source: string): boolean {
707
- const restricted = new Set([
708
- "scriptshell",
709
- "userconfig",
710
- "globalconfig",
711
- "yarnpath",
712
- "plugins",
713
- "auth",
714
- "authtoken",
715
- "password",
716
- "token",
717
- "npmauthtoken",
718
- "npmauthident",
719
- ]);
720
- for (const line of source.split(/\r?\n/u)) {
721
- let quote = "";
722
- let escaped = false;
723
- let active = "";
724
- const parts: string[] = [];
725
- let flowDepth = 0;
726
- for (const character of line) {
727
- if (escaped) {
728
- active += character;
729
- escaped = false;
730
- continue;
731
- }
732
- if (character === "\\" && quote === '"') {
733
- active += character;
734
- escaped = true;
735
- continue;
736
- }
737
- if (quote) {
738
- active += character;
739
- if (character === quote) quote = "";
740
- } else if (character === '"' || character === "'") {
741
- quote = character;
742
- active += character;
743
- } else if (
744
- character === "#" ||
745
- (character === ";" && active.trim() === "")
746
- ) {
747
- break;
748
- } else if (
749
- character === "{" ||
750
- character === "}" ||
751
- (character === "," && flowDepth > 0)
752
- ) {
753
- if (character === "{") flowDepth++;
754
- if (character === "}") flowDepth = Math.max(0, flowDepth - 1);
755
- parts.push(active);
756
- active = "";
757
- } else active += character;
758
- }
759
- parts.push(active);
760
- for (const part of parts) {
761
- if (part.includes("${")) return true;
762
- const directive = part.match(
763
- /^\s*(?:"((?:\\.|[^"\\])*)"|'([^']*)'|([^\s=]+))\s*(.*)$/u,
764
- );
765
- if (!directive) continue;
766
- const rawKey = directive[1] ?? directive[2] ?? directive[3];
767
- if (!rawKey.endsWith(":") && !directive[4]) continue;
768
- const key = rawKey
769
- .replace(/:$/u, "")
770
- .split(":")
771
- .at(-1)
772
- ?.replace(/[-_]/gu, "")
773
- .toLowerCase();
774
- if (key && restricted.has(key)) return true;
775
- }
776
- }
777
- return false;
778
- }
779
-
780
- function validateAtomicCapability(
781
- root: string,
782
- step: AtomicVerificationContract,
783
- dependencyOwner: string,
784
- runnerEnvironment: VerificationRunnerEnvironment,
785
- executionWritePaths: readonly string[],
786
- ): VerificationCapabilityResult {
787
- if (
788
- step.kind === "vitest" &&
789
- [
790
- ...step.args,
791
- ...(step.runner.kind === "package-script"
792
- ? step.runner.command.split(/\s+/u)
793
- : []),
794
- ].some((arg) => /^--(?:reporters?|outputFile)(?:[.=]|$)/u.test(arg))
795
- ) {
796
- return adapterFailure(
797
- step.id,
798
- "report-arguments-conflict",
799
- "Vitest report arguments are owned by the adapter",
800
- );
801
- }
802
- if (step.executionBindings) {
803
- for (const [relative, expected] of Object.entries(step.executionBindings)) {
804
- if (executionWritePaths.includes(relative)) continue;
805
- const observation = observeSafePath(root, relative);
806
- if (
807
- (expected === null && observation.kind !== "absent") ||
808
- (expected !== null && snapshotFile(root, relative)?.sha256 !== expected)
809
- ) {
810
- return adapterFailure(
811
- step.id,
812
- "verification-config-mismatch",
813
- "Bound execution inputs changed; recompile the verification contract",
814
- );
815
- }
816
- }
817
- }
818
- for (const relative of verificationBoundInputPaths(step).filter((file) =>
819
- VERIFICATION_CONFIGURATION_PATHS.includes(file),
820
- )) {
821
- const observation = observeSafePath(root, relative);
822
- if (!["file", "absent"].includes(observation.kind))
823
- return adapterFailure(
824
- step.id,
825
- "verification-config-unsafe",
826
- "Execution configuration must be a regular file",
827
- );
828
- if (
829
- observation.kind !== "file" ||
830
- ![
831
- ".npmrc",
832
- ".yarnrc",
833
- ".yarnrc.yml",
834
- "bunfig.toml",
835
- "pnpm-workspace.yaml",
836
- ].includes(relative)
837
- )
838
- continue;
839
- if (
840
- lstatSync(path.join(root, relative)).size > 64 * 1024 ||
841
- (executionProfile().mode === "isolated" &&
842
- unsupportedExecutionConfiguration(
843
- readFileSync(path.join(root, relative), "utf8"),
844
- ))
845
- ) {
846
- return adapterFailure(
847
- step.id,
848
- "verification-config-unsafe",
849
- "Execution configuration requires unsupported credentials or host configuration",
850
- );
851
- }
852
- }
853
- if (step.kind === "package-script") {
854
- return validatePackageScript(
855
- root,
856
- step.id,
857
- step.packageManager,
858
- step.script,
859
- step.command,
860
- dependencyOwner,
861
- runnerEnvironment,
862
- );
863
- }
864
- const runner = step.runner;
865
- if (runner.kind === "package-script") {
866
- const script = validatePackageScript(
867
- root,
868
- step.id,
869
- runner.packageManager,
870
- runner.script,
871
- runner.command,
872
- dependencyOwner,
873
- runnerEnvironment,
874
- );
875
- if (!script.ok) return script;
876
- if (
877
- step.kind === "vitest" &&
878
- !localExecutable(root, "vitest", dependencyOwner, runnerEnvironment)
879
- ) {
880
- return adapterFailure(
881
- step.id,
882
- "local-executable-missing",
883
- "Vitest is not installed in consumer node_modules",
884
- );
885
- }
886
- return script;
887
- }
888
- if (runner.kind === "node") {
889
- const node = resolveVerificationRunner("node", runnerEnvironment);
890
- return node
891
- ? capabilitySuccess(step.id, [node])
892
- : adapterFailure(
893
- step.id,
894
- "runner-missing",
895
- "Node is unavailable or cannot be mounted safely",
896
- );
897
- }
898
- if (
899
- !localExecutable(
900
- root,
901
- runner.executable,
902
- dependencyOwner,
903
- runnerEnvironment,
904
- )
905
- ) {
906
- return adapterFailure(
907
- step.id,
908
- "local-executable-missing",
909
- `local executable ${runner.executable} is unavailable`,
910
- );
911
- }
912
- if (runner.kind === "npx") {
913
- const npx = resolveVerificationRunner("npx", runnerEnvironment);
914
- return npx
915
- ? capabilitySuccess(step.id, [npx])
916
- : adapterFailure(
917
- step.id,
918
- "runner-missing",
919
- "npx is unavailable or cannot be mounted safely",
920
- );
921
- }
922
- if ((runnerEnvironment.platform ?? process.platform) === "win32") {
923
- const binding = resolveLocalVerificationRunner(
924
- dependencyOwner,
925
- runner.executable,
926
- runnerEnvironment,
927
- );
928
- return binding
929
- ? capabilitySuccess(step.id, [binding])
930
- : adapterFailure(
931
- step.id,
932
- "local-executable-missing",
933
- `local executable ${runner.executable} has no safe direct Windows binding`,
934
- );
935
- }
936
- return capabilitySuccess(step.id);
937
- }
938
-
939
- /** Enrich only a new design draft, never rewrite proof-bound canonical delivery bytes. */
940
- export function bindDraftVerificationInputs(
941
- root: string,
942
- draft: unknown,
943
- ): unknown {
944
- const visit = (value: unknown): unknown => {
945
- if (Array.isArray(value)) return value.map(visit);
946
- if (!value || typeof value !== "object") return value;
947
- const item = Object.fromEntries(
948
- Object.entries(value).map(([key, child]) => [
949
- key,
950
- // Gate A is immutable approval authority, not an execution contract.
951
- key === "changeContract" ? structuredClone(child) : visit(child),
952
- ]),
953
- );
954
- if (
955
- item.kind === "package-script" &&
956
- item.command === undefined &&
957
- typeof item.script === "string"
958
- ) {
959
- const scripts = packageManifest(root)?.scripts as
960
- | Record<string, unknown>
961
- | undefined;
962
- item.command = scripts?.[item.script];
963
- }
964
- if (
965
- ["vitest", "package-script", "static-check"].includes(
966
- String(item.kind),
967
- ) &&
968
- typeof item.id === "string"
969
- ) {
970
- const validation = validateVerificationContract(item);
971
- if (
972
- validation.ok &&
973
- verificationBoundInputPaths(validation.value).some((file) =>
974
- VERIFICATION_CONFIGURATION_PATHS.includes(file),
975
- )
976
- ) {
977
- item.executionBindings = Object.fromEntries(
978
- VERIFICATION_CONFIGURATION_PATHS.map((relative) => [
979
- relative,
980
- snapshotFile(root, relative)?.sha256 ?? null,
981
- ]),
982
- );
983
- }
984
- }
985
- return item;
986
- };
987
- return visit(draft);
988
- }
989
-
990
- export function validateVerificationCapability(
991
- root: string,
992
- value: unknown,
993
- options: VerificationCapabilityOptions = {},
994
- ): VerificationCapabilityResult {
995
- return validateCapability(root, value, options, true);
996
- }
997
-
998
- /** Validate one exact adapter and bind all of its current file inputs. */
999
- export function bindCurrentVerificationCapability(
1000
- root: string,
1001
- value: unknown,
1002
- options: VerificationCapabilityOptions = {},
1003
- ): CurrentVerificationCapabilityResult {
1004
- const capability = validateVerificationCapability(root, value, options);
1005
- if (!capability.ok) return capability;
1006
- const validation = validateVerificationContract(value);
1007
- if (!validation.ok) {
1008
- throw new Error("verification-capability-invariant");
1009
- }
1010
- let paths: string[];
1011
- let runnerDigest: string;
1012
- try {
1013
- paths = verificationRunnerFiles(
1014
- options.dependencyOwner ?? root,
1015
- validation.value,
1016
- capability.runnerBindings,
1017
- );
1018
- runnerDigest = verificationEnvironmentDigest(paths);
1019
- } catch {
1020
- return {
1021
- ok: false,
1022
- diagnostic: {
1023
- kind: "verification-adapter",
1024
- verificationId: capability.verificationId,
1025
- code: "runner-missing",
1026
- message: "Installed verification runner changed or is unavailable",
1027
- },
1028
- };
1029
- }
1030
- return {
1031
- ok: true,
1032
- value: {
1033
- verificationId: capability.verificationId,
1034
- runnerBindings: capability.runnerBindings.map((binding) => ({
1035
- ...binding,
1036
- ...(binding.fixedArgs ? { fixedArgs: [...binding.fixedArgs] } : {}),
1037
- })),
1038
- inputSnapshot: snapshotFiles(
1039
- root,
1040
- verificationBoundInputPaths(validation.value),
1041
- ),
1042
- runnerSnapshot: { paths, digest: runnerDigest },
1043
- },
1044
- };
1045
- }
1046
-
1047
- export function isVerificationCapabilityCurrent(
1048
- root: string,
1049
- capability: CurrentVerificationCapability,
1050
- ): boolean {
1051
- try {
1052
- return (
1053
- isCurrent(root, capability.inputSnapshot) &&
1054
- verificationEnvironmentDigest(capability.runnerSnapshot.paths) ===
1055
- capability.runnerSnapshot.digest
1056
- );
1057
- } catch {
1058
- return false;
1059
- }
1060
- }
1061
-
1062
- export function verificationRunnerFiles(
1063
- dependencyOwner: string,
1064
- verification: StructuredVerificationContract,
1065
- bindings: readonly VerificationRunnerBinding[],
1066
- ): string[] {
1067
- const files = bindings.flatMap((binding) => [
1068
- binding.executablePath,
1069
- ...(binding.fixedArgs ?? []).filter((arg) => path.isAbsolute(arg)),
1070
- ]);
1071
- for (const file of [...new Set(files)]) {
1072
- // Keep the launcher identity and explicitly include its resolved bytes:
1073
- // the digest intentionally does not follow arbitrary dependency symlinks.
1074
- const resolved = realpathSync(file);
1075
- files.push(resolved);
1076
- // System runners have no mountSource. Bind their installed package too,
1077
- // including implementation modules and bundled dependencies beyond bin/.
1078
- for (
1079
- let directory = path.dirname(resolved);
1080
- directory !== path.dirname(directory);
1081
- directory = path.dirname(directory)
1082
- ) {
1083
- if (
1084
- lstatSync(path.join(directory, "package.json"), {
1085
- throwIfNoEntry: false,
1086
- })?.isFile()
1087
- ) {
1088
- files.push(directory);
1089
- break;
1090
- }
1091
- }
1092
- }
1093
- for (const step of verificationSteps(verification)) {
1094
- if (
1095
- !("runner" in step) ||
1096
- step.runner.kind === "node" ||
1097
- step.runner.kind === "package-script"
1098
- )
1099
- continue;
1100
- const executable =
1101
- step.kind === "vitest" ? "vitest" : step.runner.executable;
1102
- const file = path.join(dependencyOwner, "node_modules/.bin", executable);
1103
- try {
1104
- files.push(file, realpathSync(file));
1105
- } catch {
1106
- /* Admission owns missing runner diagnostics. */
1107
- }
1108
- }
1109
- return [...new Set(files)];
1110
- }
1111
-
1112
- export function validateVerificationAdapterCapability(
1113
- root: string,
1114
- value: unknown,
1115
- options: VerificationCapabilityOptions = {},
1116
- ): VerificationCapabilityResult {
1117
- return validateCapability(root, value, options, false);
1118
- }
1119
-
1120
- function validateCapability(
1121
- root: string,
1122
- value: unknown,
1123
- options: VerificationCapabilityOptions,
1124
- requireInputAvailability: boolean,
1125
- ): VerificationCapabilityResult {
1126
- const validation = validateVerificationContract(value);
1127
- if (!validation.ok) {
1128
- const id =
1129
- value &&
1130
- typeof value === "object" &&
1131
- !Array.isArray(value) &&
1132
- typeof (value as Record<string, unknown>).id === "string"
1133
- ? String((value as Record<string, unknown>).id)
1134
- : undefined;
1135
- return {
1136
- ok: false,
1137
- diagnostic: {
1138
- kind: "design-readiness",
1139
- code: "verification-contract-unsupported",
1140
- ...(id ? { verificationId: id } : {}),
1141
- message: validation.reason,
1142
- },
1143
- };
1144
- }
1145
- const dependencyOwner = options.dependencyOwner ?? root;
1146
- return validateAcceptedVerificationCapability(
1147
- root,
1148
- validation.value,
1149
- dependencyOwner,
1150
- options.runnerEnvironment ?? {},
1151
- requireInputAvailability,
1152
- options.executionWritePaths ?? [],
1153
- );
1154
- }
1155
-
1156
- function validateAcceptedVerificationCapability(
1157
- root: string,
1158
- verification: StructuredVerificationContract,
1159
- dependencyOwner: string,
1160
- runnerEnvironment: VerificationRunnerEnvironment,
1161
- requireInputAvailability: boolean,
1162
- executionWritePaths: readonly string[],
1163
- ): VerificationCapabilityResult {
1164
- if (requireInputAvailability) {
1165
- for (const input of verificationInputPaths(verification)) {
1166
- const status = fileStatus(root, input);
1167
- if (status !== "regular") {
1168
- const inputObservation = { path: input, kind: status } as const;
1169
- return adapterFailure(
1170
- verification.id,
1171
- status === "absent" ? "input-missing" : "input-unsafe",
1172
- `verification input ${input} is ${status}`,
1173
- inputObservation,
1174
- );
1175
- }
1176
- }
1177
- }
1178
- const runnerBindings: VerificationRunnerBinding[] = [];
1179
- for (const step of verificationSteps(verification)) {
1180
- const capability = validateAtomicCapability(
1181
- root,
1182
- step,
1183
- dependencyOwner,
1184
- runnerEnvironment,
1185
- executionWritePaths,
1186
- );
1187
- if (!capability.ok) return capability;
1188
- runnerBindings.push(...capability.runnerBindings);
1189
- }
1190
- return capabilitySuccess(verification.id, runnerBindings);
1191
- }