@abelxiaoxing/cadence 1.8.3 → 2.0.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.
Files changed (208) hide show
  1. package/README.md +324 -111
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -26
  4. package/agents/diagnosis-worker.md +14 -25
  5. package/agents/implementation-worker.md +16 -35
  6. package/config/.env.example +2 -5
  7. package/config/workflow-task.example.json +7 -0
  8. package/docs/remote-access.md +60 -0
  9. package/docs/reuse-and-distribution.md +44 -0
  10. package/docs/service-management.md +108 -0
  11. package/licenses/bundled-web.txt +6245 -0
  12. package/licenses/pi-web-ui-MIT.txt +21 -0
  13. package/licenses/pi-web-ui-reuse.md +22 -0
  14. package/package.json +94 -21
  15. package/src/index.ts +22 -1807
  16. package/src/native-build.ts +6 -0
  17. package/src/native-client.ts +103 -0
  18. package/src/native-export.ts +58 -0
  19. package/src/native-history.ts +359 -0
  20. package/src/operator-cli.mjs +453 -1937
  21. package/src/plan-proposal.ts +87 -0
  22. package/src/sdk-worker.mjs +549 -0
  23. package/src/service/contracts/active.ts +82 -0
  24. package/src/service/contracts/budget.ts +148 -0
  25. package/src/service/contracts/build.ts +67 -0
  26. package/src/service/contracts/capture.ts +48 -0
  27. package/src/service/contracts/core.ts +33 -0
  28. package/src/service/contracts/current.ts +261 -0
  29. package/src/service/contracts/domain.ts +68 -0
  30. package/src/service/contracts/history.ts +2245 -0
  31. package/src/service/contracts/models.ts +139 -0
  32. package/src/service/coordination.ts +862 -0
  33. package/src/service/private-files.ts +204 -0
  34. package/src/service-cli.mjs +5592 -0
  35. package/src/service-client.ts +747 -0
  36. package/src/service-contract.ts +245 -0
  37. package/src/service-host.mjs +22382 -0
  38. package/src/service-http-client.ts +425 -0
  39. package/src/service-wire.ts +372 -0
  40. package/src/task-shell.ts +1045 -0
  41. package/src/task-store.ts +479 -0
  42. package/src/task-types.ts +184 -0
  43. package/src/verification-diagnostics.ts +29 -72
  44. package/src/worker-capture.mjs +1197 -0
  45. package/src/worker-public.mjs +1753 -0
  46. package/src/worker-redaction.mjs +608 -0
  47. package/src/worker-tools.mjs +202 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  50. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  56. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  62. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  65. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  68. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  71. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  74. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  77. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  80. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  89. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  92. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  95. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  98. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  101. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  104. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  107. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  108. package/web/dist/assets/cadence.css +2 -0
  109. package/web/dist/assets/cadence.js +7 -0
  110. package/web/dist/assets/markdown.js +296 -0
  111. package/web/dist/assets/react.js +9 -0
  112. package/web/dist/assets/rolldown-runtime.js +1 -0
  113. package/web/dist/assets/timeline-client.js +41 -0
  114. package/web/dist/assets/vis-timeline.css +1274 -0
  115. package/web/dist/assets/vis-timeline.js +32 -0
  116. package/web/dist/index.html +18 -0
  117. package/web/dist/manifest.json +505 -0
  118. package/config/plan-draft.example.json +0 -125
  119. package/config/plan-draft.multiple-tasks.example.json +0 -375
  120. package/config/plan-draft.quick.example.json +0 -78
  121. package/config/routes.example.json +0 -39
  122. package/prompts/abel-design.md +0 -382
  123. package/prompts/abel-diagnose.md +0 -71
  124. package/prompts/abel-implement.md +0 -284
  125. package/prompts/abel-init.md +0 -52
  126. package/src/activation.ts +0 -44
  127. package/src/activity-contracts.ts +0 -82
  128. package/src/agent-registry.ts +0 -53
  129. package/src/apply-transaction.ts +0 -1437
  130. package/src/artifact-store.ts +0 -213
  131. package/src/build-windows-job.mjs +0 -95
  132. package/src/candidate-artifact.ts +0 -143
  133. package/src/candidate-patch.ts +0 -271
  134. package/src/candidate-workspace.ts +0 -80
  135. package/src/canonical.ts +0 -16
  136. package/src/change-contract-schema.ts +0 -191
  137. package/src/change-contract.ts +0 -324
  138. package/src/change-verification.ts +0 -1617
  139. package/src/child-budget.ts +0 -58
  140. package/src/child-model.ts +0 -142
  141. package/src/child-session.ts +0 -640
  142. package/src/contracts.ts +0 -2476
  143. package/src/control-contracts.ts +0 -382
  144. package/src/delivery-compiler.ts +0 -1805
  145. package/src/delivery-revision.ts +0 -42
  146. package/src/design-control.ts +0 -1299
  147. package/src/design-diagnostics.ts +0 -181
  148. package/src/design-journal.ts +0 -1773
  149. package/src/directory-sync.ts +0 -36
  150. package/src/durable-contracts.ts +0 -231
  151. package/src/durable-workflow.ts +0 -978
  152. package/src/evidence-draft.ts +0 -122
  153. package/src/execution-profile.ts +0 -64
  154. package/src/execution-retention.ts +0 -77
  155. package/src/file-snapshot.ts +0 -163
  156. package/src/implement-continuation.ts +0 -559
  157. package/src/implement-graph.ts +0 -690
  158. package/src/implement-plan.ts +0 -72
  159. package/src/install-config.d.mts +0 -6
  160. package/src/isolation-backend.ts +0 -353
  161. package/src/model-source.ts +0 -22
  162. package/src/openspec-cli.ts +0 -494
  163. package/src/operator-cli-entry.mjs +0 -24
  164. package/src/operator-tools.ts +0 -309
  165. package/src/package-candidate.ts +0 -227
  166. package/src/package-delivery.ts +0 -486
  167. package/src/package-state.ts +0 -320
  168. package/src/package-verification.ts +0 -635
  169. package/src/package-workflow.ts +0 -342
  170. package/src/packet-runtime.ts +0 -570
  171. package/src/parent-provider.ts +0 -375
  172. package/src/phase-execution.ts +0 -2259
  173. package/src/pi-adapter.ts +0 -19
  174. package/src/plan-draft-summary.ts +0 -527
  175. package/src/plan-draft.ts +0 -502
  176. package/src/remove-path.ts +0 -32
  177. package/src/route-policy.ts +0 -545
  178. package/src/run-state.ts +0 -420
  179. package/src/run-store.ts +0 -1179
  180. package/src/safe-path.ts +0 -77
  181. package/src/scoped-grep-worker.mjs +0 -23
  182. package/src/scoped-tools.ts +0 -775
  183. package/src/single-task-draft.ts +0 -142
  184. package/src/sqlite-schema.ts +0 -171
  185. package/src/state-root.ts +0 -215
  186. package/src/storage-schema.ts +0 -360
  187. package/src/subagent-activity.ts +0 -1417
  188. package/src/submit-tool.ts +0 -950
  189. package/src/task-ledger.ts +0 -1338
  190. package/src/transport-budget.ts +0 -52
  191. package/src/verification-capability.ts +0 -1191
  192. package/src/verification-environment-io.ts +0 -389
  193. package/src/verification-environment.ts +0 -162
  194. package/src/verification-identity.ts +0 -77
  195. package/src/verification-scan.ts +0 -64
  196. package/src/windows-job-backend.ts +0 -419
  197. package/src/windows-job.c +0 -149
  198. package/src/worker-broker.ts +0 -806
  199. package/src/workflow-engine.ts +0 -28
  200. package/src/workflow-policy.ts +0 -1679
  201. package/src/workflow-recovery-policy.ts +0 -97
  202. package/src/workflow-scheduling.ts +0 -105
  203. package/src/workflow-state-machine.ts +0 -4355
  204. package/src/workflow-status.ts +0 -576
  205. package/src/workspace-io-entry.mjs +0 -76
  206. package/src/workspace-io-worker.mjs +0 -1312
  207. package/src/workspace-io.ts +0 -89
  208. 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
- }