@abelxiaoxing/cadence 2.0.1 → 2.0.3
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 +104 -44
- package/docs/reuse-and-distribution.md +3 -2
- package/docs/service-management.md +62 -3
- package/package.json +10 -7
- package/src/agent-panel-cadence.ts +651 -0
- package/src/agent-panel-model.ts +521 -0
- package/src/agent-panel.ts +474 -0
- package/src/index.ts +12 -4
- package/src/install-config.mjs +25 -0
- package/src/native-client.ts +142 -3
- package/src/operator-cli.mjs +52 -16
- package/src/service/coordination.ts +35 -16
- package/src/service/private-files.ts +141 -28
- package/src/service-cli.mjs +793 -149
- package/src/service-host.mjs +847 -319
- package/src/task-shell.ts +27 -14
- package/src/task-store.ts +104 -25
- package/src/worker-tools.mjs +42 -17
- package/web/dist/assets/cadence.css +1 -1
- package/web/dist/assets/cadence.js +7 -7
- package/web/dist/manifest.json +6 -6
package/src/service-host.mjs
CHANGED
|
@@ -4,37 +4,11 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// src/service/host.ts
|
|
6
6
|
import { lstatSync as lstatSync10, opendirSync as opendirSync3 } from "node:fs";
|
|
7
|
-
import { join as
|
|
7
|
+
import { join as join17 } from "node:path";
|
|
8
8
|
|
|
9
|
-
// src/
|
|
10
|
-
import { createHash as
|
|
11
|
-
import {
|
|
12
|
-
closeSync as closeSync4,
|
|
13
|
-
constants as constants4,
|
|
14
|
-
fstatSync as fstatSync4,
|
|
15
|
-
fsyncSync as fsyncSync3,
|
|
16
|
-
lstatSync as lstatSync4,
|
|
17
|
-
opendirSync,
|
|
18
|
-
openSync as openSync4,
|
|
19
|
-
statfsSync
|
|
20
|
-
} from "node:fs";
|
|
21
|
-
import { open } from "node:fs/promises";
|
|
22
|
-
import { dirname as dirname4, join as join3 } from "node:path";
|
|
23
|
-
|
|
24
|
-
// src/service/private-files.ts
|
|
25
|
-
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
26
|
-
import {
|
|
27
|
-
closeSync as closeSync2,
|
|
28
|
-
constants as constants2,
|
|
29
|
-
fstatSync as fstatSync2,
|
|
30
|
-
fsyncSync as fsyncSync2,
|
|
31
|
-
lstatSync as lstatSync2,
|
|
32
|
-
openSync as openSync2,
|
|
33
|
-
renameSync as renameSync2,
|
|
34
|
-
unlinkSync as unlinkSync2,
|
|
35
|
-
writeFileSync as writeFileSync2
|
|
36
|
-
} from "node:fs";
|
|
37
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
9
|
+
// src/agent-registry.ts
|
|
10
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
38
12
|
|
|
39
13
|
// src/task-store.ts
|
|
40
14
|
import { spawnSync } from "node:child_process";
|
|
@@ -170,11 +144,8 @@ function safeDirectory(path, create = false, inspectExisting) {
|
|
|
170
144
|
at = join(at, part);
|
|
171
145
|
let existing;
|
|
172
146
|
try {
|
|
173
|
-
const st = lstatSync(at);
|
|
174
|
-
if (!st.isDirectory())
|
|
175
|
-
throw new Error("Unsafe directory");
|
|
176
147
|
const checked = lstatSync(at);
|
|
177
|
-
if (checked.
|
|
148
|
+
if (!checked.isDirectory() || checked.isSymbolicLink())
|
|
178
149
|
throw new Error("Unsafe directory");
|
|
179
150
|
existing = checked;
|
|
180
151
|
} catch (error) {
|
|
@@ -188,7 +159,8 @@ function safeDirectory(path, create = false, inspectExisting) {
|
|
|
188
159
|
if (!object(mkdirError) || mkdirError.code !== "EEXIST")
|
|
189
160
|
throw mkdirError;
|
|
190
161
|
}
|
|
191
|
-
|
|
162
|
+
const created = lstatSync(at);
|
|
163
|
+
if (!created.isDirectory() || created.isSymbolicLink())
|
|
192
164
|
throw new Error("Unsafe directory");
|
|
193
165
|
}
|
|
194
166
|
if (existing)
|
|
@@ -196,13 +168,29 @@ function safeDirectory(path, create = false, inspectExisting) {
|
|
|
196
168
|
}
|
|
197
169
|
return true;
|
|
198
170
|
}
|
|
199
|
-
|
|
171
|
+
var transientWindows = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
172
|
+
function retryWindowsFileOperation(operation, recheck) {
|
|
173
|
+
if (process.platform !== "win32")
|
|
174
|
+
return operation();
|
|
175
|
+
const deadline = performance.now() + 2000;
|
|
176
|
+
for (let delay = 2;; delay = Math.min(delay * 2, 50)) {
|
|
177
|
+
try {
|
|
178
|
+
return operation();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (!object(error) || !transientWindows.has(String(error.code)) || performance.now() >= deadline)
|
|
181
|
+
throw error;
|
|
182
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
|
|
183
|
+
recheck?.();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function readBounded(path, max, packaged = false) {
|
|
200
188
|
if (!safeDirectory(dirname(path)))
|
|
201
189
|
return;
|
|
202
190
|
let fd;
|
|
203
191
|
try {
|
|
204
192
|
const st = lstatSync(path);
|
|
205
|
-
if (!st.isFile() || st.isSymbolicLink() || st.nlink !== 1 || st.size > max)
|
|
193
|
+
if (!st.isFile() || st.isSymbolicLink() || !packaged && st.nlink !== 1 || st.size > max)
|
|
206
194
|
throw new Error("Unsafe or oversized file");
|
|
207
195
|
fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
208
196
|
} catch (error) {
|
|
@@ -212,7 +200,7 @@ function readBounded(path, max) {
|
|
|
212
200
|
}
|
|
213
201
|
try {
|
|
214
202
|
const st = fstatSync(fd);
|
|
215
|
-
if (!st.isFile() || st.nlink !== 1 || st.size > max)
|
|
203
|
+
if (!st.isFile() || !packaged && st.nlink !== 1 || st.size > max)
|
|
216
204
|
throw new Error("Unsafe or oversized file");
|
|
217
205
|
const buffer = Buffer.alloc(max + 1);
|
|
218
206
|
let size = 0;
|
|
@@ -293,7 +281,13 @@ function canonicalAmbientRoot(path) {
|
|
|
293
281
|
return join(canonicalAmbientRoot(parent), full.slice(parent.length));
|
|
294
282
|
}
|
|
295
283
|
}
|
|
296
|
-
|
|
284
|
+
function privateRootFor(canonicalRoot, stateHome) {
|
|
285
|
+
return join(canonicalAmbientRoot(stateHome), "cadence-assistant-v1", createHash("sha256").update(canonicalRoot).digest("hex"));
|
|
286
|
+
}
|
|
287
|
+
function contains(root, path) {
|
|
288
|
+
const within = relative(root, resolve(path));
|
|
289
|
+
return !within || !within.startsWith(`..${sep}`) && within !== ".." && !isAbsolute(within);
|
|
290
|
+
}
|
|
297
291
|
class TaskStore {
|
|
298
292
|
root;
|
|
299
293
|
tasks;
|
|
@@ -301,9 +295,8 @@ class TaskStore {
|
|
|
301
295
|
constructor(root, stateHome = defaultStateHome()) {
|
|
302
296
|
this.root = realpathSync(root);
|
|
303
297
|
this.tasks = join(this.root, ".cadence", "assistant", "tasks");
|
|
304
|
-
this.privateRoot =
|
|
305
|
-
|
|
306
|
-
if (!within || !within.startsWith(`..${sep}`) && within !== ".." && !isAbsolute(within))
|
|
298
|
+
this.privateRoot = privateRootFor(this.root, stateHome);
|
|
299
|
+
if (contains(this.root, this.privateRoot))
|
|
307
300
|
throw new Error("Private state home must be outside the working copy");
|
|
308
301
|
}
|
|
309
302
|
get lockPath() {
|
|
@@ -355,7 +348,7 @@ class TaskStore {
|
|
|
355
348
|
};
|
|
356
349
|
let fd;
|
|
357
350
|
try {
|
|
358
|
-
fd = openSync(this.lockPath, "wx", 384);
|
|
351
|
+
fd = retryWindowsFileOperation(() => openSync(this.lockPath, "wx", 384));
|
|
359
352
|
} catch {
|
|
360
353
|
throw new Error("Checkout busy or unresolved lock; inspect and explicitly recover only a dead owner");
|
|
361
354
|
}
|
|
@@ -375,7 +368,7 @@ class TaskStore {
|
|
|
375
368
|
if (lock.pid !== process.pid || lock.host !== hostname())
|
|
376
369
|
throw new Error("Only the owning process may release a lock");
|
|
377
370
|
this.assertOwner(lock);
|
|
378
|
-
unlinkSync(this.lockPath);
|
|
371
|
+
retryWindowsFileOperation(() => unlinkSync(this.lockPath), () => this.assertOwner(lock));
|
|
379
372
|
}
|
|
380
373
|
recover(expected, acknowledgeEscapedProcesses) {
|
|
381
374
|
if (!acknowledgeEscapedProcesses || expected.host !== hostname())
|
|
@@ -455,7 +448,12 @@ class TaskStore {
|
|
|
455
448
|
const latest = readBounded(join(dir, "task.json"), MAX_RECORD_BYTES);
|
|
456
449
|
if ((latest ? contentIdentity(latest) : undefined) !== expectedIdentity)
|
|
457
450
|
throw new Error("Task content changed; replacement preserved without overwrite");
|
|
458
|
-
renameSync(temporary, join(dir, "task.json"))
|
|
451
|
+
retryWindowsFileOperation(() => renameSync(temporary, join(dir, "task.json")), () => {
|
|
452
|
+
this.assertOwner(lock);
|
|
453
|
+
const now = readBounded(join(dir, "task.json"), MAX_RECORD_BYTES);
|
|
454
|
+
if ((now ? contentIdentity(now) : undefined) !== expectedIdentity)
|
|
455
|
+
throw new Error("Task content changed; replacement preserved without overwrite");
|
|
456
|
+
});
|
|
459
457
|
return next;
|
|
460
458
|
} finally {
|
|
461
459
|
if (!held)
|
|
@@ -495,6 +493,61 @@ class TaskStore {
|
|
|
495
493
|
}
|
|
496
494
|
var TASK_CONFIG_SCHEMA = "cadence-assistant-checks/1";
|
|
497
495
|
|
|
496
|
+
// src/agent-registry.ts
|
|
497
|
+
var AGENT_FILES = [
|
|
498
|
+
"design-explorer.md",
|
|
499
|
+
"implementation-worker.md",
|
|
500
|
+
"diagnosis-worker.md"
|
|
501
|
+
];
|
|
502
|
+
function loadAgentDefinitions() {
|
|
503
|
+
return AGENT_FILES.map((fileName) => {
|
|
504
|
+
const path = fileURLToPath(new URL(`../agents/${fileName}`, import.meta.url));
|
|
505
|
+
const data = readBounded(path, 32768, true);
|
|
506
|
+
if (!data)
|
|
507
|
+
throw new Error("Missing package Agent");
|
|
508
|
+
return {
|
|
509
|
+
role: fileName.replace(/\.md$/, ""),
|
|
510
|
+
fileName,
|
|
511
|
+
path,
|
|
512
|
+
sha256: createHash2("sha256").update(data).digest("hex"),
|
|
513
|
+
bytes: data.length,
|
|
514
|
+
content: data.toString("utf8")
|
|
515
|
+
};
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/history/files.ts
|
|
520
|
+
import { createHash as createHash6, randomBytes, randomUUID as randomUUID3 } from "node:crypto";
|
|
521
|
+
import {
|
|
522
|
+
closeSync as closeSync4,
|
|
523
|
+
constants as constants4,
|
|
524
|
+
fstatSync as fstatSync4,
|
|
525
|
+
fsyncSync as fsyncSync3,
|
|
526
|
+
lstatSync as lstatSync4,
|
|
527
|
+
opendirSync,
|
|
528
|
+
openSync as openSync4,
|
|
529
|
+
statfsSync
|
|
530
|
+
} from "node:fs";
|
|
531
|
+
import { open } from "node:fs/promises";
|
|
532
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
533
|
+
|
|
534
|
+
// src/service/private-files.ts
|
|
535
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
536
|
+
import {
|
|
537
|
+
closeSync as closeSync2,
|
|
538
|
+
constants as constants2,
|
|
539
|
+
fstatSync as fstatSync2,
|
|
540
|
+
fsyncSync as fsyncSync2,
|
|
541
|
+
lstatSync as lstatSync2,
|
|
542
|
+
mkdirSync as mkdirSync2,
|
|
543
|
+
openSync as openSync2,
|
|
544
|
+
readSync as readSync2,
|
|
545
|
+
renameSync as renameSync2,
|
|
546
|
+
unlinkSync as unlinkSync2,
|
|
547
|
+
writeFileSync as writeFileSync2
|
|
548
|
+
} from "node:fs";
|
|
549
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
550
|
+
|
|
498
551
|
// src/worker-public.mjs
|
|
499
552
|
var SERVICE_PROTOCOL = 1;
|
|
500
553
|
var WORKER_PROTOCOL = 2;
|
|
@@ -2079,10 +2132,10 @@ function refused() {
|
|
|
2079
2132
|
throw new ContractError("corrupt-selected");
|
|
2080
2133
|
}
|
|
2081
2134
|
function hash(bytes) {
|
|
2082
|
-
return
|
|
2135
|
+
return createHash3("sha256").update(bytes).digest("hex");
|
|
2083
2136
|
}
|
|
2084
|
-
function privateMode(st) {
|
|
2085
|
-
if (process.platform !== "win32" && ((st.mode & 63) !== 0 || process.getuid && st.uid !== process.getuid()))
|
|
2137
|
+
function privateMode(st, ownerOnly = true) {
|
|
2138
|
+
if (process.platform !== "win32" && (ownerOnly && (st.mode & 63) !== 0 || process.getuid && st.uid !== process.getuid()))
|
|
2086
2139
|
refused();
|
|
2087
2140
|
}
|
|
2088
2141
|
function same(a, b) {
|
|
@@ -2091,38 +2144,110 @@ function same(a, b) {
|
|
|
2091
2144
|
function missing(error) {
|
|
2092
2145
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
2093
2146
|
}
|
|
2147
|
+
var job = 0;
|
|
2148
|
+
var advancing = false;
|
|
2149
|
+
function currentJob() {
|
|
2150
|
+
if (!advancing) {
|
|
2151
|
+
advancing = true;
|
|
2152
|
+
queueMicrotask(() => {
|
|
2153
|
+
job++;
|
|
2154
|
+
advancing = false;
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
return job;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
class DirectoryWalks {
|
|
2161
|
+
job = currentJob();
|
|
2162
|
+
walked = new Map;
|
|
2163
|
+
open() {
|
|
2164
|
+
if (this.job === job)
|
|
2165
|
+
return true;
|
|
2166
|
+
this.walked.clear();
|
|
2167
|
+
return false;
|
|
2168
|
+
}
|
|
2169
|
+
remember(path, st) {
|
|
2170
|
+
if (this.open())
|
|
2171
|
+
this.walked.set(path, { dev: st.dev, ino: st.ino });
|
|
2172
|
+
}
|
|
2173
|
+
reuse(path, ownerOnly = true) {
|
|
2174
|
+
const walked = this.open() ? this.walked.get(path) : undefined;
|
|
2175
|
+
if (!walked)
|
|
2176
|
+
return false;
|
|
2177
|
+
let st;
|
|
2178
|
+
try {
|
|
2179
|
+
st = lstatSync2(path);
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
if (!missing(error))
|
|
2182
|
+
throw error;
|
|
2183
|
+
this.walked.delete(path);
|
|
2184
|
+
return false;
|
|
2185
|
+
}
|
|
2186
|
+
if (!st.isDirectory() || st.isSymbolicLink() || st.dev !== walked.dev || st.ino !== walked.ino) {
|
|
2187
|
+
this.walked.delete(path);
|
|
2188
|
+
return false;
|
|
2189
|
+
}
|
|
2190
|
+
privateMode(st, ownerOnly);
|
|
2191
|
+
return true;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2094
2194
|
|
|
2095
2195
|
class PrivateFiles {
|
|
2096
2196
|
namespace;
|
|
2097
|
-
|
|
2197
|
+
ownerOnly;
|
|
2198
|
+
constructor(namespace, ownerOnly = true) {
|
|
2098
2199
|
this.namespace = namespace;
|
|
2200
|
+
this.ownerOnly = ownerOnly;
|
|
2099
2201
|
}
|
|
2100
|
-
directory(path, create = false) {
|
|
2202
|
+
directory(path, create = false, walks) {
|
|
2101
2203
|
const tail = relative2(this.namespace, path);
|
|
2102
2204
|
if (isAbsolute2(tail) || tail === ".." || tail.startsWith(`..${sep2}`))
|
|
2103
2205
|
refused();
|
|
2104
2206
|
if (!create) {
|
|
2207
|
+
const full = resolve2(path);
|
|
2208
|
+
if (walks?.reuse(full, this.ownerOnly))
|
|
2209
|
+
return true;
|
|
2105
2210
|
const namespace = resolve2(this.namespace);
|
|
2106
|
-
let inside = false;
|
|
2107
|
-
|
|
2211
|
+
let inside = false, last;
|
|
2212
|
+
const found = safeDirectory(path, false, (at2, st) => {
|
|
2108
2213
|
if (at2 === namespace)
|
|
2109
2214
|
inside = true;
|
|
2110
2215
|
if (inside)
|
|
2111
|
-
privateMode(st);
|
|
2216
|
+
privateMode(st, this.ownerOnly);
|
|
2217
|
+
last = st;
|
|
2112
2218
|
});
|
|
2219
|
+
if (found && last)
|
|
2220
|
+
walks?.remember(full, last);
|
|
2221
|
+
return found;
|
|
2113
2222
|
}
|
|
2223
|
+
if (!safeDirectory(this.namespace, true))
|
|
2224
|
+
return false;
|
|
2225
|
+
privateMode(lstatSync2(this.namespace), this.ownerOnly);
|
|
2114
2226
|
let at = this.namespace;
|
|
2115
|
-
for (const part of
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2227
|
+
for (const part of tail.split(sep2).filter(Boolean)) {
|
|
2228
|
+
at = join2(at, part);
|
|
2229
|
+
let st;
|
|
2230
|
+
try {
|
|
2231
|
+
st = lstatSync2(at);
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
if (!missing(error))
|
|
2234
|
+
throw error;
|
|
2235
|
+
try {
|
|
2236
|
+
mkdirSync2(at, { mode: 448 });
|
|
2237
|
+
} catch (mkdirError) {
|
|
2238
|
+
if (!(mkdirError instanceof Error && ("code" in mkdirError) && mkdirError.code === "EEXIST"))
|
|
2239
|
+
throw mkdirError;
|
|
2240
|
+
}
|
|
2241
|
+
st = lstatSync2(at);
|
|
2242
|
+
}
|
|
2243
|
+
if (!st.isDirectory() || st.isSymbolicLink())
|
|
2244
|
+
throw new Error("Unsafe directory");
|
|
2245
|
+
privateMode(st, this.ownerOnly);
|
|
2121
2246
|
}
|
|
2122
2247
|
return true;
|
|
2123
2248
|
}
|
|
2124
|
-
read(path, maximum = METADATA_BYTES) {
|
|
2125
|
-
if (!this.directory(dirname2(path)))
|
|
2249
|
+
read(path, maximum = METADATA_BYTES, walks) {
|
|
2250
|
+
if (!this.directory(dirname2(path), false, walks))
|
|
2126
2251
|
return;
|
|
2127
2252
|
let before;
|
|
2128
2253
|
try {
|
|
@@ -2134,22 +2259,30 @@ class PrivateFiles {
|
|
|
2134
2259
|
}
|
|
2135
2260
|
if (!before.isFile() || before.nlink !== 1 || before.size > maximum)
|
|
2136
2261
|
refused();
|
|
2137
|
-
privateMode(before);
|
|
2262
|
+
privateMode(before, this.ownerOnly);
|
|
2138
2263
|
const fd = openSync2(path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
|
|
2139
2264
|
try {
|
|
2140
2265
|
const opened = fstatSync2(fd);
|
|
2141
2266
|
if (!same(before, opened))
|
|
2142
2267
|
refused();
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2268
|
+
const buffer = Buffer.alloc(opened.size + 1);
|
|
2269
|
+
let size = 0;
|
|
2270
|
+
while (size < buffer.length) {
|
|
2271
|
+
const n = readSync2(fd, buffer, size, buffer.length - size, null);
|
|
2272
|
+
if (!n)
|
|
2273
|
+
break;
|
|
2274
|
+
size += n;
|
|
2275
|
+
}
|
|
2276
|
+
const bytes = buffer.subarray(0, size);
|
|
2277
|
+
if (size !== opened.size || !same(opened, fstatSync2(fd)) || !same(opened, lstatSync2(path)))
|
|
2145
2278
|
refused();
|
|
2146
2279
|
return { bytes, identity: hash(bytes), stamp: stamp(opened) };
|
|
2147
2280
|
} finally {
|
|
2148
2281
|
closeSync2(fd);
|
|
2149
2282
|
}
|
|
2150
2283
|
}
|
|
2151
|
-
assert(path, expected, maximum = METADATA_BYTES) {
|
|
2152
|
-
const current = this.read(path, maximum);
|
|
2284
|
+
assert(path, expected, maximum = METADATA_BYTES, walks) {
|
|
2285
|
+
const current = this.read(path, maximum, walks);
|
|
2153
2286
|
if (current?.identity !== expected?.identity || current?.stamp !== expected?.stamp)
|
|
2154
2287
|
throw new ContractError("conflict");
|
|
2155
2288
|
}
|
|
@@ -2157,7 +2290,7 @@ class PrivateFiles {
|
|
|
2157
2290
|
if (Buffer.byteLength(bytes) > maximum)
|
|
2158
2291
|
throw new ContractError("oversize");
|
|
2159
2292
|
this.directory(dirname2(path), true);
|
|
2160
|
-
const fd = openSync2(path, "wx", 384);
|
|
2293
|
+
const fd = retryWindowsFileOperation(() => openSync2(path, "wx", 384));
|
|
2161
2294
|
try {
|
|
2162
2295
|
writeFileSync2(fd, bytes);
|
|
2163
2296
|
fsyncSync2(fd);
|
|
@@ -2175,7 +2308,7 @@ class PrivateFiles {
|
|
|
2175
2308
|
const temporary = join2(dirname2(path), `pending-${randomUUID2()}.json`);
|
|
2176
2309
|
this.create(temporary, bytes, maximum);
|
|
2177
2310
|
this.assert(path, expected, maximum);
|
|
2178
|
-
renameSync2(temporary, path);
|
|
2311
|
+
retryWindowsFileOperation(() => renameSync2(temporary, path), () => this.assert(path, expected, maximum));
|
|
2179
2312
|
this.syncDirectory(dirname2(path));
|
|
2180
2313
|
const result = this.read(path, maximum);
|
|
2181
2314
|
if (!result || result.identity !== hash(bytes))
|
|
@@ -2184,7 +2317,7 @@ class PrivateFiles {
|
|
|
2184
2317
|
}
|
|
2185
2318
|
remove(path, expected, maximum = METADATA_BYTES) {
|
|
2186
2319
|
this.assert(path, expected, maximum);
|
|
2187
|
-
unlinkSync2(path);
|
|
2320
|
+
retryWindowsFileOperation(() => unlinkSync2(path), () => this.assert(path, expected, maximum));
|
|
2188
2321
|
this.syncDirectory(dirname2(path));
|
|
2189
2322
|
}
|
|
2190
2323
|
syncDirectory(path) {
|
|
@@ -3915,22 +4048,22 @@ function decodeRunAdmission(value) {
|
|
|
3915
4048
|
}
|
|
3916
4049
|
|
|
3917
4050
|
// src/history/read-io.ts
|
|
3918
|
-
import { createHash as
|
|
4051
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3919
4052
|
import {
|
|
3920
4053
|
closeSync as closeSync3,
|
|
3921
4054
|
constants as constants3,
|
|
3922
4055
|
fstatSync as fstatSync3,
|
|
3923
4056
|
lstatSync as lstatSync3,
|
|
3924
4057
|
openSync as openSync3,
|
|
3925
|
-
readSync as
|
|
4058
|
+
readSync as readSync3
|
|
3926
4059
|
} from "node:fs";
|
|
3927
4060
|
import { dirname as dirname3 } from "node:path";
|
|
3928
4061
|
function bad() {
|
|
3929
4062
|
throw new ContractError("corrupt-selected");
|
|
3930
4063
|
}
|
|
3931
4064
|
var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
|
|
3932
|
-
function selected(files, path, read) {
|
|
3933
|
-
if (!files.files.directory(dirname3(path)))
|
|
4065
|
+
function selected(files, path, read, walks) {
|
|
4066
|
+
if (!files.files.directory(dirname3(path), false, walks))
|
|
3934
4067
|
bad();
|
|
3935
4068
|
const before = lstatSync3(path);
|
|
3936
4069
|
if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
|
|
@@ -3967,7 +4100,7 @@ function reproveAppend(files, path, expected, length, sha256, budget) {
|
|
|
3967
4100
|
if (b.slice(0, 2).join(":") !== a.slice(0, 2).join(":") || b.slice(3, 6).join(":") !== a.slice(3, 6).join(":") || Number(b[2]) <= Number(a[2]))
|
|
3968
4101
|
bad();
|
|
3969
4102
|
const bytes = readRange(files, path, 0, length, budget);
|
|
3970
|
-
if (
|
|
4103
|
+
if (createHash4("sha256").update(bytes).digest("hex") !== sha256)
|
|
3971
4104
|
bad();
|
|
3972
4105
|
assertIncarnation(files, path, current.stamp);
|
|
3973
4106
|
return current.stamp;
|
|
@@ -3989,17 +4122,17 @@ function readRange(files, path, offset, length, budget) {
|
|
|
3989
4122
|
let n = 0;
|
|
3990
4123
|
while (n < length) {
|
|
3991
4124
|
budget.reserve(0);
|
|
3992
|
-
const got =
|
|
4125
|
+
const got = readSync3(fd, out, n, length - n, offset + n);
|
|
3993
4126
|
if (!got)
|
|
3994
4127
|
bad();
|
|
3995
4128
|
n += got;
|
|
3996
4129
|
}
|
|
3997
4130
|
return out;
|
|
3998
|
-
});
|
|
4131
|
+
}, budget.walks);
|
|
3999
4132
|
}
|
|
4000
4133
|
|
|
4001
4134
|
// src/history/types.ts
|
|
4002
|
-
import { createHash as
|
|
4135
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
4003
4136
|
var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
|
|
4004
4137
|
var LEGACY_FORMAT = "cadence-history-store/2";
|
|
4005
4138
|
var FORMAT = "cadence-history-store/3";
|
|
@@ -4365,7 +4498,7 @@ function batchArtifacts(rows) {
|
|
|
4365
4498
|
}
|
|
4366
4499
|
var decodedLines = new Map;
|
|
4367
4500
|
var decodedBytes = 0;
|
|
4368
|
-
var lineKey = (text2) =>
|
|
4501
|
+
var lineKey = (text2) => createHash5("sha256").update(text2).digest("hex");
|
|
4369
4502
|
function remember(text2, value) {
|
|
4370
4503
|
const key = lineKey(text2);
|
|
4371
4504
|
if (decodedLines.has(key))
|
|
@@ -4428,13 +4561,45 @@ function decodeJournalText(line) {
|
|
|
4428
4561
|
|
|
4429
4562
|
// src/history/files.ts
|
|
4430
4563
|
class HistoryReadBudget {
|
|
4564
|
+
clock;
|
|
4431
4565
|
started = performance.now();
|
|
4432
4566
|
bytes = 0;
|
|
4567
|
+
walks = new DirectoryWalks;
|
|
4568
|
+
constructor(clock = true) {
|
|
4569
|
+
this.clock = clock;
|
|
4570
|
+
}
|
|
4571
|
+
static authority() {
|
|
4572
|
+
return new HistoryReadBudget(false);
|
|
4573
|
+
}
|
|
4433
4574
|
reserve(bytes) {
|
|
4434
|
-
if (!Number.isSafeInteger(bytes) || bytes < 0 || this.bytes + bytes > LIMITS.searchBytes ||
|
|
4575
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0 || this.bytes + bytes > LIMITS.searchBytes || this.elapsed)
|
|
4435
4576
|
throw new ContractError("receipt-inconclusive");
|
|
4436
4577
|
this.bytes += bytes;
|
|
4437
4578
|
}
|
|
4579
|
+
get elapsed() {
|
|
4580
|
+
return this.clock && performance.now() - this.started >= LIMITS.searchMs;
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
|
|
4584
|
+
class StalledQuantum extends ContractError {
|
|
4585
|
+
constructor() {
|
|
4586
|
+
super("receipt-inconclusive");
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
function noProgress(budget) {
|
|
4590
|
+
throw budget.elapsed ? new StalledQuantum : new ContractError("receipt-inconclusive");
|
|
4591
|
+
}
|
|
4592
|
+
var STALL_RETRIES = 2;
|
|
4593
|
+
async function retryStalled(read) {
|
|
4594
|
+
for (let retry = 0;; retry++) {
|
|
4595
|
+
try {
|
|
4596
|
+
return await read();
|
|
4597
|
+
} catch (error) {
|
|
4598
|
+
if (!(error instanceof StalledQuantum) || retry >= STALL_RETRIES)
|
|
4599
|
+
throw error;
|
|
4600
|
+
await new Promise((resolve3) => setImmediate(resolve3));
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4438
4603
|
}
|
|
4439
4604
|
function corrupt() {
|
|
4440
4605
|
throw new ContractError("corrupt-selected");
|
|
@@ -4596,12 +4761,12 @@ class HistoryFiles {
|
|
|
4596
4761
|
try {
|
|
4597
4762
|
size = lstatSync4(path).size;
|
|
4598
4763
|
} catch {
|
|
4599
|
-
return this.files.read(path, 0);
|
|
4764
|
+
return this.files.read(path, 0, budget.walks);
|
|
4600
4765
|
}
|
|
4601
4766
|
if (size > maximum)
|
|
4602
4767
|
corrupt();
|
|
4603
4768
|
budget.reserve(size);
|
|
4604
|
-
return this.files.read(path, size);
|
|
4769
|
+
return this.files.read(path, size, budget.walks);
|
|
4605
4770
|
}
|
|
4606
4771
|
readCommand(commandId, budget) {
|
|
4607
4772
|
decodeId(commandId);
|
|
@@ -4754,7 +4919,7 @@ class HistoryFiles {
|
|
|
4754
4919
|
state.bytes = file.bytes.length;
|
|
4755
4920
|
state.identity = file.identity;
|
|
4756
4921
|
state.records = 0;
|
|
4757
|
-
let at = 0, pending = [], pendingBytes = 0, checksum =
|
|
4922
|
+
let at = 0, pending = [], pendingBytes = 0, checksum = createHash6("sha256");
|
|
4758
4923
|
while (at < file.bytes.length) {
|
|
4759
4924
|
budget?.reserve(0);
|
|
4760
4925
|
const end = file.bytes.indexOf(10, at);
|
|
@@ -4822,7 +4987,7 @@ class HistoryFiles {
|
|
|
4822
4987
|
};
|
|
4823
4988
|
pending = [];
|
|
4824
4989
|
pendingBytes = 0;
|
|
4825
|
-
checksum =
|
|
4990
|
+
checksum = createHash6("sha256");
|
|
4826
4991
|
}
|
|
4827
4992
|
if (pending.length)
|
|
4828
4993
|
state.tail = true;
|
|
@@ -5014,7 +5179,7 @@ class HistoryFiles {
|
|
|
5014
5179
|
lease.assert();
|
|
5015
5180
|
this.assertWritable();
|
|
5016
5181
|
const after = this.files.read(path, LIMITS.segmentBytes);
|
|
5017
|
-
if (!after || after.identity !==
|
|
5182
|
+
if (!after || after.identity !== createHash6("sha256").update(before.bytes).update(body).update(trailer).digest("hex"))
|
|
5018
5183
|
corrupt();
|
|
5019
5184
|
return {
|
|
5020
5185
|
committedSeq: marker.last,
|
|
@@ -5283,7 +5448,7 @@ class HistoryLive {
|
|
|
5283
5448
|
}
|
|
5284
5449
|
|
|
5285
5450
|
// src/history/navigation.ts
|
|
5286
|
-
import { createHash as
|
|
5451
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
5287
5452
|
import { lstatSync as lstatSync6 } from "node:fs";
|
|
5288
5453
|
import { dirname as dirname5, join as join5 } from "node:path";
|
|
5289
5454
|
|
|
@@ -5465,7 +5630,7 @@ class IndexTree {
|
|
|
5465
5630
|
|
|
5466
5631
|
// src/history/run-state.ts
|
|
5467
5632
|
var uncertainSettlement = (completion) => completion.native.closure === "unconfirmed" || completion.recording.status === "pending" && completion.recording.selected !== null || completion.recording.status === "uncertain";
|
|
5468
|
-
function readRunState(files, runId, snapshotSeq, budget =
|
|
5633
|
+
function readRunState(files, runId, snapshotSeq, budget = HistoryReadBudget.authority()) {
|
|
5469
5634
|
const state = new RunState(runId);
|
|
5470
5635
|
if (snapshotSeq === "0")
|
|
5471
5636
|
return state;
|
|
@@ -5543,6 +5708,27 @@ class TaskPublicationLineage {
|
|
|
5543
5708
|
};
|
|
5544
5709
|
}
|
|
5545
5710
|
}
|
|
5711
|
+
get operationId() {
|
|
5712
|
+
return this.operation?.operationId ?? null;
|
|
5713
|
+
}
|
|
5714
|
+
progress() {
|
|
5715
|
+
return {
|
|
5716
|
+
operationId: this.operation?.operationId ?? null,
|
|
5717
|
+
state: structuredClone(this.state),
|
|
5718
|
+
endpoint: this.endpoint && { ...this.endpoint },
|
|
5719
|
+
graphIdentity: this.graphIdentity
|
|
5720
|
+
};
|
|
5721
|
+
}
|
|
5722
|
+
static resumed(operation, progress) {
|
|
5723
|
+
if ((operation?.operationId ?? null) !== progress.operationId)
|
|
5724
|
+
throw new ContractError("invalid");
|
|
5725
|
+
const lineage = new TaskPublicationLineage;
|
|
5726
|
+
lineage.operation = operation;
|
|
5727
|
+
lineage.state = structuredClone(progress.state);
|
|
5728
|
+
lineage.endpoint = progress.endpoint && { ...progress.endpoint };
|
|
5729
|
+
lineage.graphIdentity = progress.graphIdentity;
|
|
5730
|
+
return lineage;
|
|
5731
|
+
}
|
|
5546
5732
|
current() {
|
|
5547
5733
|
requireFact(this.operation && this.state.status === "published" && this.endpoint?.identity);
|
|
5548
5734
|
return {
|
|
@@ -5596,6 +5782,21 @@ class OperationState {
|
|
|
5596
5782
|
}, fact, seq);
|
|
5597
5783
|
}
|
|
5598
5784
|
}
|
|
5785
|
+
progress() {
|
|
5786
|
+
return {
|
|
5787
|
+
reviewed: this.reviewed,
|
|
5788
|
+
publication: structuredClone(this.publication)
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
static resumed(operationId, command, progress) {
|
|
5792
|
+
if (command.intent.operationId !== operationId)
|
|
5793
|
+
throw new ContractError("invalid");
|
|
5794
|
+
const state = new OperationState(operationId);
|
|
5795
|
+
state.command = command;
|
|
5796
|
+
state.reviewed = progress.reviewed;
|
|
5797
|
+
state.publication = structuredClone(progress.publication);
|
|
5798
|
+
return state;
|
|
5799
|
+
}
|
|
5599
5800
|
publicationExpected() {
|
|
5600
5801
|
const identity = this.command?.intent.identity;
|
|
5601
5802
|
requireFact(identity?.kind === "action" && (!this.publication.intent || this.publication.status === "published"));
|
|
@@ -5621,7 +5822,7 @@ class OperationState {
|
|
|
5621
5822
|
}
|
|
5622
5823
|
function readOperationState(files, operationId) {
|
|
5623
5824
|
const state = new OperationState(operationId);
|
|
5624
|
-
for (const batch of files.scan(emptyScan(), false,
|
|
5825
|
+
for (const batch of files.scan(emptyScan(), false, HistoryReadBudget.authority()))
|
|
5625
5826
|
for (const row of batch.rows)
|
|
5626
5827
|
state.apply(row, batch.marker.last);
|
|
5627
5828
|
return state;
|
|
@@ -6247,7 +6448,7 @@ class HistoryNavigation {
|
|
|
6247
6448
|
return structuredClone(source.value);
|
|
6248
6449
|
}
|
|
6249
6450
|
try {
|
|
6250
|
-
if (!this.files.files.directory(dirname5(path)))
|
|
6451
|
+
if (!this.files.files.directory(dirname5(path), false, budget.walks))
|
|
6251
6452
|
return source ? structuredClone(source.value) : undefined;
|
|
6252
6453
|
const size = lstatSync6(path).size;
|
|
6253
6454
|
if (size > RECORD_BYTES)
|
|
@@ -6474,7 +6675,7 @@ class HistoryNavigation {
|
|
|
6474
6675
|
continue;
|
|
6475
6676
|
}
|
|
6476
6677
|
let pending = Buffer.alloc(0), at = offset, bodyBytes = 0;
|
|
6477
|
-
const checksum =
|
|
6678
|
+
const checksum = createHash7("sha256"), rows = [];
|
|
6478
6679
|
while (at < size) {
|
|
6479
6680
|
const chunk = this.block(path, at, size, budget);
|
|
6480
6681
|
at += chunk.length;
|
|
@@ -6758,16 +6959,41 @@ class HistoryNavigation {
|
|
|
6758
6959
|
throw new ContractError("receipt-inconclusive");
|
|
6759
6960
|
let head = expected;
|
|
6760
6961
|
while (head) {
|
|
6761
|
-
const
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6962
|
+
const link = this.link(roster ? "roster-links" : "entity-links", id, head, budget);
|
|
6963
|
+
yield this.batch(link.pointer, budget);
|
|
6964
|
+
head = link.previous;
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
link(group, id, head, budget) {
|
|
6968
|
+
const value = this.index(group, `${id}/${head.seq}`, budget);
|
|
6969
|
+
if (!value)
|
|
6970
|
+
throw new ContractError("receipt-inconclusive");
|
|
6971
|
+
const o = closed(value, ["pointer", "previous", "previousHash", "hash"]), p = pointer(o.pointer);
|
|
6972
|
+
const previous = o.previous === null ? null : decodeSequence(o.previous), previousHash = o.previousHash === null ? null : typeof o.previousHash === "string" ? o.previousHash : undefined;
|
|
6973
|
+
if (previousHash === undefined || previous === null !== (previousHash === null) || p.seq !== head.seq || o.hash !== head.hash || chainHash(p.seq, previous, previousHash) !== head.hash || previous !== null && BigInt(previous) >= BigInt(p.seq))
|
|
6974
|
+
throw new ContractError("receipt-inconclusive");
|
|
6975
|
+
return {
|
|
6976
|
+
pointer: p,
|
|
6977
|
+
previous: previous !== null && previousHash !== null ? { seq: previous, hash: previousHash } : null
|
|
6978
|
+
};
|
|
6979
|
+
}
|
|
6980
|
+
taskSuffix(taskId, snapshot, budget, accept) {
|
|
6981
|
+
decodeId(taskId);
|
|
6982
|
+
try {
|
|
6983
|
+
let head = this.anchoredHead(taskId, snapshot, budget) ?? null;
|
|
6984
|
+
const newer = [];
|
|
6985
|
+
while (head) {
|
|
6986
|
+
const base = accept(head);
|
|
6987
|
+
if (base !== undefined)
|
|
6988
|
+
return { base, batches: newer.reverse() };
|
|
6989
|
+
const link = this.link("entity-links", taskId, head, budget);
|
|
6990
|
+
newer.push(this.batch(link.pointer, budget));
|
|
6991
|
+
head = link.previous;
|
|
6992
|
+
}
|
|
6993
|
+
} catch {
|
|
6994
|
+
budget.reserve(0);
|
|
6770
6995
|
}
|
|
6996
|
+
return;
|
|
6771
6997
|
}
|
|
6772
6998
|
runBatches(id, snapshot, budget) {
|
|
6773
6999
|
const expected = this.anchoredHead(id, snapshot, budget);
|
|
@@ -6952,19 +7178,77 @@ function navigationKeys(row) {
|
|
|
6952
7178
|
}
|
|
6953
7179
|
|
|
6954
7180
|
// src/history/projection.ts
|
|
6955
|
-
import { createHash as
|
|
7181
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
7182
|
+
|
|
7183
|
+
// src/history/task-folds.ts
|
|
7184
|
+
var TASKS = 1024;
|
|
7185
|
+
var STATES = 8;
|
|
7186
|
+
|
|
7187
|
+
class TaskFoldMemory {
|
|
7188
|
+
tasks = new Map;
|
|
7189
|
+
has(taskId) {
|
|
7190
|
+
return this.tasks.has(taskId);
|
|
7191
|
+
}
|
|
7192
|
+
remember(taskId, anchor, state) {
|
|
7193
|
+
let states = this.tasks.get(taskId);
|
|
7194
|
+
if (!states) {
|
|
7195
|
+
if (this.tasks.size >= TASKS)
|
|
7196
|
+
this.tasks.delete(this.tasks.keys().next().value ?? "");
|
|
7197
|
+
states = new Map;
|
|
7198
|
+
this.tasks.set(taskId, states);
|
|
7199
|
+
}
|
|
7200
|
+
states.delete(anchor.seq);
|
|
7201
|
+
states.set(anchor.seq, {
|
|
7202
|
+
hash: anchor.hash,
|
|
7203
|
+
state: structuredClone(state)
|
|
7204
|
+
});
|
|
7205
|
+
if (states.size > STATES)
|
|
7206
|
+
states.delete(states.keys().next().value ?? "");
|
|
7207
|
+
}
|
|
7208
|
+
recall(taskId, anchor) {
|
|
7209
|
+
const entry = this.tasks.get(taskId)?.get(anchor.seq);
|
|
7210
|
+
return entry?.hash === anchor.hash ? structuredClone(entry.state) : undefined;
|
|
7211
|
+
}
|
|
7212
|
+
}
|
|
7213
|
+
var memories = new Map;
|
|
7214
|
+
function taskFoldMemory(files) {
|
|
7215
|
+
const key = `${files.directory}\x00${files.format.storeId}`;
|
|
7216
|
+
let memory = memories.get(key);
|
|
7217
|
+
if (!memory) {
|
|
7218
|
+
memory = new TaskFoldMemory;
|
|
7219
|
+
memories.set(key, memory);
|
|
7220
|
+
}
|
|
7221
|
+
return memory;
|
|
7222
|
+
}
|
|
7223
|
+
function resumeTaskFold(files, navigation, taskId, snapshot, budget, resume) {
|
|
7224
|
+
const memory = taskFoldMemory(files);
|
|
7225
|
+
const suffix = memory.has(taskId) ? navigation.taskSuffix(taskId, snapshot, budget, (head) => {
|
|
7226
|
+
const state = memory.recall(taskId, head);
|
|
7227
|
+
if (!state)
|
|
7228
|
+
return;
|
|
7229
|
+
try {
|
|
7230
|
+
return resume(state);
|
|
7231
|
+
} catch {
|
|
7232
|
+
budget.reserve(0);
|
|
7233
|
+
return;
|
|
7234
|
+
}
|
|
7235
|
+
}) : undefined;
|
|
7236
|
+
return suffix ?? {
|
|
7237
|
+
base: undefined,
|
|
7238
|
+
batches: navigation.entityBatches(taskId, snapshot, budget) ?? navigation.scanSnapshot(snapshot, budget)
|
|
7239
|
+
};
|
|
7240
|
+
}
|
|
6956
7241
|
|
|
6957
7242
|
// src/history/checks.ts
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
for (const row of b.rows) {
|
|
7243
|
+
class CheckFold {
|
|
7244
|
+
starts;
|
|
7245
|
+
settled;
|
|
7246
|
+
constructor(state) {
|
|
7247
|
+
this.starts = new Map(state?.starts);
|
|
7248
|
+
this.settled = new Map(state?.settled);
|
|
7249
|
+
}
|
|
7250
|
+
apply(batch, taskId) {
|
|
7251
|
+
for (const row of batch.rows) {
|
|
6968
7252
|
const v = row.value;
|
|
6969
7253
|
if (v.kind !== "owner" || v.link.taskId !== taskId)
|
|
6970
7254
|
continue;
|
|
@@ -6973,44 +7257,83 @@ function currentChecks(files, taskId, configured, current, snapshotSeq) {
|
|
|
6973
7257
|
continue;
|
|
6974
7258
|
const f = e.value.fact;
|
|
6975
7259
|
if (f.kind === "check-start")
|
|
6976
|
-
starts.set(f.checkId, {
|
|
7260
|
+
this.starts.set(f.checkId, {
|
|
6977
7261
|
selection: f.selection,
|
|
6978
7262
|
epoch: v.link.epoch,
|
|
6979
7263
|
cwdId: f.cwdId,
|
|
6980
7264
|
checkEnvGeneration: f.checkEnvGeneration
|
|
6981
7265
|
});
|
|
6982
7266
|
if (f.kind === "check-settled") {
|
|
6983
|
-
const s = starts.get(f.checkId);
|
|
7267
|
+
const s = this.starts.get(f.checkId);
|
|
6984
7268
|
if (!s)
|
|
6985
7269
|
throw new ContractError("corrupt-selected");
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
7270
|
+
this.settled.set(canonicalJson(s.selection), {
|
|
7271
|
+
...s,
|
|
7272
|
+
checkId: f.checkId,
|
|
7273
|
+
status: f.status,
|
|
7274
|
+
after: {
|
|
7275
|
+
identity: f.after.identity,
|
|
7276
|
+
complete: f.after.complete,
|
|
7277
|
+
checkoutGeneration: f.after.checkoutGeneration
|
|
7278
|
+
},
|
|
7279
|
+
incomplete: f.output === null || f.coverage.observed !== "complete" || f.coverage.source !== "complete" || f.coverage.omissions.length > 0
|
|
7280
|
+
});
|
|
7281
|
+
this.starts.delete(f.checkId);
|
|
6996
7282
|
}
|
|
6997
|
-
if (starts.size > LIMITS.childReservations
|
|
7283
|
+
if (this.starts.size > LIMITS.childReservations)
|
|
6998
7284
|
throw new ContractError("receipt-inconclusive");
|
|
6999
7285
|
}
|
|
7000
7286
|
}
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7287
|
+
}
|
|
7288
|
+
state() {
|
|
7289
|
+
return {
|
|
7290
|
+
starts: [...this.starts],
|
|
7291
|
+
settled: [...this.settled]
|
|
7292
|
+
};
|
|
7293
|
+
}
|
|
7294
|
+
evaluate(taskId, configured, current) {
|
|
7295
|
+
const results = new Map;
|
|
7296
|
+
const incomplete = new Map;
|
|
7297
|
+
for (const [key, s] of this.settled) {
|
|
7298
|
+
if (s.selection.kind !== "manual" && !configured.selections.some((c) => canonicalJson(c) === key))
|
|
7299
|
+
continue;
|
|
7300
|
+
const fresh = current.status === "available" && current.version.epoch === s.epoch && current.version.cwdId === s.cwdId && current.version.checkEnvGeneration === s.checkEnvGeneration && current.version.observation.identity === s.after.identity && current.version.observation.complete === s.after.complete && current.version.checkoutGeneration === s.after.checkoutGeneration;
|
|
7301
|
+
incomplete.set(key, s.incomplete);
|
|
7302
|
+
results.set(key, {
|
|
7303
|
+
selection: s.selection,
|
|
7304
|
+
status: s.status === "passed" && !fresh ? "stale" : s.status,
|
|
7305
|
+
checkId: s.checkId
|
|
7306
|
+
});
|
|
7307
|
+
}
|
|
7308
|
+
if (results.size > 32)
|
|
7309
|
+
throw new ContractError("receipt-inconclusive");
|
|
7310
|
+
return {
|
|
7311
|
+
checks: decodeCheckObligations({
|
|
7312
|
+
taskId,
|
|
7313
|
+
revision: configured.revision,
|
|
7314
|
+
definitions: configured.selections.map((selection) => results.get(canonicalJson(selection)) ?? {
|
|
7315
|
+
selection,
|
|
7316
|
+
status: "not-run",
|
|
7317
|
+
checkId: null
|
|
7318
|
+
})
|
|
7319
|
+
}),
|
|
7320
|
+
recorded: [...results.values()],
|
|
7321
|
+
recordingIncomplete: [...incomplete.values()].some(Boolean)
|
|
7322
|
+
};
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7325
|
+
function checkFold(files, taskId, snapshot, budget, navigation = new HistoryNavigation(files)) {
|
|
7326
|
+
navigation.snapshot(snapshot, budget);
|
|
7327
|
+
const { base, batches } = resumeTaskFold(files, navigation, taskId, snapshot, budget, (state) => state.checks);
|
|
7328
|
+
const fold = new CheckFold(base);
|
|
7329
|
+
for (const b of batches)
|
|
7330
|
+
fold.apply(b, taskId);
|
|
7331
|
+
return fold;
|
|
7332
|
+
}
|
|
7333
|
+
function currentChecks(files, taskId, configured, current, snapshotSeq) {
|
|
7334
|
+
const budget = HistoryReadBudget.authority(), navigation = new HistoryNavigation(files);
|
|
7335
|
+
const snapshot = snapshotSeq ?? navigation.latest(budget);
|
|
7336
|
+
return checkFold(files, taskId, snapshot, budget, navigation).evaluate(taskId, configured, current);
|
|
7014
7337
|
}
|
|
7015
7338
|
|
|
7016
7339
|
// src/history/entity-state.ts
|
|
@@ -7198,17 +7521,17 @@ function materializeEntity(s, sources) {
|
|
|
7198
7521
|
}
|
|
7199
7522
|
|
|
7200
7523
|
// src/history/export.ts
|
|
7201
|
-
import { createHash as
|
|
7524
|
+
import { createHash as createHash11, randomUUID as randomUUID4 } from "node:crypto";
|
|
7202
7525
|
import { readFileSync } from "node:fs";
|
|
7203
7526
|
|
|
7204
7527
|
// src/history/search.ts
|
|
7205
|
-
import { createHash as
|
|
7528
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
7206
7529
|
|
|
7207
7530
|
// src/history/read-proof.ts
|
|
7208
7531
|
import {
|
|
7209
7532
|
createCipheriv,
|
|
7210
7533
|
createDecipheriv,
|
|
7211
|
-
createHash as
|
|
7534
|
+
createHash as createHash8,
|
|
7212
7535
|
randomBytes as randomBytes2
|
|
7213
7536
|
} from "node:crypto";
|
|
7214
7537
|
var PREFIX = "rp1.";
|
|
@@ -7260,7 +7583,7 @@ class HistoryReadProof {
|
|
|
7260
7583
|
root: this.files.format.rootIdentity,
|
|
7261
7584
|
format: material.formatStamp,
|
|
7262
7585
|
key: material.keyStamp,
|
|
7263
|
-
request:
|
|
7586
|
+
request: createHash8("sha256").update(canonicalJson(readIdentity(request))).digest("hex")
|
|
7264
7587
|
}));
|
|
7265
7588
|
}
|
|
7266
7589
|
seal(context, plaintext, budget) {
|
|
@@ -8032,11 +8355,11 @@ function decodeRetainedProgress(value) {
|
|
|
8032
8355
|
}
|
|
8033
8356
|
|
|
8034
8357
|
// src/history/retained-traversal.ts
|
|
8035
|
-
import { createHash as
|
|
8358
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
8036
8359
|
function bad5() {
|
|
8037
8360
|
throw new ContractError("corrupt-selected");
|
|
8038
8361
|
}
|
|
8039
|
-
var fingerprint = (v) =>
|
|
8362
|
+
var fingerprint = (v) => createHash9("sha256").update(canonicalJson(v)).digest("hex");
|
|
8040
8363
|
var ready = (v) => {
|
|
8041
8364
|
const s = v.state;
|
|
8042
8365
|
if (s.phase !== "ready")
|
|
@@ -8392,11 +8715,11 @@ var digits = (id) => {
|
|
|
8392
8715
|
return s.slice(0, 12) + s.slice(13, 16) + s.slice(17);
|
|
8393
8716
|
};
|
|
8394
8717
|
var publicTextRef = (id, label, text2, field, batch, row, slot) => ({
|
|
8395
|
-
recordId: uuid(address(batch.pointer).toString(16).padStart(14, "0") +
|
|
8718
|
+
recordId: uuid(address(batch.pointer).toString(16).padStart(14, "0") + createHash10("sha256").update(`${id}/${label}`).digest("hex").slice(0, 16)),
|
|
8396
8719
|
version: batch.rows.indexOf(row) * EVENT_SLOTS + slot + 1,
|
|
8397
8720
|
field,
|
|
8398
8721
|
bytes: Buffer.byteLength(text2),
|
|
8399
|
-
sha256:
|
|
8722
|
+
sha256: createHash10("sha256").update(text2).digest("hex")
|
|
8400
8723
|
});
|
|
8401
8724
|
var initial = () => ({
|
|
8402
8725
|
pos: { segment: 1, offset: 0 },
|
|
@@ -8412,7 +8735,7 @@ function scanCursor(base, p) {
|
|
|
8412
8735
|
const snapshot = p.snapshot.toString(16).padStart(14, "0");
|
|
8413
8736
|
if (snapshot.length !== 14)
|
|
8414
8737
|
throw new ContractError("capacity");
|
|
8415
|
-
const binding =
|
|
8738
|
+
const binding = createHash10("sha256").update(canonicalJson({
|
|
8416
8739
|
...readIdentity(base),
|
|
8417
8740
|
recordId: null,
|
|
8418
8741
|
recordVersion: version,
|
|
@@ -8753,7 +9076,7 @@ class RetainedScan {
|
|
|
8753
9076
|
graph: {
|
|
8754
9077
|
recordId: v.intent.operationId,
|
|
8755
9078
|
revision: graph.revision,
|
|
8756
|
-
sha256:
|
|
9079
|
+
sha256: createHash10("sha256").update(canonicalJson(graph)).digest("hex")
|
|
8757
9080
|
}
|
|
8758
9081
|
},
|
|
8759
9082
|
graph
|
|
@@ -8941,6 +9264,9 @@ class RetainedScan {
|
|
|
8941
9264
|
}
|
|
8942
9265
|
}
|
|
8943
9266
|
async function searchHistory(reader, value, limit = LIMITS.pageEntries) {
|
|
9267
|
+
return retryStalled(() => searchQuantum(reader, value, limit));
|
|
9268
|
+
}
|
|
9269
|
+
async function searchQuantum(reader, value, limit = LIMITS.pageEntries) {
|
|
8944
9270
|
const cursor = decodeCursor(value);
|
|
8945
9271
|
if (!cursor.query || !Number.isInteger(limit) || limit < 1 || limit > LIMITS.maxPageEntries)
|
|
8946
9272
|
throw new ContractError("invalid");
|
|
@@ -9034,7 +9360,7 @@ async function searchHistory(reader, value, limit = LIMITS.pageEntries) {
|
|
|
9034
9360
|
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
9035
9361
|
throw error;
|
|
9036
9362
|
if (!hits.length && before === coordinate())
|
|
9037
|
-
|
|
9363
|
+
noProgress(originalBudget);
|
|
9038
9364
|
done = false;
|
|
9039
9365
|
}
|
|
9040
9366
|
scan.traversal?.assertCurrent();
|
|
@@ -9049,7 +9375,7 @@ async function searchHistory(reader, value, limit = LIMITS.pageEntries) {
|
|
|
9049
9375
|
// src/history/export.ts
|
|
9050
9376
|
var exporterIdentity;
|
|
9051
9377
|
function buildIdentity() {
|
|
9052
|
-
exporterIdentity ??=
|
|
9378
|
+
exporterIdentity ??= createHash11("sha256").update(readFileSync(new URL(import.meta.url))).digest("hex");
|
|
9053
9379
|
return exporterIdentity;
|
|
9054
9380
|
}
|
|
9055
9381
|
function aborted(signal) {
|
|
@@ -9109,7 +9435,7 @@ Coverage: ${canonicalJson(event.coverage)}
|
|
|
9109
9435
|
}
|
|
9110
9436
|
}
|
|
9111
9437
|
async function* exportHistory(projection, input, signal) {
|
|
9112
|
-
const request = decodeExportRequest(input), exportId = randomUUID4(), digest =
|
|
9438
|
+
const request = decodeExportRequest(input), exportId = randomUUID4(), digest = createHash11("sha256");
|
|
9113
9439
|
let ordinal = 0, encodedBytes = 0, coverage = { ...COMPLETE, omissions: [] };
|
|
9114
9440
|
const frame = (event) => {
|
|
9115
9441
|
aborted(signal);
|
|
@@ -9138,7 +9464,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9138
9464
|
snapshot: 0
|
|
9139
9465
|
};
|
|
9140
9466
|
const scan = new RetainedScan(projection.reader, request, new HistoryReadBudget, true);
|
|
9141
|
-
for (;; ) {
|
|
9467
|
+
for (let stalls2 = 0;; ) {
|
|
9142
9468
|
aborted(signal);
|
|
9143
9469
|
const before = canonicalJson({ point, state: scan.checkpoint() });
|
|
9144
9470
|
scan.renew(new HistoryReadBudget);
|
|
@@ -9146,8 +9472,13 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9146
9472
|
scan.prepare(point);
|
|
9147
9473
|
break;
|
|
9148
9474
|
} catch (error) {
|
|
9149
|
-
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive"
|
|
9475
|
+
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
9150
9476
|
throw error;
|
|
9477
|
+
if (before === canonicalJson({ point, state: scan.checkpoint() })) {
|
|
9478
|
+
if (!scan.budget.elapsed || ++stalls2 > STALL_RETRIES)
|
|
9479
|
+
throw error;
|
|
9480
|
+
} else
|
|
9481
|
+
stalls2 = 0;
|
|
9151
9482
|
await yieldTurn();
|
|
9152
9483
|
}
|
|
9153
9484
|
}
|
|
@@ -9173,7 +9504,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9173
9504
|
const { detail, owner } = item, original = open2.get(detail.content.recordId);
|
|
9174
9505
|
if (detail.content.bytes ? !original || original.bytes !== detail.content.bytes || original.content !== canonicalJson(detail.content) : original !== undefined)
|
|
9175
9506
|
throw new ContractError("corrupt-selected");
|
|
9176
|
-
if ((original?.hash ??
|
|
9507
|
+
if ((original?.hash ?? createHash11("sha256")).digest("hex") !== detail.content.sha256)
|
|
9177
9508
|
throw new ContractError("corrupt-selected");
|
|
9178
9509
|
open2.delete(detail.content.recordId);
|
|
9179
9510
|
const events = [];
|
|
@@ -9204,7 +9535,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9204
9535
|
coverage = combineCoverage(coverage, item.coverage);
|
|
9205
9536
|
const length = Buffer.byteLength(item.text);
|
|
9206
9537
|
if (item.inline) {
|
|
9207
|
-
if (item.start !== 0 || length !== item.content.bytes ||
|
|
9538
|
+
if (item.start !== 0 || length !== item.content.bytes || createHash11("sha256").update(item.text).digest("hex") !== item.content.sha256)
|
|
9208
9539
|
throw new ContractError("corrupt-selected");
|
|
9209
9540
|
} else {
|
|
9210
9541
|
let original = open2.get(item.content.recordId);
|
|
@@ -9214,7 +9545,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9214
9545
|
original = {
|
|
9215
9546
|
bytes: 0,
|
|
9216
9547
|
content: canonicalJson(item.content),
|
|
9217
|
-
hash:
|
|
9548
|
+
hash: createHash11("sha256")
|
|
9218
9549
|
};
|
|
9219
9550
|
open2.set(item.content.recordId, original);
|
|
9220
9551
|
}
|
|
@@ -9234,7 +9565,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9234
9565
|
}
|
|
9235
9566
|
];
|
|
9236
9567
|
};
|
|
9237
|
-
let finished = false;
|
|
9568
|
+
let finished = false, stalls = 0;
|
|
9238
9569
|
while (!finished) {
|
|
9239
9570
|
aborted(signal);
|
|
9240
9571
|
const before = canonicalJson({ point, state: scan.checkpoint() });
|
|
@@ -9293,8 +9624,13 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9293
9624
|
if (finished)
|
|
9294
9625
|
scan.finish();
|
|
9295
9626
|
} catch (error) {
|
|
9296
|
-
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive"
|
|
9627
|
+
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
9297
9628
|
throw error;
|
|
9629
|
+
if (before === canonicalJson({ point, state: scan.checkpoint() })) {
|
|
9630
|
+
if (!scan.budget.elapsed || ++stalls > STALL_RETRIES)
|
|
9631
|
+
throw error;
|
|
9632
|
+
} else
|
|
9633
|
+
stalls = 0;
|
|
9298
9634
|
finished = false;
|
|
9299
9635
|
}
|
|
9300
9636
|
coverage = combineCoverage(coverage, scan.coverage);
|
|
@@ -9324,7 +9660,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9324
9660
|
}
|
|
9325
9661
|
|
|
9326
9662
|
// src/history/list-progress.ts
|
|
9327
|
-
import { createHash as
|
|
9663
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
9328
9664
|
|
|
9329
9665
|
// src/history/detail-progress.ts
|
|
9330
9666
|
function bad7() {
|
|
@@ -9365,6 +9701,9 @@ function detailPreparation(cursor, completion) {
|
|
|
9365
9701
|
});
|
|
9366
9702
|
}
|
|
9367
9703
|
async function readRetainedDetail(reader, input) {
|
|
9704
|
+
return retryStalled(() => detailQuantum(reader, input));
|
|
9705
|
+
}
|
|
9706
|
+
async function detailQuantum(reader, input) {
|
|
9368
9707
|
const cursor = decodeCursor(input);
|
|
9369
9708
|
if (cursor.storeId !== reader.storeId || !cursor.recordId)
|
|
9370
9709
|
bad7();
|
|
@@ -9466,8 +9805,10 @@ async function readRetainedDetail(reader, input) {
|
|
|
9466
9805
|
p.event = 0;
|
|
9467
9806
|
}
|
|
9468
9807
|
} catch (error) {
|
|
9469
|
-
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive"
|
|
9808
|
+
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
9470
9809
|
throw error;
|
|
9810
|
+
if (coordinate() === before)
|
|
9811
|
+
noProgress(original);
|
|
9471
9812
|
return decodeDetailReadResult({
|
|
9472
9813
|
kind: "progress",
|
|
9473
9814
|
cursor,
|
|
@@ -9793,7 +10134,7 @@ function fits(link, scope) {
|
|
|
9793
10134
|
return scope.kind === "workspace" || (scope.kind === "task" ? link.taskId === scope.id : scope.kind === "run" ? link.runId === scope.id : link.childRunId === scope.id);
|
|
9794
10135
|
}
|
|
9795
10136
|
function nextAfter(request, n) {
|
|
9796
|
-
const h =
|
|
10137
|
+
const h = createHash12("sha256").update(canonicalJson({ ...readIdentity(request), after: null, ordinal: n })).digest("hex");
|
|
9797
10138
|
return {
|
|
9798
10139
|
recordId: `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-8${h.slice(17, 20)}-${h.slice(20, 32)}`,
|
|
9799
10140
|
version: 1,
|
|
@@ -9900,10 +10241,10 @@ function advanceTarget(p, t, snapshot, budget, sources) {
|
|
|
9900
10241
|
}
|
|
9901
10242
|
}
|
|
9902
10243
|
async function readRetainedList(p, request) {
|
|
9903
|
-
return read(p, request, false);
|
|
10244
|
+
return retryStalled(() => read(p, request, false));
|
|
9904
10245
|
}
|
|
9905
10246
|
async function readRetainedEntity(p, request) {
|
|
9906
|
-
return read(p, request, true);
|
|
10247
|
+
return retryStalled(() => read(p, request, true));
|
|
9907
10248
|
}
|
|
9908
10249
|
async function read(p, request, entity) {
|
|
9909
10250
|
const budget = new HistoryReadBudget, proof = new HistoryReadProof(p.reader.files), completion = proof.prepareCompletion(budget);
|
|
@@ -9932,7 +10273,7 @@ async function read(p, request, entity) {
|
|
|
9932
10273
|
};
|
|
9933
10274
|
const progress = () => {
|
|
9934
10275
|
if (coordinate() === before)
|
|
9935
|
-
|
|
10276
|
+
noProgress(budget);
|
|
9936
10277
|
const next = seal(request);
|
|
9937
10278
|
return entity ? decodeEntityReadResult({
|
|
9938
10279
|
kind: "progress",
|
|
@@ -10177,7 +10518,7 @@ async function read(p, request, entity) {
|
|
|
10177
10518
|
const tasks = [...p.environment.legacyTasks(budget)].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
10178
10519
|
if (tasks.length > 1000)
|
|
10179
10520
|
throw new ContractError("capacity");
|
|
10180
|
-
const sum =
|
|
10521
|
+
const sum = createHash12("sha256");
|
|
10181
10522
|
for (const task2 of tasks) {
|
|
10182
10523
|
budget.reserve(0);
|
|
10183
10524
|
sum.update(JSON.stringify({
|
|
@@ -10190,7 +10531,7 @@ async function read(p, request, entity) {
|
|
|
10190
10531
|
if (s.legacy > tasks.length)
|
|
10191
10532
|
bad9();
|
|
10192
10533
|
if (!s.legacyIdentity && request.after?.version === Number.MAX_SAFE_INTEGER) {
|
|
10193
|
-
const h =
|
|
10534
|
+
const h = createHash12("sha256").update(canonicalJson({
|
|
10194
10535
|
...readIdentity(request),
|
|
10195
10536
|
after: null,
|
|
10196
10537
|
legacy: s.legacy === 0 ? "begin" : identity,
|
|
@@ -10470,7 +10811,7 @@ class HistoryProjection {
|
|
|
10470
10811
|
};
|
|
10471
10812
|
cursor(request, pos, row, item) {
|
|
10472
10813
|
const offset = pos.offset * (LIMITS.segmentRecords + 1) * ROW_SLOTS + row * ROW_SLOTS + item;
|
|
10473
|
-
const digest =
|
|
10814
|
+
const digest = createHash13("sha256").update(canonicalJson({
|
|
10474
10815
|
...readIdentity(request),
|
|
10475
10816
|
after: null,
|
|
10476
10817
|
position: { ...pos, row, item }
|
|
@@ -10661,7 +11002,7 @@ class HistoryProjection {
|
|
|
10661
11002
|
if (legacyPhase)
|
|
10662
11003
|
this.navigation.snapshot(snapshot, budget);
|
|
10663
11004
|
const cursor = (offset2, identity2) => {
|
|
10664
|
-
const digest2 =
|
|
11005
|
+
const digest2 = createHash13("sha256").update(canonicalJson({ ...request, after: null, legacy: identity2, offset: offset2 })).digest("hex");
|
|
10665
11006
|
return {
|
|
10666
11007
|
recordId: `${digest2.slice(0, 8)}-${digest2.slice(8, 12)}-4${digest2.slice(13, 16)}-8${digest2.slice(17, 20)}-${digest2.slice(20, 32)}`,
|
|
10667
11008
|
version: legacyVersion,
|
|
@@ -10688,7 +11029,7 @@ class HistoryProjection {
|
|
|
10688
11029
|
}
|
|
10689
11030
|
if (legacy.length > 1000)
|
|
10690
11031
|
throw new ContractError("capacity");
|
|
10691
|
-
const items = [...legacy].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0), digest =
|
|
11032
|
+
const items = [...legacy].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0), digest = createHash13("sha256");
|
|
10692
11033
|
for (const item of items) {
|
|
10693
11034
|
budget.reserve(0);
|
|
10694
11035
|
if (Buffer.byteLength(item.goal) > 8192)
|
|
@@ -10878,7 +11219,7 @@ class HistoryProjection {
|
|
|
10878
11219
|
}
|
|
10879
11220
|
|
|
10880
11221
|
// src/history/reader.ts
|
|
10881
|
-
import { createHash as
|
|
11222
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
10882
11223
|
import { existsSync } from "node:fs";
|
|
10883
11224
|
import { join as join6 } from "node:path";
|
|
10884
11225
|
|
|
@@ -11010,6 +11351,9 @@ function graphPreparation(request, completion) {
|
|
|
11010
11351
|
});
|
|
11011
11352
|
}
|
|
11012
11353
|
async function readRetainedGraph(reader, request) {
|
|
11354
|
+
return retryStalled(() => graphQuantum(reader, request));
|
|
11355
|
+
}
|
|
11356
|
+
async function graphQuantum(reader, request) {
|
|
11013
11357
|
if (request.storeId !== reader.storeId)
|
|
11014
11358
|
throw new ContractError("corrupt-selected");
|
|
11015
11359
|
const budget = new HistoryReadBudget, proof = new HistoryReadProof(reader.files), completion = proof.prepareCompletion(budget);
|
|
@@ -11069,8 +11413,10 @@ async function readRetainedGraph(reader, request) {
|
|
|
11069
11413
|
point.event = 0;
|
|
11070
11414
|
}
|
|
11071
11415
|
} catch (error) {
|
|
11072
|
-
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive"
|
|
11416
|
+
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
11073
11417
|
throw error;
|
|
11418
|
+
if (coordinate() === before)
|
|
11419
|
+
noProgress(budget);
|
|
11074
11420
|
traversal.assertCurrent();
|
|
11075
11421
|
const next = {
|
|
11076
11422
|
...readIdentity(request),
|
|
@@ -11089,6 +11435,95 @@ async function readRetainedGraph(reader, request) {
|
|
|
11089
11435
|
}
|
|
11090
11436
|
|
|
11091
11437
|
// src/history/reader.ts
|
|
11438
|
+
class PlanFold {
|
|
11439
|
+
lineage;
|
|
11440
|
+
operation = null;
|
|
11441
|
+
graphIdentity = "";
|
|
11442
|
+
savePlanAt = null;
|
|
11443
|
+
lineageAt = null;
|
|
11444
|
+
constructor(lineage) {
|
|
11445
|
+
this.lineage = lineage;
|
|
11446
|
+
}
|
|
11447
|
+
apply(batch, taskId) {
|
|
11448
|
+
batch.rows.forEach((row, index) => {
|
|
11449
|
+
const value = row.value;
|
|
11450
|
+
if (value.kind === "admission" && value.intent.identity.kind === "action" && value.intent.identity.action.kind === "save-plan" && value.intent.identity.action.taskId === taskId) {
|
|
11451
|
+
if (value.intent.runId !== null)
|
|
11452
|
+
throw new ContractError("corrupt-selected");
|
|
11453
|
+
this.operation = new OperationState(value.intent.operationId);
|
|
11454
|
+
this.graphIdentity = hash(canonicalJson(value.intent.identity.action.graph));
|
|
11455
|
+
this.savePlanAt = { pointer: batch.pointer, row: index };
|
|
11456
|
+
}
|
|
11457
|
+
this.operation?.apply(row, batch.pointer.seq);
|
|
11458
|
+
if (!this.lineage)
|
|
11459
|
+
return;
|
|
11460
|
+
this.lineage.apply(row, batch.pointer.seq, taskId, this.graphIdentity);
|
|
11461
|
+
if (value.kind === "admission" && this.lineage.operationId === value.intent.operationId)
|
|
11462
|
+
this.lineageAt = { pointer: batch.pointer, row: index };
|
|
11463
|
+
});
|
|
11464
|
+
}
|
|
11465
|
+
state() {
|
|
11466
|
+
if (!this.lineage)
|
|
11467
|
+
throw new ContractError("invalid");
|
|
11468
|
+
return {
|
|
11469
|
+
savePlan: this.operation && this.savePlanAt ? { at: this.savePlanAt, ...this.operation.progress() } : null,
|
|
11470
|
+
graphIdentity: this.graphIdentity,
|
|
11471
|
+
lineage: { at: this.lineageAt, progress: this.lineage.progress() }
|
|
11472
|
+
};
|
|
11473
|
+
}
|
|
11474
|
+
static resumed(state, withLineage, read2) {
|
|
11475
|
+
let lineage = null;
|
|
11476
|
+
if (withLineage) {
|
|
11477
|
+
const row = state.lineage.at && read2(state.lineage.at).value;
|
|
11478
|
+
if (row && row.kind !== "admission")
|
|
11479
|
+
throw new ContractError("invalid");
|
|
11480
|
+
lineage = TaskPublicationLineage.resumed(row ? row.intent : null, state.lineage.progress);
|
|
11481
|
+
}
|
|
11482
|
+
const fold = new PlanFold(lineage);
|
|
11483
|
+
fold.lineageAt = state.lineage.at;
|
|
11484
|
+
fold.graphIdentity = state.graphIdentity;
|
|
11485
|
+
if (!state.savePlan) {
|
|
11486
|
+
if (state.graphIdentity !== "")
|
|
11487
|
+
throw new ContractError("invalid");
|
|
11488
|
+
return fold;
|
|
11489
|
+
}
|
|
11490
|
+
const v = read2(state.savePlan.at).value;
|
|
11491
|
+
if (v.kind !== "admission" || v.intent.identity.kind !== "action" || v.intent.identity.action.kind !== "save-plan" || hash(canonicalJson(v.intent.identity.action.graph)) !== state.graphIdentity)
|
|
11492
|
+
throw new ContractError("invalid");
|
|
11493
|
+
fold.operation = OperationState.resumed(v.intent.operationId, { intent: v.intent, record: v.record }, state.savePlan);
|
|
11494
|
+
fold.savePlanAt = state.savePlan.at;
|
|
11495
|
+
return fold;
|
|
11496
|
+
}
|
|
11497
|
+
}
|
|
11498
|
+
function planFold(files, taskId, snapshot, budget, navigation, withLineage) {
|
|
11499
|
+
navigation.snapshot(snapshot, budget);
|
|
11500
|
+
const read2 = (at) => {
|
|
11501
|
+
if (BigInt(at.pointer.seq) > BigInt(snapshot))
|
|
11502
|
+
throw new ContractError("invalid");
|
|
11503
|
+
const row = navigation.batch(at.pointer, budget).rows[at.row];
|
|
11504
|
+
if (!row)
|
|
11505
|
+
throw new ContractError("invalid");
|
|
11506
|
+
return row;
|
|
11507
|
+
};
|
|
11508
|
+
const { base, batches } = resumeTaskFold(files, navigation, taskId, snapshot, budget, (state) => PlanFold.resumed(state.plan, withLineage, read2));
|
|
11509
|
+
const fold = base ?? new PlanFold(withLineage ? new TaskPublicationLineage : null);
|
|
11510
|
+
for (const batch of batches)
|
|
11511
|
+
fold.apply(batch, taskId);
|
|
11512
|
+
return fold;
|
|
11513
|
+
}
|
|
11514
|
+
function latestTaskRun(files, taskId, snapshot, budget, navigation = new HistoryNavigation(files)) {
|
|
11515
|
+
navigation.snapshot(snapshot, budget);
|
|
11516
|
+
const { base, batches } = resumeTaskFold(files, navigation, taskId, snapshot, budget, (state) => state.run);
|
|
11517
|
+
let latest = base ?? null;
|
|
11518
|
+
for (const batch of batches)
|
|
11519
|
+
for (const row of batch.rows) {
|
|
11520
|
+
const v = row.value;
|
|
11521
|
+
if (v.kind === "admission" && v.intent.identity.kind === "action" && v.intent.identity.expected.task?.id === taskId && (v.intent.identity.action.kind === "start" || v.intent.identity.action.kind === "generate-plan"))
|
|
11522
|
+
latest = v.intent.runId;
|
|
11523
|
+
}
|
|
11524
|
+
return latest;
|
|
11525
|
+
}
|
|
11526
|
+
|
|
11092
11527
|
class HistoryReader {
|
|
11093
11528
|
files;
|
|
11094
11529
|
cached;
|
|
@@ -11104,7 +11539,7 @@ class HistoryReader {
|
|
|
11104
11539
|
admission(runId, snapshotSeq) {
|
|
11105
11540
|
decodeId(runId);
|
|
11106
11541
|
decodeSequence(snapshotSeq);
|
|
11107
|
-
const budget =
|
|
11542
|
+
const budget = HistoryReadBudget.authority();
|
|
11108
11543
|
this.navigation.snapshot(snapshotSeq, budget);
|
|
11109
11544
|
const batches = this.navigation.entityBatches(runId, snapshotSeq, budget);
|
|
11110
11545
|
const state = batches ? new RunState(runId) : readRunState(this.files, runId, snapshotSeq, budget);
|
|
@@ -11119,26 +11554,9 @@ class HistoryReader {
|
|
|
11119
11554
|
publishedPlan(taskId, snapshotSeq, current2) {
|
|
11120
11555
|
decodeId(taskId);
|
|
11121
11556
|
decodeSequence(snapshotSeq);
|
|
11122
|
-
const budget =
|
|
11123
|
-
this.
|
|
11124
|
-
const
|
|
11125
|
-
let operation = null;
|
|
11126
|
-
const lineage = new TaskPublicationLineage;
|
|
11127
|
-
let graphIdentity = "";
|
|
11128
|
-
for (const batch of batches) {
|
|
11129
|
-
for (const row of batch.rows) {
|
|
11130
|
-
const value = row.value;
|
|
11131
|
-
if (value.kind === "admission" && value.intent.identity.kind === "action" && value.intent.identity.action.kind === "save-plan" && value.intent.identity.action.taskId === taskId) {
|
|
11132
|
-
if (value.intent.runId !== null)
|
|
11133
|
-
throw new ContractError("corrupt-selected");
|
|
11134
|
-
operation = new OperationState(value.intent.operationId);
|
|
11135
|
-
graphIdentity = hash(canonicalJson(value.intent.identity.action.graph));
|
|
11136
|
-
}
|
|
11137
|
-
operation?.apply(row, batch.pointer.seq);
|
|
11138
|
-
if (current2)
|
|
11139
|
-
lineage.apply(row, batch.pointer.seq, taskId, graphIdentity);
|
|
11140
|
-
}
|
|
11141
|
-
}
|
|
11557
|
+
const budget = HistoryReadBudget.authority();
|
|
11558
|
+
const fold = planFold(this.files, taskId, snapshotSeq, budget, this.navigation, !!current2);
|
|
11559
|
+
const { operation, lineage } = fold;
|
|
11142
11560
|
if (!operation)
|
|
11143
11561
|
return null;
|
|
11144
11562
|
const command = operation.command;
|
|
@@ -11149,7 +11567,7 @@ class HistoryReader {
|
|
|
11149
11567
|
throw new ContractError("conflict");
|
|
11150
11568
|
const { graph, plan } = command.intent.identity.action;
|
|
11151
11569
|
const ref = graph.plan;
|
|
11152
|
-
const detail = this.
|
|
11570
|
+
const { detail, bytes: text2 } = this.planText({
|
|
11153
11571
|
storeId: this.storeId,
|
|
11154
11572
|
scope: { kind: "task", id: taskId },
|
|
11155
11573
|
snapshotSeq,
|
|
@@ -11161,13 +11579,10 @@ class HistoryReader {
|
|
|
11161
11579
|
}, budget);
|
|
11162
11580
|
if (canonicalJson(detail.content) !== canonicalJson(ref) || detail.origin.kind !== "owner" || detail.origin.id !== operation.operationId || detail.owner.taskId !== taskId || detail.owner.runId !== null || detail.owner.childRunId !== null || detail.owner.entity.kind !== "task" || detail.owner.entity.id !== taskId || detail.owner.entity.version !== graph.planRevision || ref.bytes > 32768)
|
|
11163
11581
|
throw new ContractError("corrupt-selected");
|
|
11164
|
-
|
|
11165
|
-
for (const part of this.parts(detail, snapshotSeq, budget))
|
|
11166
|
-
chunks.push(part.bytes);
|
|
11167
|
-
if (Buffer.concat(chunks).toString("utf8") !== plan)
|
|
11582
|
+
if (text2.toString("utf8") !== plan)
|
|
11168
11583
|
throw new ContractError("corrupt-selected");
|
|
11169
|
-
const publication = current2 ? lineage
|
|
11170
|
-
if (!publication
|
|
11584
|
+
const publication = current2 ? lineage?.current() : operation.publicationExpected();
|
|
11585
|
+
if (!publication?.identity)
|
|
11171
11586
|
throw new ContractError("corrupt-selected");
|
|
11172
11587
|
if (current2 && (current2.identity !== publication.identity || current2.revision !== publication.revision || current2.plan !== plan))
|
|
11173
11588
|
throw new ContractError("stale");
|
|
@@ -11186,19 +11601,11 @@ class HistoryReader {
|
|
|
11186
11601
|
committedSeq: receipt.receipt.committedSeq
|
|
11187
11602
|
};
|
|
11188
11603
|
}
|
|
11189
|
-
reviewRun(taskId, runId, snapshotSeq, budget =
|
|
11604
|
+
reviewRun(taskId, runId, snapshotSeq, budget = HistoryReadBudget.authority()) {
|
|
11190
11605
|
decodeId(taskId);
|
|
11191
11606
|
decodeId(runId);
|
|
11192
11607
|
decodeSequence(snapshotSeq);
|
|
11193
|
-
this.
|
|
11194
|
-
const batches = () => this.navigation.entityBatches(taskId, snapshotSeq, budget) ?? this.navigation.scanSnapshot(snapshotSeq, budget);
|
|
11195
|
-
let latest = null;
|
|
11196
|
-
for (const batch of batches())
|
|
11197
|
-
for (const row of batch.rows) {
|
|
11198
|
-
const v = row.value;
|
|
11199
|
-
if (v.kind === "admission" && v.intent.identity.kind === "action" && v.intent.identity.expected.task?.id === taskId && (v.intent.identity.action.kind === "start" || v.intent.identity.action.kind === "generate-plan"))
|
|
11200
|
-
latest = v.intent.runId;
|
|
11201
|
-
}
|
|
11608
|
+
const latest = latestTaskRun(this.files, taskId, snapshotSeq, budget, this.navigation);
|
|
11202
11609
|
if (latest !== runId)
|
|
11203
11610
|
throw new ContractError("stale");
|
|
11204
11611
|
const state = new RunState(runId);
|
|
@@ -11212,7 +11619,7 @@ class HistoryReader {
|
|
|
11212
11619
|
return state;
|
|
11213
11620
|
}
|
|
11214
11621
|
reviewSelection(taskId, runId, snapshotSeq) {
|
|
11215
|
-
const budget =
|
|
11622
|
+
const budget = HistoryReadBudget.authority();
|
|
11216
11623
|
const run = this.reviewRun(taskId, runId, snapshotSeq, budget);
|
|
11217
11624
|
if (!run.admission)
|
|
11218
11625
|
throw new ContractError("corrupt-selected");
|
|
@@ -11220,7 +11627,7 @@ class HistoryReader {
|
|
|
11220
11627
|
if (!graph)
|
|
11221
11628
|
return { run, graph, readablePlan: "" };
|
|
11222
11629
|
const ref = graph.plan;
|
|
11223
|
-
const detail = this.
|
|
11630
|
+
const { detail, bytes } = this.planText({
|
|
11224
11631
|
storeId: this.storeId,
|
|
11225
11632
|
scope: { kind: "task", id: taskId },
|
|
11226
11633
|
snapshotSeq,
|
|
@@ -11232,7 +11639,6 @@ class HistoryReader {
|
|
|
11232
11639
|
}, budget);
|
|
11233
11640
|
if (ref.bytes > 32768 || canonicalJson(detail.content) !== canonicalJson(ref) || detail.owner.taskId !== taskId || detail.owner.runId !== null || detail.owner.childRunId !== null || detail.owner.entity.kind !== "task" || detail.owner.entity.id !== taskId || detail.owner.entity.version !== graph.planRevision)
|
|
11234
11641
|
throw new ContractError("corrupt-selected");
|
|
11235
|
-
const bytes = Buffer.concat([...this.parts(detail, snapshotSeq, budget)].map((p) => p.bytes));
|
|
11236
11642
|
return { run, graph, readablePlan: bytes.toString("utf8") };
|
|
11237
11643
|
}
|
|
11238
11644
|
async graph(value) {
|
|
@@ -11437,6 +11843,52 @@ class HistoryReader {
|
|
|
11437
11843
|
throw new ContractError("invalid");
|
|
11438
11844
|
return this.cached.detail;
|
|
11439
11845
|
}
|
|
11846
|
+
planText(cursor, budget) {
|
|
11847
|
+
const indexed = this.indexedText(cursor, budget);
|
|
11848
|
+
if (indexed)
|
|
11849
|
+
return indexed;
|
|
11850
|
+
const detail = this.selected(cursor, budget);
|
|
11851
|
+
return {
|
|
11852
|
+
detail,
|
|
11853
|
+
bytes: Buffer.concat([...this.parts(detail, cursor.snapshotSeq, budget)].map((p) => p.bytes))
|
|
11854
|
+
};
|
|
11855
|
+
}
|
|
11856
|
+
indexedText(cursor, budget) {
|
|
11857
|
+
const nav = this.navigation, id = cursor.recordId;
|
|
11858
|
+
if (!id)
|
|
11859
|
+
return;
|
|
11860
|
+
try {
|
|
11861
|
+
this.files.assertReadFormat(budget);
|
|
11862
|
+
nav.snapshot(cursor.snapshotSeq, budget);
|
|
11863
|
+
const detail = nav.content(id, cursor.snapshotSeq, budget);
|
|
11864
|
+
if (!detail || detail.content.version !== cursor.recordVersion || detail.content.field !== cursor.field)
|
|
11865
|
+
return;
|
|
11866
|
+
const pieces = [];
|
|
11867
|
+
let end = 0;
|
|
11868
|
+
if (detail.content.bytes) {
|
|
11869
|
+
const count2 = nav.partCount(id, budget);
|
|
11870
|
+
for (let part = 0;part < count2; part++) {
|
|
11871
|
+
const chunks = nav.part(id, part, cursor.snapshotSeq, budget);
|
|
11872
|
+
if (!chunks?.length)
|
|
11873
|
+
return;
|
|
11874
|
+
const bytes = this.files.readArtifact(chunks[0].artifact, budget);
|
|
11875
|
+
for (const c of chunks) {
|
|
11876
|
+
if (c.offset !== end || c.start + c.length > bytes.length)
|
|
11877
|
+
return;
|
|
11878
|
+
pieces.push(bytes.subarray(c.start, c.start + c.length));
|
|
11879
|
+
end += c.length;
|
|
11880
|
+
}
|
|
11881
|
+
}
|
|
11882
|
+
}
|
|
11883
|
+
const text2 = Buffer.concat(pieces);
|
|
11884
|
+
if (end !== detail.content.bytes || createHash14("sha256").update(text2).digest("hex") !== detail.content.sha256)
|
|
11885
|
+
return;
|
|
11886
|
+
return { detail, bytes: text2 };
|
|
11887
|
+
} catch {
|
|
11888
|
+
budget.reserve(0);
|
|
11889
|
+
return;
|
|
11890
|
+
}
|
|
11891
|
+
}
|
|
11440
11892
|
*parts(detail, snapshotSeq, budget, range2) {
|
|
11441
11893
|
if (budget)
|
|
11442
11894
|
this.files.assertReadFormat(budget);
|
|
@@ -11464,7 +11916,7 @@ class HistoryReader {
|
|
|
11464
11916
|
if (!current2 || current2.length < expected.bytes || hash(current2.subarray(0, expected.bytes)) !== expected.identity)
|
|
11465
11917
|
throw new ContractError("corrupt-selected");
|
|
11466
11918
|
}
|
|
11467
|
-
const checksum =
|
|
11919
|
+
const checksum = createHash14("sha256");
|
|
11468
11920
|
let bytes = 0;
|
|
11469
11921
|
let cached;
|
|
11470
11922
|
for (const e of locator.chunks) {
|
|
@@ -11738,7 +12190,7 @@ class HistoryReader {
|
|
|
11738
12190
|
}
|
|
11739
12191
|
|
|
11740
12192
|
// src/history/writer.ts
|
|
11741
|
-
import { createHash as
|
|
12193
|
+
import { createHash as createHash16, randomUUID as randomUUID6 } from "node:crypto";
|
|
11742
12194
|
import { join as join8 } from "node:path";
|
|
11743
12195
|
|
|
11744
12196
|
// src/worker-redaction.mjs
|
|
@@ -12100,11 +12552,11 @@ class DetailSanitizer {
|
|
|
12100
12552
|
}
|
|
12101
12553
|
|
|
12102
12554
|
// src/history/plans.ts
|
|
12103
|
-
import { createHash as
|
|
12555
|
+
import { createHash as createHash15, randomUUID as randomUUID5 } from "node:crypto";
|
|
12104
12556
|
function assertPlanContent(plan, reference, secrets) {
|
|
12105
12557
|
text(plan, 32768);
|
|
12106
12558
|
const ref = decodeContentRef(reference);
|
|
12107
|
-
if (ref.field !== "plan" || ref.bytes !== Buffer.byteLength(plan) || ref.sha256 !==
|
|
12559
|
+
if (ref.field !== "plan" || ref.bytes !== Buffer.byteLength(plan) || ref.sha256 !== createHash15("sha256").update(plan).digest("hex") || sanitizeText(plan, secrets) !== plan)
|
|
12108
12560
|
throw new ContractError("invalid");
|
|
12109
12561
|
}
|
|
12110
12562
|
function proposePlanContent(plan, version, secrets) {
|
|
@@ -12114,7 +12566,7 @@ function proposePlanContent(plan, version, secrets) {
|
|
|
12114
12566
|
version,
|
|
12115
12567
|
field: "plan",
|
|
12116
12568
|
bytes: Buffer.byteLength(plan),
|
|
12117
|
-
sha256:
|
|
12569
|
+
sha256: createHash15("sha256").update(plan).digest("hex")
|
|
12118
12570
|
});
|
|
12119
12571
|
assertPlanContent(plan, reference, secrets);
|
|
12120
12572
|
return reference;
|
|
@@ -12833,9 +13285,9 @@ class HistoryWriter {
|
|
|
12833
13285
|
const continuation = jobs.every((j) => j.safety || j.continuation);
|
|
12834
13286
|
try {
|
|
12835
13287
|
this.assertWritable(safety, continuation);
|
|
12836
|
-
for (const
|
|
13288
|
+
for (const job2 of jobs) {
|
|
12837
13289
|
const seq2 = String(BigInt(this.state.committedSeq) + BigInt(rows.length) + 1n);
|
|
12838
|
-
const value = await
|
|
13290
|
+
const value = await job2.build(seq2);
|
|
12839
13291
|
if (value) {
|
|
12840
13292
|
const row = {
|
|
12841
13293
|
kind: "record",
|
|
@@ -12844,7 +13296,7 @@ class HistoryWriter {
|
|
|
12844
13296
|
value
|
|
12845
13297
|
};
|
|
12846
13298
|
rows.push(row);
|
|
12847
|
-
ownRows.set(
|
|
13299
|
+
ownRows.set(job2, row);
|
|
12848
13300
|
}
|
|
12849
13301
|
}
|
|
12850
13302
|
if (rows.length) {
|
|
@@ -12900,8 +13352,8 @@ class HistoryWriter {
|
|
|
12900
13352
|
this.assertReviews(rows);
|
|
12901
13353
|
}, preparedIndex?.root, (encodedBatch) => {
|
|
12902
13354
|
let markerBytes = encodedBatch.trailerBytes, index = 0;
|
|
12903
|
-
for (const [
|
|
12904
|
-
this.charge(encodedBatch.rowBytes[index++] + markerBytes, this.rowRunId(row.value),
|
|
13355
|
+
for (const [job2, row] of ownRows) {
|
|
13356
|
+
this.charge(encodedBatch.rowBytes[index++] + markerBytes, this.rowRunId(row.value), job2.control);
|
|
12905
13357
|
markerBytes = 0;
|
|
12906
13358
|
}
|
|
12907
13359
|
}, () => this.assertReviews(rows));
|
|
@@ -12916,6 +13368,7 @@ class HistoryWriter {
|
|
|
12916
13368
|
seq: this.state.committedSeq
|
|
12917
13369
|
}, rows, (bytes) => this.charge(bytes, null, true), preparedIndex);
|
|
12918
13370
|
} catch {}
|
|
13371
|
+
this.rememberTaskFolds(rows, preparedIndex);
|
|
12919
13372
|
}
|
|
12920
13373
|
const batch = {
|
|
12921
13374
|
committedSeq: this.state.committedSeq,
|
|
@@ -13040,14 +13493,14 @@ class HistoryWriter {
|
|
|
13040
13493
|
this.fence();
|
|
13041
13494
|
result = failure(true);
|
|
13042
13495
|
}
|
|
13043
|
-
for (const
|
|
13496
|
+
for (const job2 of jobs) {
|
|
13044
13497
|
if (result.status !== "committed") {
|
|
13045
|
-
|
|
13498
|
+
job2.resolve(result);
|
|
13046
13499
|
continue;
|
|
13047
13500
|
}
|
|
13048
|
-
const v = ownRows.get(
|
|
13501
|
+
const v = ownRows.get(job2)?.value;
|
|
13049
13502
|
const ends = v?.kind === "capture" || v?.kind === "owner" ? v.events.flatMap((e) => e.kind === "end" ? [e.detail] : []) : [];
|
|
13050
|
-
|
|
13503
|
+
job2.resolve({
|
|
13051
13504
|
status: "committed",
|
|
13052
13505
|
batch: {
|
|
13053
13506
|
...result.batch,
|
|
@@ -13093,7 +13546,7 @@ class HistoryWriter {
|
|
|
13093
13546
|
reservedIdentity(commandId) {
|
|
13094
13547
|
decodeId(commandId);
|
|
13095
13548
|
try {
|
|
13096
|
-
const bytes = this.files.readCommand(commandId,
|
|
13549
|
+
const bytes = this.files.readCommand(commandId, HistoryReadBudget.authority());
|
|
13097
13550
|
return bytes ? decodeAdmissionIntent(json(bytes, RECORD_BYTES)).identity : null;
|
|
13098
13551
|
} catch {
|
|
13099
13552
|
return null;
|
|
@@ -13111,7 +13564,7 @@ class HistoryWriter {
|
|
|
13111
13564
|
async lookupCommitted(commandId) {
|
|
13112
13565
|
decodeId(commandId);
|
|
13113
13566
|
try {
|
|
13114
|
-
const budget =
|
|
13567
|
+
const budget = HistoryReadBudget.authority();
|
|
13115
13568
|
const intent = this.files.readCommand(commandId, budget);
|
|
13116
13569
|
const directoryExists = this.files.files.directory(join8(this.files.directory, "commands", commandId));
|
|
13117
13570
|
const wanted = intent ? decodeAdmissionIntent(json(intent, RECORD_BYTES)) : null;
|
|
@@ -13381,6 +13834,29 @@ class HistoryWriter {
|
|
|
13381
13834
|
return this.ownerValue(input, null, selected2.plan);
|
|
13382
13835
|
}, lease.budget.retainedBytes + lease.budget.outputBytes, true, () => begun ? lease.settled() : lease.release());
|
|
13383
13836
|
}
|
|
13837
|
+
rememberTaskFolds(rows, prepared) {
|
|
13838
|
+
if (!prepared)
|
|
13839
|
+
return;
|
|
13840
|
+
const tasks = new Set;
|
|
13841
|
+
for (const { value: v } of rows)
|
|
13842
|
+
if (v.kind === "owner" && v.events.some((e) => e.kind === "owner" && e.value.kind === "fact" && e.value.fact.kind === "publication-settled"))
|
|
13843
|
+
tasks.add(v.link.taskId);
|
|
13844
|
+
for (const taskId of tasks) {
|
|
13845
|
+
const head = prepared.heads.get(taskId);
|
|
13846
|
+
if (!head || head.seq !== this.state.committedSeq)
|
|
13847
|
+
continue;
|
|
13848
|
+
try {
|
|
13849
|
+
const checks = checkFold(this.files, taskId, head.seq, HistoryReadBudget.authority()).state();
|
|
13850
|
+
const run = latestTaskRun(this.files, taskId, head.seq, HistoryReadBudget.authority());
|
|
13851
|
+
const plan = planFold(this.files, taskId, head.seq, HistoryReadBudget.authority(), new HistoryNavigation(this.files), true).state();
|
|
13852
|
+
taskFoldMemory(this.files).remember(taskId, head, {
|
|
13853
|
+
checks,
|
|
13854
|
+
run,
|
|
13855
|
+
plan
|
|
13856
|
+
});
|
|
13857
|
+
} catch {}
|
|
13858
|
+
}
|
|
13859
|
+
}
|
|
13384
13860
|
async publishTask(request) {
|
|
13385
13861
|
request = structuredClone(request);
|
|
13386
13862
|
decodeId(request.operationId);
|
|
@@ -13572,7 +14048,7 @@ class HistoryWriter {
|
|
|
13572
14048
|
}
|
|
13573
14049
|
}
|
|
13574
14050
|
retainedRun(runId) {
|
|
13575
|
-
const budget =
|
|
14051
|
+
const budget = HistoryReadBudget.authority();
|
|
13576
14052
|
const snapshot = this.state.committedSeq;
|
|
13577
14053
|
this.navigation.snapshot(snapshot, budget);
|
|
13578
14054
|
const batches = this.navigation.runBatches(runId, snapshot, budget);
|
|
@@ -13585,7 +14061,7 @@ class HistoryWriter {
|
|
|
13585
14061
|
return state;
|
|
13586
14062
|
}
|
|
13587
14063
|
prior(matches) {
|
|
13588
|
-
for (const batch of this.files.scan(emptyScan(), false,
|
|
14064
|
+
for (const batch of this.files.scan(emptyScan(), false, HistoryReadBudget.authority())) {
|
|
13589
14065
|
if (batch.rows.some((row) => matches(row.value)))
|
|
13590
14066
|
return true;
|
|
13591
14067
|
}
|
|
@@ -13757,7 +14233,7 @@ class HistoryWriter {
|
|
|
13757
14233
|
recordId: randomUUID6(),
|
|
13758
14234
|
version: 1,
|
|
13759
14235
|
bytes: 0,
|
|
13760
|
-
hash:
|
|
14236
|
+
hash: createHash16("sha256"),
|
|
13761
14237
|
parts: 0,
|
|
13762
14238
|
ended: null,
|
|
13763
14239
|
committedSeq: null
|
|
@@ -13949,7 +14425,7 @@ class HistoryWriter {
|
|
|
13949
14425
|
assertReview(input, fact) {
|
|
13950
14426
|
let accepted2 = null;
|
|
13951
14427
|
let changes = null;
|
|
13952
|
-
for (const batch of this.files.scan(emptyScan(), false,
|
|
14428
|
+
for (const batch of this.files.scan(emptyScan(), false, HistoryReadBudget.authority()))
|
|
13953
14429
|
for (const row of batch.rows) {
|
|
13954
14430
|
const v = row.value;
|
|
13955
14431
|
if (v.kind === "admission" && v.intent.operationId === input.link.operationId)
|
|
@@ -14106,7 +14582,7 @@ function readOperationStateForPublication(files, operationId) {
|
|
|
14106
14582
|
let publication = null;
|
|
14107
14583
|
const state = new OperationState(operationId);
|
|
14108
14584
|
const facts = [];
|
|
14109
|
-
for (const batch of files.scan(emptyScan(), false,
|
|
14585
|
+
for (const batch of files.scan(emptyScan(), false, HistoryReadBudget.authority()))
|
|
14110
14586
|
for (const row of batch.rows) {
|
|
14111
14587
|
const v = row.value;
|
|
14112
14588
|
if (v.kind === "recovery" && intent)
|
|
@@ -14133,10 +14609,10 @@ function readOperationStateForPublication(files, operationId) {
|
|
|
14133
14609
|
}
|
|
14134
14610
|
|
|
14135
14611
|
// src/service-assets.ts
|
|
14136
|
-
import { createHash as
|
|
14612
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
14137
14613
|
import { lstatSync as lstatSync7, readdirSync as readdirSync2 } from "node:fs";
|
|
14138
14614
|
import { join as join9 } from "node:path";
|
|
14139
|
-
import { fileURLToPath } from "node:url";
|
|
14615
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
14140
14616
|
var ASSET_MEMBER_BYTES = 16 * 1024 * 1024;
|
|
14141
14617
|
var ASSET_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
14142
14618
|
var fixed = new Set([
|
|
@@ -14209,7 +14685,7 @@ function decodeAssetManifest(input) {
|
|
|
14209
14685
|
if ([...fixed].some((name) => !names.has(name)) || assets.filter((a) => font.test(a.name)).length !== 60 || assets.some((a) => new Set(a.references).size !== a.references.length || a.references.some((r) => !names.has(r))))
|
|
14210
14686
|
throw new Error("asset-closure-refused");
|
|
14211
14687
|
const sourceHash = hash2(o.sourceHash), contractHash = hash2(o.contractHash), buildId = hash2(o.buildId);
|
|
14212
|
-
if (buildId !==
|
|
14688
|
+
if (buildId !== createHash17("sha256").update(JSON.stringify({ sourceHash, contractHash })).digest("hex"))
|
|
14213
14689
|
throw new Error("asset-identity-refused");
|
|
14214
14690
|
return { schema: o.schema, sourceHash, contractHash, buildId, assets };
|
|
14215
14691
|
}
|
|
@@ -14224,7 +14700,7 @@ function incarnation2(st) {
|
|
|
14224
14700
|
st.ctimeMs
|
|
14225
14701
|
].join(":");
|
|
14226
14702
|
}
|
|
14227
|
-
function loadWebAssets(directory =
|
|
14703
|
+
function loadWebAssets(directory = fileURLToPath2(new URL("../web/dist/", import.meta.url))) {
|
|
14228
14704
|
if (!safeDirectory(directory) || !safeDirectory(join9(directory, "assets")))
|
|
14229
14705
|
throw new Error("assets-unavailable");
|
|
14230
14706
|
const observed = new Map;
|
|
@@ -14232,7 +14708,7 @@ function loadWebAssets(directory = fileURLToPath(new URL("../web/dist/", import.
|
|
|
14232
14708
|
observed.set(path, incarnation2(lstatSync7(path)));
|
|
14233
14709
|
const read2 = (path, limit) => {
|
|
14234
14710
|
const before = incarnation2(lstatSync7(path));
|
|
14235
|
-
const body = readBounded(path, limit);
|
|
14711
|
+
const body = readBounded(path, limit, true);
|
|
14236
14712
|
if (!body || before !== incarnation2(lstatSync7(path)))
|
|
14237
14713
|
throw new Error("asset-read-changed");
|
|
14238
14714
|
observed.set(path, before);
|
|
@@ -14248,7 +14724,7 @@ function loadWebAssets(directory = fileURLToPath(new URL("../web/dist/", import.
|
|
|
14248
14724
|
throw new Error("asset-members-refused");
|
|
14249
14725
|
const assets = manifest.assets.map((a) => {
|
|
14250
14726
|
const body = read2(join9(directory, a.name), a.bytes);
|
|
14251
|
-
if (!body || body.length !== a.bytes ||
|
|
14727
|
+
if (!body || body.length !== a.bytes || createHash17("sha256").update(body).digest("hex") !== a.sha256)
|
|
14252
14728
|
throw new Error("asset-body-refused");
|
|
14253
14729
|
return {
|
|
14254
14730
|
path: a.name === "index.html" ? "/app" : `/${a.name}`,
|
|
@@ -14261,13 +14737,13 @@ function loadWebAssets(directory = fileURLToPath(new URL("../web/dist/", import.
|
|
|
14261
14737
|
return {
|
|
14262
14738
|
manifest,
|
|
14263
14739
|
assets,
|
|
14264
|
-
identity:
|
|
14740
|
+
identity: createHash17("sha256").update(bytes).digest("hex")
|
|
14265
14741
|
};
|
|
14266
14742
|
}
|
|
14267
14743
|
|
|
14268
14744
|
// src/service/auth.ts
|
|
14269
14745
|
import {
|
|
14270
|
-
createHash as
|
|
14746
|
+
createHash as createHash18,
|
|
14271
14747
|
randomBytes as randomBytes3,
|
|
14272
14748
|
randomUUID as randomUUID7,
|
|
14273
14749
|
timingSafeEqual
|
|
@@ -14331,8 +14807,8 @@ function token(value) {
|
|
|
14331
14807
|
return result;
|
|
14332
14808
|
}
|
|
14333
14809
|
function sameSecret(candidate, expected) {
|
|
14334
|
-
const a =
|
|
14335
|
-
const b =
|
|
14810
|
+
const a = createHash18("sha256").update(candidate).digest();
|
|
14811
|
+
const b = createHash18("sha256").update(expected).digest();
|
|
14336
14812
|
return timingSafeEqual(a, b);
|
|
14337
14813
|
}
|
|
14338
14814
|
function buildTokenCookie(name, encodedValue, maxAge, secure) {
|
|
@@ -14652,7 +15128,10 @@ function safeChildEnv() {
|
|
|
14652
15128
|
"TMPDIR",
|
|
14653
15129
|
"LANG",
|
|
14654
15130
|
"LC_ALL",
|
|
14655
|
-
"PATHEXT"
|
|
15131
|
+
"PATHEXT",
|
|
15132
|
+
"ProgramFiles",
|
|
15133
|
+
"ProgramFiles(x86)",
|
|
15134
|
+
"ProgramW6432"
|
|
14656
15135
|
])
|
|
14657
15136
|
if (process.env[key2] !== undefined)
|
|
14658
15137
|
env[key2] = process.env[key2];
|
|
@@ -15014,8 +15493,8 @@ class ServiceCoordination {
|
|
|
15014
15493
|
}
|
|
15015
15494
|
};
|
|
15016
15495
|
}
|
|
15017
|
-
assertNamespace() {
|
|
15018
|
-
if (!this.files.directory(this.directory))
|
|
15496
|
+
assertNamespace(walks) {
|
|
15497
|
+
if (!this.files.directory(this.directory, false, walks))
|
|
15019
15498
|
refused();
|
|
15020
15499
|
names(this.directory, [
|
|
15021
15500
|
"format.json",
|
|
@@ -15028,14 +15507,15 @@ class ServiceCoordination {
|
|
|
15028
15507
|
"host-config.json",
|
|
15029
15508
|
"history-v2"
|
|
15030
15509
|
]);
|
|
15031
|
-
this.files.directory(join11(this.directory, "history-v2"));
|
|
15510
|
+
this.files.directory(join11(this.directory, "history-v2"), false, walks);
|
|
15032
15511
|
}
|
|
15033
15512
|
selected() {
|
|
15034
|
-
|
|
15513
|
+
const walks = new DirectoryWalks;
|
|
15514
|
+
if (!this.files.directory(this.directory, false, walks))
|
|
15035
15515
|
return;
|
|
15036
|
-
this.assertNamespace();
|
|
15037
|
-
const manifest = this.files.read(this.path("format.json"));
|
|
15038
|
-
const config = this.files.read(this.path("config.json"));
|
|
15516
|
+
this.assertNamespace(walks);
|
|
15517
|
+
const manifest = this.files.read(this.path("format.json"), undefined, walks);
|
|
15518
|
+
const config = this.files.read(this.path("config.json"), undefined, walks);
|
|
15039
15519
|
if (!manifest || !config)
|
|
15040
15520
|
refused();
|
|
15041
15521
|
const f = closed(json(manifest), [
|
|
@@ -15049,13 +15529,13 @@ class ServiceCoordination {
|
|
|
15049
15529
|
literal(f.stateHome, this.stateHome);
|
|
15050
15530
|
literal(f.configIdentity, config.identity);
|
|
15051
15531
|
const c = decodeOwnerConfig(json(config));
|
|
15052
|
-
const hostConfig = this.files.read(this.path("host-config.json"));
|
|
15532
|
+
const hostConfig = this.files.read(this.path("host-config.json"), undefined, walks);
|
|
15053
15533
|
if (hostConfig && hostConfig.identity !== c.policyIdentity)
|
|
15054
15534
|
refused();
|
|
15055
|
-
const registration = this.files.read(this.path("registration.json"));
|
|
15056
|
-
const owner = this.files.read(this.path("owner.lock"));
|
|
15057
|
-
const capability = this.files.read(this.path("local-capability.json"));
|
|
15058
|
-
const checkout = this.files.read(this.store.lockPath, 8192);
|
|
15535
|
+
const registration = this.files.read(this.path("registration.json"), undefined, walks);
|
|
15536
|
+
const owner = this.files.read(this.path("owner.lock"), undefined, walks);
|
|
15537
|
+
const capability = this.files.read(this.path("local-capability.json"), undefined, walks);
|
|
15538
|
+
const checkout = this.files.read(this.store.lockPath, 8192, walks);
|
|
15059
15539
|
const value = registration ? decodeRegistration(json(registration)) : undefined;
|
|
15060
15540
|
if (value && (value.root !== this.store.root || value.stateHome !== this.stateHome || value.configIdentity !== config.identity || !endpointMatches(c.endpoint, value.endpoint)))
|
|
15061
15541
|
refused();
|
|
@@ -15402,8 +15882,8 @@ class OwnerLease {
|
|
|
15402
15882
|
assert() {
|
|
15403
15883
|
if (this.released || this.selected.value.pid !== process.pid || this.selected.value.host !== hostname2())
|
|
15404
15884
|
throw new ContractError("conflict");
|
|
15405
|
-
const c = this.coordination;
|
|
15406
|
-
c.assertNamespace();
|
|
15885
|
+
const c = this.coordination, walks = new DirectoryWalks;
|
|
15886
|
+
c.assertNamespace(walks);
|
|
15407
15887
|
for (const [name, snapshot] of [
|
|
15408
15888
|
["config.json", this.selected.config],
|
|
15409
15889
|
["host-config.json", this.selected.hostConfig],
|
|
@@ -15412,8 +15892,8 @@ class OwnerLease {
|
|
|
15412
15892
|
["owner.lock", this.selected.owner],
|
|
15413
15893
|
["local-capability.json", this.selected.capability]
|
|
15414
15894
|
])
|
|
15415
|
-
c.files.assert(c.path(name), snapshot);
|
|
15416
|
-
c.files.assert(c.store.lockPath, this.selected.checkout, 8192);
|
|
15895
|
+
c.files.assert(c.path(name), snapshot, undefined, walks);
|
|
15896
|
+
c.files.assert(c.store.lockPath, this.selected.checkout, 8192, walks);
|
|
15417
15897
|
c.store.assertOwner(this.checkout);
|
|
15418
15898
|
}
|
|
15419
15899
|
phase(phase) {
|
|
@@ -15448,7 +15928,7 @@ class OwnerLease {
|
|
|
15448
15928
|
}
|
|
15449
15929
|
|
|
15450
15930
|
// src/service/http.ts
|
|
15451
|
-
import { createHash as
|
|
15931
|
+
import { createHash as createHash19, randomUUID as randomUUID9 } from "node:crypto";
|
|
15452
15932
|
import {
|
|
15453
15933
|
createServer
|
|
15454
15934
|
} from "node:http";
|
|
@@ -15732,7 +16212,7 @@ class ServiceHttpServer {
|
|
|
15732
16212
|
this.assets.set(asset.path, {
|
|
15733
16213
|
...asset,
|
|
15734
16214
|
bytes: Uint8Array.from(asset.bytes),
|
|
15735
|
-
etag: `"${
|
|
16215
|
+
etag: `"${createHash19("sha256").update(asset.bytes).digest("hex")}"`
|
|
15736
16216
|
});
|
|
15737
16217
|
}
|
|
15738
16218
|
this.server = createServer({
|
|
@@ -16241,7 +16721,7 @@ Content-Length: 0\r
|
|
|
16241
16721
|
res.setHeader("Content-Type", operation === "download" && request.format === "markdown" ? "text/markdown; charset=utf-8" : "application/x-ndjson; charset=utf-8");
|
|
16242
16722
|
res.setHeader("Content-Disposition", `attachment; filename="cadence-export.${operation === "download" && request.format === "markdown" ? "md" : "ndjson"}"`);
|
|
16243
16723
|
let trailer;
|
|
16244
|
-
const digest =
|
|
16724
|
+
const digest = createHash19("sha256");
|
|
16245
16725
|
const write = async (frame) => {
|
|
16246
16726
|
const data = Buffer.from(operation === "download" ? encodeExportFrame(frame, request.format) : `${canonicalJson(frame)}
|
|
16247
16727
|
`);
|
|
@@ -16552,9 +17032,6 @@ class OwnerModelChoice {
|
|
|
16552
17032
|
}
|
|
16553
17033
|
}
|
|
16554
17034
|
|
|
16555
|
-
// src/service/model-context.ts
|
|
16556
|
-
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
16557
|
-
|
|
16558
17035
|
// src/service/model-config.ts
|
|
16559
17036
|
import { lstatSync as lstatSync8, realpathSync as realpathSync4 } from "node:fs";
|
|
16560
17037
|
import { dirname as dirname9, isAbsolute as isAbsolute6, join as join12, resolve as resolve3 } from "node:path";
|
|
@@ -16633,7 +17110,7 @@ function piSource(agentDir, home, cwd, env) {
|
|
|
16633
17110
|
const directory = join12(realpathSync4(dirname9(selected2)), selected2.slice(dirname9(selected2).length + 1));
|
|
16634
17111
|
if (!lstatSync8(directory).isDirectory() || lstatSync8(directory).isSymbolicLink())
|
|
16635
17112
|
refuse2();
|
|
16636
|
-
const files = new PrivateFiles(directory);
|
|
17113
|
+
const files = new PrivateFiles(directory, false);
|
|
16637
17114
|
if (!files.directory(directory))
|
|
16638
17115
|
refuse2();
|
|
16639
17116
|
return {
|
|
@@ -16723,7 +17200,8 @@ function compat(value) {
|
|
|
16723
17200
|
"forceAdaptiveThinking",
|
|
16724
17201
|
"allowEmptySignature",
|
|
16725
17202
|
"supportsStrictTools",
|
|
16726
|
-
"supportsToolReferences"
|
|
17203
|
+
"supportsToolReferences",
|
|
17204
|
+
"supportsMidConvoSystemMessages"
|
|
16727
17205
|
];
|
|
16728
17206
|
const enums = {
|
|
16729
17207
|
maxTokensField: ["max_completion_tokens", "max_tokens"],
|
|
@@ -16921,6 +17399,11 @@ function modelJson(snapshot) {
|
|
|
16921
17399
|
return parseWireJson(normalized, CONFIG_BYTES);
|
|
16922
17400
|
}
|
|
16923
17401
|
function readPiSnapshot(source) {
|
|
17402
|
+
const { auth, credentials } = readPiAuth(source);
|
|
17403
|
+
const { models, providers } = readPiModels(source);
|
|
17404
|
+
return { auth, models, credentials, providers };
|
|
17405
|
+
}
|
|
17406
|
+
function readPiAuth(source) {
|
|
16924
17407
|
const auth = source.files.read(source.authPath, CONFIG_BYTES);
|
|
16925
17408
|
if (!auth)
|
|
16926
17409
|
refuse2();
|
|
@@ -16944,6 +17427,9 @@ function readPiSnapshot(source) {
|
|
|
16944
17427
|
refuse2();
|
|
16945
17428
|
credentials[provider] = credential;
|
|
16946
17429
|
}
|
|
17430
|
+
return { auth, credentials };
|
|
17431
|
+
}
|
|
17432
|
+
function readPiModels(source) {
|
|
16947
17433
|
const models = source.files.read(source.modelsPath, CONFIG_BYTES);
|
|
16948
17434
|
const providers = Object.create(null);
|
|
16949
17435
|
if (models) {
|
|
@@ -16987,7 +17473,7 @@ function readPiSnapshot(source) {
|
|
|
16987
17473
|
providers[id] = provider;
|
|
16988
17474
|
}
|
|
16989
17475
|
}
|
|
16990
|
-
return {
|
|
17476
|
+
return { models, providers };
|
|
16991
17477
|
}
|
|
16992
17478
|
function sourceIdentity(snapshot, provider) {
|
|
16993
17479
|
const credential = snapshot.credentials[provider];
|
|
@@ -17074,6 +17560,7 @@ function clonePrivate(value) {
|
|
|
17074
17560
|
}
|
|
17075
17561
|
|
|
17076
17562
|
// src/service/model-context.ts
|
|
17563
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
17077
17564
|
function committed(result) {
|
|
17078
17565
|
if (result.status !== "committed")
|
|
17079
17566
|
throw new ContractError("recording-exhausted");
|
|
@@ -18117,6 +18604,28 @@ function decodeHostInput(value) {
|
|
|
18117
18604
|
environmentToken
|
|
18118
18605
|
};
|
|
18119
18606
|
}
|
|
18607
|
+
class StartupRefusal extends ContractError {
|
|
18608
|
+
stage;
|
|
18609
|
+
quarantined;
|
|
18610
|
+
constructor(code, stage, quarantined = false) {
|
|
18611
|
+
super(code);
|
|
18612
|
+
this.stage = stage;
|
|
18613
|
+
this.quarantined = quarantined;
|
|
18614
|
+
}
|
|
18615
|
+
}
|
|
18616
|
+
function startupReport(error) {
|
|
18617
|
+
if (error instanceof StartupRefusal)
|
|
18618
|
+
return {
|
|
18619
|
+
status: error.quarantined ? "quarantined" : "refused",
|
|
18620
|
+
error: error.code,
|
|
18621
|
+
stage: error.stage
|
|
18622
|
+
};
|
|
18623
|
+
return {
|
|
18624
|
+
status: "refused",
|
|
18625
|
+
error: error instanceof ContractError ? error.code : "unavailable",
|
|
18626
|
+
stage: "service"
|
|
18627
|
+
};
|
|
18628
|
+
}
|
|
18120
18629
|
|
|
18121
18630
|
// src/service/task-actions.ts
|
|
18122
18631
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
@@ -18124,13 +18633,13 @@ import { realpathSync as realpathSync5 } from "node:fs";
|
|
|
18124
18633
|
|
|
18125
18634
|
// src/task-diff.ts
|
|
18126
18635
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
18127
|
-
import { createHash as
|
|
18636
|
+
import { createHash as createHash20 } from "node:crypto";
|
|
18128
18637
|
import { lstatSync as lstatSync9, readdirSync as readdirSync3, readlinkSync } from "node:fs";
|
|
18129
18638
|
import { join as join14, relative as relative5 } from "node:path";
|
|
18130
18639
|
var admin = (path) => path === ".cadence/assistant/tasks" || path.startsWith(".cadence/assistant/tasks/");
|
|
18131
18640
|
function captureChanges(root) {
|
|
18132
18641
|
const deadline = Date.now() + 3000;
|
|
18133
|
-
const hash3 =
|
|
18642
|
+
const hash3 = createHash20("sha256");
|
|
18134
18643
|
const parts = [];
|
|
18135
18644
|
let complete = true;
|
|
18136
18645
|
const limits = [];
|
|
@@ -18187,7 +18696,7 @@ function captureChanges(root) {
|
|
|
18187
18696
|
fileBytes += data2.length;
|
|
18188
18697
|
add(name, data2);
|
|
18189
18698
|
const binary = data2.includes(0);
|
|
18190
|
-
parts.push(`${name} (${binary ? "binary" : "text"}, ${data2.length} bytes, sha256 ${
|
|
18699
|
+
parts.push(`${name} (${binary ? "binary" : "text"}, ${data2.length} bytes, sha256 ${createHash20("sha256").update(data2).digest("hex")})`);
|
|
18191
18700
|
} catch {
|
|
18192
18701
|
complete = false;
|
|
18193
18702
|
limits.push(`Unreadable/changing: ${name}`);
|
|
@@ -18867,33 +19376,6 @@ class ActionAdmission {
|
|
|
18867
19376
|
// src/service/graph.ts
|
|
18868
19377
|
import { createHash as createHash21 } from "node:crypto";
|
|
18869
19378
|
import { join as join15 } from "node:path";
|
|
18870
|
-
|
|
18871
|
-
// src/agent-registry.ts
|
|
18872
|
-
import { createHash as createHash20 } from "node:crypto";
|
|
18873
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
18874
|
-
var AGENT_FILES = [
|
|
18875
|
-
"design-explorer.md",
|
|
18876
|
-
"implementation-worker.md",
|
|
18877
|
-
"diagnosis-worker.md"
|
|
18878
|
-
];
|
|
18879
|
-
function loadAgentDefinitions() {
|
|
18880
|
-
return AGENT_FILES.map((fileName) => {
|
|
18881
|
-
const path = fileURLToPath2(new URL(`../agents/${fileName}`, import.meta.url));
|
|
18882
|
-
const data2 = readBounded(path, 32768);
|
|
18883
|
-
if (!data2)
|
|
18884
|
-
throw new Error("Missing package Agent");
|
|
18885
|
-
return {
|
|
18886
|
-
role: fileName.replace(/\.md$/, ""),
|
|
18887
|
-
fileName,
|
|
18888
|
-
path,
|
|
18889
|
-
sha256: createHash20("sha256").update(data2).digest("hex"),
|
|
18890
|
-
bytes: data2.length,
|
|
18891
|
-
content: data2.toString("utf8")
|
|
18892
|
-
};
|
|
18893
|
-
});
|
|
18894
|
-
}
|
|
18895
|
-
|
|
18896
|
-
// src/service/graph.ts
|
|
18897
19379
|
var hash3 = (text2) => createHash21("sha256").update(text2).digest("hex");
|
|
18898
19380
|
|
|
18899
19381
|
class GraphContext {
|
|
@@ -19040,6 +19522,7 @@ import { randomUUID as randomUUID14 } from "node:crypto";
|
|
|
19040
19522
|
|
|
19041
19523
|
// src/subagent-process.ts
|
|
19042
19524
|
import { spawn } from "node:child_process";
|
|
19525
|
+
import { join as join16 } from "node:path";
|
|
19043
19526
|
import { StringDecoder } from "node:string_decoder";
|
|
19044
19527
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
19045
19528
|
// src/worker-capture.mjs
|
|
@@ -19622,6 +20105,15 @@ class WorkerRecording {
|
|
|
19622
20105
|
}
|
|
19623
20106
|
|
|
19624
20107
|
// src/subagent-process.ts
|
|
20108
|
+
function terminateWindowsTree(pid, fallback) {
|
|
20109
|
+
const root = process.env.SystemRoot;
|
|
20110
|
+
const taskkill = spawn(root ? join16(root, "System32", "taskkill.exe") : "taskkill.exe", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
20111
|
+
taskkill.once("error", fallback);
|
|
20112
|
+
taskkill.once("close", (code) => {
|
|
20113
|
+
if (code !== 0)
|
|
20114
|
+
fallback();
|
|
20115
|
+
});
|
|
20116
|
+
}
|
|
19625
20117
|
function runProcess(spec) {
|
|
19626
20118
|
const started = Date.now();
|
|
19627
20119
|
const early = (outcome, text2) => ({
|
|
@@ -19739,6 +20231,8 @@ function runProcess(spec) {
|
|
|
19739
20231
|
return;
|
|
19740
20232
|
if (process.platform !== "win32" && child.pid)
|
|
19741
20233
|
process.kill(-child.pid, signal);
|
|
20234
|
+
else if (child.pid && child.exitCode === null && child.signalCode === null)
|
|
20235
|
+
terminateWindowsTree(child.pid, () => child.kill(signal));
|
|
19742
20236
|
else
|
|
19743
20237
|
child.kill(signal);
|
|
19744
20238
|
} catch {}
|
|
@@ -20706,8 +21200,8 @@ class TaskScheduler {
|
|
|
20706
21200
|
this.pumping = false;
|
|
20707
21201
|
}
|
|
20708
21202
|
}
|
|
20709
|
-
current(e, n) {
|
|
20710
|
-
return !this.closed && !e.reason && !n.abort.signal.aborted && this.options.owner.state === "idle" && this.owned() && e.input.current() && e.input.sources.current() && canonicalJson(e.input.observe(false).observation) === canonicalJson(e.expectedObservation);
|
|
21203
|
+
current(e, n, changes = false) {
|
|
21204
|
+
return !this.closed && !e.reason && !n.abort.signal.aborted && this.options.owner.state === "idle" && this.owned() && e.input.current() && e.input.sources.current() && (!changes || canonicalJson(e.input.observe(false).observation) === canonicalJson(e.expectedObservation));
|
|
20711
21205
|
}
|
|
20712
21206
|
async execute(e, n) {
|
|
20713
21207
|
if (!n.assignment || !n.result || e.input.kind === "check")
|
|
@@ -20734,7 +21228,7 @@ class TaskScheduler {
|
|
|
20734
21228
|
reservationId: n.childRunId,
|
|
20735
21229
|
lane,
|
|
20736
21230
|
state: "held",
|
|
20737
|
-
checkoutGeneration: e.input.
|
|
21231
|
+
checkoutGeneration: e.input.generation()
|
|
20738
21232
|
});
|
|
20739
21233
|
held = true;
|
|
20740
21234
|
if (!this.current(e, n)) {
|
|
@@ -20744,7 +21238,7 @@ class TaskScheduler {
|
|
|
20744
21238
|
const observed = e.input.observe(false), before = observed.observation;
|
|
20745
21239
|
const baseline = await this.changes(e, "before", observed);
|
|
20746
21240
|
e.baseline ??= baseline;
|
|
20747
|
-
if (!this.current(e, n)) {
|
|
21241
|
+
if (!this.current(e, n) || canonicalJson(before) !== canonicalJson(e.expectedObservation)) {
|
|
20748
21242
|
await this.close(e, e.reason ?? "stale-dependency");
|
|
20749
21243
|
return;
|
|
20750
21244
|
}
|
|
@@ -20790,7 +21284,7 @@ class TaskScheduler {
|
|
|
20790
21284
|
});
|
|
20791
21285
|
originalAuth = auth.settled;
|
|
20792
21286
|
const prepared = await auth.result;
|
|
20793
|
-
if (prepared.status !== "prepared" || !this.current(e, n)) {
|
|
21287
|
+
if (prepared.status !== "prepared" || !this.current(e, n, true)) {
|
|
20794
21288
|
await this.close(e, e.reason ?? "failed");
|
|
20795
21289
|
await this.fact(e, {
|
|
20796
21290
|
kind: "assignment-state",
|
|
@@ -20860,7 +21354,7 @@ class TaskScheduler {
|
|
|
20860
21354
|
reservationId: n.childRunId,
|
|
20861
21355
|
lane,
|
|
20862
21356
|
state: "released",
|
|
20863
|
-
checkoutGeneration: e.input.
|
|
21357
|
+
checkoutGeneration: e.input.generation()
|
|
20864
21358
|
});
|
|
20865
21359
|
this.holders.delete(n.childRunId);
|
|
20866
21360
|
n.state = "done";
|
|
@@ -20908,7 +21402,7 @@ class TaskScheduler {
|
|
|
20908
21402
|
if (this.current(e, n) && input.definitionCurrent()) {
|
|
20909
21403
|
const output = recording;
|
|
20910
21404
|
const original = Promise.resolve().then(async () => {
|
|
20911
|
-
if (!this.current(e, n) || !input.definitionCurrent())
|
|
21405
|
+
if (!this.current(e, n, true) || !input.definitionCurrent())
|
|
20912
21406
|
return native;
|
|
20913
21407
|
await runCheck(input.check, input.cwd, n.abort.signal, {
|
|
20914
21408
|
environment: input.environment,
|
|
@@ -20940,7 +21434,7 @@ class TaskScheduler {
|
|
|
20940
21434
|
}
|
|
20941
21435
|
};
|
|
20942
21436
|
const observed = input.observe(false).observation;
|
|
20943
|
-
const after = held ? input.
|
|
21437
|
+
const after = held ? { ...observed, checkoutGeneration: input.advance() } : observed;
|
|
20944
21438
|
const ownAdvance = held && observed.checkoutGeneration === before.checkoutGeneration ? {
|
|
20945
21439
|
operationId: e.link.operationId,
|
|
20946
21440
|
reservationId: checkId,
|
|
@@ -21107,6 +21601,14 @@ class TaskScheduler {
|
|
|
21107
21601
|
|
|
21108
21602
|
// src/service/task-actions.ts
|
|
21109
21603
|
var actionOwners = new WeakSet;
|
|
21604
|
+
function snapshotReads() {
|
|
21605
|
+
const kept = new Map;
|
|
21606
|
+
return (key2, read2) => {
|
|
21607
|
+
if (!kept.has(key2))
|
|
21608
|
+
kept.set(key2, read2());
|
|
21609
|
+
return kept.get(key2);
|
|
21610
|
+
};
|
|
21611
|
+
}
|
|
21110
21612
|
|
|
21111
21613
|
class TaskActions {
|
|
21112
21614
|
options;
|
|
@@ -21586,14 +22088,16 @@ class TaskActions {
|
|
|
21586
22088
|
assertCurrent();
|
|
21587
22089
|
return checks;
|
|
21588
22090
|
}
|
|
21589
|
-
obligations(version, snapshot) {
|
|
22091
|
+
obligations(version, snapshot, reads) {
|
|
21590
22092
|
if (!version.task)
|
|
21591
22093
|
throw new ContractError("stale");
|
|
21592
|
-
return this.obligationsFor(version.task.id, version.task.kind, { status: "available", version }, snapshot);
|
|
22094
|
+
return this.obligationsFor(version.task.id, version.task.kind, { status: "available", version }, snapshot, reads);
|
|
21593
22095
|
}
|
|
21594
|
-
obligationsFor(taskId, kind, context, snapshot) {
|
|
22096
|
+
obligationsFor(taskId, kind, context, snapshot, reads) {
|
|
21595
22097
|
const configured = this.checks(taskId);
|
|
21596
|
-
const
|
|
22098
|
+
const selections = kind === "code" ? configured : { revision: configured.revision, selections: [] };
|
|
22099
|
+
const fold = () => currentChecks(this.options.host.reader.files, taskId, selections, context, snapshot);
|
|
22100
|
+
const state = reads ? reads(`checks:${canonicalJson({ taskId, selections, context, snapshot })}`, fold) : fold();
|
|
21597
22101
|
if (canonicalJson(configured) !== canonicalJson(this.checks(taskId)))
|
|
21598
22102
|
throw new ContractError("stale");
|
|
21599
22103
|
if (context.status !== "available")
|
|
@@ -21805,6 +22309,8 @@ class TaskActions {
|
|
|
21805
22309
|
return false;
|
|
21806
22310
|
}
|
|
21807
22311
|
},
|
|
22312
|
+
generation: () => this.generation,
|
|
22313
|
+
advance: () => ++this.generation,
|
|
21808
22314
|
observe: (advance) => {
|
|
21809
22315
|
if (advance)
|
|
21810
22316
|
this.generation++;
|
|
@@ -21831,13 +22337,14 @@ class TaskActions {
|
|
|
21831
22337
|
});
|
|
21832
22338
|
accepted2.activate(work);
|
|
21833
22339
|
}
|
|
21834
|
-
reviewObservation(runId, version, snapshot = this.options.host.writer.committedSeq) {
|
|
22340
|
+
reviewObservation(runId, version, snapshot = this.options.host.writer.committedSeq, reads) {
|
|
21835
22341
|
if (!version.task)
|
|
21836
22342
|
throw new ContractError("stale");
|
|
21837
22343
|
const h = this.options.host, taskId = version.task.id;
|
|
21838
|
-
const
|
|
22344
|
+
const select = () => h.reader.reviewSelection(taskId, runId, snapshot);
|
|
22345
|
+
const selection = reads ? reads(`selection:${taskId}:${runId}:${snapshot}`, select) : select();
|
|
21839
22346
|
const { run } = selection;
|
|
21840
|
-
const state = this.obligations(version, snapshot);
|
|
22347
|
+
const state = this.obligations(version, snapshot, reads);
|
|
21841
22348
|
const current2 = {
|
|
21842
22349
|
...state,
|
|
21843
22350
|
checks: state.checks,
|
|
@@ -21869,6 +22376,7 @@ class TaskActions {
|
|
|
21869
22376
|
const currentGraph = graph?.graph ?? (this.options.choice ? this.defaultGraph(selected2.record, selected2.identity, expected.model) : null);
|
|
21870
22377
|
if ((currentGraph ? hash(canonicalJson(currentGraph)) : null) !== task.graphIdentity || (currentGraph?.revision ?? null) !== task.graphRevision)
|
|
21871
22378
|
throw new ContractError("stale");
|
|
22379
|
+
const reads = snapshotReads();
|
|
21872
22380
|
const validate = (published) => {
|
|
21873
22381
|
this.assertChoice(expected);
|
|
21874
22382
|
held.assertHeld();
|
|
@@ -21878,14 +22386,14 @@ class TaskActions {
|
|
|
21878
22386
|
const changes2 = captureChanges(h.coordination.store.root);
|
|
21879
22387
|
if (changes2.fingerprint !== expected.observation.identity || changes2.complete !== expected.observation.complete)
|
|
21880
22388
|
throw new ContractError("stale");
|
|
21881
|
-
const proven = h.reader.publishedPlan(task.id, snapshot, {
|
|
22389
|
+
const proven = reads(`plan:${task.id}:${snapshot}`, () => h.reader.publishedPlan(task.id, snapshot, {
|
|
21882
22390
|
revision: selected2.record.revision,
|
|
21883
22391
|
identity: selected2.identity,
|
|
21884
22392
|
plan: selected2.record.plan
|
|
21885
|
-
});
|
|
22393
|
+
}));
|
|
21886
22394
|
if ((proven?.reference.sha256 ?? (currentGraph ? hash(canonicalJson(currentGraph)) : null)) !== task.graphIdentity)
|
|
21887
22395
|
throw new ContractError("stale");
|
|
21888
|
-
const current3 = this.reviewObservation(action.runId, expected, snapshot);
|
|
22396
|
+
const current3 = this.reviewObservation(action.runId, expected, snapshot, reads);
|
|
21889
22397
|
if (current3.checks.revision !== action.checksRevision || current3.issues.length && !action.acceptIssues)
|
|
21890
22398
|
throw new ContractError("stale");
|
|
21891
22399
|
return current3;
|
|
@@ -22077,7 +22585,7 @@ function legacy(store, budget, reader) {
|
|
|
22077
22585
|
decodeId(entry.name);
|
|
22078
22586
|
if (created.has(entry.name))
|
|
22079
22587
|
continue;
|
|
22080
|
-
const path =
|
|
22588
|
+
const path = join17(store.tasks, entry.name, "task.json");
|
|
22081
22589
|
const size = lstatSync10(path).size;
|
|
22082
22590
|
if (size > 128 * 1024)
|
|
22083
22591
|
throw new ContractError("corrupt-selected");
|
|
@@ -22092,12 +22600,23 @@ function legacy(store, budget, reader) {
|
|
|
22092
22600
|
}
|
|
22093
22601
|
return records;
|
|
22094
22602
|
}
|
|
22603
|
+
function refuseAt(stage, step) {
|
|
22604
|
+
try {
|
|
22605
|
+
return step();
|
|
22606
|
+
} catch (error2) {
|
|
22607
|
+
throw new StartupRefusal(error2 instanceof ContractError ? error2.code : "unavailable", stage);
|
|
22608
|
+
}
|
|
22609
|
+
}
|
|
22095
22610
|
async function createServiceHost(value, sourceBuild) {
|
|
22096
22611
|
const embedded = "e7e5248f8273a373827ac70d2d8be1d5aaea1e329379e59e4947fc10f82be818";
|
|
22097
22612
|
if (embedded && sourceBuild)
|
|
22098
22613
|
throw new ContractError("unsupported-context");
|
|
22099
22614
|
const contractHash = decodeHash(embedded ?? sourceBuild?.contractHash);
|
|
22100
|
-
const web =
|
|
22615
|
+
const web = refuseAt("package", () => {
|
|
22616
|
+
if (!sourceBuild?.deferActions)
|
|
22617
|
+
loadAgentDefinitions();
|
|
22618
|
+
return loadWebAssets(sourceBuild?.webDirectory);
|
|
22619
|
+
});
|
|
22101
22620
|
const build2 = {
|
|
22102
22621
|
contractHash,
|
|
22103
22622
|
assets: {
|
|
@@ -22124,12 +22643,17 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22124
22643
|
throw new ContractError("corrupt-selected");
|
|
22125
22644
|
history.assertWritable();
|
|
22126
22645
|
}
|
|
22646
|
+
const home = launch.HOME ?? launch.USERPROFILE ?? input.config.agentDir;
|
|
22647
|
+
const pi = refuseAt("pi-agent-directory", () => piSource(input.config.agentDir, home, input.config.cwd, launch));
|
|
22648
|
+
refuseAt("pi-auth", () => readPiAuth(pi));
|
|
22649
|
+
refuseAt("pi-models", () => readPiModels(pi));
|
|
22127
22650
|
const http = new ServiceHttpServer({
|
|
22128
22651
|
policy: input.config.http,
|
|
22129
22652
|
assets: web.assets
|
|
22130
22653
|
});
|
|
22131
22654
|
const endpoint2 = await http.listen();
|
|
22132
22655
|
let owner;
|
|
22656
|
+
let stage = "service";
|
|
22133
22657
|
try {
|
|
22134
22658
|
const resolved = {
|
|
22135
22659
|
...input.config,
|
|
@@ -22179,7 +22703,7 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22179
22703
|
owner,
|
|
22180
22704
|
accounting: writer.auth,
|
|
22181
22705
|
network,
|
|
22182
|
-
home
|
|
22706
|
+
home,
|
|
22183
22707
|
agentDir: input.config.agentDir,
|
|
22184
22708
|
cwd: input.config.cwd,
|
|
22185
22709
|
environment: launch
|
|
@@ -22320,9 +22844,11 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22320
22844
|
owner.quarantine();
|
|
22321
22845
|
throw new ContractError("quarantine");
|
|
22322
22846
|
}
|
|
22847
|
+
stage = "pi-runtime";
|
|
22323
22848
|
const loading = models.load();
|
|
22324
22849
|
owner.trackAuth(loading);
|
|
22325
22850
|
await loading;
|
|
22851
|
+
stage = "service";
|
|
22326
22852
|
lease.assert();
|
|
22327
22853
|
if (owner.state !== "idle")
|
|
22328
22854
|
throw new ContractError("quarantine");
|
|
@@ -22353,7 +22879,7 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22353
22879
|
} catch {}
|
|
22354
22880
|
} else
|
|
22355
22881
|
await http.close();
|
|
22356
|
-
throw new
|
|
22882
|
+
throw new StartupRefusal(error2 instanceof ContractError ? error2.code : "unavailable", stage, !!owner);
|
|
22357
22883
|
}
|
|
22358
22884
|
}
|
|
22359
22885
|
|
|
@@ -22375,7 +22901,9 @@ try {
|
|
|
22375
22901
|
const host = await createServiceHost(input);
|
|
22376
22902
|
process.stdout.write(`${JSON.stringify({ status: "ready", epoch: host.owner.lease.registration.epoch })}
|
|
22377
22903
|
`);
|
|
22378
|
-
} catch {
|
|
22904
|
+
} catch (error2) {
|
|
22905
|
+
process.stdout.write(`${JSON.stringify(startupReport(error2))}
|
|
22906
|
+
`);
|
|
22379
22907
|
process.stderr.write(`{"error":"service-host-refused"}
|
|
22380
22908
|
`);
|
|
22381
22909
|
process.exitCode = 1;
|