@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.
- package/README.md +324 -117
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/agents/design-explorer.md +14 -18
- package/agents/diagnosis-worker.md +14 -14
- package/agents/implementation-worker.md +16 -17
- package/config/workflow-task.example.json +7 -0
- package/docs/remote-access.md +60 -0
- package/docs/reuse-and-distribution.md +44 -0
- package/docs/service-management.md +108 -0
- package/licenses/bundled-web.txt +6245 -0
- package/licenses/pi-web-ui-MIT.txt +21 -0
- package/licenses/pi-web-ui-reuse.md +22 -0
- package/package.json +92 -20
- package/src/index.ts +21 -1802
- package/src/native-build.ts +6 -0
- package/src/native-client.ts +103 -0
- package/src/native-export.ts +58 -0
- package/src/native-history.ts +359 -0
- package/src/operator-cli.mjs +453 -1937
- package/src/plan-proposal.ts +87 -0
- package/src/sdk-worker.mjs +549 -0
- package/src/service/contracts/active.ts +82 -0
- package/src/service/contracts/budget.ts +148 -0
- package/src/service/contracts/build.ts +67 -0
- package/src/service/contracts/capture.ts +48 -0
- package/src/service/contracts/core.ts +33 -0
- package/src/service/contracts/current.ts +261 -0
- package/src/service/contracts/domain.ts +68 -0
- package/src/service/contracts/history.ts +2245 -0
- package/src/service/contracts/models.ts +139 -0
- package/src/service/coordination.ts +862 -0
- package/src/service/private-files.ts +204 -0
- package/src/service-cli.mjs +5592 -0
- package/src/service-client.ts +747 -0
- package/src/service-contract.ts +245 -0
- package/src/service-host.mjs +22382 -0
- package/src/service-http-client.ts +425 -0
- package/src/service-wire.ts +372 -0
- package/src/task-shell.ts +1045 -0
- package/src/task-store.ts +479 -0
- package/src/task-types.ts +184 -0
- package/src/verification-diagnostics.ts +29 -72
- package/src/worker-capture.mjs +1197 -0
- package/src/worker-public.mjs +1753 -0
- package/src/worker-redaction.mjs +608 -0
- package/src/worker-tools.mjs +202 -0
- package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/web/dist/assets/cadence.css +2 -0
- package/web/dist/assets/cadence.js +7 -0
- package/web/dist/assets/markdown.js +296 -0
- package/web/dist/assets/react.js +9 -0
- package/web/dist/assets/rolldown-runtime.js +1 -0
- package/web/dist/assets/timeline-client.js +41 -0
- package/web/dist/assets/vis-timeline.css +1274 -0
- package/web/dist/assets/vis-timeline.js +32 -0
- package/web/dist/index.html +18 -0
- package/web/dist/manifest.json +505 -0
- package/config/plan-draft.example.json +0 -125
- package/config/plan-draft.multiple-tasks.example.json +0 -375
- package/config/plan-draft.quick.example.json +0 -78
- package/prompts/abel-design.md +0 -382
- package/prompts/abel-diagnose.md +0 -71
- package/prompts/abel-implement.md +0 -284
- package/prompts/abel-init.md +0 -52
- package/src/activation.ts +0 -44
- package/src/activity-contracts.ts +0 -82
- package/src/agent-registry.ts +0 -53
- package/src/apply-transaction.ts +0 -1437
- package/src/artifact-store.ts +0 -213
- package/src/build-windows-job.mjs +0 -95
- package/src/candidate-artifact.ts +0 -143
- package/src/candidate-context.ts +0 -289
- package/src/candidate-patch.ts +0 -271
- package/src/candidate-workspace.ts +0 -80
- package/src/canonical.ts +0 -16
- package/src/change-contract-schema.ts +0 -191
- package/src/change-contract.ts +0 -324
- package/src/change-verification.ts +0 -1617
- package/src/contracts.ts +0 -2466
- package/src/control-contracts.ts +0 -382
- package/src/delivery-compiler.ts +0 -1805
- package/src/delivery-revision.ts +0 -42
- package/src/design-control.ts +0 -1299
- package/src/design-diagnostics.ts +0 -181
- package/src/design-journal.ts +0 -1773
- package/src/directory-sync.ts +0 -36
- package/src/durable-contracts.ts +0 -227
- package/src/durable-workflow.ts +0 -822
- package/src/evidence-draft.ts +0 -122
- package/src/execution-profile.ts +0 -64
- package/src/execution-retention.ts +0 -77
- package/src/file-snapshot.ts +0 -163
- package/src/implement-continuation.ts +0 -559
- package/src/implement-graph.ts +0 -690
- package/src/implement-plan.ts +0 -72
- package/src/install-config.d.mts +0 -6
- package/src/isolation-backend.ts +0 -353
- package/src/model-source.ts +0 -22
- package/src/openspec-cli.ts +0 -494
- package/src/operator-cli-entry.mjs +0 -24
- package/src/operator-tools.ts +0 -309
- package/src/package-candidate-process.ts +0 -206
- package/src/package-candidate.ts +0 -24
- package/src/package-delivery.ts +0 -486
- package/src/package-state.ts +0 -320
- package/src/package-verification.ts +0 -635
- package/src/package-workflow.ts +0 -305
- package/src/packet-runtime.ts +0 -459
- package/src/phase-execution.ts +0 -2163
- package/src/pi-adapter.ts +0 -19
- package/src/plan-draft-summary.ts +0 -527
- package/src/plan-draft.ts +0 -502
- package/src/remove-path.ts +0 -32
- package/src/run-state.ts +0 -420
- package/src/run-store.ts +0 -1179
- package/src/safe-path.ts +0 -77
- package/src/scoped-grep-worker.mjs +0 -23
- package/src/scoped-tools.ts +0 -775
- package/src/single-task-draft.ts +0 -142
- package/src/sqlite-schema.ts +0 -171
- package/src/state-root.ts +0 -215
- package/src/storage-schema.ts +0 -360
- package/src/subagent-activity.ts +0 -1417
- package/src/subagent-process.ts +0 -476
- package/src/task-ledger.ts +0 -1338
- package/src/transport-budget.ts +0 -50
- package/src/verification-capability.ts +0 -1191
- package/src/verification-environment-io.ts +0 -389
- package/src/verification-environment.ts +0 -162
- package/src/verification-identity.ts +0 -77
- package/src/verification-scan.ts +0 -64
- package/src/windows-job-backend.ts +0 -419
- package/src/windows-job.c +0 -149
- package/src/workflow-engine.ts +0 -28
- package/src/workflow-policy.ts +0 -1670
- package/src/workflow-recovery-policy.ts +0 -97
- package/src/workflow-scheduling.ts +0 -105
- package/src/workflow-state-machine.ts +0 -4341
- package/src/workflow-status.ts +0 -576
- package/src/workspace-io-entry.mjs +0 -76
- package/src/workspace-io-worker.mjs +0 -1312
- package/src/workspace-io.ts +0 -89
- 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
|
-
}
|