@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,1312 +0,0 @@
|
|
|
1
|
-
// Generated by scripts/build-workspace-io.mjs; edit workspace-io-entry.mjs and its source modules.
|
|
2
|
-
// src/workspace-io-entry.mjs
|
|
3
|
-
import { parentPort, workerData } from "node:worker_threads";
|
|
4
|
-
|
|
5
|
-
// src/artifact-store.ts
|
|
6
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import {
|
|
8
|
-
chmodSync,
|
|
9
|
-
closeSync as closeSync2,
|
|
10
|
-
constants as constants2,
|
|
11
|
-
copyFileSync,
|
|
12
|
-
fsyncSync as fsyncSync2,
|
|
13
|
-
lstatSync,
|
|
14
|
-
mkdirSync,
|
|
15
|
-
openSync as openSync2,
|
|
16
|
-
readFileSync,
|
|
17
|
-
renameSync,
|
|
18
|
-
unlinkSync,
|
|
19
|
-
writeFileSync
|
|
20
|
-
} from "node:fs";
|
|
21
|
-
import path from "node:path";
|
|
22
|
-
|
|
23
|
-
// src/directory-sync.ts
|
|
24
|
-
import { closeSync, constants, fstatSync, fsyncSync, openSync } from "node:fs";
|
|
25
|
-
function syncDirectory(directory) {
|
|
26
|
-
const descriptor = openSync(directory, constants.O_RDONLY);
|
|
27
|
-
try {
|
|
28
|
-
try {
|
|
29
|
-
fsyncSync(descriptor);
|
|
30
|
-
return "synced";
|
|
31
|
-
} catch (error) {
|
|
32
|
-
const failure = error;
|
|
33
|
-
if (process.platform === "win32" && failure?.code === "EPERM" && failure.syscall === "fsync" && fstatSync(descriptor).isDirectory()) {
|
|
34
|
-
return "unsupported-directory-barrier";
|
|
35
|
-
}
|
|
36
|
-
throw error;
|
|
37
|
-
}
|
|
38
|
-
} finally {
|
|
39
|
-
closeSync(descriptor);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/artifact-store.ts
|
|
44
|
-
var SHA256 = /^[a-f0-9]{64}$/u;
|
|
45
|
-
function digest(bytes) {
|
|
46
|
-
return createHash("sha256").update(bytes).digest("hex");
|
|
47
|
-
}
|
|
48
|
-
function requireHash(hash) {
|
|
49
|
-
if (!SHA256.test(hash))
|
|
50
|
-
throw new Error("artifact-hash-invalid");
|
|
51
|
-
}
|
|
52
|
-
function ensureDirectory(directory) {
|
|
53
|
-
const stat = lstatSync(directory, { throwIfNoEntry: false });
|
|
54
|
-
if (stat) {
|
|
55
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
56
|
-
throw new Error("artifact-store-path-unsafe");
|
|
57
|
-
}
|
|
58
|
-
chmodSync(directory, 448);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
let cursor = path.dirname(directory);
|
|
62
|
-
while (cursor !== path.dirname(cursor)) {
|
|
63
|
-
const ancestor = lstatSync(cursor, { throwIfNoEntry: false });
|
|
64
|
-
if (ancestor) {
|
|
65
|
-
if (ancestor.isSymbolicLink() || !ancestor.isDirectory()) {
|
|
66
|
-
throw new Error("artifact-store-path-unsafe");
|
|
67
|
-
}
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
cursor = path.dirname(cursor);
|
|
71
|
-
}
|
|
72
|
-
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
73
|
-
const created = lstatSync(directory);
|
|
74
|
-
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
75
|
-
throw new Error("artifact-store-path-unsafe");
|
|
76
|
-
}
|
|
77
|
-
chmodSync(directory, 448);
|
|
78
|
-
}
|
|
79
|
-
function writeAtomic(target, bytes) {
|
|
80
|
-
const directory = path.dirname(target);
|
|
81
|
-
ensureDirectory(directory);
|
|
82
|
-
const temporary = path.join(directory, `.${path.basename(target)}.${process.pid}.${randomUUID()}.tmp`);
|
|
83
|
-
const descriptor = openSync2(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
|
|
84
|
-
try {
|
|
85
|
-
writeFileSync(descriptor, bytes);
|
|
86
|
-
fsyncSync2(descriptor);
|
|
87
|
-
} finally {
|
|
88
|
-
closeSync2(descriptor);
|
|
89
|
-
}
|
|
90
|
-
try {
|
|
91
|
-
renameSync(temporary, target);
|
|
92
|
-
chmodSync(target, 384);
|
|
93
|
-
syncDirectory(directory);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
try {
|
|
96
|
-
unlinkSync(temporary);
|
|
97
|
-
} catch {}
|
|
98
|
-
throw error;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
class ArtifactStore {
|
|
103
|
-
root;
|
|
104
|
-
#blobs;
|
|
105
|
-
#references;
|
|
106
|
-
constructor(root) {
|
|
107
|
-
if (!path.isAbsolute(root))
|
|
108
|
-
throw new Error("artifact-store-root-invalid");
|
|
109
|
-
this.root = path.resolve(root);
|
|
110
|
-
this.#blobs = path.join(this.root, "blobs");
|
|
111
|
-
this.#references = path.join(this.root, "references");
|
|
112
|
-
ensureDirectory(this.root);
|
|
113
|
-
ensureDirectory(this.#blobs);
|
|
114
|
-
ensureDirectory(this.#references);
|
|
115
|
-
}
|
|
116
|
-
#blobPath(hash) {
|
|
117
|
-
requireHash(hash);
|
|
118
|
-
return path.join(this.#blobs, hash.slice(0, 2), hash);
|
|
119
|
-
}
|
|
120
|
-
#referencePath(hash) {
|
|
121
|
-
requireHash(hash);
|
|
122
|
-
return path.join(this.#references, hash.slice(0, 2), `${hash}.ref`);
|
|
123
|
-
}
|
|
124
|
-
put(input) {
|
|
125
|
-
if (!(input instanceof Uint8Array))
|
|
126
|
-
throw new Error("artifact-bytes-invalid");
|
|
127
|
-
const bytes = Buffer.from(input);
|
|
128
|
-
const hash = digest(bytes);
|
|
129
|
-
const target = this.#blobPath(hash);
|
|
130
|
-
const existing = lstatSync(target, { throwIfNoEntry: false });
|
|
131
|
-
if (existing) {
|
|
132
|
-
if (existing.isSymbolicLink() || !existing.isFile()) {
|
|
133
|
-
throw new Error("artifact-store-path-unsafe");
|
|
134
|
-
}
|
|
135
|
-
const current = readFileSync(target);
|
|
136
|
-
if (current.length !== bytes.length || digest(current) !== hash) {
|
|
137
|
-
throw new Error("artifact-integrity-invalid");
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
writeAtomic(target, bytes);
|
|
141
|
-
const stored = readFileSync(target);
|
|
142
|
-
if (stored.length !== bytes.length || digest(stored) !== hash) {
|
|
143
|
-
throw new Error("artifact-integrity-invalid");
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return Object.freeze({ hash, bytes: bytes.length });
|
|
147
|
-
}
|
|
148
|
-
read(hash) {
|
|
149
|
-
const target = this.#blobPath(hash);
|
|
150
|
-
const stat = lstatSync(target, { throwIfNoEntry: false });
|
|
151
|
-
if (!stat?.isFile() || stat.isSymbolicLink()) {
|
|
152
|
-
throw new Error("artifact-unavailable");
|
|
153
|
-
}
|
|
154
|
-
const bytes = readFileSync(target);
|
|
155
|
-
if (digest(bytes) !== hash)
|
|
156
|
-
throw new Error("artifact-integrity-invalid");
|
|
157
|
-
return Buffer.from(bytes);
|
|
158
|
-
}
|
|
159
|
-
copyVerified(hash, destination) {
|
|
160
|
-
const source = this.#blobPath(hash);
|
|
161
|
-
const stat = lstatSync(source, { throwIfNoEntry: false });
|
|
162
|
-
if (!stat?.isFile() || stat.isSymbolicLink())
|
|
163
|
-
throw new Error("artifact-unavailable");
|
|
164
|
-
copyFileSync(source, destination, constants2.COPYFILE_EXCL | constants2.COPYFILE_FICLONE);
|
|
165
|
-
const bytes = readFileSync(destination);
|
|
166
|
-
if (digest(bytes) !== hash)
|
|
167
|
-
throw new Error("artifact-integrity-invalid");
|
|
168
|
-
return bytes.byteLength;
|
|
169
|
-
}
|
|
170
|
-
referenceCount(hash) {
|
|
171
|
-
requireHash(hash);
|
|
172
|
-
const referencePath = this.#referencePath(hash);
|
|
173
|
-
const stat = lstatSync(referencePath, { throwIfNoEntry: false });
|
|
174
|
-
if (!stat)
|
|
175
|
-
return 0;
|
|
176
|
-
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
177
|
-
throw new Error("artifact-reference-invalid");
|
|
178
|
-
}
|
|
179
|
-
const raw = readFileSync(referencePath, "utf8");
|
|
180
|
-
if (!/^(?:0|[1-9][0-9]*)\n$/u.test(raw)) {
|
|
181
|
-
throw new Error("artifact-reference-invalid");
|
|
182
|
-
}
|
|
183
|
-
const count = Number(raw.trim());
|
|
184
|
-
if (!Number.isSafeInteger(count))
|
|
185
|
-
throw new Error("artifact-reference-invalid");
|
|
186
|
-
return count;
|
|
187
|
-
}
|
|
188
|
-
#setReferenceCount(hash, count) {
|
|
189
|
-
if (!Number.isSafeInteger(count) || count < 0) {
|
|
190
|
-
throw new Error("artifact-reference-invalid");
|
|
191
|
-
}
|
|
192
|
-
writeAtomic(this.#referencePath(hash), Buffer.from(`${count}
|
|
193
|
-
`));
|
|
194
|
-
return count;
|
|
195
|
-
}
|
|
196
|
-
retain(hash) {
|
|
197
|
-
this.read(hash);
|
|
198
|
-
const current = this.referenceCount(hash);
|
|
199
|
-
if (current === Number.MAX_SAFE_INTEGER) {
|
|
200
|
-
throw new Error("artifact-reference-overflow");
|
|
201
|
-
}
|
|
202
|
-
return this.#setReferenceCount(hash, current + 1);
|
|
203
|
-
}
|
|
204
|
-
release(hash) {
|
|
205
|
-
this.read(hash);
|
|
206
|
-
const current = this.referenceCount(hash);
|
|
207
|
-
if (current === 0)
|
|
208
|
-
throw new Error("artifact-reference-underflow");
|
|
209
|
-
return this.#setReferenceCount(hash, current - 1);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// src/remove-path.ts
|
|
214
|
-
import { lstatSync as lstatSync2, readdirSync, rmdirSync, rmSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
215
|
-
import path2 from "node:path";
|
|
216
|
-
function removePathSync(target, options = {}) {
|
|
217
|
-
if (process.platform !== "win32") {
|
|
218
|
-
rmSync(target, options);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
let stat;
|
|
222
|
-
try {
|
|
223
|
-
stat = lstatSync2(target);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
if (options.force && error.code === "ENOENT")
|
|
226
|
-
return;
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
229
|
-
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
230
|
-
if (!options.recursive)
|
|
231
|
-
throw Object.assign(new Error("Path is a directory"), {
|
|
232
|
-
code: "ERR_FS_EISDIR"
|
|
233
|
-
});
|
|
234
|
-
for (const name of readdirSync(target))
|
|
235
|
-
removePathSync(path2.join(target, name), options);
|
|
236
|
-
rmdirSync(target);
|
|
237
|
-
} else
|
|
238
|
-
unlinkSync2(target);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// src/verification-environment-io.ts
|
|
242
|
-
import {
|
|
243
|
-
constants as constants3,
|
|
244
|
-
cpSync,
|
|
245
|
-
lstatSync as lstatSync3,
|
|
246
|
-
mkdirSync as mkdirSync2,
|
|
247
|
-
readdirSync as readdirSync2,
|
|
248
|
-
readlinkSync,
|
|
249
|
-
realpathSync,
|
|
250
|
-
symlinkSync,
|
|
251
|
-
writeFileSync as writeFileSync2
|
|
252
|
-
} from "node:fs";
|
|
253
|
-
import path3 from "node:path";
|
|
254
|
-
var CACHES = [".vite", ".vite-temp"];
|
|
255
|
-
function within(root, candidate) {
|
|
256
|
-
const relative = path3.relative(root, candidate);
|
|
257
|
-
return relative !== ".." && !relative.startsWith(`..${path3.sep}`) && !path3.isAbsolute(relative);
|
|
258
|
-
}
|
|
259
|
-
function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings, profile, environment, checkCancelled, privateRoot) {
|
|
260
|
-
const cleanup = () => removePathSync(privateRoot, { recursive: true, force: true });
|
|
261
|
-
try {
|
|
262
|
-
const local = profile.mode !== "isolated";
|
|
263
|
-
const native = profile.mode === "host-trusted";
|
|
264
|
-
if (local && realpathSync(root) === realpathSync(dependencyOwner))
|
|
265
|
-
throw new Error("trusted-candidate-required");
|
|
266
|
-
const mounts = [
|
|
267
|
-
{ source: privateRoot, target: "/cadence", writable: true }
|
|
268
|
-
];
|
|
269
|
-
for (const directory of [
|
|
270
|
-
"home",
|
|
271
|
-
"cache",
|
|
272
|
-
"tmp",
|
|
273
|
-
"reports",
|
|
274
|
-
"dependencies"
|
|
275
|
-
]) {
|
|
276
|
-
mkdirSync2(path3.join(privateRoot, directory), { mode: 448 });
|
|
277
|
-
}
|
|
278
|
-
if (native) {
|
|
279
|
-
mkdirSync2(path3.join(privateRoot, "home/AppData/Roaming"), {
|
|
280
|
-
recursive: true
|
|
281
|
-
});
|
|
282
|
-
mkdirSync2(path3.join(privateRoot, "home/AppData/Local"), {
|
|
283
|
-
recursive: true
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
writeFileSync2(path3.join(privateRoot, "home/user.npmrc"), "");
|
|
287
|
-
writeFileSync2(path3.join(privateRoot, "home/global.npmrc"), "");
|
|
288
|
-
const dependencies = path3.join(dependencyOwner, "node_modules");
|
|
289
|
-
const stat = lstatSync3(dependencies, { throwIfNoEntry: false });
|
|
290
|
-
if (stat && !native) {
|
|
291
|
-
const target = path3.join(root, "node_modules");
|
|
292
|
-
const targetStat = lstatSync3(target, { throwIfNoEntry: false });
|
|
293
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || targetStat && (!targetStat.isDirectory() || targetStat.isSymbolicLink())) {
|
|
294
|
-
throw new Error("dependency-path-unsafe");
|
|
295
|
-
}
|
|
296
|
-
if (!targetStat)
|
|
297
|
-
mkdirSync2(target, { mode: 448 });
|
|
298
|
-
const view = path3.join(privateRoot, "dependencies");
|
|
299
|
-
mounts.push({ source: view, target: "/workspace/node_modules" });
|
|
300
|
-
const entries = readdirSync2(dependencies);
|
|
301
|
-
if (entries.length > 20000)
|
|
302
|
-
throw new Error("dependency-view-too-large");
|
|
303
|
-
const canonical = realpathSync(dependencies);
|
|
304
|
-
let viewEntries = 0;
|
|
305
|
-
const projectEntry = (name) => {
|
|
306
|
-
checkCancelled();
|
|
307
|
-
if (++viewEntries > 20000)
|
|
308
|
-
throw new Error("dependency-view-too-large");
|
|
309
|
-
if (CACHES.includes(name))
|
|
310
|
-
return;
|
|
311
|
-
const source = path3.join(dependencies, name);
|
|
312
|
-
const placeholder = path3.join(view, name);
|
|
313
|
-
const entry = lstatSync3(source);
|
|
314
|
-
if (entry.isSymbolicLink()) {
|
|
315
|
-
const resolved = realpathSync(source);
|
|
316
|
-
if (within(canonical, resolved)) {
|
|
317
|
-
symlinkSync(path3.relative(path3.dirname(source), resolved), placeholder);
|
|
318
|
-
} else if (within(realpathSync(dependencyOwner), resolved)) {
|
|
319
|
-
const relative = path3.relative(realpathSync(dependencyOwner), resolved);
|
|
320
|
-
const candidate = path3.join(root, relative);
|
|
321
|
-
if (!lstatSync3(candidate, { throwIfNoEntry: false }))
|
|
322
|
-
throw new Error("workspace-dependency-missing");
|
|
323
|
-
symlinkSync(local ? candidate : `/workspace/${relative.split(path3.sep).join("/")}`, placeholder);
|
|
324
|
-
} else
|
|
325
|
-
throw new Error("dependency-path-unsafe");
|
|
326
|
-
} else if (entry.isDirectory() && name.startsWith("@") && !name.includes(path3.sep)) {
|
|
327
|
-
mkdirSync2(placeholder);
|
|
328
|
-
for (const child of readdirSync2(source))
|
|
329
|
-
projectEntry(path3.join(name, child));
|
|
330
|
-
} else {
|
|
331
|
-
if (entry.isDirectory())
|
|
332
|
-
mkdirSync2(placeholder);
|
|
333
|
-
else if (entry.isFile())
|
|
334
|
-
writeFileSync2(placeholder, "");
|
|
335
|
-
else
|
|
336
|
-
throw new Error("dependency-path-unsafe");
|
|
337
|
-
mounts.push({
|
|
338
|
-
source,
|
|
339
|
-
target: `/workspace/node_modules/${name.split(path3.sep).join("/")}`
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
for (const name of entries)
|
|
344
|
-
projectEntry(name);
|
|
345
|
-
for (const cache of CACHES) {
|
|
346
|
-
mkdirSync2(path3.join(view, cache));
|
|
347
|
-
const source = path3.join(privateRoot, "cache", cache);
|
|
348
|
-
mkdirSync2(source);
|
|
349
|
-
mounts.push({
|
|
350
|
-
source,
|
|
351
|
-
target: `/workspace/node_modules/${cache}`,
|
|
352
|
-
writable: true
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (local && stat && !native) {
|
|
357
|
-
const copyEntry = (name) => {
|
|
358
|
-
checkCancelled();
|
|
359
|
-
const source = path3.join(dependencies, name);
|
|
360
|
-
const destination = path3.join(root, "node_modules", name);
|
|
361
|
-
if (CACHES.includes(name)) {
|
|
362
|
-
mkdirSync2(destination, { recursive: true });
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
removeEntry(destination, checkCancelled);
|
|
366
|
-
const stat2 = lstatSync3(source);
|
|
367
|
-
if (stat2.isSymbolicLink()) {
|
|
368
|
-
const resolved = realpathSync(source);
|
|
369
|
-
const translated = within(canonicalRoot(dependencyOwner), resolved) ? path3.join(root, path3.relative(canonicalRoot(dependencyOwner), resolved)) : resolved;
|
|
370
|
-
symlinkSync(translated, destination);
|
|
371
|
-
} else if (stat2.isDirectory() && name.startsWith("@") && !name.includes(path3.sep)) {
|
|
372
|
-
mkdirSync2(destination);
|
|
373
|
-
for (const child of readdirSync2(source))
|
|
374
|
-
copyEntry(path3.join(name, child));
|
|
375
|
-
} else
|
|
376
|
-
cpSync(source, destination, {
|
|
377
|
-
recursive: true,
|
|
378
|
-
filter: () => {
|
|
379
|
-
checkCancelled();
|
|
380
|
-
return true;
|
|
381
|
-
},
|
|
382
|
-
mode: constants3.COPYFILE_FICLONE,
|
|
383
|
-
verbatimSymlinks: true
|
|
384
|
-
});
|
|
385
|
-
};
|
|
386
|
-
for (const name of readdirSync2(path3.join(privateRoot, "dependencies")))
|
|
387
|
-
copyEntry(name);
|
|
388
|
-
}
|
|
389
|
-
if (native && stat) {
|
|
390
|
-
const target = path3.join(root, "node_modules");
|
|
391
|
-
const targetStat = lstatSync3(target, { throwIfNoEntry: false });
|
|
392
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || targetStat && (!targetStat.isDirectory() || targetStat.isSymbolicLink()))
|
|
393
|
-
throw new Error("dependency-path-unsafe");
|
|
394
|
-
const owner = realpathSync(dependencyOwner);
|
|
395
|
-
let count = 0;
|
|
396
|
-
const copy = (source, destination) => {
|
|
397
|
-
checkCancelled();
|
|
398
|
-
if (++count > 500000)
|
|
399
|
-
throw new Error("dependency-view-too-large");
|
|
400
|
-
const entry = lstatSync3(source);
|
|
401
|
-
if (entry.isSymbolicLink()) {
|
|
402
|
-
const resolved = realpathSync(source);
|
|
403
|
-
if (!within(owner, resolved))
|
|
404
|
-
throw new Error("dependency-path-unsafe");
|
|
405
|
-
const candidate = path3.join(root, path3.relative(owner, resolved));
|
|
406
|
-
if (!within(realpathSync(dependencies), resolved) && !lstatSync3(candidate, { throwIfNoEntry: false }))
|
|
407
|
-
throw new Error("workspace-dependency-missing");
|
|
408
|
-
const linked = lstatSync3(resolved);
|
|
409
|
-
if (linked.isDirectory()) {
|
|
410
|
-
symlinkSync(candidate, destination, process.platform === "win32" ? "junction" : "dir");
|
|
411
|
-
} else if (linked.isFile()) {
|
|
412
|
-
const file = within(realpathSync(dependencies), resolved) ? resolved : candidate;
|
|
413
|
-
cpSync(file, destination, { mode: constants3.COPYFILE_FICLONE });
|
|
414
|
-
} else
|
|
415
|
-
throw new Error("dependency-path-unsafe");
|
|
416
|
-
} else if (entry.isDirectory()) {
|
|
417
|
-
mkdirSync2(destination, { recursive: true });
|
|
418
|
-
for (const name of readdirSync2(source)) {
|
|
419
|
-
if (source === dependencies && CACHES.includes(name)) {
|
|
420
|
-
mkdirSync2(path3.join(destination, name));
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
copy(path3.join(source, name), path3.join(destination, name));
|
|
424
|
-
}
|
|
425
|
-
} else if (entry.isFile())
|
|
426
|
-
cpSync(source, destination, { mode: constants3.COPYFILE_FICLONE });
|
|
427
|
-
else
|
|
428
|
-
throw new Error("dependency-path-unsafe");
|
|
429
|
-
};
|
|
430
|
-
removeEntry(target, checkCancelled);
|
|
431
|
-
copy(dependencies, target);
|
|
432
|
-
}
|
|
433
|
-
const sources = new Map;
|
|
434
|
-
const bindings = runnerBindings.map((binding) => {
|
|
435
|
-
if (native) {
|
|
436
|
-
const translate2 = (value) => path3.isAbsolute(value) && within(dependencyOwner, value) ? path3.join(root, path3.relative(dependencyOwner, value)) : value;
|
|
437
|
-
return {
|
|
438
|
-
...binding,
|
|
439
|
-
executablePath: translate2(binding.executablePath),
|
|
440
|
-
...binding.fixedArgs ? { fixedArgs: binding.fixedArgs.map(translate2) } : {}
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
if (local || !binding.mountSource)
|
|
444
|
-
return { ...binding };
|
|
445
|
-
const source = path3.resolve(binding.mountSource);
|
|
446
|
-
const stat2 = lstatSync3(source);
|
|
447
|
-
const executable = realpathSync(binding.executablePath);
|
|
448
|
-
if (!stat2.isDirectory() || stat2.isSymbolicLink() || !within(source, executable)) {
|
|
449
|
-
throw new Error("runner-mount-unavailable");
|
|
450
|
-
}
|
|
451
|
-
let target = sources.get(source);
|
|
452
|
-
if (!target) {
|
|
453
|
-
target = `/cadence-runners/${sources.size}`;
|
|
454
|
-
sources.set(source, target);
|
|
455
|
-
mounts.push({ source, target });
|
|
456
|
-
}
|
|
457
|
-
const translate = (file) => path3.posix.join(target, ...path3.relative(source, file).split(path3.sep));
|
|
458
|
-
return {
|
|
459
|
-
...binding,
|
|
460
|
-
executablePath: translate(lstatSync3(binding.executablePath).isSymbolicLink() && !path3.isAbsolute(readlinkSync(binding.executablePath)) ? binding.executablePath : executable),
|
|
461
|
-
...binding.fixedArgs ? {
|
|
462
|
-
fixedArgs: binding.fixedArgs.map((arg) => path3.isAbsolute(arg) && within(source, arg) ? translate(arg) : arg)
|
|
463
|
-
} : {}
|
|
464
|
-
};
|
|
465
|
-
});
|
|
466
|
-
checkCancelled();
|
|
467
|
-
return {
|
|
468
|
-
privateRoot,
|
|
469
|
-
mounts: native ? [] : mounts,
|
|
470
|
-
bindings,
|
|
471
|
-
reportRoot: local ? privateRoot : "/cadence",
|
|
472
|
-
environment: {
|
|
473
|
-
...local ? Object.fromEntries(profile.inheritEnvironment.flatMap((name) => environment[name] === undefined ? [] : [[name, environment[name] ?? ""]])) : {},
|
|
474
|
-
...native ? Object.fromEntries(Object.entries(environment).filter(([key]) => ["SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT"].includes(key.toUpperCase()))) : {},
|
|
475
|
-
...native ? {
|
|
476
|
-
USERPROFILE: path3.join(privateRoot, "home"),
|
|
477
|
-
APPDATA: path3.join(privateRoot, "home", "AppData", "Roaming"),
|
|
478
|
-
LOCALAPPDATA: path3.join(privateRoot, "home", "AppData", "Local"),
|
|
479
|
-
TEMP: path3.join(privateRoot, "tmp"),
|
|
480
|
-
TMP: path3.join(privateRoot, "tmp")
|
|
481
|
-
} : {},
|
|
482
|
-
CI: "1",
|
|
483
|
-
HOME: local ? path3.join(privateRoot, "home") : "/cadence/home",
|
|
484
|
-
XDG_CACHE_HOME: local ? path3.join(privateRoot, "cache") : "/cadence/cache",
|
|
485
|
-
TMPDIR: local ? path3.join(privateRoot, "tmp") : "/cadence/tmp",
|
|
486
|
-
npm_config_cache: local ? path3.join(privateRoot, "cache/npm") : "/cadence/cache/npm",
|
|
487
|
-
npm_config_userconfig: local ? path3.join(privateRoot, "home/user.npmrc") : "/cadence/home/user.npmrc",
|
|
488
|
-
npm_config_globalconfig: local ? path3.join(privateRoot, "home/global.npmrc") : "/cadence/home/global.npmrc",
|
|
489
|
-
PATH: [
|
|
490
|
-
...new Set([
|
|
491
|
-
...bindings.filter((binding) => binding.mountSource),
|
|
492
|
-
...bindings.filter((binding) => !binding.mountSource)
|
|
493
|
-
].map((binding) => (local ? path3 : path3.posix).dirname(binding.executablePath))),
|
|
494
|
-
...local ? (environment.PATH ?? "").split(path3.delimiter) : [],
|
|
495
|
-
...native ? [] : ["/usr/local/bin", "/usr/bin", "/bin"]
|
|
496
|
-
].join(local ? path3.delimiter : ":")
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
} catch (error) {
|
|
500
|
-
cleanup();
|
|
501
|
-
throw error;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
function canonicalRoot(root) {
|
|
505
|
-
return realpathSync(root);
|
|
506
|
-
}
|
|
507
|
-
function removeEntry(file, checkCancelled) {
|
|
508
|
-
checkCancelled();
|
|
509
|
-
const stat = lstatSync3(file, { throwIfNoEntry: false });
|
|
510
|
-
if (!stat)
|
|
511
|
-
return;
|
|
512
|
-
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
513
|
-
for (const name of readdirSync2(file))
|
|
514
|
-
removeEntry(path3.join(file, name), checkCancelled);
|
|
515
|
-
removePathSync(file, {
|
|
516
|
-
recursive: stat.isDirectory() && !stat.isSymbolicLink(),
|
|
517
|
-
force: true
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// src/verification-identity.ts
|
|
522
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
523
|
-
import {
|
|
524
|
-
closeSync as closeSync3,
|
|
525
|
-
constants as constants4,
|
|
526
|
-
fstatSync as fstatSync2,
|
|
527
|
-
lstatSync as lstatSync4,
|
|
528
|
-
openSync as openSync3,
|
|
529
|
-
readdirSync as readdirSync3,
|
|
530
|
-
readlinkSync as readlinkSync2,
|
|
531
|
-
readSync
|
|
532
|
-
} from "node:fs";
|
|
533
|
-
import path4 from "node:path";
|
|
534
|
-
function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
535
|
-
const digest2 = createHash2("sha256");
|
|
536
|
-
let entries = 0;
|
|
537
|
-
let bytes = 0;
|
|
538
|
-
const started = Date.now();
|
|
539
|
-
const buffer = Buffer.alloc(256 * 1024);
|
|
540
|
-
const visit = (file, relative, dependencyRoot) => {
|
|
541
|
-
checkCancelled();
|
|
542
|
-
if (++entries > 500000 || Date.now() - started > 60000)
|
|
543
|
-
throw new Error("verification-environment-limit");
|
|
544
|
-
const stat = lstatSync4(file, { throwIfNoEntry: false });
|
|
545
|
-
digest2.update(JSON.stringify([relative, stat ? stat.mode & 511 : null]));
|
|
546
|
-
if (!stat) {
|
|
547
|
-
digest2.update("absent");
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
if (stat.isSymbolicLink()) {
|
|
551
|
-
digest2.update(JSON.stringify(["link", readlinkSync2(file)]));
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
if (stat.isDirectory()) {
|
|
555
|
-
digest2.update("directory");
|
|
556
|
-
for (const name of readdirSync3(file).sort()) {
|
|
557
|
-
if (dependencyRoot && [".vite", ".vite-temp"].includes(name))
|
|
558
|
-
continue;
|
|
559
|
-
visit(path4.join(file, name), `${relative}/${name}`, false);
|
|
560
|
-
}
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
if (!stat.isFile())
|
|
564
|
-
throw new Error("verification-environment-unsafe");
|
|
565
|
-
const fd = openSync3(file, constants4.O_RDONLY | constants4.O_NOFOLLOW);
|
|
566
|
-
try {
|
|
567
|
-
const before = fstatSync2(fd);
|
|
568
|
-
if (before.ino !== stat.ino || before.dev !== stat.dev)
|
|
569
|
-
throw new Error("verification-environment-changed");
|
|
570
|
-
const content = createHash2("sha256");
|
|
571
|
-
for (;; ) {
|
|
572
|
-
const count = readSync(fd, buffer, 0, buffer.length, null);
|
|
573
|
-
if (count === 0)
|
|
574
|
-
break;
|
|
575
|
-
checkCancelled();
|
|
576
|
-
bytes += count;
|
|
577
|
-
if (bytes > 16 * 1024 ** 3 || Date.now() - started > 60000)
|
|
578
|
-
throw new Error("verification-environment-limit");
|
|
579
|
-
content.update(buffer.subarray(0, count));
|
|
580
|
-
}
|
|
581
|
-
const after = fstatSync2(fd);
|
|
582
|
-
if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs)
|
|
583
|
-
throw new Error("verification-environment-changed");
|
|
584
|
-
digest2.update(JSON.stringify(["file", content.digest("hex")]));
|
|
585
|
-
} finally {
|
|
586
|
-
closeSync3(fd);
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
for (const root of [...new Set(roots)].sort())
|
|
590
|
-
visit(root, root, path4.basename(root) === "node_modules");
|
|
591
|
-
return digest2.digest("hex");
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// src/workspace-store.ts
|
|
595
|
-
import { execFile, execFileSync } from "node:child_process";
|
|
596
|
-
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
597
|
-
import {
|
|
598
|
-
chmodSync as chmodSync2,
|
|
599
|
-
closeSync as closeSync4,
|
|
600
|
-
constants as constants5,
|
|
601
|
-
fsyncSync as fsyncSync3,
|
|
602
|
-
lstatSync as lstatSync6,
|
|
603
|
-
mkdirSync as mkdirSync3,
|
|
604
|
-
openSync as openSync4,
|
|
605
|
-
readFileSync as readFileSync2,
|
|
606
|
-
renameSync as renameSync2,
|
|
607
|
-
unlinkSync as unlinkSync3,
|
|
608
|
-
writeFileSync as writeFileSync3
|
|
609
|
-
} from "node:fs";
|
|
610
|
-
import path6 from "node:path";
|
|
611
|
-
import { promisify } from "node:util";
|
|
612
|
-
|
|
613
|
-
// src/canonical.ts
|
|
614
|
-
function compareCanonicalStrings(left, right) {
|
|
615
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// src/control-contracts.ts
|
|
619
|
-
var CONTROL_SCHEMA_PROPERTIES = {
|
|
620
|
-
command: {
|
|
621
|
-
type: "string",
|
|
622
|
-
enum: ["start", "status", "resume", "rebind", "cancel", "discard"]
|
|
623
|
-
},
|
|
624
|
-
stage: { type: "string", enum: ["abel-implement"] },
|
|
625
|
-
change: { type: "string" },
|
|
626
|
-
operationId: { type: "string" },
|
|
627
|
-
deliveryRevision: { type: "integer", minimum: 1 },
|
|
628
|
-
receiptHash: { type: "string" },
|
|
629
|
-
routeId: { type: "string" },
|
|
630
|
-
recovery: {
|
|
631
|
-
type: "object",
|
|
632
|
-
additionalProperties: false,
|
|
633
|
-
properties: {
|
|
634
|
-
incidentKey: { type: "string", pattern: "^[a-f0-9]{64}$" },
|
|
635
|
-
failureSequence: { type: "integer", minimum: 1 },
|
|
636
|
-
reason: {
|
|
637
|
-
type: "string",
|
|
638
|
-
enum: ["parent-directed-retry", "route-changed", "context-extended"]
|
|
639
|
-
}
|
|
640
|
-
},
|
|
641
|
-
required: ["incidentKey", "failureSequence", "reason"]
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
var commandSchema = (command, fields) => ({
|
|
645
|
-
type: "object",
|
|
646
|
-
properties: {
|
|
647
|
-
command: { type: "string", enum: [command] },
|
|
648
|
-
...Object.fromEntries(fields.map((field) => [field, CONTROL_SCHEMA_PROPERTIES[field]]))
|
|
649
|
-
},
|
|
650
|
-
required: ["command", ...fields],
|
|
651
|
-
additionalProperties: false
|
|
652
|
-
});
|
|
653
|
-
var CONTROL_COMMAND_PARAMETERS = {
|
|
654
|
-
type: "object",
|
|
655
|
-
properties: CONTROL_SCHEMA_PROPERTIES,
|
|
656
|
-
required: ["command", "stage", "change"],
|
|
657
|
-
anyOf: [
|
|
658
|
-
commandSchema("start", ["stage", "change", "operationId"]),
|
|
659
|
-
commandSchema("status", ["stage", "change"]),
|
|
660
|
-
commandSchema("resume", ["stage", "change", "operationId"]),
|
|
661
|
-
commandSchema("resume", ["stage", "change", "operationId", "recovery"]),
|
|
662
|
-
commandSchema("resume", [
|
|
663
|
-
"stage",
|
|
664
|
-
"change",
|
|
665
|
-
"operationId",
|
|
666
|
-
"deliveryRevision",
|
|
667
|
-
"receiptHash",
|
|
668
|
-
"recovery"
|
|
669
|
-
]),
|
|
670
|
-
commandSchema("resume", [
|
|
671
|
-
"stage",
|
|
672
|
-
"change",
|
|
673
|
-
"operationId",
|
|
674
|
-
"deliveryRevision",
|
|
675
|
-
"receiptHash"
|
|
676
|
-
]),
|
|
677
|
-
commandSchema("rebind", ["stage", "change", "operationId", "routeId"]),
|
|
678
|
-
commandSchema("cancel", ["stage", "change", "operationId"]),
|
|
679
|
-
commandSchema("discard", ["stage", "change", "operationId"])
|
|
680
|
-
],
|
|
681
|
-
additionalProperties: false
|
|
682
|
-
};
|
|
683
|
-
var CONTROL_TOOL_FIELDS = new Set([
|
|
684
|
-
"command",
|
|
685
|
-
"stage",
|
|
686
|
-
"change",
|
|
687
|
-
"operationId",
|
|
688
|
-
"deliveryRevision",
|
|
689
|
-
"receiptHash",
|
|
690
|
-
"routeId",
|
|
691
|
-
"recovery"
|
|
692
|
-
]);
|
|
693
|
-
var COMMAND_FIELDS = Object.freeze({
|
|
694
|
-
start: new Set(["command", "stage", "change", "operationId"]),
|
|
695
|
-
status: new Set(["command", "stage", "change"]),
|
|
696
|
-
resume: new Set([
|
|
697
|
-
"command",
|
|
698
|
-
"stage",
|
|
699
|
-
"change",
|
|
700
|
-
"operationId",
|
|
701
|
-
"deliveryRevision",
|
|
702
|
-
"receiptHash",
|
|
703
|
-
"recovery"
|
|
704
|
-
]),
|
|
705
|
-
rebind: new Set(["command", "stage", "change", "operationId", "routeId"]),
|
|
706
|
-
cancel: new Set(["command", "stage", "change", "operationId"]),
|
|
707
|
-
discard: new Set(["command", "stage", "change", "operationId"])
|
|
708
|
-
});
|
|
709
|
-
// src/contracts.ts
|
|
710
|
-
var LIMITS = {
|
|
711
|
-
maxActiveChildSessions: 4,
|
|
712
|
-
maxEnvelopeBytes: 64 * 1024,
|
|
713
|
-
phaseTimeoutMs: 20 * 60 * 1000,
|
|
714
|
-
maxCompleteResultBytes: 64 * 1024
|
|
715
|
-
};
|
|
716
|
-
var VERIFICATION_LIMITS = Object.freeze({
|
|
717
|
-
minSteps: 1,
|
|
718
|
-
maxSteps: 8,
|
|
719
|
-
minTestFiles: 1,
|
|
720
|
-
maxTestFiles: 64
|
|
721
|
-
});
|
|
722
|
-
var NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
|
|
723
|
-
var RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
|
|
724
|
-
function isValidRelativePath(p) {
|
|
725
|
-
return typeof p === "string" && p.length > 0 && p.length <= 512 && !NONCANONICAL.test(p) && !p.startsWith("/") && !/^[a-z]:\//iu.test(p) && (p === "." || !p.startsWith("./") && !p.endsWith("/") && !p.split("/").includes(".")) && !p.includes("..") && !p.includes("\\") && !p.includes("\x00");
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// src/safe-path.ts
|
|
729
|
-
import { lstatSync as lstatSync5 } from "node:fs";
|
|
730
|
-
import path5 from "node:path";
|
|
731
|
-
function observeSafePath(root, relative) {
|
|
732
|
-
if (!isValidRelativePath(relative)) {
|
|
733
|
-
return { kind: "unsafe", reason: "invalid-path" };
|
|
734
|
-
}
|
|
735
|
-
try {
|
|
736
|
-
const resolvedRoot = path5.resolve(root);
|
|
737
|
-
const rootStat = lstatSync5(resolvedRoot, { throwIfNoEntry: false });
|
|
738
|
-
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
739
|
-
return { kind: "unsafe", reason: "root-unavailable" };
|
|
740
|
-
}
|
|
741
|
-
if (relative === ".")
|
|
742
|
-
return { kind: "directory" };
|
|
743
|
-
let current = resolvedRoot;
|
|
744
|
-
const parts = relative.split("/");
|
|
745
|
-
for (const [index, part] of parts.entries()) {
|
|
746
|
-
current = path5.join(current, part);
|
|
747
|
-
if (!current.startsWith(`${resolvedRoot}${path5.sep}`)) {
|
|
748
|
-
return { kind: "unsafe", reason: "invalid-path" };
|
|
749
|
-
}
|
|
750
|
-
const stat = lstatSync5(current, { throwIfNoEntry: false });
|
|
751
|
-
if (!stat)
|
|
752
|
-
return { kind: "absent" };
|
|
753
|
-
if (stat.isSymbolicLink()) {
|
|
754
|
-
return { kind: "unsafe", reason: "symlink" };
|
|
755
|
-
}
|
|
756
|
-
if (index < parts.length - 1) {
|
|
757
|
-
if (!stat.isDirectory()) {
|
|
758
|
-
return { kind: "unsafe", reason: "parent-not-directory" };
|
|
759
|
-
}
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
if (stat.isFile())
|
|
763
|
-
return { kind: "file" };
|
|
764
|
-
if (stat.isDirectory())
|
|
765
|
-
return { kind: "directory" };
|
|
766
|
-
return { kind: "unsafe", reason: "special-file" };
|
|
767
|
-
}
|
|
768
|
-
} catch {
|
|
769
|
-
return { kind: "unsafe", reason: "path-unavailable" };
|
|
770
|
-
}
|
|
771
|
-
return { kind: "unsafe", reason: "invalid-path" };
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// src/workspace-io.ts
|
|
775
|
-
import { Worker } from "node:worker_threads";
|
|
776
|
-
async function runWorkspaceIo(input) {
|
|
777
|
-
input.signal?.throwIfAborted();
|
|
778
|
-
const cancelled = new SharedArrayBuffer(4);
|
|
779
|
-
const flag = new Int32Array(cancelled);
|
|
780
|
-
const started = performance.now();
|
|
781
|
-
const worker = new Worker(new URL("./workspace-io-worker.mjs", import.meta.url), {
|
|
782
|
-
workerData: {
|
|
783
|
-
root: input.root,
|
|
784
|
-
artifactRoot: input.artifactRoot,
|
|
785
|
-
operation: input.operation,
|
|
786
|
-
args: input.args,
|
|
787
|
-
cancelled
|
|
788
|
-
},
|
|
789
|
-
execArgv: []
|
|
790
|
-
});
|
|
791
|
-
const abort = () => {
|
|
792
|
-
Atomics.store(flag, 0, 1);
|
|
793
|
-
worker.postMessage("cancel");
|
|
794
|
-
};
|
|
795
|
-
input.signal?.addEventListener("abort", abort, { once: true });
|
|
796
|
-
if (input.signal?.aborted)
|
|
797
|
-
abort();
|
|
798
|
-
try {
|
|
799
|
-
return await new Promise((resolve, reject) => {
|
|
800
|
-
let message;
|
|
801
|
-
let failure;
|
|
802
|
-
worker.once("message", (value) => {
|
|
803
|
-
message = value;
|
|
804
|
-
});
|
|
805
|
-
worker.once("error", (error) => {
|
|
806
|
-
failure = error;
|
|
807
|
-
});
|
|
808
|
-
worker.once("exit", (code) => {
|
|
809
|
-
try {
|
|
810
|
-
input.onMetrics?.({
|
|
811
|
-
operation: input.operation,
|
|
812
|
-
elapsedMs: performance.now() - started,
|
|
813
|
-
files: message?.files ?? 0,
|
|
814
|
-
bytes: message?.bytes ?? 0
|
|
815
|
-
});
|
|
816
|
-
} catch {}
|
|
817
|
-
if (input.signal?.aborted)
|
|
818
|
-
reject(input.signal.reason);
|
|
819
|
-
else if (failure || code !== 0 || !message)
|
|
820
|
-
reject(new Error("workspace-io-unavailable"));
|
|
821
|
-
else if (!message.ok)
|
|
822
|
-
reject(new Error(message.error ?? "workspace-io-failed"));
|
|
823
|
-
else
|
|
824
|
-
resolve(message.result);
|
|
825
|
-
});
|
|
826
|
-
});
|
|
827
|
-
} finally {
|
|
828
|
-
input.signal?.removeEventListener("abort", abort);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
// src/workspace-store.ts
|
|
833
|
-
var SHA2562 = /^[a-f0-9]{64}$/u;
|
|
834
|
-
function hash(...values) {
|
|
835
|
-
const digest2 = createHash3("sha256");
|
|
836
|
-
for (const value of values) {
|
|
837
|
-
const bytes = typeof value === "string" ? Buffer.from(value) : value;
|
|
838
|
-
digest2.update(`${bytes.byteLength}:`);
|
|
839
|
-
digest2.update(bytes);
|
|
840
|
-
}
|
|
841
|
-
return digest2.digest("hex");
|
|
842
|
-
}
|
|
843
|
-
function stableEntries(entries) {
|
|
844
|
-
return Object.fromEntries(Object.entries(entries).sort(([left], [right]) => compareCanonicalStrings(left, right)).map(([relative, entry]) => [relative, { ...entry }]));
|
|
845
|
-
}
|
|
846
|
-
function manifestHash(entries) {
|
|
847
|
-
return hash("cadence-workspace-manifest", JSON.stringify(stableEntries(entries)));
|
|
848
|
-
}
|
|
849
|
-
function revisionId(parentRevisionId, manifest) {
|
|
850
|
-
return hash("cadence-workspace-revision", parentRevisionId ?? "", manifest);
|
|
851
|
-
}
|
|
852
|
-
function ensureDirectory2(directory) {
|
|
853
|
-
const stat = lstatSync6(directory, { throwIfNoEntry: false });
|
|
854
|
-
if (stat) {
|
|
855
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
856
|
-
throw new Error("unsafe-workspace-path");
|
|
857
|
-
}
|
|
858
|
-
chmodSync2(directory, 448);
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
let cursor = path6.dirname(directory);
|
|
862
|
-
while (cursor !== path6.dirname(cursor)) {
|
|
863
|
-
const ancestor = lstatSync6(cursor, { throwIfNoEntry: false });
|
|
864
|
-
if (ancestor) {
|
|
865
|
-
if (ancestor.isSymbolicLink() || !ancestor.isDirectory()) {
|
|
866
|
-
throw new Error("unsafe-workspace-path");
|
|
867
|
-
}
|
|
868
|
-
break;
|
|
869
|
-
}
|
|
870
|
-
cursor = path6.dirname(cursor);
|
|
871
|
-
}
|
|
872
|
-
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
873
|
-
const created = lstatSync6(directory);
|
|
874
|
-
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
875
|
-
throw new Error("unsafe-workspace-path");
|
|
876
|
-
}
|
|
877
|
-
chmodSync2(directory, 448);
|
|
878
|
-
}
|
|
879
|
-
function writeAtomic2(target, bytes) {
|
|
880
|
-
const directory = path6.dirname(target);
|
|
881
|
-
ensureDirectory2(directory);
|
|
882
|
-
const temporary = path6.join(directory, `.${path6.basename(target)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
883
|
-
const descriptor = openSync4(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
884
|
-
try {
|
|
885
|
-
writeFileSync3(descriptor, bytes);
|
|
886
|
-
fsyncSync3(descriptor);
|
|
887
|
-
} finally {
|
|
888
|
-
closeSync4(descriptor);
|
|
889
|
-
}
|
|
890
|
-
try {
|
|
891
|
-
renameSync2(temporary, target);
|
|
892
|
-
chmodSync2(target, 384);
|
|
893
|
-
syncDirectory(directory);
|
|
894
|
-
} catch (error) {
|
|
895
|
-
try {
|
|
896
|
-
unlinkSync3(temporary);
|
|
897
|
-
} catch {}
|
|
898
|
-
throw error;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
function requireSafeRelative(relative) {
|
|
902
|
-
if (!isValidRelativePath(relative) || relative === ".") {
|
|
903
|
-
throw new Error("unsafe-workspace-path");
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
function isExcluded(relative, excluded) {
|
|
907
|
-
return excluded.some((entry) => relative === entry || relative.startsWith(`${entry}/`));
|
|
908
|
-
}
|
|
909
|
-
function parseNullSeparated(bytes) {
|
|
910
|
-
const decoded = bytes.toString("utf8");
|
|
911
|
-
if (!Buffer.from(decoded, "utf8").equals(bytes)) {
|
|
912
|
-
throw new Error("unsafe-workspace-path");
|
|
913
|
-
}
|
|
914
|
-
return decoded.split("\x00").filter((entry) => entry.length > 0);
|
|
915
|
-
}
|
|
916
|
-
function sameEntry(left, right) {
|
|
917
|
-
if (!left || !right)
|
|
918
|
-
return left === right;
|
|
919
|
-
if (left.kind !== right.kind)
|
|
920
|
-
return false;
|
|
921
|
-
return left.kind === "absent" || right.kind === "file" && left.hash === right.hash && left.bytes === right.bytes && left.mode === right.mode;
|
|
922
|
-
}
|
|
923
|
-
function requireMode(mode) {
|
|
924
|
-
if (!Number.isInteger(mode) || mode < 0 || mode > 511) {
|
|
925
|
-
throw new Error("workspace-file-mode-invalid");
|
|
926
|
-
}
|
|
927
|
-
return mode;
|
|
928
|
-
}
|
|
929
|
-
function requireRevisionShape(value) {
|
|
930
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
931
|
-
throw new Error("workspace-revision-invalid");
|
|
932
|
-
}
|
|
933
|
-
const candidate = value;
|
|
934
|
-
if (typeof candidate.revisionId !== "string" || !SHA2562.test(candidate.revisionId) || candidate.parentRevisionId !== null && (typeof candidate.parentRevisionId !== "string" || !SHA2562.test(candidate.parentRevisionId)) || typeof candidate.manifestHash !== "string" || !SHA2562.test(candidate.manifestHash) || !candidate.entries || typeof candidate.entries !== "object" || Array.isArray(candidate.entries)) {
|
|
935
|
-
throw new Error("workspace-revision-invalid");
|
|
936
|
-
}
|
|
937
|
-
for (const [relative, entry] of Object.entries(candidate.entries)) {
|
|
938
|
-
requireSafeRelative(relative);
|
|
939
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
940
|
-
throw new Error("workspace-revision-invalid");
|
|
941
|
-
}
|
|
942
|
-
if (entry.kind === "absent") {
|
|
943
|
-
if (Object.keys(entry).length !== 1) {
|
|
944
|
-
throw new Error("workspace-revision-invalid");
|
|
945
|
-
}
|
|
946
|
-
continue;
|
|
947
|
-
}
|
|
948
|
-
const file = entry;
|
|
949
|
-
if (file.kind !== "file" || !SHA2562.test(file.hash) || !Number.isSafeInteger(file.bytes) || file.bytes < 0 || Object.keys(file).length !== 4) {
|
|
950
|
-
throw new Error("workspace-revision-invalid");
|
|
951
|
-
}
|
|
952
|
-
requireMode(file.mode);
|
|
953
|
-
}
|
|
954
|
-
return candidate;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
class WorkspaceStore {
|
|
958
|
-
root;
|
|
959
|
-
#revisions;
|
|
960
|
-
#artifacts;
|
|
961
|
-
#checkCancelled;
|
|
962
|
-
#onMetrics;
|
|
963
|
-
constructor(root, artifacts, options = {}) {
|
|
964
|
-
this.#checkCancelled = options.checkCancelled ?? (() => {});
|
|
965
|
-
this.#onMetrics = options.onMetrics;
|
|
966
|
-
if (!path6.isAbsolute(root))
|
|
967
|
-
throw new Error("workspace-store-root-invalid");
|
|
968
|
-
this.root = path6.resolve(root);
|
|
969
|
-
this.#revisions = path6.join(this.root, "revisions");
|
|
970
|
-
this.#artifacts = artifacts;
|
|
971
|
-
ensureDirectory2(this.root);
|
|
972
|
-
ensureDirectory2(this.#revisions);
|
|
973
|
-
}
|
|
974
|
-
#revisionPath(id) {
|
|
975
|
-
if (!SHA2562.test(id))
|
|
976
|
-
throw new Error("workspace-revision-id-invalid");
|
|
977
|
-
return path6.join(this.#revisions, id.slice(0, 2), `${id}.json`);
|
|
978
|
-
}
|
|
979
|
-
#storeRevision(parentRevisionId, rawEntries) {
|
|
980
|
-
const entries = stableEntries(rawEntries);
|
|
981
|
-
const manifest = manifestHash(entries);
|
|
982
|
-
const id = revisionId(parentRevisionId, manifest);
|
|
983
|
-
const revision = {
|
|
984
|
-
revisionId: id,
|
|
985
|
-
parentRevisionId,
|
|
986
|
-
manifestHash: manifest,
|
|
987
|
-
entries
|
|
988
|
-
};
|
|
989
|
-
const target = this.#revisionPath(id);
|
|
990
|
-
const existing = lstatSync6(target, { throwIfNoEntry: false });
|
|
991
|
-
if (existing) {
|
|
992
|
-
const stored = this.getRevision(id);
|
|
993
|
-
if (JSON.stringify(stored) !== JSON.stringify(revision)) {
|
|
994
|
-
throw new Error("workspace-revision-conflict");
|
|
995
|
-
}
|
|
996
|
-
return stored;
|
|
997
|
-
}
|
|
998
|
-
writeAtomic2(target, Buffer.from(`${JSON.stringify(revision)}
|
|
999
|
-
`));
|
|
1000
|
-
const inherited = parentRevisionId ? this.getRevision(parentRevisionId).entries : {};
|
|
1001
|
-
for (const [relative, entry] of Object.entries(entries)) {
|
|
1002
|
-
if (entry.kind === "file" && !sameEntry(entry, inherited[relative]))
|
|
1003
|
-
this.#artifacts.retain(entry.hash);
|
|
1004
|
-
}
|
|
1005
|
-
return structuredClone(revision);
|
|
1006
|
-
}
|
|
1007
|
-
#isAncestor(ancestorId, revision) {
|
|
1008
|
-
let cursor = revision;
|
|
1009
|
-
const visited = new Set;
|
|
1010
|
-
while (cursor) {
|
|
1011
|
-
if (cursor.revisionId === ancestorId)
|
|
1012
|
-
return true;
|
|
1013
|
-
if (visited.has(cursor.revisionId)) {
|
|
1014
|
-
throw new Error("workspace-revision-cycle");
|
|
1015
|
-
}
|
|
1016
|
-
visited.add(cursor.revisionId);
|
|
1017
|
-
cursor = cursor.parentRevisionId ? this.getRevision(cursor.parentRevisionId) : null;
|
|
1018
|
-
}
|
|
1019
|
-
return false;
|
|
1020
|
-
}
|
|
1021
|
-
captureBaseline(input) {
|
|
1022
|
-
const consumerRoot = path6.resolve(input.consumerRoot);
|
|
1023
|
-
const rootStat = lstatSync6(consumerRoot, { throwIfNoEntry: false });
|
|
1024
|
-
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
1025
|
-
throw new Error("unsafe-workspace-path");
|
|
1026
|
-
}
|
|
1027
|
-
const excluded = [".git", "node_modules", ...input.excludedPaths ?? []];
|
|
1028
|
-
for (const relative of excluded)
|
|
1029
|
-
requireSafeRelative(relative);
|
|
1030
|
-
return this.#captureBaselineFiles(input, this.#trackedFiles(input), consumerRoot, excluded);
|
|
1031
|
-
}
|
|
1032
|
-
#trackedFiles(input) {
|
|
1033
|
-
const timeout = input.gitTimeoutMs ?? 30000;
|
|
1034
|
-
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30000)
|
|
1035
|
-
throw new Error("workspace-git-timeout-invalid");
|
|
1036
|
-
const consumerRoot = path6.resolve(input.consumerRoot);
|
|
1037
|
-
let tracked;
|
|
1038
|
-
try {
|
|
1039
|
-
tracked = parseNullSeparated(execFileSync("git", ["ls-files", "-z", "--cached"], {
|
|
1040
|
-
cwd: consumerRoot,
|
|
1041
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
1042
|
-
timeout,
|
|
1043
|
-
killSignal: "SIGKILL"
|
|
1044
|
-
}));
|
|
1045
|
-
} catch (error) {
|
|
1046
|
-
if (error instanceof Error && error.message === "unsafe-workspace-path") {
|
|
1047
|
-
throw error;
|
|
1048
|
-
}
|
|
1049
|
-
throw new Error("workspace-git-baseline-unavailable");
|
|
1050
|
-
}
|
|
1051
|
-
return tracked;
|
|
1052
|
-
}
|
|
1053
|
-
#captureBaselineFiles(input, tracked, consumerRoot, excluded) {
|
|
1054
|
-
const approved = input.approvedUntracked ?? [];
|
|
1055
|
-
const absent = input.absent ?? [];
|
|
1056
|
-
const selected = [...new Set([...tracked, ...approved])].sort();
|
|
1057
|
-
const entries = {};
|
|
1058
|
-
for (const relative of selected) {
|
|
1059
|
-
this.#checkCancelled();
|
|
1060
|
-
requireSafeRelative(relative);
|
|
1061
|
-
if (isExcluded(relative, excluded))
|
|
1062
|
-
continue;
|
|
1063
|
-
const observation = observeSafePath(consumerRoot, relative);
|
|
1064
|
-
if (observation.kind === "absent") {
|
|
1065
|
-
entries[relative] = { kind: "absent" };
|
|
1066
|
-
continue;
|
|
1067
|
-
}
|
|
1068
|
-
if (observation.kind !== "file")
|
|
1069
|
-
throw new Error("unsafe-workspace-path");
|
|
1070
|
-
const absolute = path6.join(consumerRoot, ...relative.split("/"));
|
|
1071
|
-
const bytes = readFileSync2(absolute);
|
|
1072
|
-
const artifact = this.#artifacts.put(bytes);
|
|
1073
|
-
const stat = lstatSync6(absolute);
|
|
1074
|
-
entries[relative] = {
|
|
1075
|
-
kind: "file",
|
|
1076
|
-
hash: artifact.hash,
|
|
1077
|
-
bytes: artifact.bytes,
|
|
1078
|
-
mode: requireMode(stat.mode & 511)
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
for (const relative of [...new Set(absent)].sort()) {
|
|
1082
|
-
requireSafeRelative(relative);
|
|
1083
|
-
if (isExcluded(relative, excluded))
|
|
1084
|
-
throw new Error("unsafe-workspace-path");
|
|
1085
|
-
if (observeSafePath(consumerRoot, relative).kind !== "absent") {
|
|
1086
|
-
throw new Error("workspace-path-not-absent");
|
|
1087
|
-
}
|
|
1088
|
-
entries[relative] = { kind: "absent" };
|
|
1089
|
-
}
|
|
1090
|
-
return this.#storeRevision(null, entries);
|
|
1091
|
-
}
|
|
1092
|
-
getRevision(id) {
|
|
1093
|
-
const target = this.#revisionPath(id);
|
|
1094
|
-
const stat = lstatSync6(target, { throwIfNoEntry: false });
|
|
1095
|
-
if (!stat?.isFile() || stat.isSymbolicLink()) {
|
|
1096
|
-
throw new Error("workspace-revision-unavailable");
|
|
1097
|
-
}
|
|
1098
|
-
let parsed;
|
|
1099
|
-
try {
|
|
1100
|
-
parsed = JSON.parse(readFileSync2(target, "utf8"));
|
|
1101
|
-
} catch {
|
|
1102
|
-
throw new Error("workspace-revision-invalid");
|
|
1103
|
-
}
|
|
1104
|
-
const revision = requireRevisionShape(parsed);
|
|
1105
|
-
if (revision.revisionId !== id || manifestHash(revision.entries) !== revision.manifestHash && hash("cadence-workspace-manifest", JSON.stringify(revision.entries)) !== revision.manifestHash || revisionId(revision.parentRevisionId, revision.manifestHash) !== id) {
|
|
1106
|
-
throw new Error("workspace-revision-integrity-invalid");
|
|
1107
|
-
}
|
|
1108
|
-
return structuredClone(revision);
|
|
1109
|
-
}
|
|
1110
|
-
createRevision(input) {
|
|
1111
|
-
const parent = this.getRevision(input.parentRevisionId);
|
|
1112
|
-
const entries = stableEntries(parent.entries);
|
|
1113
|
-
for (const [relative, change] of Object.entries(input.changes).sort(([left], [right]) => compareCanonicalStrings(left, right))) {
|
|
1114
|
-
requireSafeRelative(relative);
|
|
1115
|
-
if (change === null || typeof change === "object" && "kind" in change && change.kind === "absent") {
|
|
1116
|
-
entries[relative] = { kind: "absent" };
|
|
1117
|
-
continue;
|
|
1118
|
-
}
|
|
1119
|
-
const descriptor = change instanceof Uint8Array ? {
|
|
1120
|
-
kind: "file",
|
|
1121
|
-
bytes: change,
|
|
1122
|
-
mode: entries[relative]?.kind === "file" ? entries[relative].mode : 420
|
|
1123
|
-
} : change;
|
|
1124
|
-
if (descriptor?.kind !== "file" || !(descriptor.bytes instanceof Uint8Array)) {
|
|
1125
|
-
throw new Error("workspace-change-invalid");
|
|
1126
|
-
}
|
|
1127
|
-
const artifact = this.#artifacts.put(descriptor.bytes);
|
|
1128
|
-
entries[relative] = {
|
|
1129
|
-
kind: "file",
|
|
1130
|
-
hash: artifact.hash,
|
|
1131
|
-
bytes: artifact.bytes,
|
|
1132
|
-
mode: requireMode(descriptor.mode ?? 420)
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
return this.#storeRevision(parent.revisionId, entries);
|
|
1136
|
-
}
|
|
1137
|
-
mergeRevision(input) {
|
|
1138
|
-
const base = this.getRevision(input.baseRevisionId);
|
|
1139
|
-
const current = this.getRevision(input.currentRevisionId);
|
|
1140
|
-
const candidate = this.getRevision(input.candidateRevisionId);
|
|
1141
|
-
if (candidate.parentRevisionId !== base.revisionId || !this.#isAncestor(base.revisionId, current)) {
|
|
1142
|
-
throw new Error("workspace-revision-base-invalid");
|
|
1143
|
-
}
|
|
1144
|
-
const boundPaths = [...new Set(input.boundPaths)];
|
|
1145
|
-
for (const relative of boundPaths) {
|
|
1146
|
-
requireSafeRelative(relative);
|
|
1147
|
-
if (!sameEntry(base.entries[relative], current.entries[relative])) {
|
|
1148
|
-
throw new Error("workspace-revision-stale");
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
const allPaths = new Set([
|
|
1152
|
-
...Object.keys(base.entries),
|
|
1153
|
-
...Object.keys(candidate.entries)
|
|
1154
|
-
]);
|
|
1155
|
-
const delta = [...allPaths].filter((relative) => !sameEntry(base.entries[relative], candidate.entries[relative]));
|
|
1156
|
-
for (const relative of delta) {
|
|
1157
|
-
if (!sameEntry(base.entries[relative], current.entries[relative])) {
|
|
1158
|
-
throw new Error("workspace-revision-stale");
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
const merged = stableEntries(current.entries);
|
|
1162
|
-
for (const relative of delta.sort()) {
|
|
1163
|
-
const entry = candidate.entries[relative];
|
|
1164
|
-
if (entry)
|
|
1165
|
-
merged[relative] = { ...entry };
|
|
1166
|
-
else
|
|
1167
|
-
delete merged[relative];
|
|
1168
|
-
}
|
|
1169
|
-
return this.#storeRevision(current.revisionId, merged);
|
|
1170
|
-
}
|
|
1171
|
-
materialize(revisionIdValue, destination) {
|
|
1172
|
-
const revision = this.getRevision(revisionIdValue);
|
|
1173
|
-
const root = path6.resolve(destination);
|
|
1174
|
-
ensureDirectory2(root);
|
|
1175
|
-
for (const [relative, entry] of Object.entries(revision.entries)) {
|
|
1176
|
-
this.#checkCancelled();
|
|
1177
|
-
requireSafeRelative(relative);
|
|
1178
|
-
const observation = observeSafePath(root, relative);
|
|
1179
|
-
if (entry.kind === "absent") {
|
|
1180
|
-
const segments2 = relative.split("/");
|
|
1181
|
-
const supersededByFileAncestor = segments2.slice(0, -1).some((_segment, index) => revision.entries[segments2.slice(0, index + 1).join("/")]?.kind === "file");
|
|
1182
|
-
if (supersededByFileAncestor)
|
|
1183
|
-
continue;
|
|
1184
|
-
if (observation.kind !== "absent") {
|
|
1185
|
-
throw new Error("workspace-materialize-conflict");
|
|
1186
|
-
}
|
|
1187
|
-
continue;
|
|
1188
|
-
}
|
|
1189
|
-
const segments = relative.split("/");
|
|
1190
|
-
let parent = root;
|
|
1191
|
-
for (const segment of segments.slice(0, -1)) {
|
|
1192
|
-
parent = path6.join(parent, segment);
|
|
1193
|
-
ensureDirectory2(parent);
|
|
1194
|
-
}
|
|
1195
|
-
const target = path6.join(root, ...segments);
|
|
1196
|
-
if (observation.kind !== "absent") {
|
|
1197
|
-
throw new Error("workspace-materialize-conflict");
|
|
1198
|
-
}
|
|
1199
|
-
const bytes = this.#artifacts.copyVerified(entry.hash, target);
|
|
1200
|
-
if (bytes !== entry.bytes)
|
|
1201
|
-
throw new Error("workspace-revision-integrity-invalid");
|
|
1202
|
-
chmodSync2(target, entry.mode);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
async captureBaselineWithGit(input, signal) {
|
|
1206
|
-
signal?.throwIfAborted();
|
|
1207
|
-
const consumerRoot = path6.resolve(input.consumerRoot);
|
|
1208
|
-
const stat = lstatSync6(consumerRoot, { throwIfNoEntry: false });
|
|
1209
|
-
if (!stat?.isDirectory() || stat.isSymbolicLink())
|
|
1210
|
-
throw new Error("unsafe-workspace-path");
|
|
1211
|
-
const excluded = [".git", "node_modules", ...input.excludedPaths ?? []];
|
|
1212
|
-
for (const relative of excluded)
|
|
1213
|
-
requireSafeRelative(relative);
|
|
1214
|
-
const timeout = input.gitTimeoutMs ?? 30000;
|
|
1215
|
-
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30000)
|
|
1216
|
-
throw new Error("workspace-git-timeout-invalid");
|
|
1217
|
-
let tracked;
|
|
1218
|
-
try {
|
|
1219
|
-
const { stdout } = await promisify(execFile)("git", ["ls-files", "-z", "--cached"], {
|
|
1220
|
-
cwd: consumerRoot,
|
|
1221
|
-
encoding: "buffer",
|
|
1222
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
1223
|
-
timeout,
|
|
1224
|
-
killSignal: "SIGKILL",
|
|
1225
|
-
signal
|
|
1226
|
-
});
|
|
1227
|
-
tracked = parseNullSeparated(stdout);
|
|
1228
|
-
} catch (error) {
|
|
1229
|
-
signal?.throwIfAborted();
|
|
1230
|
-
if (error instanceof Error && error.message === "unsafe-workspace-path")
|
|
1231
|
-
throw error;
|
|
1232
|
-
throw new Error("workspace-git-baseline-unavailable");
|
|
1233
|
-
}
|
|
1234
|
-
this.#checkCancelled();
|
|
1235
|
-
return this.#captureBaselineFiles(input, tracked, consumerRoot, excluded);
|
|
1236
|
-
}
|
|
1237
|
-
captureBaselineAsync(input, signal) {
|
|
1238
|
-
return runWorkspaceIo({
|
|
1239
|
-
root: this.root,
|
|
1240
|
-
artifactRoot: this.#artifacts.root,
|
|
1241
|
-
operation: "captureBaseline",
|
|
1242
|
-
args: [input],
|
|
1243
|
-
signal,
|
|
1244
|
-
onMetrics: this.#onMetrics
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
materializeAsync(revisionId2, destination, signal) {
|
|
1248
|
-
return runWorkspaceIo({
|
|
1249
|
-
root: this.root,
|
|
1250
|
-
artifactRoot: this.#artifacts.root,
|
|
1251
|
-
operation: "materialize",
|
|
1252
|
-
args: [revisionId2, destination],
|
|
1253
|
-
signal,
|
|
1254
|
-
onMetrics: this.#onMetrics
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
// src/workspace-io-entry.mjs
|
|
1260
|
-
var controller = new AbortController;
|
|
1261
|
-
var flag = new Int32Array(workerData.cancelled);
|
|
1262
|
-
parentPort.on("message", () => controller.abort(new Error("cancelled")));
|
|
1263
|
-
var checkCancelled = () => {
|
|
1264
|
-
if (Atomics.load(flag, 0))
|
|
1265
|
-
throw new Error("cancelled");
|
|
1266
|
-
};
|
|
1267
|
-
try {
|
|
1268
|
-
if (workerData.operation === "prepareVerification") {
|
|
1269
|
-
const [root, owner, bindings, profile, environment, scratch] = workerData.args;
|
|
1270
|
-
const result = prepareVerificationEnvironmentIo(root, owner, bindings, profile, environment, checkCancelled, scratch);
|
|
1271
|
-
parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
|
|
1272
|
-
} else if (workerData.operation === "cleanupVerification") {
|
|
1273
|
-
removePathSync(workerData.args[0], { recursive: true, force: true });
|
|
1274
|
-
parentPort.postMessage({ ok: true, files: 0, bytes: 0 });
|
|
1275
|
-
} else if (workerData.operation === "verificationIdentity") {
|
|
1276
|
-
const result = verificationEnvironmentDigest(workerData.args[0], checkCancelled);
|
|
1277
|
-
parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
|
|
1278
|
-
} else {
|
|
1279
|
-
const artifacts = new ArtifactStore(workerData.artifactRoot);
|
|
1280
|
-
const store = new WorkspaceStore(workerData.root, artifacts, {
|
|
1281
|
-
checkCancelled
|
|
1282
|
-
});
|
|
1283
|
-
let result;
|
|
1284
|
-
let revision;
|
|
1285
|
-
checkCancelled();
|
|
1286
|
-
if (workerData.operation === "captureBaseline") {
|
|
1287
|
-
revision = await store.captureBaselineWithGit(workerData.args[0], controller.signal);
|
|
1288
|
-
result = revision;
|
|
1289
|
-
} else if (workerData.operation === "materialize") {
|
|
1290
|
-
revision = store.getRevision(workerData.args[0]);
|
|
1291
|
-
store.materialize(workerData.args[0], workerData.args[1]);
|
|
1292
|
-
} else {
|
|
1293
|
-
throw new Error("workspace-io-operation-invalid");
|
|
1294
|
-
}
|
|
1295
|
-
const files = Object.values(revision.entries).filter((entry) => entry.kind === "file");
|
|
1296
|
-
parentPort.postMessage({
|
|
1297
|
-
ok: true,
|
|
1298
|
-
result,
|
|
1299
|
-
files: files.length,
|
|
1300
|
-
bytes: files.reduce((sum, entry) => sum + entry.bytes, 0)
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
} catch (error) {
|
|
1304
|
-
parentPort.postMessage({
|
|
1305
|
-
ok: false,
|
|
1306
|
-
error: error instanceof Error ? error.message : "workspace-io-failed",
|
|
1307
|
-
files: 0,
|
|
1308
|
-
bytes: 0
|
|
1309
|
-
});
|
|
1310
|
-
} finally {
|
|
1311
|
-
parentPort.close();
|
|
1312
|
-
}
|