@abelxiaoxing/cadence 2.0.0 → 2.0.2
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 +61 -41
- package/docs/reuse-and-distribution.md +2 -1
- package/docs/service-management.md +50 -3
- package/package.json +9 -10
- package/src/install-config.mjs +25 -0
- package/src/native-client.ts +31 -3
- package/src/operator-cli.mjs +52 -16
- package/src/service/coordination.ts +35 -16
- package/src/service/private-files.ts +128 -23
- package/src/service-cli.mjs +734 -144
- package/src/service-host.mjs +839 -313
- package/src/task-shell.ts +16 -6
- package/src/task-store.ts +104 -25
- package/src/worker-tools.mjs +42 -17
- package/web/dist/assets/cadence.js +1 -1
- package/web/dist/manifest.json +3 -3
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,7 +2132,7 @@ 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
2137
|
function privateMode(st) {
|
|
2085
2138
|
if (process.platform !== "win32" && ((st.mode & 63) !== 0 || process.getuid && st.uid !== process.getuid()))
|
|
@@ -2091,38 +2144,108 @@ 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) {
|
|
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);
|
|
2191
|
+
return true;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2094
2194
|
|
|
2095
2195
|
class PrivateFiles {
|
|
2096
2196
|
namespace;
|
|
2097
2197
|
constructor(namespace) {
|
|
2098
2198
|
this.namespace = namespace;
|
|
2099
2199
|
}
|
|
2100
|
-
directory(path, create = false) {
|
|
2200
|
+
directory(path, create = false, walks) {
|
|
2101
2201
|
const tail = relative2(this.namespace, path);
|
|
2102
2202
|
if (isAbsolute2(tail) || tail === ".." || tail.startsWith(`..${sep2}`))
|
|
2103
2203
|
refused();
|
|
2104
2204
|
if (!create) {
|
|
2205
|
+
const full = resolve2(path);
|
|
2206
|
+
if (walks?.reuse(full))
|
|
2207
|
+
return true;
|
|
2105
2208
|
const namespace = resolve2(this.namespace);
|
|
2106
|
-
let inside = false;
|
|
2107
|
-
|
|
2209
|
+
let inside = false, last;
|
|
2210
|
+
const found = safeDirectory(path, false, (at2, st) => {
|
|
2108
2211
|
if (at2 === namespace)
|
|
2109
2212
|
inside = true;
|
|
2110
2213
|
if (inside)
|
|
2111
2214
|
privateMode(st);
|
|
2215
|
+
last = st;
|
|
2112
2216
|
});
|
|
2217
|
+
if (found && last)
|
|
2218
|
+
walks?.remember(full, last);
|
|
2219
|
+
return found;
|
|
2113
2220
|
}
|
|
2221
|
+
if (!safeDirectory(this.namespace, true))
|
|
2222
|
+
return false;
|
|
2223
|
+
privateMode(lstatSync2(this.namespace));
|
|
2114
2224
|
let at = this.namespace;
|
|
2115
|
-
for (const part of
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2225
|
+
for (const part of tail.split(sep2).filter(Boolean)) {
|
|
2226
|
+
at = join2(at, part);
|
|
2227
|
+
let st;
|
|
2228
|
+
try {
|
|
2229
|
+
st = lstatSync2(at);
|
|
2230
|
+
} catch (error) {
|
|
2231
|
+
if (!missing(error))
|
|
2232
|
+
throw error;
|
|
2233
|
+
try {
|
|
2234
|
+
mkdirSync2(at, { mode: 448 });
|
|
2235
|
+
} catch (mkdirError) {
|
|
2236
|
+
if (!(mkdirError instanceof Error && ("code" in mkdirError) && mkdirError.code === "EEXIST"))
|
|
2237
|
+
throw mkdirError;
|
|
2238
|
+
}
|
|
2239
|
+
st = lstatSync2(at);
|
|
2240
|
+
}
|
|
2241
|
+
if (!st.isDirectory() || st.isSymbolicLink())
|
|
2242
|
+
throw new Error("Unsafe directory");
|
|
2243
|
+
privateMode(st);
|
|
2121
2244
|
}
|
|
2122
2245
|
return true;
|
|
2123
2246
|
}
|
|
2124
|
-
read(path, maximum = METADATA_BYTES) {
|
|
2125
|
-
if (!this.directory(dirname2(path)))
|
|
2247
|
+
read(path, maximum = METADATA_BYTES, walks) {
|
|
2248
|
+
if (!this.directory(dirname2(path), false, walks))
|
|
2126
2249
|
return;
|
|
2127
2250
|
let before;
|
|
2128
2251
|
try {
|
|
@@ -2140,16 +2263,24 @@ class PrivateFiles {
|
|
|
2140
2263
|
const opened = fstatSync2(fd);
|
|
2141
2264
|
if (!same(before, opened))
|
|
2142
2265
|
refused();
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2266
|
+
const buffer = Buffer.alloc(opened.size + 1);
|
|
2267
|
+
let size = 0;
|
|
2268
|
+
while (size < buffer.length) {
|
|
2269
|
+
const n = readSync2(fd, buffer, size, buffer.length - size, null);
|
|
2270
|
+
if (!n)
|
|
2271
|
+
break;
|
|
2272
|
+
size += n;
|
|
2273
|
+
}
|
|
2274
|
+
const bytes = buffer.subarray(0, size);
|
|
2275
|
+
if (size !== opened.size || !same(opened, fstatSync2(fd)) || !same(opened, lstatSync2(path)))
|
|
2145
2276
|
refused();
|
|
2146
2277
|
return { bytes, identity: hash(bytes), stamp: stamp(opened) };
|
|
2147
2278
|
} finally {
|
|
2148
2279
|
closeSync2(fd);
|
|
2149
2280
|
}
|
|
2150
2281
|
}
|
|
2151
|
-
assert(path, expected, maximum = METADATA_BYTES) {
|
|
2152
|
-
const current = this.read(path, maximum);
|
|
2282
|
+
assert(path, expected, maximum = METADATA_BYTES, walks) {
|
|
2283
|
+
const current = this.read(path, maximum, walks);
|
|
2153
2284
|
if (current?.identity !== expected?.identity || current?.stamp !== expected?.stamp)
|
|
2154
2285
|
throw new ContractError("conflict");
|
|
2155
2286
|
}
|
|
@@ -2157,7 +2288,7 @@ class PrivateFiles {
|
|
|
2157
2288
|
if (Buffer.byteLength(bytes) > maximum)
|
|
2158
2289
|
throw new ContractError("oversize");
|
|
2159
2290
|
this.directory(dirname2(path), true);
|
|
2160
|
-
const fd = openSync2(path, "wx", 384);
|
|
2291
|
+
const fd = retryWindowsFileOperation(() => openSync2(path, "wx", 384));
|
|
2161
2292
|
try {
|
|
2162
2293
|
writeFileSync2(fd, bytes);
|
|
2163
2294
|
fsyncSync2(fd);
|
|
@@ -2175,7 +2306,7 @@ class PrivateFiles {
|
|
|
2175
2306
|
const temporary = join2(dirname2(path), `pending-${randomUUID2()}.json`);
|
|
2176
2307
|
this.create(temporary, bytes, maximum);
|
|
2177
2308
|
this.assert(path, expected, maximum);
|
|
2178
|
-
renameSync2(temporary, path);
|
|
2309
|
+
retryWindowsFileOperation(() => renameSync2(temporary, path), () => this.assert(path, expected, maximum));
|
|
2179
2310
|
this.syncDirectory(dirname2(path));
|
|
2180
2311
|
const result = this.read(path, maximum);
|
|
2181
2312
|
if (!result || result.identity !== hash(bytes))
|
|
@@ -2184,7 +2315,7 @@ class PrivateFiles {
|
|
|
2184
2315
|
}
|
|
2185
2316
|
remove(path, expected, maximum = METADATA_BYTES) {
|
|
2186
2317
|
this.assert(path, expected, maximum);
|
|
2187
|
-
unlinkSync2(path);
|
|
2318
|
+
retryWindowsFileOperation(() => unlinkSync2(path), () => this.assert(path, expected, maximum));
|
|
2188
2319
|
this.syncDirectory(dirname2(path));
|
|
2189
2320
|
}
|
|
2190
2321
|
syncDirectory(path) {
|
|
@@ -3915,22 +4046,22 @@ function decodeRunAdmission(value) {
|
|
|
3915
4046
|
}
|
|
3916
4047
|
|
|
3917
4048
|
// src/history/read-io.ts
|
|
3918
|
-
import { createHash as
|
|
4049
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3919
4050
|
import {
|
|
3920
4051
|
closeSync as closeSync3,
|
|
3921
4052
|
constants as constants3,
|
|
3922
4053
|
fstatSync as fstatSync3,
|
|
3923
4054
|
lstatSync as lstatSync3,
|
|
3924
4055
|
openSync as openSync3,
|
|
3925
|
-
readSync as
|
|
4056
|
+
readSync as readSync3
|
|
3926
4057
|
} from "node:fs";
|
|
3927
4058
|
import { dirname as dirname3 } from "node:path";
|
|
3928
4059
|
function bad() {
|
|
3929
4060
|
throw new ContractError("corrupt-selected");
|
|
3930
4061
|
}
|
|
3931
4062
|
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)))
|
|
4063
|
+
function selected(files, path, read, walks) {
|
|
4064
|
+
if (!files.files.directory(dirname3(path), false, walks))
|
|
3934
4065
|
bad();
|
|
3935
4066
|
const before = lstatSync3(path);
|
|
3936
4067
|
if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
|
|
@@ -3967,7 +4098,7 @@ function reproveAppend(files, path, expected, length, sha256, budget) {
|
|
|
3967
4098
|
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
4099
|
bad();
|
|
3969
4100
|
const bytes = readRange(files, path, 0, length, budget);
|
|
3970
|
-
if (
|
|
4101
|
+
if (createHash4("sha256").update(bytes).digest("hex") !== sha256)
|
|
3971
4102
|
bad();
|
|
3972
4103
|
assertIncarnation(files, path, current.stamp);
|
|
3973
4104
|
return current.stamp;
|
|
@@ -3989,17 +4120,17 @@ function readRange(files, path, offset, length, budget) {
|
|
|
3989
4120
|
let n = 0;
|
|
3990
4121
|
while (n < length) {
|
|
3991
4122
|
budget.reserve(0);
|
|
3992
|
-
const got =
|
|
4123
|
+
const got = readSync3(fd, out, n, length - n, offset + n);
|
|
3993
4124
|
if (!got)
|
|
3994
4125
|
bad();
|
|
3995
4126
|
n += got;
|
|
3996
4127
|
}
|
|
3997
4128
|
return out;
|
|
3998
|
-
});
|
|
4129
|
+
}, budget.walks);
|
|
3999
4130
|
}
|
|
4000
4131
|
|
|
4001
4132
|
// src/history/types.ts
|
|
4002
|
-
import { createHash as
|
|
4133
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
4003
4134
|
var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
|
|
4004
4135
|
var LEGACY_FORMAT = "cadence-history-store/2";
|
|
4005
4136
|
var FORMAT = "cadence-history-store/3";
|
|
@@ -4365,7 +4496,7 @@ function batchArtifacts(rows) {
|
|
|
4365
4496
|
}
|
|
4366
4497
|
var decodedLines = new Map;
|
|
4367
4498
|
var decodedBytes = 0;
|
|
4368
|
-
var lineKey = (text2) =>
|
|
4499
|
+
var lineKey = (text2) => createHash5("sha256").update(text2).digest("hex");
|
|
4369
4500
|
function remember(text2, value) {
|
|
4370
4501
|
const key = lineKey(text2);
|
|
4371
4502
|
if (decodedLines.has(key))
|
|
@@ -4428,13 +4559,45 @@ function decodeJournalText(line) {
|
|
|
4428
4559
|
|
|
4429
4560
|
// src/history/files.ts
|
|
4430
4561
|
class HistoryReadBudget {
|
|
4562
|
+
clock;
|
|
4431
4563
|
started = performance.now();
|
|
4432
4564
|
bytes = 0;
|
|
4565
|
+
walks = new DirectoryWalks;
|
|
4566
|
+
constructor(clock = true) {
|
|
4567
|
+
this.clock = clock;
|
|
4568
|
+
}
|
|
4569
|
+
static authority() {
|
|
4570
|
+
return new HistoryReadBudget(false);
|
|
4571
|
+
}
|
|
4433
4572
|
reserve(bytes) {
|
|
4434
|
-
if (!Number.isSafeInteger(bytes) || bytes < 0 || this.bytes + bytes > LIMITS.searchBytes ||
|
|
4573
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0 || this.bytes + bytes > LIMITS.searchBytes || this.elapsed)
|
|
4435
4574
|
throw new ContractError("receipt-inconclusive");
|
|
4436
4575
|
this.bytes += bytes;
|
|
4437
4576
|
}
|
|
4577
|
+
get elapsed() {
|
|
4578
|
+
return this.clock && performance.now() - this.started >= LIMITS.searchMs;
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
class StalledQuantum extends ContractError {
|
|
4583
|
+
constructor() {
|
|
4584
|
+
super("receipt-inconclusive");
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
function noProgress(budget) {
|
|
4588
|
+
throw budget.elapsed ? new StalledQuantum : new ContractError("receipt-inconclusive");
|
|
4589
|
+
}
|
|
4590
|
+
var STALL_RETRIES = 2;
|
|
4591
|
+
async function retryStalled(read) {
|
|
4592
|
+
for (let retry = 0;; retry++) {
|
|
4593
|
+
try {
|
|
4594
|
+
return await read();
|
|
4595
|
+
} catch (error) {
|
|
4596
|
+
if (!(error instanceof StalledQuantum) || retry >= STALL_RETRIES)
|
|
4597
|
+
throw error;
|
|
4598
|
+
await new Promise((resolve3) => setImmediate(resolve3));
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4438
4601
|
}
|
|
4439
4602
|
function corrupt() {
|
|
4440
4603
|
throw new ContractError("corrupt-selected");
|
|
@@ -4596,12 +4759,12 @@ class HistoryFiles {
|
|
|
4596
4759
|
try {
|
|
4597
4760
|
size = lstatSync4(path).size;
|
|
4598
4761
|
} catch {
|
|
4599
|
-
return this.files.read(path, 0);
|
|
4762
|
+
return this.files.read(path, 0, budget.walks);
|
|
4600
4763
|
}
|
|
4601
4764
|
if (size > maximum)
|
|
4602
4765
|
corrupt();
|
|
4603
4766
|
budget.reserve(size);
|
|
4604
|
-
return this.files.read(path, size);
|
|
4767
|
+
return this.files.read(path, size, budget.walks);
|
|
4605
4768
|
}
|
|
4606
4769
|
readCommand(commandId, budget) {
|
|
4607
4770
|
decodeId(commandId);
|
|
@@ -4754,7 +4917,7 @@ class HistoryFiles {
|
|
|
4754
4917
|
state.bytes = file.bytes.length;
|
|
4755
4918
|
state.identity = file.identity;
|
|
4756
4919
|
state.records = 0;
|
|
4757
|
-
let at = 0, pending = [], pendingBytes = 0, checksum =
|
|
4920
|
+
let at = 0, pending = [], pendingBytes = 0, checksum = createHash6("sha256");
|
|
4758
4921
|
while (at < file.bytes.length) {
|
|
4759
4922
|
budget?.reserve(0);
|
|
4760
4923
|
const end = file.bytes.indexOf(10, at);
|
|
@@ -4822,7 +4985,7 @@ class HistoryFiles {
|
|
|
4822
4985
|
};
|
|
4823
4986
|
pending = [];
|
|
4824
4987
|
pendingBytes = 0;
|
|
4825
|
-
checksum =
|
|
4988
|
+
checksum = createHash6("sha256");
|
|
4826
4989
|
}
|
|
4827
4990
|
if (pending.length)
|
|
4828
4991
|
state.tail = true;
|
|
@@ -5014,7 +5177,7 @@ class HistoryFiles {
|
|
|
5014
5177
|
lease.assert();
|
|
5015
5178
|
this.assertWritable();
|
|
5016
5179
|
const after = this.files.read(path, LIMITS.segmentBytes);
|
|
5017
|
-
if (!after || after.identity !==
|
|
5180
|
+
if (!after || after.identity !== createHash6("sha256").update(before.bytes).update(body).update(trailer).digest("hex"))
|
|
5018
5181
|
corrupt();
|
|
5019
5182
|
return {
|
|
5020
5183
|
committedSeq: marker.last,
|
|
@@ -5283,7 +5446,7 @@ class HistoryLive {
|
|
|
5283
5446
|
}
|
|
5284
5447
|
|
|
5285
5448
|
// src/history/navigation.ts
|
|
5286
|
-
import { createHash as
|
|
5449
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
5287
5450
|
import { lstatSync as lstatSync6 } from "node:fs";
|
|
5288
5451
|
import { dirname as dirname5, join as join5 } from "node:path";
|
|
5289
5452
|
|
|
@@ -5465,7 +5628,7 @@ class IndexTree {
|
|
|
5465
5628
|
|
|
5466
5629
|
// src/history/run-state.ts
|
|
5467
5630
|
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 =
|
|
5631
|
+
function readRunState(files, runId, snapshotSeq, budget = HistoryReadBudget.authority()) {
|
|
5469
5632
|
const state = new RunState(runId);
|
|
5470
5633
|
if (snapshotSeq === "0")
|
|
5471
5634
|
return state;
|
|
@@ -5543,6 +5706,27 @@ class TaskPublicationLineage {
|
|
|
5543
5706
|
};
|
|
5544
5707
|
}
|
|
5545
5708
|
}
|
|
5709
|
+
get operationId() {
|
|
5710
|
+
return this.operation?.operationId ?? null;
|
|
5711
|
+
}
|
|
5712
|
+
progress() {
|
|
5713
|
+
return {
|
|
5714
|
+
operationId: this.operation?.operationId ?? null,
|
|
5715
|
+
state: structuredClone(this.state),
|
|
5716
|
+
endpoint: this.endpoint && { ...this.endpoint },
|
|
5717
|
+
graphIdentity: this.graphIdentity
|
|
5718
|
+
};
|
|
5719
|
+
}
|
|
5720
|
+
static resumed(operation, progress) {
|
|
5721
|
+
if ((operation?.operationId ?? null) !== progress.operationId)
|
|
5722
|
+
throw new ContractError("invalid");
|
|
5723
|
+
const lineage = new TaskPublicationLineage;
|
|
5724
|
+
lineage.operation = operation;
|
|
5725
|
+
lineage.state = structuredClone(progress.state);
|
|
5726
|
+
lineage.endpoint = progress.endpoint && { ...progress.endpoint };
|
|
5727
|
+
lineage.graphIdentity = progress.graphIdentity;
|
|
5728
|
+
return lineage;
|
|
5729
|
+
}
|
|
5546
5730
|
current() {
|
|
5547
5731
|
requireFact(this.operation && this.state.status === "published" && this.endpoint?.identity);
|
|
5548
5732
|
return {
|
|
@@ -5596,6 +5780,21 @@ class OperationState {
|
|
|
5596
5780
|
}, fact, seq);
|
|
5597
5781
|
}
|
|
5598
5782
|
}
|
|
5783
|
+
progress() {
|
|
5784
|
+
return {
|
|
5785
|
+
reviewed: this.reviewed,
|
|
5786
|
+
publication: structuredClone(this.publication)
|
|
5787
|
+
};
|
|
5788
|
+
}
|
|
5789
|
+
static resumed(operationId, command, progress) {
|
|
5790
|
+
if (command.intent.operationId !== operationId)
|
|
5791
|
+
throw new ContractError("invalid");
|
|
5792
|
+
const state = new OperationState(operationId);
|
|
5793
|
+
state.command = command;
|
|
5794
|
+
state.reviewed = progress.reviewed;
|
|
5795
|
+
state.publication = structuredClone(progress.publication);
|
|
5796
|
+
return state;
|
|
5797
|
+
}
|
|
5599
5798
|
publicationExpected() {
|
|
5600
5799
|
const identity = this.command?.intent.identity;
|
|
5601
5800
|
requireFact(identity?.kind === "action" && (!this.publication.intent || this.publication.status === "published"));
|
|
@@ -5621,7 +5820,7 @@ class OperationState {
|
|
|
5621
5820
|
}
|
|
5622
5821
|
function readOperationState(files, operationId) {
|
|
5623
5822
|
const state = new OperationState(operationId);
|
|
5624
|
-
for (const batch of files.scan(emptyScan(), false,
|
|
5823
|
+
for (const batch of files.scan(emptyScan(), false, HistoryReadBudget.authority()))
|
|
5625
5824
|
for (const row of batch.rows)
|
|
5626
5825
|
state.apply(row, batch.marker.last);
|
|
5627
5826
|
return state;
|
|
@@ -6247,7 +6446,7 @@ class HistoryNavigation {
|
|
|
6247
6446
|
return structuredClone(source.value);
|
|
6248
6447
|
}
|
|
6249
6448
|
try {
|
|
6250
|
-
if (!this.files.files.directory(dirname5(path)))
|
|
6449
|
+
if (!this.files.files.directory(dirname5(path), false, budget.walks))
|
|
6251
6450
|
return source ? structuredClone(source.value) : undefined;
|
|
6252
6451
|
const size = lstatSync6(path).size;
|
|
6253
6452
|
if (size > RECORD_BYTES)
|
|
@@ -6474,7 +6673,7 @@ class HistoryNavigation {
|
|
|
6474
6673
|
continue;
|
|
6475
6674
|
}
|
|
6476
6675
|
let pending = Buffer.alloc(0), at = offset, bodyBytes = 0;
|
|
6477
|
-
const checksum =
|
|
6676
|
+
const checksum = createHash7("sha256"), rows = [];
|
|
6478
6677
|
while (at < size) {
|
|
6479
6678
|
const chunk = this.block(path, at, size, budget);
|
|
6480
6679
|
at += chunk.length;
|
|
@@ -6758,16 +6957,41 @@ class HistoryNavigation {
|
|
|
6758
6957
|
throw new ContractError("receipt-inconclusive");
|
|
6759
6958
|
let head = expected;
|
|
6760
6959
|
while (head) {
|
|
6761
|
-
const
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6960
|
+
const link = this.link(roster ? "roster-links" : "entity-links", id, head, budget);
|
|
6961
|
+
yield this.batch(link.pointer, budget);
|
|
6962
|
+
head = link.previous;
|
|
6963
|
+
}
|
|
6964
|
+
}
|
|
6965
|
+
link(group, id, head, budget) {
|
|
6966
|
+
const value = this.index(group, `${id}/${head.seq}`, budget);
|
|
6967
|
+
if (!value)
|
|
6968
|
+
throw new ContractError("receipt-inconclusive");
|
|
6969
|
+
const o = closed(value, ["pointer", "previous", "previousHash", "hash"]), p = pointer(o.pointer);
|
|
6970
|
+
const previous = o.previous === null ? null : decodeSequence(o.previous), previousHash = o.previousHash === null ? null : typeof o.previousHash === "string" ? o.previousHash : undefined;
|
|
6971
|
+
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))
|
|
6972
|
+
throw new ContractError("receipt-inconclusive");
|
|
6973
|
+
return {
|
|
6974
|
+
pointer: p,
|
|
6975
|
+
previous: previous !== null && previousHash !== null ? { seq: previous, hash: previousHash } : null
|
|
6976
|
+
};
|
|
6977
|
+
}
|
|
6978
|
+
taskSuffix(taskId, snapshot, budget, accept) {
|
|
6979
|
+
decodeId(taskId);
|
|
6980
|
+
try {
|
|
6981
|
+
let head = this.anchoredHead(taskId, snapshot, budget) ?? null;
|
|
6982
|
+
const newer = [];
|
|
6983
|
+
while (head) {
|
|
6984
|
+
const base = accept(head);
|
|
6985
|
+
if (base !== undefined)
|
|
6986
|
+
return { base, batches: newer.reverse() };
|
|
6987
|
+
const link = this.link("entity-links", taskId, head, budget);
|
|
6988
|
+
newer.push(this.batch(link.pointer, budget));
|
|
6989
|
+
head = link.previous;
|
|
6990
|
+
}
|
|
6991
|
+
} catch {
|
|
6992
|
+
budget.reserve(0);
|
|
6770
6993
|
}
|
|
6994
|
+
return;
|
|
6771
6995
|
}
|
|
6772
6996
|
runBatches(id, snapshot, budget) {
|
|
6773
6997
|
const expected = this.anchoredHead(id, snapshot, budget);
|
|
@@ -6952,19 +7176,77 @@ function navigationKeys(row) {
|
|
|
6952
7176
|
}
|
|
6953
7177
|
|
|
6954
7178
|
// src/history/projection.ts
|
|
6955
|
-
import { createHash as
|
|
7179
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
7180
|
+
|
|
7181
|
+
// src/history/task-folds.ts
|
|
7182
|
+
var TASKS = 1024;
|
|
7183
|
+
var STATES = 8;
|
|
7184
|
+
|
|
7185
|
+
class TaskFoldMemory {
|
|
7186
|
+
tasks = new Map;
|
|
7187
|
+
has(taskId) {
|
|
7188
|
+
return this.tasks.has(taskId);
|
|
7189
|
+
}
|
|
7190
|
+
remember(taskId, anchor, state) {
|
|
7191
|
+
let states = this.tasks.get(taskId);
|
|
7192
|
+
if (!states) {
|
|
7193
|
+
if (this.tasks.size >= TASKS)
|
|
7194
|
+
this.tasks.delete(this.tasks.keys().next().value ?? "");
|
|
7195
|
+
states = new Map;
|
|
7196
|
+
this.tasks.set(taskId, states);
|
|
7197
|
+
}
|
|
7198
|
+
states.delete(anchor.seq);
|
|
7199
|
+
states.set(anchor.seq, {
|
|
7200
|
+
hash: anchor.hash,
|
|
7201
|
+
state: structuredClone(state)
|
|
7202
|
+
});
|
|
7203
|
+
if (states.size > STATES)
|
|
7204
|
+
states.delete(states.keys().next().value ?? "");
|
|
7205
|
+
}
|
|
7206
|
+
recall(taskId, anchor) {
|
|
7207
|
+
const entry = this.tasks.get(taskId)?.get(anchor.seq);
|
|
7208
|
+
return entry?.hash === anchor.hash ? structuredClone(entry.state) : undefined;
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
var memories = new Map;
|
|
7212
|
+
function taskFoldMemory(files) {
|
|
7213
|
+
const key = `${files.directory}\x00${files.format.storeId}`;
|
|
7214
|
+
let memory = memories.get(key);
|
|
7215
|
+
if (!memory) {
|
|
7216
|
+
memory = new TaskFoldMemory;
|
|
7217
|
+
memories.set(key, memory);
|
|
7218
|
+
}
|
|
7219
|
+
return memory;
|
|
7220
|
+
}
|
|
7221
|
+
function resumeTaskFold(files, navigation, taskId, snapshot, budget, resume) {
|
|
7222
|
+
const memory = taskFoldMemory(files);
|
|
7223
|
+
const suffix = memory.has(taskId) ? navigation.taskSuffix(taskId, snapshot, budget, (head) => {
|
|
7224
|
+
const state = memory.recall(taskId, head);
|
|
7225
|
+
if (!state)
|
|
7226
|
+
return;
|
|
7227
|
+
try {
|
|
7228
|
+
return resume(state);
|
|
7229
|
+
} catch {
|
|
7230
|
+
budget.reserve(0);
|
|
7231
|
+
return;
|
|
7232
|
+
}
|
|
7233
|
+
}) : undefined;
|
|
7234
|
+
return suffix ?? {
|
|
7235
|
+
base: undefined,
|
|
7236
|
+
batches: navigation.entityBatches(taskId, snapshot, budget) ?? navigation.scanSnapshot(snapshot, budget)
|
|
7237
|
+
};
|
|
7238
|
+
}
|
|
6956
7239
|
|
|
6957
7240
|
// src/history/checks.ts
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
for (const row of b.rows) {
|
|
7241
|
+
class CheckFold {
|
|
7242
|
+
starts;
|
|
7243
|
+
settled;
|
|
7244
|
+
constructor(state) {
|
|
7245
|
+
this.starts = new Map(state?.starts);
|
|
7246
|
+
this.settled = new Map(state?.settled);
|
|
7247
|
+
}
|
|
7248
|
+
apply(batch, taskId) {
|
|
7249
|
+
for (const row of batch.rows) {
|
|
6968
7250
|
const v = row.value;
|
|
6969
7251
|
if (v.kind !== "owner" || v.link.taskId !== taskId)
|
|
6970
7252
|
continue;
|
|
@@ -6973,44 +7255,83 @@ function currentChecks(files, taskId, configured, current, snapshotSeq) {
|
|
|
6973
7255
|
continue;
|
|
6974
7256
|
const f = e.value.fact;
|
|
6975
7257
|
if (f.kind === "check-start")
|
|
6976
|
-
starts.set(f.checkId, {
|
|
7258
|
+
this.starts.set(f.checkId, {
|
|
6977
7259
|
selection: f.selection,
|
|
6978
7260
|
epoch: v.link.epoch,
|
|
6979
7261
|
cwdId: f.cwdId,
|
|
6980
7262
|
checkEnvGeneration: f.checkEnvGeneration
|
|
6981
7263
|
});
|
|
6982
7264
|
if (f.kind === "check-settled") {
|
|
6983
|
-
const s = starts.get(f.checkId);
|
|
7265
|
+
const s = this.starts.get(f.checkId);
|
|
6984
7266
|
if (!s)
|
|
6985
7267
|
throw new ContractError("corrupt-selected");
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
7268
|
+
this.settled.set(canonicalJson(s.selection), {
|
|
7269
|
+
...s,
|
|
7270
|
+
checkId: f.checkId,
|
|
7271
|
+
status: f.status,
|
|
7272
|
+
after: {
|
|
7273
|
+
identity: f.after.identity,
|
|
7274
|
+
complete: f.after.complete,
|
|
7275
|
+
checkoutGeneration: f.after.checkoutGeneration
|
|
7276
|
+
},
|
|
7277
|
+
incomplete: f.output === null || f.coverage.observed !== "complete" || f.coverage.source !== "complete" || f.coverage.omissions.length > 0
|
|
7278
|
+
});
|
|
7279
|
+
this.starts.delete(f.checkId);
|
|
6996
7280
|
}
|
|
6997
|
-
if (starts.size > LIMITS.childReservations
|
|
7281
|
+
if (this.starts.size > LIMITS.childReservations)
|
|
6998
7282
|
throw new ContractError("receipt-inconclusive");
|
|
6999
7283
|
}
|
|
7000
7284
|
}
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7285
|
+
}
|
|
7286
|
+
state() {
|
|
7287
|
+
return {
|
|
7288
|
+
starts: [...this.starts],
|
|
7289
|
+
settled: [...this.settled]
|
|
7290
|
+
};
|
|
7291
|
+
}
|
|
7292
|
+
evaluate(taskId, configured, current) {
|
|
7293
|
+
const results = new Map;
|
|
7294
|
+
const incomplete = new Map;
|
|
7295
|
+
for (const [key, s] of this.settled) {
|
|
7296
|
+
if (s.selection.kind !== "manual" && !configured.selections.some((c) => canonicalJson(c) === key))
|
|
7297
|
+
continue;
|
|
7298
|
+
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;
|
|
7299
|
+
incomplete.set(key, s.incomplete);
|
|
7300
|
+
results.set(key, {
|
|
7301
|
+
selection: s.selection,
|
|
7302
|
+
status: s.status === "passed" && !fresh ? "stale" : s.status,
|
|
7303
|
+
checkId: s.checkId
|
|
7304
|
+
});
|
|
7305
|
+
}
|
|
7306
|
+
if (results.size > 32)
|
|
7307
|
+
throw new ContractError("receipt-inconclusive");
|
|
7308
|
+
return {
|
|
7309
|
+
checks: decodeCheckObligations({
|
|
7310
|
+
taskId,
|
|
7311
|
+
revision: configured.revision,
|
|
7312
|
+
definitions: configured.selections.map((selection) => results.get(canonicalJson(selection)) ?? {
|
|
7313
|
+
selection,
|
|
7314
|
+
status: "not-run",
|
|
7315
|
+
checkId: null
|
|
7316
|
+
})
|
|
7317
|
+
}),
|
|
7318
|
+
recorded: [...results.values()],
|
|
7319
|
+
recordingIncomplete: [...incomplete.values()].some(Boolean)
|
|
7320
|
+
};
|
|
7321
|
+
}
|
|
7322
|
+
}
|
|
7323
|
+
function checkFold(files, taskId, snapshot, budget, navigation = new HistoryNavigation(files)) {
|
|
7324
|
+
navigation.snapshot(snapshot, budget);
|
|
7325
|
+
const { base, batches } = resumeTaskFold(files, navigation, taskId, snapshot, budget, (state) => state.checks);
|
|
7326
|
+
const fold = new CheckFold(base);
|
|
7327
|
+
for (const b of batches)
|
|
7328
|
+
fold.apply(b, taskId);
|
|
7329
|
+
return fold;
|
|
7330
|
+
}
|
|
7331
|
+
function currentChecks(files, taskId, configured, current, snapshotSeq) {
|
|
7332
|
+
const budget = HistoryReadBudget.authority(), navigation = new HistoryNavigation(files);
|
|
7333
|
+
const snapshot = snapshotSeq ?? navigation.latest(budget);
|
|
7334
|
+
return checkFold(files, taskId, snapshot, budget, navigation).evaluate(taskId, configured, current);
|
|
7014
7335
|
}
|
|
7015
7336
|
|
|
7016
7337
|
// src/history/entity-state.ts
|
|
@@ -7198,17 +7519,17 @@ function materializeEntity(s, sources) {
|
|
|
7198
7519
|
}
|
|
7199
7520
|
|
|
7200
7521
|
// src/history/export.ts
|
|
7201
|
-
import { createHash as
|
|
7522
|
+
import { createHash as createHash11, randomUUID as randomUUID4 } from "node:crypto";
|
|
7202
7523
|
import { readFileSync } from "node:fs";
|
|
7203
7524
|
|
|
7204
7525
|
// src/history/search.ts
|
|
7205
|
-
import { createHash as
|
|
7526
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
7206
7527
|
|
|
7207
7528
|
// src/history/read-proof.ts
|
|
7208
7529
|
import {
|
|
7209
7530
|
createCipheriv,
|
|
7210
7531
|
createDecipheriv,
|
|
7211
|
-
createHash as
|
|
7532
|
+
createHash as createHash8,
|
|
7212
7533
|
randomBytes as randomBytes2
|
|
7213
7534
|
} from "node:crypto";
|
|
7214
7535
|
var PREFIX = "rp1.";
|
|
@@ -7260,7 +7581,7 @@ class HistoryReadProof {
|
|
|
7260
7581
|
root: this.files.format.rootIdentity,
|
|
7261
7582
|
format: material.formatStamp,
|
|
7262
7583
|
key: material.keyStamp,
|
|
7263
|
-
request:
|
|
7584
|
+
request: createHash8("sha256").update(canonicalJson(readIdentity(request))).digest("hex")
|
|
7264
7585
|
}));
|
|
7265
7586
|
}
|
|
7266
7587
|
seal(context, plaintext, budget) {
|
|
@@ -8032,11 +8353,11 @@ function decodeRetainedProgress(value) {
|
|
|
8032
8353
|
}
|
|
8033
8354
|
|
|
8034
8355
|
// src/history/retained-traversal.ts
|
|
8035
|
-
import { createHash as
|
|
8356
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
8036
8357
|
function bad5() {
|
|
8037
8358
|
throw new ContractError("corrupt-selected");
|
|
8038
8359
|
}
|
|
8039
|
-
var fingerprint = (v) =>
|
|
8360
|
+
var fingerprint = (v) => createHash9("sha256").update(canonicalJson(v)).digest("hex");
|
|
8040
8361
|
var ready = (v) => {
|
|
8041
8362
|
const s = v.state;
|
|
8042
8363
|
if (s.phase !== "ready")
|
|
@@ -8392,11 +8713,11 @@ var digits = (id) => {
|
|
|
8392
8713
|
return s.slice(0, 12) + s.slice(13, 16) + s.slice(17);
|
|
8393
8714
|
};
|
|
8394
8715
|
var publicTextRef = (id, label, text2, field, batch, row, slot) => ({
|
|
8395
|
-
recordId: uuid(address(batch.pointer).toString(16).padStart(14, "0") +
|
|
8716
|
+
recordId: uuid(address(batch.pointer).toString(16).padStart(14, "0") + createHash10("sha256").update(`${id}/${label}`).digest("hex").slice(0, 16)),
|
|
8396
8717
|
version: batch.rows.indexOf(row) * EVENT_SLOTS + slot + 1,
|
|
8397
8718
|
field,
|
|
8398
8719
|
bytes: Buffer.byteLength(text2),
|
|
8399
|
-
sha256:
|
|
8720
|
+
sha256: createHash10("sha256").update(text2).digest("hex")
|
|
8400
8721
|
});
|
|
8401
8722
|
var initial = () => ({
|
|
8402
8723
|
pos: { segment: 1, offset: 0 },
|
|
@@ -8412,7 +8733,7 @@ function scanCursor(base, p) {
|
|
|
8412
8733
|
const snapshot = p.snapshot.toString(16).padStart(14, "0");
|
|
8413
8734
|
if (snapshot.length !== 14)
|
|
8414
8735
|
throw new ContractError("capacity");
|
|
8415
|
-
const binding =
|
|
8736
|
+
const binding = createHash10("sha256").update(canonicalJson({
|
|
8416
8737
|
...readIdentity(base),
|
|
8417
8738
|
recordId: null,
|
|
8418
8739
|
recordVersion: version,
|
|
@@ -8753,7 +9074,7 @@ class RetainedScan {
|
|
|
8753
9074
|
graph: {
|
|
8754
9075
|
recordId: v.intent.operationId,
|
|
8755
9076
|
revision: graph.revision,
|
|
8756
|
-
sha256:
|
|
9077
|
+
sha256: createHash10("sha256").update(canonicalJson(graph)).digest("hex")
|
|
8757
9078
|
}
|
|
8758
9079
|
},
|
|
8759
9080
|
graph
|
|
@@ -8941,6 +9262,9 @@ class RetainedScan {
|
|
|
8941
9262
|
}
|
|
8942
9263
|
}
|
|
8943
9264
|
async function searchHistory(reader, value, limit = LIMITS.pageEntries) {
|
|
9265
|
+
return retryStalled(() => searchQuantum(reader, value, limit));
|
|
9266
|
+
}
|
|
9267
|
+
async function searchQuantum(reader, value, limit = LIMITS.pageEntries) {
|
|
8944
9268
|
const cursor = decodeCursor(value);
|
|
8945
9269
|
if (!cursor.query || !Number.isInteger(limit) || limit < 1 || limit > LIMITS.maxPageEntries)
|
|
8946
9270
|
throw new ContractError("invalid");
|
|
@@ -9034,7 +9358,7 @@ async function searchHistory(reader, value, limit = LIMITS.pageEntries) {
|
|
|
9034
9358
|
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
9035
9359
|
throw error;
|
|
9036
9360
|
if (!hits.length && before === coordinate())
|
|
9037
|
-
|
|
9361
|
+
noProgress(originalBudget);
|
|
9038
9362
|
done = false;
|
|
9039
9363
|
}
|
|
9040
9364
|
scan.traversal?.assertCurrent();
|
|
@@ -9049,7 +9373,7 @@ async function searchHistory(reader, value, limit = LIMITS.pageEntries) {
|
|
|
9049
9373
|
// src/history/export.ts
|
|
9050
9374
|
var exporterIdentity;
|
|
9051
9375
|
function buildIdentity() {
|
|
9052
|
-
exporterIdentity ??=
|
|
9376
|
+
exporterIdentity ??= createHash11("sha256").update(readFileSync(new URL(import.meta.url))).digest("hex");
|
|
9053
9377
|
return exporterIdentity;
|
|
9054
9378
|
}
|
|
9055
9379
|
function aborted(signal) {
|
|
@@ -9109,7 +9433,7 @@ Coverage: ${canonicalJson(event.coverage)}
|
|
|
9109
9433
|
}
|
|
9110
9434
|
}
|
|
9111
9435
|
async function* exportHistory(projection, input, signal) {
|
|
9112
|
-
const request = decodeExportRequest(input), exportId = randomUUID4(), digest =
|
|
9436
|
+
const request = decodeExportRequest(input), exportId = randomUUID4(), digest = createHash11("sha256");
|
|
9113
9437
|
let ordinal = 0, encodedBytes = 0, coverage = { ...COMPLETE, omissions: [] };
|
|
9114
9438
|
const frame = (event) => {
|
|
9115
9439
|
aborted(signal);
|
|
@@ -9138,7 +9462,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9138
9462
|
snapshot: 0
|
|
9139
9463
|
};
|
|
9140
9464
|
const scan = new RetainedScan(projection.reader, request, new HistoryReadBudget, true);
|
|
9141
|
-
for (;; ) {
|
|
9465
|
+
for (let stalls2 = 0;; ) {
|
|
9142
9466
|
aborted(signal);
|
|
9143
9467
|
const before = canonicalJson({ point, state: scan.checkpoint() });
|
|
9144
9468
|
scan.renew(new HistoryReadBudget);
|
|
@@ -9146,8 +9470,13 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9146
9470
|
scan.prepare(point);
|
|
9147
9471
|
break;
|
|
9148
9472
|
} catch (error) {
|
|
9149
|
-
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive"
|
|
9473
|
+
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
9150
9474
|
throw error;
|
|
9475
|
+
if (before === canonicalJson({ point, state: scan.checkpoint() })) {
|
|
9476
|
+
if (!scan.budget.elapsed || ++stalls2 > STALL_RETRIES)
|
|
9477
|
+
throw error;
|
|
9478
|
+
} else
|
|
9479
|
+
stalls2 = 0;
|
|
9151
9480
|
await yieldTurn();
|
|
9152
9481
|
}
|
|
9153
9482
|
}
|
|
@@ -9173,7 +9502,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9173
9502
|
const { detail, owner } = item, original = open2.get(detail.content.recordId);
|
|
9174
9503
|
if (detail.content.bytes ? !original || original.bytes !== detail.content.bytes || original.content !== canonicalJson(detail.content) : original !== undefined)
|
|
9175
9504
|
throw new ContractError("corrupt-selected");
|
|
9176
|
-
if ((original?.hash ??
|
|
9505
|
+
if ((original?.hash ?? createHash11("sha256")).digest("hex") !== detail.content.sha256)
|
|
9177
9506
|
throw new ContractError("corrupt-selected");
|
|
9178
9507
|
open2.delete(detail.content.recordId);
|
|
9179
9508
|
const events = [];
|
|
@@ -9204,7 +9533,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9204
9533
|
coverage = combineCoverage(coverage, item.coverage);
|
|
9205
9534
|
const length = Buffer.byteLength(item.text);
|
|
9206
9535
|
if (item.inline) {
|
|
9207
|
-
if (item.start !== 0 || length !== item.content.bytes ||
|
|
9536
|
+
if (item.start !== 0 || length !== item.content.bytes || createHash11("sha256").update(item.text).digest("hex") !== item.content.sha256)
|
|
9208
9537
|
throw new ContractError("corrupt-selected");
|
|
9209
9538
|
} else {
|
|
9210
9539
|
let original = open2.get(item.content.recordId);
|
|
@@ -9214,7 +9543,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9214
9543
|
original = {
|
|
9215
9544
|
bytes: 0,
|
|
9216
9545
|
content: canonicalJson(item.content),
|
|
9217
|
-
hash:
|
|
9546
|
+
hash: createHash11("sha256")
|
|
9218
9547
|
};
|
|
9219
9548
|
open2.set(item.content.recordId, original);
|
|
9220
9549
|
}
|
|
@@ -9234,7 +9563,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9234
9563
|
}
|
|
9235
9564
|
];
|
|
9236
9565
|
};
|
|
9237
|
-
let finished = false;
|
|
9566
|
+
let finished = false, stalls = 0;
|
|
9238
9567
|
while (!finished) {
|
|
9239
9568
|
aborted(signal);
|
|
9240
9569
|
const before = canonicalJson({ point, state: scan.checkpoint() });
|
|
@@ -9293,8 +9622,13 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9293
9622
|
if (finished)
|
|
9294
9623
|
scan.finish();
|
|
9295
9624
|
} catch (error) {
|
|
9296
|
-
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive"
|
|
9625
|
+
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
9297
9626
|
throw error;
|
|
9627
|
+
if (before === canonicalJson({ point, state: scan.checkpoint() })) {
|
|
9628
|
+
if (!scan.budget.elapsed || ++stalls > STALL_RETRIES)
|
|
9629
|
+
throw error;
|
|
9630
|
+
} else
|
|
9631
|
+
stalls = 0;
|
|
9298
9632
|
finished = false;
|
|
9299
9633
|
}
|
|
9300
9634
|
coverage = combineCoverage(coverage, scan.coverage);
|
|
@@ -9324,7 +9658,7 @@ async function* exportHistory(projection, input, signal) {
|
|
|
9324
9658
|
}
|
|
9325
9659
|
|
|
9326
9660
|
// src/history/list-progress.ts
|
|
9327
|
-
import { createHash as
|
|
9661
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
9328
9662
|
|
|
9329
9663
|
// src/history/detail-progress.ts
|
|
9330
9664
|
function bad7() {
|
|
@@ -9365,6 +9699,9 @@ function detailPreparation(cursor, completion) {
|
|
|
9365
9699
|
});
|
|
9366
9700
|
}
|
|
9367
9701
|
async function readRetainedDetail(reader, input) {
|
|
9702
|
+
return retryStalled(() => detailQuantum(reader, input));
|
|
9703
|
+
}
|
|
9704
|
+
async function detailQuantum(reader, input) {
|
|
9368
9705
|
const cursor = decodeCursor(input);
|
|
9369
9706
|
if (cursor.storeId !== reader.storeId || !cursor.recordId)
|
|
9370
9707
|
bad7();
|
|
@@ -9466,8 +9803,10 @@ async function readRetainedDetail(reader, input) {
|
|
|
9466
9803
|
p.event = 0;
|
|
9467
9804
|
}
|
|
9468
9805
|
} catch (error) {
|
|
9469
|
-
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive"
|
|
9806
|
+
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
9470
9807
|
throw error;
|
|
9808
|
+
if (coordinate() === before)
|
|
9809
|
+
noProgress(original);
|
|
9471
9810
|
return decodeDetailReadResult({
|
|
9472
9811
|
kind: "progress",
|
|
9473
9812
|
cursor,
|
|
@@ -9793,7 +10132,7 @@ function fits(link, scope) {
|
|
|
9793
10132
|
return scope.kind === "workspace" || (scope.kind === "task" ? link.taskId === scope.id : scope.kind === "run" ? link.runId === scope.id : link.childRunId === scope.id);
|
|
9794
10133
|
}
|
|
9795
10134
|
function nextAfter(request, n) {
|
|
9796
|
-
const h =
|
|
10135
|
+
const h = createHash12("sha256").update(canonicalJson({ ...readIdentity(request), after: null, ordinal: n })).digest("hex");
|
|
9797
10136
|
return {
|
|
9798
10137
|
recordId: `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-8${h.slice(17, 20)}-${h.slice(20, 32)}`,
|
|
9799
10138
|
version: 1,
|
|
@@ -9900,10 +10239,10 @@ function advanceTarget(p, t, snapshot, budget, sources) {
|
|
|
9900
10239
|
}
|
|
9901
10240
|
}
|
|
9902
10241
|
async function readRetainedList(p, request) {
|
|
9903
|
-
return read(p, request, false);
|
|
10242
|
+
return retryStalled(() => read(p, request, false));
|
|
9904
10243
|
}
|
|
9905
10244
|
async function readRetainedEntity(p, request) {
|
|
9906
|
-
return read(p, request, true);
|
|
10245
|
+
return retryStalled(() => read(p, request, true));
|
|
9907
10246
|
}
|
|
9908
10247
|
async function read(p, request, entity) {
|
|
9909
10248
|
const budget = new HistoryReadBudget, proof = new HistoryReadProof(p.reader.files), completion = proof.prepareCompletion(budget);
|
|
@@ -9932,7 +10271,7 @@ async function read(p, request, entity) {
|
|
|
9932
10271
|
};
|
|
9933
10272
|
const progress = () => {
|
|
9934
10273
|
if (coordinate() === before)
|
|
9935
|
-
|
|
10274
|
+
noProgress(budget);
|
|
9936
10275
|
const next = seal(request);
|
|
9937
10276
|
return entity ? decodeEntityReadResult({
|
|
9938
10277
|
kind: "progress",
|
|
@@ -10177,7 +10516,7 @@ async function read(p, request, entity) {
|
|
|
10177
10516
|
const tasks = [...p.environment.legacyTasks(budget)].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
10178
10517
|
if (tasks.length > 1000)
|
|
10179
10518
|
throw new ContractError("capacity");
|
|
10180
|
-
const sum =
|
|
10519
|
+
const sum = createHash12("sha256");
|
|
10181
10520
|
for (const task2 of tasks) {
|
|
10182
10521
|
budget.reserve(0);
|
|
10183
10522
|
sum.update(JSON.stringify({
|
|
@@ -10190,7 +10529,7 @@ async function read(p, request, entity) {
|
|
|
10190
10529
|
if (s.legacy > tasks.length)
|
|
10191
10530
|
bad9();
|
|
10192
10531
|
if (!s.legacyIdentity && request.after?.version === Number.MAX_SAFE_INTEGER) {
|
|
10193
|
-
const h =
|
|
10532
|
+
const h = createHash12("sha256").update(canonicalJson({
|
|
10194
10533
|
...readIdentity(request),
|
|
10195
10534
|
after: null,
|
|
10196
10535
|
legacy: s.legacy === 0 ? "begin" : identity,
|
|
@@ -10470,7 +10809,7 @@ class HistoryProjection {
|
|
|
10470
10809
|
};
|
|
10471
10810
|
cursor(request, pos, row, item) {
|
|
10472
10811
|
const offset = pos.offset * (LIMITS.segmentRecords + 1) * ROW_SLOTS + row * ROW_SLOTS + item;
|
|
10473
|
-
const digest =
|
|
10812
|
+
const digest = createHash13("sha256").update(canonicalJson({
|
|
10474
10813
|
...readIdentity(request),
|
|
10475
10814
|
after: null,
|
|
10476
10815
|
position: { ...pos, row, item }
|
|
@@ -10661,7 +11000,7 @@ class HistoryProjection {
|
|
|
10661
11000
|
if (legacyPhase)
|
|
10662
11001
|
this.navigation.snapshot(snapshot, budget);
|
|
10663
11002
|
const cursor = (offset2, identity2) => {
|
|
10664
|
-
const digest2 =
|
|
11003
|
+
const digest2 = createHash13("sha256").update(canonicalJson({ ...request, after: null, legacy: identity2, offset: offset2 })).digest("hex");
|
|
10665
11004
|
return {
|
|
10666
11005
|
recordId: `${digest2.slice(0, 8)}-${digest2.slice(8, 12)}-4${digest2.slice(13, 16)}-8${digest2.slice(17, 20)}-${digest2.slice(20, 32)}`,
|
|
10667
11006
|
version: legacyVersion,
|
|
@@ -10688,7 +11027,7 @@ class HistoryProjection {
|
|
|
10688
11027
|
}
|
|
10689
11028
|
if (legacy.length > 1000)
|
|
10690
11029
|
throw new ContractError("capacity");
|
|
10691
|
-
const items = [...legacy].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0), digest =
|
|
11030
|
+
const items = [...legacy].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0), digest = createHash13("sha256");
|
|
10692
11031
|
for (const item of items) {
|
|
10693
11032
|
budget.reserve(0);
|
|
10694
11033
|
if (Buffer.byteLength(item.goal) > 8192)
|
|
@@ -10878,7 +11217,7 @@ class HistoryProjection {
|
|
|
10878
11217
|
}
|
|
10879
11218
|
|
|
10880
11219
|
// src/history/reader.ts
|
|
10881
|
-
import { createHash as
|
|
11220
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
10882
11221
|
import { existsSync } from "node:fs";
|
|
10883
11222
|
import { join as join6 } from "node:path";
|
|
10884
11223
|
|
|
@@ -11010,6 +11349,9 @@ function graphPreparation(request, completion) {
|
|
|
11010
11349
|
});
|
|
11011
11350
|
}
|
|
11012
11351
|
async function readRetainedGraph(reader, request) {
|
|
11352
|
+
return retryStalled(() => graphQuantum(reader, request));
|
|
11353
|
+
}
|
|
11354
|
+
async function graphQuantum(reader, request) {
|
|
11013
11355
|
if (request.storeId !== reader.storeId)
|
|
11014
11356
|
throw new ContractError("corrupt-selected");
|
|
11015
11357
|
const budget = new HistoryReadBudget, proof = new HistoryReadProof(reader.files), completion = proof.prepareCompletion(budget);
|
|
@@ -11069,8 +11411,10 @@ async function readRetainedGraph(reader, request) {
|
|
|
11069
11411
|
point.event = 0;
|
|
11070
11412
|
}
|
|
11071
11413
|
} catch (error) {
|
|
11072
|
-
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive"
|
|
11414
|
+
if (!(error instanceof ContractError) || error.code !== "receipt-inconclusive")
|
|
11073
11415
|
throw error;
|
|
11416
|
+
if (coordinate() === before)
|
|
11417
|
+
noProgress(budget);
|
|
11074
11418
|
traversal.assertCurrent();
|
|
11075
11419
|
const next = {
|
|
11076
11420
|
...readIdentity(request),
|
|
@@ -11089,6 +11433,95 @@ async function readRetainedGraph(reader, request) {
|
|
|
11089
11433
|
}
|
|
11090
11434
|
|
|
11091
11435
|
// src/history/reader.ts
|
|
11436
|
+
class PlanFold {
|
|
11437
|
+
lineage;
|
|
11438
|
+
operation = null;
|
|
11439
|
+
graphIdentity = "";
|
|
11440
|
+
savePlanAt = null;
|
|
11441
|
+
lineageAt = null;
|
|
11442
|
+
constructor(lineage) {
|
|
11443
|
+
this.lineage = lineage;
|
|
11444
|
+
}
|
|
11445
|
+
apply(batch, taskId) {
|
|
11446
|
+
batch.rows.forEach((row, index) => {
|
|
11447
|
+
const value = row.value;
|
|
11448
|
+
if (value.kind === "admission" && value.intent.identity.kind === "action" && value.intent.identity.action.kind === "save-plan" && value.intent.identity.action.taskId === taskId) {
|
|
11449
|
+
if (value.intent.runId !== null)
|
|
11450
|
+
throw new ContractError("corrupt-selected");
|
|
11451
|
+
this.operation = new OperationState(value.intent.operationId);
|
|
11452
|
+
this.graphIdentity = hash(canonicalJson(value.intent.identity.action.graph));
|
|
11453
|
+
this.savePlanAt = { pointer: batch.pointer, row: index };
|
|
11454
|
+
}
|
|
11455
|
+
this.operation?.apply(row, batch.pointer.seq);
|
|
11456
|
+
if (!this.lineage)
|
|
11457
|
+
return;
|
|
11458
|
+
this.lineage.apply(row, batch.pointer.seq, taskId, this.graphIdentity);
|
|
11459
|
+
if (value.kind === "admission" && this.lineage.operationId === value.intent.operationId)
|
|
11460
|
+
this.lineageAt = { pointer: batch.pointer, row: index };
|
|
11461
|
+
});
|
|
11462
|
+
}
|
|
11463
|
+
state() {
|
|
11464
|
+
if (!this.lineage)
|
|
11465
|
+
throw new ContractError("invalid");
|
|
11466
|
+
return {
|
|
11467
|
+
savePlan: this.operation && this.savePlanAt ? { at: this.savePlanAt, ...this.operation.progress() } : null,
|
|
11468
|
+
graphIdentity: this.graphIdentity,
|
|
11469
|
+
lineage: { at: this.lineageAt, progress: this.lineage.progress() }
|
|
11470
|
+
};
|
|
11471
|
+
}
|
|
11472
|
+
static resumed(state, withLineage, read2) {
|
|
11473
|
+
let lineage = null;
|
|
11474
|
+
if (withLineage) {
|
|
11475
|
+
const row = state.lineage.at && read2(state.lineage.at).value;
|
|
11476
|
+
if (row && row.kind !== "admission")
|
|
11477
|
+
throw new ContractError("invalid");
|
|
11478
|
+
lineage = TaskPublicationLineage.resumed(row ? row.intent : null, state.lineage.progress);
|
|
11479
|
+
}
|
|
11480
|
+
const fold = new PlanFold(lineage);
|
|
11481
|
+
fold.lineageAt = state.lineage.at;
|
|
11482
|
+
fold.graphIdentity = state.graphIdentity;
|
|
11483
|
+
if (!state.savePlan) {
|
|
11484
|
+
if (state.graphIdentity !== "")
|
|
11485
|
+
throw new ContractError("invalid");
|
|
11486
|
+
return fold;
|
|
11487
|
+
}
|
|
11488
|
+
const v = read2(state.savePlan.at).value;
|
|
11489
|
+
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)
|
|
11490
|
+
throw new ContractError("invalid");
|
|
11491
|
+
fold.operation = OperationState.resumed(v.intent.operationId, { intent: v.intent, record: v.record }, state.savePlan);
|
|
11492
|
+
fold.savePlanAt = state.savePlan.at;
|
|
11493
|
+
return fold;
|
|
11494
|
+
}
|
|
11495
|
+
}
|
|
11496
|
+
function planFold(files, taskId, snapshot, budget, navigation, withLineage) {
|
|
11497
|
+
navigation.snapshot(snapshot, budget);
|
|
11498
|
+
const read2 = (at) => {
|
|
11499
|
+
if (BigInt(at.pointer.seq) > BigInt(snapshot))
|
|
11500
|
+
throw new ContractError("invalid");
|
|
11501
|
+
const row = navigation.batch(at.pointer, budget).rows[at.row];
|
|
11502
|
+
if (!row)
|
|
11503
|
+
throw new ContractError("invalid");
|
|
11504
|
+
return row;
|
|
11505
|
+
};
|
|
11506
|
+
const { base, batches } = resumeTaskFold(files, navigation, taskId, snapshot, budget, (state) => PlanFold.resumed(state.plan, withLineage, read2));
|
|
11507
|
+
const fold = base ?? new PlanFold(withLineage ? new TaskPublicationLineage : null);
|
|
11508
|
+
for (const batch of batches)
|
|
11509
|
+
fold.apply(batch, taskId);
|
|
11510
|
+
return fold;
|
|
11511
|
+
}
|
|
11512
|
+
function latestTaskRun(files, taskId, snapshot, budget, navigation = new HistoryNavigation(files)) {
|
|
11513
|
+
navigation.snapshot(snapshot, budget);
|
|
11514
|
+
const { base, batches } = resumeTaskFold(files, navigation, taskId, snapshot, budget, (state) => state.run);
|
|
11515
|
+
let latest = base ?? null;
|
|
11516
|
+
for (const batch of batches)
|
|
11517
|
+
for (const row of batch.rows) {
|
|
11518
|
+
const v = row.value;
|
|
11519
|
+
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"))
|
|
11520
|
+
latest = v.intent.runId;
|
|
11521
|
+
}
|
|
11522
|
+
return latest;
|
|
11523
|
+
}
|
|
11524
|
+
|
|
11092
11525
|
class HistoryReader {
|
|
11093
11526
|
files;
|
|
11094
11527
|
cached;
|
|
@@ -11104,7 +11537,7 @@ class HistoryReader {
|
|
|
11104
11537
|
admission(runId, snapshotSeq) {
|
|
11105
11538
|
decodeId(runId);
|
|
11106
11539
|
decodeSequence(snapshotSeq);
|
|
11107
|
-
const budget =
|
|
11540
|
+
const budget = HistoryReadBudget.authority();
|
|
11108
11541
|
this.navigation.snapshot(snapshotSeq, budget);
|
|
11109
11542
|
const batches = this.navigation.entityBatches(runId, snapshotSeq, budget);
|
|
11110
11543
|
const state = batches ? new RunState(runId) : readRunState(this.files, runId, snapshotSeq, budget);
|
|
@@ -11119,26 +11552,9 @@ class HistoryReader {
|
|
|
11119
11552
|
publishedPlan(taskId, snapshotSeq, current2) {
|
|
11120
11553
|
decodeId(taskId);
|
|
11121
11554
|
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
|
-
}
|
|
11555
|
+
const budget = HistoryReadBudget.authority();
|
|
11556
|
+
const fold = planFold(this.files, taskId, snapshotSeq, budget, this.navigation, !!current2);
|
|
11557
|
+
const { operation, lineage } = fold;
|
|
11142
11558
|
if (!operation)
|
|
11143
11559
|
return null;
|
|
11144
11560
|
const command = operation.command;
|
|
@@ -11149,7 +11565,7 @@ class HistoryReader {
|
|
|
11149
11565
|
throw new ContractError("conflict");
|
|
11150
11566
|
const { graph, plan } = command.intent.identity.action;
|
|
11151
11567
|
const ref = graph.plan;
|
|
11152
|
-
const detail = this.
|
|
11568
|
+
const { detail, bytes: text2 } = this.planText({
|
|
11153
11569
|
storeId: this.storeId,
|
|
11154
11570
|
scope: { kind: "task", id: taskId },
|
|
11155
11571
|
snapshotSeq,
|
|
@@ -11161,13 +11577,10 @@ class HistoryReader {
|
|
|
11161
11577
|
}, budget);
|
|
11162
11578
|
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
11579
|
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)
|
|
11580
|
+
if (text2.toString("utf8") !== plan)
|
|
11168
11581
|
throw new ContractError("corrupt-selected");
|
|
11169
|
-
const publication = current2 ? lineage
|
|
11170
|
-
if (!publication
|
|
11582
|
+
const publication = current2 ? lineage?.current() : operation.publicationExpected();
|
|
11583
|
+
if (!publication?.identity)
|
|
11171
11584
|
throw new ContractError("corrupt-selected");
|
|
11172
11585
|
if (current2 && (current2.identity !== publication.identity || current2.revision !== publication.revision || current2.plan !== plan))
|
|
11173
11586
|
throw new ContractError("stale");
|
|
@@ -11186,19 +11599,11 @@ class HistoryReader {
|
|
|
11186
11599
|
committedSeq: receipt.receipt.committedSeq
|
|
11187
11600
|
};
|
|
11188
11601
|
}
|
|
11189
|
-
reviewRun(taskId, runId, snapshotSeq, budget =
|
|
11602
|
+
reviewRun(taskId, runId, snapshotSeq, budget = HistoryReadBudget.authority()) {
|
|
11190
11603
|
decodeId(taskId);
|
|
11191
11604
|
decodeId(runId);
|
|
11192
11605
|
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
|
-
}
|
|
11606
|
+
const latest = latestTaskRun(this.files, taskId, snapshotSeq, budget, this.navigation);
|
|
11202
11607
|
if (latest !== runId)
|
|
11203
11608
|
throw new ContractError("stale");
|
|
11204
11609
|
const state = new RunState(runId);
|
|
@@ -11212,7 +11617,7 @@ class HistoryReader {
|
|
|
11212
11617
|
return state;
|
|
11213
11618
|
}
|
|
11214
11619
|
reviewSelection(taskId, runId, snapshotSeq) {
|
|
11215
|
-
const budget =
|
|
11620
|
+
const budget = HistoryReadBudget.authority();
|
|
11216
11621
|
const run = this.reviewRun(taskId, runId, snapshotSeq, budget);
|
|
11217
11622
|
if (!run.admission)
|
|
11218
11623
|
throw new ContractError("corrupt-selected");
|
|
@@ -11220,7 +11625,7 @@ class HistoryReader {
|
|
|
11220
11625
|
if (!graph)
|
|
11221
11626
|
return { run, graph, readablePlan: "" };
|
|
11222
11627
|
const ref = graph.plan;
|
|
11223
|
-
const detail = this.
|
|
11628
|
+
const { detail, bytes } = this.planText({
|
|
11224
11629
|
storeId: this.storeId,
|
|
11225
11630
|
scope: { kind: "task", id: taskId },
|
|
11226
11631
|
snapshotSeq,
|
|
@@ -11232,7 +11637,6 @@ class HistoryReader {
|
|
|
11232
11637
|
}, budget);
|
|
11233
11638
|
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
11639
|
throw new ContractError("corrupt-selected");
|
|
11235
|
-
const bytes = Buffer.concat([...this.parts(detail, snapshotSeq, budget)].map((p) => p.bytes));
|
|
11236
11640
|
return { run, graph, readablePlan: bytes.toString("utf8") };
|
|
11237
11641
|
}
|
|
11238
11642
|
async graph(value) {
|
|
@@ -11437,6 +11841,52 @@ class HistoryReader {
|
|
|
11437
11841
|
throw new ContractError("invalid");
|
|
11438
11842
|
return this.cached.detail;
|
|
11439
11843
|
}
|
|
11844
|
+
planText(cursor, budget) {
|
|
11845
|
+
const indexed = this.indexedText(cursor, budget);
|
|
11846
|
+
if (indexed)
|
|
11847
|
+
return indexed;
|
|
11848
|
+
const detail = this.selected(cursor, budget);
|
|
11849
|
+
return {
|
|
11850
|
+
detail,
|
|
11851
|
+
bytes: Buffer.concat([...this.parts(detail, cursor.snapshotSeq, budget)].map((p) => p.bytes))
|
|
11852
|
+
};
|
|
11853
|
+
}
|
|
11854
|
+
indexedText(cursor, budget) {
|
|
11855
|
+
const nav = this.navigation, id = cursor.recordId;
|
|
11856
|
+
if (!id)
|
|
11857
|
+
return;
|
|
11858
|
+
try {
|
|
11859
|
+
this.files.assertReadFormat(budget);
|
|
11860
|
+
nav.snapshot(cursor.snapshotSeq, budget);
|
|
11861
|
+
const detail = nav.content(id, cursor.snapshotSeq, budget);
|
|
11862
|
+
if (!detail || detail.content.version !== cursor.recordVersion || detail.content.field !== cursor.field)
|
|
11863
|
+
return;
|
|
11864
|
+
const pieces = [];
|
|
11865
|
+
let end = 0;
|
|
11866
|
+
if (detail.content.bytes) {
|
|
11867
|
+
const count2 = nav.partCount(id, budget);
|
|
11868
|
+
for (let part = 0;part < count2; part++) {
|
|
11869
|
+
const chunks = nav.part(id, part, cursor.snapshotSeq, budget);
|
|
11870
|
+
if (!chunks?.length)
|
|
11871
|
+
return;
|
|
11872
|
+
const bytes = this.files.readArtifact(chunks[0].artifact, budget);
|
|
11873
|
+
for (const c of chunks) {
|
|
11874
|
+
if (c.offset !== end || c.start + c.length > bytes.length)
|
|
11875
|
+
return;
|
|
11876
|
+
pieces.push(bytes.subarray(c.start, c.start + c.length));
|
|
11877
|
+
end += c.length;
|
|
11878
|
+
}
|
|
11879
|
+
}
|
|
11880
|
+
}
|
|
11881
|
+
const text2 = Buffer.concat(pieces);
|
|
11882
|
+
if (end !== detail.content.bytes || createHash14("sha256").update(text2).digest("hex") !== detail.content.sha256)
|
|
11883
|
+
return;
|
|
11884
|
+
return { detail, bytes: text2 };
|
|
11885
|
+
} catch {
|
|
11886
|
+
budget.reserve(0);
|
|
11887
|
+
return;
|
|
11888
|
+
}
|
|
11889
|
+
}
|
|
11440
11890
|
*parts(detail, snapshotSeq, budget, range2) {
|
|
11441
11891
|
if (budget)
|
|
11442
11892
|
this.files.assertReadFormat(budget);
|
|
@@ -11464,7 +11914,7 @@ class HistoryReader {
|
|
|
11464
11914
|
if (!current2 || current2.length < expected.bytes || hash(current2.subarray(0, expected.bytes)) !== expected.identity)
|
|
11465
11915
|
throw new ContractError("corrupt-selected");
|
|
11466
11916
|
}
|
|
11467
|
-
const checksum =
|
|
11917
|
+
const checksum = createHash14("sha256");
|
|
11468
11918
|
let bytes = 0;
|
|
11469
11919
|
let cached;
|
|
11470
11920
|
for (const e of locator.chunks) {
|
|
@@ -11738,7 +12188,7 @@ class HistoryReader {
|
|
|
11738
12188
|
}
|
|
11739
12189
|
|
|
11740
12190
|
// src/history/writer.ts
|
|
11741
|
-
import { createHash as
|
|
12191
|
+
import { createHash as createHash16, randomUUID as randomUUID6 } from "node:crypto";
|
|
11742
12192
|
import { join as join8 } from "node:path";
|
|
11743
12193
|
|
|
11744
12194
|
// src/worker-redaction.mjs
|
|
@@ -12100,11 +12550,11 @@ class DetailSanitizer {
|
|
|
12100
12550
|
}
|
|
12101
12551
|
|
|
12102
12552
|
// src/history/plans.ts
|
|
12103
|
-
import { createHash as
|
|
12553
|
+
import { createHash as createHash15, randomUUID as randomUUID5 } from "node:crypto";
|
|
12104
12554
|
function assertPlanContent(plan, reference, secrets) {
|
|
12105
12555
|
text(plan, 32768);
|
|
12106
12556
|
const ref = decodeContentRef(reference);
|
|
12107
|
-
if (ref.field !== "plan" || ref.bytes !== Buffer.byteLength(plan) || ref.sha256 !==
|
|
12557
|
+
if (ref.field !== "plan" || ref.bytes !== Buffer.byteLength(plan) || ref.sha256 !== createHash15("sha256").update(plan).digest("hex") || sanitizeText(plan, secrets) !== plan)
|
|
12108
12558
|
throw new ContractError("invalid");
|
|
12109
12559
|
}
|
|
12110
12560
|
function proposePlanContent(plan, version, secrets) {
|
|
@@ -12114,7 +12564,7 @@ function proposePlanContent(plan, version, secrets) {
|
|
|
12114
12564
|
version,
|
|
12115
12565
|
field: "plan",
|
|
12116
12566
|
bytes: Buffer.byteLength(plan),
|
|
12117
|
-
sha256:
|
|
12567
|
+
sha256: createHash15("sha256").update(plan).digest("hex")
|
|
12118
12568
|
});
|
|
12119
12569
|
assertPlanContent(plan, reference, secrets);
|
|
12120
12570
|
return reference;
|
|
@@ -12833,9 +13283,9 @@ class HistoryWriter {
|
|
|
12833
13283
|
const continuation = jobs.every((j) => j.safety || j.continuation);
|
|
12834
13284
|
try {
|
|
12835
13285
|
this.assertWritable(safety, continuation);
|
|
12836
|
-
for (const
|
|
13286
|
+
for (const job2 of jobs) {
|
|
12837
13287
|
const seq2 = String(BigInt(this.state.committedSeq) + BigInt(rows.length) + 1n);
|
|
12838
|
-
const value = await
|
|
13288
|
+
const value = await job2.build(seq2);
|
|
12839
13289
|
if (value) {
|
|
12840
13290
|
const row = {
|
|
12841
13291
|
kind: "record",
|
|
@@ -12844,7 +13294,7 @@ class HistoryWriter {
|
|
|
12844
13294
|
value
|
|
12845
13295
|
};
|
|
12846
13296
|
rows.push(row);
|
|
12847
|
-
ownRows.set(
|
|
13297
|
+
ownRows.set(job2, row);
|
|
12848
13298
|
}
|
|
12849
13299
|
}
|
|
12850
13300
|
if (rows.length) {
|
|
@@ -12900,8 +13350,8 @@ class HistoryWriter {
|
|
|
12900
13350
|
this.assertReviews(rows);
|
|
12901
13351
|
}, preparedIndex?.root, (encodedBatch) => {
|
|
12902
13352
|
let markerBytes = encodedBatch.trailerBytes, index = 0;
|
|
12903
|
-
for (const [
|
|
12904
|
-
this.charge(encodedBatch.rowBytes[index++] + markerBytes, this.rowRunId(row.value),
|
|
13353
|
+
for (const [job2, row] of ownRows) {
|
|
13354
|
+
this.charge(encodedBatch.rowBytes[index++] + markerBytes, this.rowRunId(row.value), job2.control);
|
|
12905
13355
|
markerBytes = 0;
|
|
12906
13356
|
}
|
|
12907
13357
|
}, () => this.assertReviews(rows));
|
|
@@ -12916,6 +13366,7 @@ class HistoryWriter {
|
|
|
12916
13366
|
seq: this.state.committedSeq
|
|
12917
13367
|
}, rows, (bytes) => this.charge(bytes, null, true), preparedIndex);
|
|
12918
13368
|
} catch {}
|
|
13369
|
+
this.rememberTaskFolds(rows, preparedIndex);
|
|
12919
13370
|
}
|
|
12920
13371
|
const batch = {
|
|
12921
13372
|
committedSeq: this.state.committedSeq,
|
|
@@ -13040,14 +13491,14 @@ class HistoryWriter {
|
|
|
13040
13491
|
this.fence();
|
|
13041
13492
|
result = failure(true);
|
|
13042
13493
|
}
|
|
13043
|
-
for (const
|
|
13494
|
+
for (const job2 of jobs) {
|
|
13044
13495
|
if (result.status !== "committed") {
|
|
13045
|
-
|
|
13496
|
+
job2.resolve(result);
|
|
13046
13497
|
continue;
|
|
13047
13498
|
}
|
|
13048
|
-
const v = ownRows.get(
|
|
13499
|
+
const v = ownRows.get(job2)?.value;
|
|
13049
13500
|
const ends = v?.kind === "capture" || v?.kind === "owner" ? v.events.flatMap((e) => e.kind === "end" ? [e.detail] : []) : [];
|
|
13050
|
-
|
|
13501
|
+
job2.resolve({
|
|
13051
13502
|
status: "committed",
|
|
13052
13503
|
batch: {
|
|
13053
13504
|
...result.batch,
|
|
@@ -13093,7 +13544,7 @@ class HistoryWriter {
|
|
|
13093
13544
|
reservedIdentity(commandId) {
|
|
13094
13545
|
decodeId(commandId);
|
|
13095
13546
|
try {
|
|
13096
|
-
const bytes = this.files.readCommand(commandId,
|
|
13547
|
+
const bytes = this.files.readCommand(commandId, HistoryReadBudget.authority());
|
|
13097
13548
|
return bytes ? decodeAdmissionIntent(json(bytes, RECORD_BYTES)).identity : null;
|
|
13098
13549
|
} catch {
|
|
13099
13550
|
return null;
|
|
@@ -13111,7 +13562,7 @@ class HistoryWriter {
|
|
|
13111
13562
|
async lookupCommitted(commandId) {
|
|
13112
13563
|
decodeId(commandId);
|
|
13113
13564
|
try {
|
|
13114
|
-
const budget =
|
|
13565
|
+
const budget = HistoryReadBudget.authority();
|
|
13115
13566
|
const intent = this.files.readCommand(commandId, budget);
|
|
13116
13567
|
const directoryExists = this.files.files.directory(join8(this.files.directory, "commands", commandId));
|
|
13117
13568
|
const wanted = intent ? decodeAdmissionIntent(json(intent, RECORD_BYTES)) : null;
|
|
@@ -13381,6 +13832,29 @@ class HistoryWriter {
|
|
|
13381
13832
|
return this.ownerValue(input, null, selected2.plan);
|
|
13382
13833
|
}, lease.budget.retainedBytes + lease.budget.outputBytes, true, () => begun ? lease.settled() : lease.release());
|
|
13383
13834
|
}
|
|
13835
|
+
rememberTaskFolds(rows, prepared) {
|
|
13836
|
+
if (!prepared)
|
|
13837
|
+
return;
|
|
13838
|
+
const tasks = new Set;
|
|
13839
|
+
for (const { value: v } of rows)
|
|
13840
|
+
if (v.kind === "owner" && v.events.some((e) => e.kind === "owner" && e.value.kind === "fact" && e.value.fact.kind === "publication-settled"))
|
|
13841
|
+
tasks.add(v.link.taskId);
|
|
13842
|
+
for (const taskId of tasks) {
|
|
13843
|
+
const head = prepared.heads.get(taskId);
|
|
13844
|
+
if (!head || head.seq !== this.state.committedSeq)
|
|
13845
|
+
continue;
|
|
13846
|
+
try {
|
|
13847
|
+
const checks = checkFold(this.files, taskId, head.seq, HistoryReadBudget.authority()).state();
|
|
13848
|
+
const run = latestTaskRun(this.files, taskId, head.seq, HistoryReadBudget.authority());
|
|
13849
|
+
const plan = planFold(this.files, taskId, head.seq, HistoryReadBudget.authority(), new HistoryNavigation(this.files), true).state();
|
|
13850
|
+
taskFoldMemory(this.files).remember(taskId, head, {
|
|
13851
|
+
checks,
|
|
13852
|
+
run,
|
|
13853
|
+
plan
|
|
13854
|
+
});
|
|
13855
|
+
} catch {}
|
|
13856
|
+
}
|
|
13857
|
+
}
|
|
13384
13858
|
async publishTask(request) {
|
|
13385
13859
|
request = structuredClone(request);
|
|
13386
13860
|
decodeId(request.operationId);
|
|
@@ -13572,7 +14046,7 @@ class HistoryWriter {
|
|
|
13572
14046
|
}
|
|
13573
14047
|
}
|
|
13574
14048
|
retainedRun(runId) {
|
|
13575
|
-
const budget =
|
|
14049
|
+
const budget = HistoryReadBudget.authority();
|
|
13576
14050
|
const snapshot = this.state.committedSeq;
|
|
13577
14051
|
this.navigation.snapshot(snapshot, budget);
|
|
13578
14052
|
const batches = this.navigation.runBatches(runId, snapshot, budget);
|
|
@@ -13585,7 +14059,7 @@ class HistoryWriter {
|
|
|
13585
14059
|
return state;
|
|
13586
14060
|
}
|
|
13587
14061
|
prior(matches) {
|
|
13588
|
-
for (const batch of this.files.scan(emptyScan(), false,
|
|
14062
|
+
for (const batch of this.files.scan(emptyScan(), false, HistoryReadBudget.authority())) {
|
|
13589
14063
|
if (batch.rows.some((row) => matches(row.value)))
|
|
13590
14064
|
return true;
|
|
13591
14065
|
}
|
|
@@ -13757,7 +14231,7 @@ class HistoryWriter {
|
|
|
13757
14231
|
recordId: randomUUID6(),
|
|
13758
14232
|
version: 1,
|
|
13759
14233
|
bytes: 0,
|
|
13760
|
-
hash:
|
|
14234
|
+
hash: createHash16("sha256"),
|
|
13761
14235
|
parts: 0,
|
|
13762
14236
|
ended: null,
|
|
13763
14237
|
committedSeq: null
|
|
@@ -13949,7 +14423,7 @@ class HistoryWriter {
|
|
|
13949
14423
|
assertReview(input, fact) {
|
|
13950
14424
|
let accepted2 = null;
|
|
13951
14425
|
let changes = null;
|
|
13952
|
-
for (const batch of this.files.scan(emptyScan(), false,
|
|
14426
|
+
for (const batch of this.files.scan(emptyScan(), false, HistoryReadBudget.authority()))
|
|
13953
14427
|
for (const row of batch.rows) {
|
|
13954
14428
|
const v = row.value;
|
|
13955
14429
|
if (v.kind === "admission" && v.intent.operationId === input.link.operationId)
|
|
@@ -14106,7 +14580,7 @@ function readOperationStateForPublication(files, operationId) {
|
|
|
14106
14580
|
let publication = null;
|
|
14107
14581
|
const state = new OperationState(operationId);
|
|
14108
14582
|
const facts = [];
|
|
14109
|
-
for (const batch of files.scan(emptyScan(), false,
|
|
14583
|
+
for (const batch of files.scan(emptyScan(), false, HistoryReadBudget.authority()))
|
|
14110
14584
|
for (const row of batch.rows) {
|
|
14111
14585
|
const v = row.value;
|
|
14112
14586
|
if (v.kind === "recovery" && intent)
|
|
@@ -14133,10 +14607,10 @@ function readOperationStateForPublication(files, operationId) {
|
|
|
14133
14607
|
}
|
|
14134
14608
|
|
|
14135
14609
|
// src/service-assets.ts
|
|
14136
|
-
import { createHash as
|
|
14610
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
14137
14611
|
import { lstatSync as lstatSync7, readdirSync as readdirSync2 } from "node:fs";
|
|
14138
14612
|
import { join as join9 } from "node:path";
|
|
14139
|
-
import { fileURLToPath } from "node:url";
|
|
14613
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
14140
14614
|
var ASSET_MEMBER_BYTES = 16 * 1024 * 1024;
|
|
14141
14615
|
var ASSET_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
14142
14616
|
var fixed = new Set([
|
|
@@ -14209,7 +14683,7 @@ function decodeAssetManifest(input) {
|
|
|
14209
14683
|
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
14684
|
throw new Error("asset-closure-refused");
|
|
14211
14685
|
const sourceHash = hash2(o.sourceHash), contractHash = hash2(o.contractHash), buildId = hash2(o.buildId);
|
|
14212
|
-
if (buildId !==
|
|
14686
|
+
if (buildId !== createHash17("sha256").update(JSON.stringify({ sourceHash, contractHash })).digest("hex"))
|
|
14213
14687
|
throw new Error("asset-identity-refused");
|
|
14214
14688
|
return { schema: o.schema, sourceHash, contractHash, buildId, assets };
|
|
14215
14689
|
}
|
|
@@ -14224,7 +14698,7 @@ function incarnation2(st) {
|
|
|
14224
14698
|
st.ctimeMs
|
|
14225
14699
|
].join(":");
|
|
14226
14700
|
}
|
|
14227
|
-
function loadWebAssets(directory =
|
|
14701
|
+
function loadWebAssets(directory = fileURLToPath2(new URL("../web/dist/", import.meta.url))) {
|
|
14228
14702
|
if (!safeDirectory(directory) || !safeDirectory(join9(directory, "assets")))
|
|
14229
14703
|
throw new Error("assets-unavailable");
|
|
14230
14704
|
const observed = new Map;
|
|
@@ -14232,7 +14706,7 @@ function loadWebAssets(directory = fileURLToPath(new URL("../web/dist/", import.
|
|
|
14232
14706
|
observed.set(path, incarnation2(lstatSync7(path)));
|
|
14233
14707
|
const read2 = (path, limit) => {
|
|
14234
14708
|
const before = incarnation2(lstatSync7(path));
|
|
14235
|
-
const body = readBounded(path, limit);
|
|
14709
|
+
const body = readBounded(path, limit, true);
|
|
14236
14710
|
if (!body || before !== incarnation2(lstatSync7(path)))
|
|
14237
14711
|
throw new Error("asset-read-changed");
|
|
14238
14712
|
observed.set(path, before);
|
|
@@ -14248,7 +14722,7 @@ function loadWebAssets(directory = fileURLToPath(new URL("../web/dist/", import.
|
|
|
14248
14722
|
throw new Error("asset-members-refused");
|
|
14249
14723
|
const assets = manifest.assets.map((a) => {
|
|
14250
14724
|
const body = read2(join9(directory, a.name), a.bytes);
|
|
14251
|
-
if (!body || body.length !== a.bytes ||
|
|
14725
|
+
if (!body || body.length !== a.bytes || createHash17("sha256").update(body).digest("hex") !== a.sha256)
|
|
14252
14726
|
throw new Error("asset-body-refused");
|
|
14253
14727
|
return {
|
|
14254
14728
|
path: a.name === "index.html" ? "/app" : `/${a.name}`,
|
|
@@ -14261,13 +14735,13 @@ function loadWebAssets(directory = fileURLToPath(new URL("../web/dist/", import.
|
|
|
14261
14735
|
return {
|
|
14262
14736
|
manifest,
|
|
14263
14737
|
assets,
|
|
14264
|
-
identity:
|
|
14738
|
+
identity: createHash17("sha256").update(bytes).digest("hex")
|
|
14265
14739
|
};
|
|
14266
14740
|
}
|
|
14267
14741
|
|
|
14268
14742
|
// src/service/auth.ts
|
|
14269
14743
|
import {
|
|
14270
|
-
createHash as
|
|
14744
|
+
createHash as createHash18,
|
|
14271
14745
|
randomBytes as randomBytes3,
|
|
14272
14746
|
randomUUID as randomUUID7,
|
|
14273
14747
|
timingSafeEqual
|
|
@@ -14331,8 +14805,8 @@ function token(value) {
|
|
|
14331
14805
|
return result;
|
|
14332
14806
|
}
|
|
14333
14807
|
function sameSecret(candidate, expected) {
|
|
14334
|
-
const a =
|
|
14335
|
-
const b =
|
|
14808
|
+
const a = createHash18("sha256").update(candidate).digest();
|
|
14809
|
+
const b = createHash18("sha256").update(expected).digest();
|
|
14336
14810
|
return timingSafeEqual(a, b);
|
|
14337
14811
|
}
|
|
14338
14812
|
function buildTokenCookie(name, encodedValue, maxAge, secure) {
|
|
@@ -14652,7 +15126,10 @@ function safeChildEnv() {
|
|
|
14652
15126
|
"TMPDIR",
|
|
14653
15127
|
"LANG",
|
|
14654
15128
|
"LC_ALL",
|
|
14655
|
-
"PATHEXT"
|
|
15129
|
+
"PATHEXT",
|
|
15130
|
+
"ProgramFiles",
|
|
15131
|
+
"ProgramFiles(x86)",
|
|
15132
|
+
"ProgramW6432"
|
|
14656
15133
|
])
|
|
14657
15134
|
if (process.env[key2] !== undefined)
|
|
14658
15135
|
env[key2] = process.env[key2];
|
|
@@ -15014,8 +15491,8 @@ class ServiceCoordination {
|
|
|
15014
15491
|
}
|
|
15015
15492
|
};
|
|
15016
15493
|
}
|
|
15017
|
-
assertNamespace() {
|
|
15018
|
-
if (!this.files.directory(this.directory))
|
|
15494
|
+
assertNamespace(walks) {
|
|
15495
|
+
if (!this.files.directory(this.directory, false, walks))
|
|
15019
15496
|
refused();
|
|
15020
15497
|
names(this.directory, [
|
|
15021
15498
|
"format.json",
|
|
@@ -15028,14 +15505,15 @@ class ServiceCoordination {
|
|
|
15028
15505
|
"host-config.json",
|
|
15029
15506
|
"history-v2"
|
|
15030
15507
|
]);
|
|
15031
|
-
this.files.directory(join11(this.directory, "history-v2"));
|
|
15508
|
+
this.files.directory(join11(this.directory, "history-v2"), false, walks);
|
|
15032
15509
|
}
|
|
15033
15510
|
selected() {
|
|
15034
|
-
|
|
15511
|
+
const walks = new DirectoryWalks;
|
|
15512
|
+
if (!this.files.directory(this.directory, false, walks))
|
|
15035
15513
|
return;
|
|
15036
|
-
this.assertNamespace();
|
|
15037
|
-
const manifest = this.files.read(this.path("format.json"));
|
|
15038
|
-
const config = this.files.read(this.path("config.json"));
|
|
15514
|
+
this.assertNamespace(walks);
|
|
15515
|
+
const manifest = this.files.read(this.path("format.json"), undefined, walks);
|
|
15516
|
+
const config = this.files.read(this.path("config.json"), undefined, walks);
|
|
15039
15517
|
if (!manifest || !config)
|
|
15040
15518
|
refused();
|
|
15041
15519
|
const f = closed(json(manifest), [
|
|
@@ -15049,13 +15527,13 @@ class ServiceCoordination {
|
|
|
15049
15527
|
literal(f.stateHome, this.stateHome);
|
|
15050
15528
|
literal(f.configIdentity, config.identity);
|
|
15051
15529
|
const c = decodeOwnerConfig(json(config));
|
|
15052
|
-
const hostConfig = this.files.read(this.path("host-config.json"));
|
|
15530
|
+
const hostConfig = this.files.read(this.path("host-config.json"), undefined, walks);
|
|
15053
15531
|
if (hostConfig && hostConfig.identity !== c.policyIdentity)
|
|
15054
15532
|
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);
|
|
15533
|
+
const registration = this.files.read(this.path("registration.json"), undefined, walks);
|
|
15534
|
+
const owner = this.files.read(this.path("owner.lock"), undefined, walks);
|
|
15535
|
+
const capability = this.files.read(this.path("local-capability.json"), undefined, walks);
|
|
15536
|
+
const checkout = this.files.read(this.store.lockPath, 8192, walks);
|
|
15059
15537
|
const value = registration ? decodeRegistration(json(registration)) : undefined;
|
|
15060
15538
|
if (value && (value.root !== this.store.root || value.stateHome !== this.stateHome || value.configIdentity !== config.identity || !endpointMatches(c.endpoint, value.endpoint)))
|
|
15061
15539
|
refused();
|
|
@@ -15402,8 +15880,8 @@ class OwnerLease {
|
|
|
15402
15880
|
assert() {
|
|
15403
15881
|
if (this.released || this.selected.value.pid !== process.pid || this.selected.value.host !== hostname2())
|
|
15404
15882
|
throw new ContractError("conflict");
|
|
15405
|
-
const c = this.coordination;
|
|
15406
|
-
c.assertNamespace();
|
|
15883
|
+
const c = this.coordination, walks = new DirectoryWalks;
|
|
15884
|
+
c.assertNamespace(walks);
|
|
15407
15885
|
for (const [name, snapshot] of [
|
|
15408
15886
|
["config.json", this.selected.config],
|
|
15409
15887
|
["host-config.json", this.selected.hostConfig],
|
|
@@ -15412,8 +15890,8 @@ class OwnerLease {
|
|
|
15412
15890
|
["owner.lock", this.selected.owner],
|
|
15413
15891
|
["local-capability.json", this.selected.capability]
|
|
15414
15892
|
])
|
|
15415
|
-
c.files.assert(c.path(name), snapshot);
|
|
15416
|
-
c.files.assert(c.store.lockPath, this.selected.checkout, 8192);
|
|
15893
|
+
c.files.assert(c.path(name), snapshot, undefined, walks);
|
|
15894
|
+
c.files.assert(c.store.lockPath, this.selected.checkout, 8192, walks);
|
|
15417
15895
|
c.store.assertOwner(this.checkout);
|
|
15418
15896
|
}
|
|
15419
15897
|
phase(phase) {
|
|
@@ -15448,7 +15926,7 @@ class OwnerLease {
|
|
|
15448
15926
|
}
|
|
15449
15927
|
|
|
15450
15928
|
// src/service/http.ts
|
|
15451
|
-
import { createHash as
|
|
15929
|
+
import { createHash as createHash19, randomUUID as randomUUID9 } from "node:crypto";
|
|
15452
15930
|
import {
|
|
15453
15931
|
createServer
|
|
15454
15932
|
} from "node:http";
|
|
@@ -15732,7 +16210,7 @@ class ServiceHttpServer {
|
|
|
15732
16210
|
this.assets.set(asset.path, {
|
|
15733
16211
|
...asset,
|
|
15734
16212
|
bytes: Uint8Array.from(asset.bytes),
|
|
15735
|
-
etag: `"${
|
|
16213
|
+
etag: `"${createHash19("sha256").update(asset.bytes).digest("hex")}"`
|
|
15736
16214
|
});
|
|
15737
16215
|
}
|
|
15738
16216
|
this.server = createServer({
|
|
@@ -16241,7 +16719,7 @@ Content-Length: 0\r
|
|
|
16241
16719
|
res.setHeader("Content-Type", operation === "download" && request.format === "markdown" ? "text/markdown; charset=utf-8" : "application/x-ndjson; charset=utf-8");
|
|
16242
16720
|
res.setHeader("Content-Disposition", `attachment; filename="cadence-export.${operation === "download" && request.format === "markdown" ? "md" : "ndjson"}"`);
|
|
16243
16721
|
let trailer;
|
|
16244
|
-
const digest =
|
|
16722
|
+
const digest = createHash19("sha256");
|
|
16245
16723
|
const write = async (frame) => {
|
|
16246
16724
|
const data = Buffer.from(operation === "download" ? encodeExportFrame(frame, request.format) : `${canonicalJson(frame)}
|
|
16247
16725
|
`);
|
|
@@ -16552,9 +17030,6 @@ class OwnerModelChoice {
|
|
|
16552
17030
|
}
|
|
16553
17031
|
}
|
|
16554
17032
|
|
|
16555
|
-
// src/service/model-context.ts
|
|
16556
|
-
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
16557
|
-
|
|
16558
17033
|
// src/service/model-config.ts
|
|
16559
17034
|
import { lstatSync as lstatSync8, realpathSync as realpathSync4 } from "node:fs";
|
|
16560
17035
|
import { dirname as dirname9, isAbsolute as isAbsolute6, join as join12, resolve as resolve3 } from "node:path";
|
|
@@ -16723,7 +17198,8 @@ function compat(value) {
|
|
|
16723
17198
|
"forceAdaptiveThinking",
|
|
16724
17199
|
"allowEmptySignature",
|
|
16725
17200
|
"supportsStrictTools",
|
|
16726
|
-
"supportsToolReferences"
|
|
17201
|
+
"supportsToolReferences",
|
|
17202
|
+
"supportsMidConvoSystemMessages"
|
|
16727
17203
|
];
|
|
16728
17204
|
const enums = {
|
|
16729
17205
|
maxTokensField: ["max_completion_tokens", "max_tokens"],
|
|
@@ -16921,6 +17397,11 @@ function modelJson(snapshot) {
|
|
|
16921
17397
|
return parseWireJson(normalized, CONFIG_BYTES);
|
|
16922
17398
|
}
|
|
16923
17399
|
function readPiSnapshot(source) {
|
|
17400
|
+
const { auth, credentials } = readPiAuth(source);
|
|
17401
|
+
const { models, providers } = readPiModels(source);
|
|
17402
|
+
return { auth, models, credentials, providers };
|
|
17403
|
+
}
|
|
17404
|
+
function readPiAuth(source) {
|
|
16924
17405
|
const auth = source.files.read(source.authPath, CONFIG_BYTES);
|
|
16925
17406
|
if (!auth)
|
|
16926
17407
|
refuse2();
|
|
@@ -16944,6 +17425,9 @@ function readPiSnapshot(source) {
|
|
|
16944
17425
|
refuse2();
|
|
16945
17426
|
credentials[provider] = credential;
|
|
16946
17427
|
}
|
|
17428
|
+
return { auth, credentials };
|
|
17429
|
+
}
|
|
17430
|
+
function readPiModels(source) {
|
|
16947
17431
|
const models = source.files.read(source.modelsPath, CONFIG_BYTES);
|
|
16948
17432
|
const providers = Object.create(null);
|
|
16949
17433
|
if (models) {
|
|
@@ -16987,7 +17471,7 @@ function readPiSnapshot(source) {
|
|
|
16987
17471
|
providers[id] = provider;
|
|
16988
17472
|
}
|
|
16989
17473
|
}
|
|
16990
|
-
return {
|
|
17474
|
+
return { models, providers };
|
|
16991
17475
|
}
|
|
16992
17476
|
function sourceIdentity(snapshot, provider) {
|
|
16993
17477
|
const credential = snapshot.credentials[provider];
|
|
@@ -17074,6 +17558,7 @@ function clonePrivate(value) {
|
|
|
17074
17558
|
}
|
|
17075
17559
|
|
|
17076
17560
|
// src/service/model-context.ts
|
|
17561
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
17077
17562
|
function committed(result) {
|
|
17078
17563
|
if (result.status !== "committed")
|
|
17079
17564
|
throw new ContractError("recording-exhausted");
|
|
@@ -18117,6 +18602,28 @@ function decodeHostInput(value) {
|
|
|
18117
18602
|
environmentToken
|
|
18118
18603
|
};
|
|
18119
18604
|
}
|
|
18605
|
+
class StartupRefusal extends ContractError {
|
|
18606
|
+
stage;
|
|
18607
|
+
quarantined;
|
|
18608
|
+
constructor(code, stage, quarantined = false) {
|
|
18609
|
+
super(code);
|
|
18610
|
+
this.stage = stage;
|
|
18611
|
+
this.quarantined = quarantined;
|
|
18612
|
+
}
|
|
18613
|
+
}
|
|
18614
|
+
function startupReport(error) {
|
|
18615
|
+
if (error instanceof StartupRefusal)
|
|
18616
|
+
return {
|
|
18617
|
+
status: error.quarantined ? "quarantined" : "refused",
|
|
18618
|
+
error: error.code,
|
|
18619
|
+
stage: error.stage
|
|
18620
|
+
};
|
|
18621
|
+
return {
|
|
18622
|
+
status: "refused",
|
|
18623
|
+
error: error instanceof ContractError ? error.code : "unavailable",
|
|
18624
|
+
stage: "service"
|
|
18625
|
+
};
|
|
18626
|
+
}
|
|
18120
18627
|
|
|
18121
18628
|
// src/service/task-actions.ts
|
|
18122
18629
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
@@ -18124,13 +18631,13 @@ import { realpathSync as realpathSync5 } from "node:fs";
|
|
|
18124
18631
|
|
|
18125
18632
|
// src/task-diff.ts
|
|
18126
18633
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
18127
|
-
import { createHash as
|
|
18634
|
+
import { createHash as createHash20 } from "node:crypto";
|
|
18128
18635
|
import { lstatSync as lstatSync9, readdirSync as readdirSync3, readlinkSync } from "node:fs";
|
|
18129
18636
|
import { join as join14, relative as relative5 } from "node:path";
|
|
18130
18637
|
var admin = (path) => path === ".cadence/assistant/tasks" || path.startsWith(".cadence/assistant/tasks/");
|
|
18131
18638
|
function captureChanges(root) {
|
|
18132
18639
|
const deadline = Date.now() + 3000;
|
|
18133
|
-
const hash3 =
|
|
18640
|
+
const hash3 = createHash20("sha256");
|
|
18134
18641
|
const parts = [];
|
|
18135
18642
|
let complete = true;
|
|
18136
18643
|
const limits = [];
|
|
@@ -18187,7 +18694,7 @@ function captureChanges(root) {
|
|
|
18187
18694
|
fileBytes += data2.length;
|
|
18188
18695
|
add(name, data2);
|
|
18189
18696
|
const binary = data2.includes(0);
|
|
18190
|
-
parts.push(`${name} (${binary ? "binary" : "text"}, ${data2.length} bytes, sha256 ${
|
|
18697
|
+
parts.push(`${name} (${binary ? "binary" : "text"}, ${data2.length} bytes, sha256 ${createHash20("sha256").update(data2).digest("hex")})`);
|
|
18191
18698
|
} catch {
|
|
18192
18699
|
complete = false;
|
|
18193
18700
|
limits.push(`Unreadable/changing: ${name}`);
|
|
@@ -18867,33 +19374,6 @@ class ActionAdmission {
|
|
|
18867
19374
|
// src/service/graph.ts
|
|
18868
19375
|
import { createHash as createHash21 } from "node:crypto";
|
|
18869
19376
|
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
19377
|
var hash3 = (text2) => createHash21("sha256").update(text2).digest("hex");
|
|
18898
19378
|
|
|
18899
19379
|
class GraphContext {
|
|
@@ -19040,6 +19520,7 @@ import { randomUUID as randomUUID14 } from "node:crypto";
|
|
|
19040
19520
|
|
|
19041
19521
|
// src/subagent-process.ts
|
|
19042
19522
|
import { spawn } from "node:child_process";
|
|
19523
|
+
import { join as join16 } from "node:path";
|
|
19043
19524
|
import { StringDecoder } from "node:string_decoder";
|
|
19044
19525
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
19045
19526
|
// src/worker-capture.mjs
|
|
@@ -19622,6 +20103,15 @@ class WorkerRecording {
|
|
|
19622
20103
|
}
|
|
19623
20104
|
|
|
19624
20105
|
// src/subagent-process.ts
|
|
20106
|
+
function terminateWindowsTree(pid, fallback) {
|
|
20107
|
+
const root = process.env.SystemRoot;
|
|
20108
|
+
const taskkill = spawn(root ? join16(root, "System32", "taskkill.exe") : "taskkill.exe", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
20109
|
+
taskkill.once("error", fallback);
|
|
20110
|
+
taskkill.once("close", (code) => {
|
|
20111
|
+
if (code !== 0)
|
|
20112
|
+
fallback();
|
|
20113
|
+
});
|
|
20114
|
+
}
|
|
19625
20115
|
function runProcess(spec) {
|
|
19626
20116
|
const started = Date.now();
|
|
19627
20117
|
const early = (outcome, text2) => ({
|
|
@@ -19739,6 +20229,8 @@ function runProcess(spec) {
|
|
|
19739
20229
|
return;
|
|
19740
20230
|
if (process.platform !== "win32" && child.pid)
|
|
19741
20231
|
process.kill(-child.pid, signal);
|
|
20232
|
+
else if (child.pid && child.exitCode === null && child.signalCode === null)
|
|
20233
|
+
terminateWindowsTree(child.pid, () => child.kill(signal));
|
|
19742
20234
|
else
|
|
19743
20235
|
child.kill(signal);
|
|
19744
20236
|
} catch {}
|
|
@@ -20706,8 +21198,8 @@ class TaskScheduler {
|
|
|
20706
21198
|
this.pumping = false;
|
|
20707
21199
|
}
|
|
20708
21200
|
}
|
|
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);
|
|
21201
|
+
current(e, n, changes = false) {
|
|
21202
|
+
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
21203
|
}
|
|
20712
21204
|
async execute(e, n) {
|
|
20713
21205
|
if (!n.assignment || !n.result || e.input.kind === "check")
|
|
@@ -20734,7 +21226,7 @@ class TaskScheduler {
|
|
|
20734
21226
|
reservationId: n.childRunId,
|
|
20735
21227
|
lane,
|
|
20736
21228
|
state: "held",
|
|
20737
|
-
checkoutGeneration: e.input.
|
|
21229
|
+
checkoutGeneration: e.input.generation()
|
|
20738
21230
|
});
|
|
20739
21231
|
held = true;
|
|
20740
21232
|
if (!this.current(e, n)) {
|
|
@@ -20744,7 +21236,7 @@ class TaskScheduler {
|
|
|
20744
21236
|
const observed = e.input.observe(false), before = observed.observation;
|
|
20745
21237
|
const baseline = await this.changes(e, "before", observed);
|
|
20746
21238
|
e.baseline ??= baseline;
|
|
20747
|
-
if (!this.current(e, n)) {
|
|
21239
|
+
if (!this.current(e, n) || canonicalJson(before) !== canonicalJson(e.expectedObservation)) {
|
|
20748
21240
|
await this.close(e, e.reason ?? "stale-dependency");
|
|
20749
21241
|
return;
|
|
20750
21242
|
}
|
|
@@ -20790,7 +21282,7 @@ class TaskScheduler {
|
|
|
20790
21282
|
});
|
|
20791
21283
|
originalAuth = auth.settled;
|
|
20792
21284
|
const prepared = await auth.result;
|
|
20793
|
-
if (prepared.status !== "prepared" || !this.current(e, n)) {
|
|
21285
|
+
if (prepared.status !== "prepared" || !this.current(e, n, true)) {
|
|
20794
21286
|
await this.close(e, e.reason ?? "failed");
|
|
20795
21287
|
await this.fact(e, {
|
|
20796
21288
|
kind: "assignment-state",
|
|
@@ -20860,7 +21352,7 @@ class TaskScheduler {
|
|
|
20860
21352
|
reservationId: n.childRunId,
|
|
20861
21353
|
lane,
|
|
20862
21354
|
state: "released",
|
|
20863
|
-
checkoutGeneration: e.input.
|
|
21355
|
+
checkoutGeneration: e.input.generation()
|
|
20864
21356
|
});
|
|
20865
21357
|
this.holders.delete(n.childRunId);
|
|
20866
21358
|
n.state = "done";
|
|
@@ -20908,7 +21400,7 @@ class TaskScheduler {
|
|
|
20908
21400
|
if (this.current(e, n) && input.definitionCurrent()) {
|
|
20909
21401
|
const output = recording;
|
|
20910
21402
|
const original = Promise.resolve().then(async () => {
|
|
20911
|
-
if (!this.current(e, n) || !input.definitionCurrent())
|
|
21403
|
+
if (!this.current(e, n, true) || !input.definitionCurrent())
|
|
20912
21404
|
return native;
|
|
20913
21405
|
await runCheck(input.check, input.cwd, n.abort.signal, {
|
|
20914
21406
|
environment: input.environment,
|
|
@@ -20940,7 +21432,7 @@ class TaskScheduler {
|
|
|
20940
21432
|
}
|
|
20941
21433
|
};
|
|
20942
21434
|
const observed = input.observe(false).observation;
|
|
20943
|
-
const after = held ? input.
|
|
21435
|
+
const after = held ? { ...observed, checkoutGeneration: input.advance() } : observed;
|
|
20944
21436
|
const ownAdvance = held && observed.checkoutGeneration === before.checkoutGeneration ? {
|
|
20945
21437
|
operationId: e.link.operationId,
|
|
20946
21438
|
reservationId: checkId,
|
|
@@ -21107,6 +21599,14 @@ class TaskScheduler {
|
|
|
21107
21599
|
|
|
21108
21600
|
// src/service/task-actions.ts
|
|
21109
21601
|
var actionOwners = new WeakSet;
|
|
21602
|
+
function snapshotReads() {
|
|
21603
|
+
const kept = new Map;
|
|
21604
|
+
return (key2, read2) => {
|
|
21605
|
+
if (!kept.has(key2))
|
|
21606
|
+
kept.set(key2, read2());
|
|
21607
|
+
return kept.get(key2);
|
|
21608
|
+
};
|
|
21609
|
+
}
|
|
21110
21610
|
|
|
21111
21611
|
class TaskActions {
|
|
21112
21612
|
options;
|
|
@@ -21586,14 +22086,16 @@ class TaskActions {
|
|
|
21586
22086
|
assertCurrent();
|
|
21587
22087
|
return checks;
|
|
21588
22088
|
}
|
|
21589
|
-
obligations(version, snapshot) {
|
|
22089
|
+
obligations(version, snapshot, reads) {
|
|
21590
22090
|
if (!version.task)
|
|
21591
22091
|
throw new ContractError("stale");
|
|
21592
|
-
return this.obligationsFor(version.task.id, version.task.kind, { status: "available", version }, snapshot);
|
|
22092
|
+
return this.obligationsFor(version.task.id, version.task.kind, { status: "available", version }, snapshot, reads);
|
|
21593
22093
|
}
|
|
21594
|
-
obligationsFor(taskId, kind, context, snapshot) {
|
|
22094
|
+
obligationsFor(taskId, kind, context, snapshot, reads) {
|
|
21595
22095
|
const configured = this.checks(taskId);
|
|
21596
|
-
const
|
|
22096
|
+
const selections = kind === "code" ? configured : { revision: configured.revision, selections: [] };
|
|
22097
|
+
const fold = () => currentChecks(this.options.host.reader.files, taskId, selections, context, snapshot);
|
|
22098
|
+
const state = reads ? reads(`checks:${canonicalJson({ taskId, selections, context, snapshot })}`, fold) : fold();
|
|
21597
22099
|
if (canonicalJson(configured) !== canonicalJson(this.checks(taskId)))
|
|
21598
22100
|
throw new ContractError("stale");
|
|
21599
22101
|
if (context.status !== "available")
|
|
@@ -21805,6 +22307,8 @@ class TaskActions {
|
|
|
21805
22307
|
return false;
|
|
21806
22308
|
}
|
|
21807
22309
|
},
|
|
22310
|
+
generation: () => this.generation,
|
|
22311
|
+
advance: () => ++this.generation,
|
|
21808
22312
|
observe: (advance) => {
|
|
21809
22313
|
if (advance)
|
|
21810
22314
|
this.generation++;
|
|
@@ -21831,13 +22335,14 @@ class TaskActions {
|
|
|
21831
22335
|
});
|
|
21832
22336
|
accepted2.activate(work);
|
|
21833
22337
|
}
|
|
21834
|
-
reviewObservation(runId, version, snapshot = this.options.host.writer.committedSeq) {
|
|
22338
|
+
reviewObservation(runId, version, snapshot = this.options.host.writer.committedSeq, reads) {
|
|
21835
22339
|
if (!version.task)
|
|
21836
22340
|
throw new ContractError("stale");
|
|
21837
22341
|
const h = this.options.host, taskId = version.task.id;
|
|
21838
|
-
const
|
|
22342
|
+
const select = () => h.reader.reviewSelection(taskId, runId, snapshot);
|
|
22343
|
+
const selection = reads ? reads(`selection:${taskId}:${runId}:${snapshot}`, select) : select();
|
|
21839
22344
|
const { run } = selection;
|
|
21840
|
-
const state = this.obligations(version, snapshot);
|
|
22345
|
+
const state = this.obligations(version, snapshot, reads);
|
|
21841
22346
|
const current2 = {
|
|
21842
22347
|
...state,
|
|
21843
22348
|
checks: state.checks,
|
|
@@ -21869,6 +22374,7 @@ class TaskActions {
|
|
|
21869
22374
|
const currentGraph = graph?.graph ?? (this.options.choice ? this.defaultGraph(selected2.record, selected2.identity, expected.model) : null);
|
|
21870
22375
|
if ((currentGraph ? hash(canonicalJson(currentGraph)) : null) !== task.graphIdentity || (currentGraph?.revision ?? null) !== task.graphRevision)
|
|
21871
22376
|
throw new ContractError("stale");
|
|
22377
|
+
const reads = snapshotReads();
|
|
21872
22378
|
const validate = (published) => {
|
|
21873
22379
|
this.assertChoice(expected);
|
|
21874
22380
|
held.assertHeld();
|
|
@@ -21878,14 +22384,14 @@ class TaskActions {
|
|
|
21878
22384
|
const changes2 = captureChanges(h.coordination.store.root);
|
|
21879
22385
|
if (changes2.fingerprint !== expected.observation.identity || changes2.complete !== expected.observation.complete)
|
|
21880
22386
|
throw new ContractError("stale");
|
|
21881
|
-
const proven = h.reader.publishedPlan(task.id, snapshot, {
|
|
22387
|
+
const proven = reads(`plan:${task.id}:${snapshot}`, () => h.reader.publishedPlan(task.id, snapshot, {
|
|
21882
22388
|
revision: selected2.record.revision,
|
|
21883
22389
|
identity: selected2.identity,
|
|
21884
22390
|
plan: selected2.record.plan
|
|
21885
|
-
});
|
|
22391
|
+
}));
|
|
21886
22392
|
if ((proven?.reference.sha256 ?? (currentGraph ? hash(canonicalJson(currentGraph)) : null)) !== task.graphIdentity)
|
|
21887
22393
|
throw new ContractError("stale");
|
|
21888
|
-
const current3 = this.reviewObservation(action.runId, expected, snapshot);
|
|
22394
|
+
const current3 = this.reviewObservation(action.runId, expected, snapshot, reads);
|
|
21889
22395
|
if (current3.checks.revision !== action.checksRevision || current3.issues.length && !action.acceptIssues)
|
|
21890
22396
|
throw new ContractError("stale");
|
|
21891
22397
|
return current3;
|
|
@@ -22077,7 +22583,7 @@ function legacy(store, budget, reader) {
|
|
|
22077
22583
|
decodeId(entry.name);
|
|
22078
22584
|
if (created.has(entry.name))
|
|
22079
22585
|
continue;
|
|
22080
|
-
const path =
|
|
22586
|
+
const path = join17(store.tasks, entry.name, "task.json");
|
|
22081
22587
|
const size = lstatSync10(path).size;
|
|
22082
22588
|
if (size > 128 * 1024)
|
|
22083
22589
|
throw new ContractError("corrupt-selected");
|
|
@@ -22092,12 +22598,23 @@ function legacy(store, budget, reader) {
|
|
|
22092
22598
|
}
|
|
22093
22599
|
return records;
|
|
22094
22600
|
}
|
|
22601
|
+
function refuseAt(stage, step) {
|
|
22602
|
+
try {
|
|
22603
|
+
return step();
|
|
22604
|
+
} catch (error2) {
|
|
22605
|
+
throw new StartupRefusal(error2 instanceof ContractError ? error2.code : "unavailable", stage);
|
|
22606
|
+
}
|
|
22607
|
+
}
|
|
22095
22608
|
async function createServiceHost(value, sourceBuild) {
|
|
22096
22609
|
const embedded = "e7e5248f8273a373827ac70d2d8be1d5aaea1e329379e59e4947fc10f82be818";
|
|
22097
22610
|
if (embedded && sourceBuild)
|
|
22098
22611
|
throw new ContractError("unsupported-context");
|
|
22099
22612
|
const contractHash = decodeHash(embedded ?? sourceBuild?.contractHash);
|
|
22100
|
-
const web =
|
|
22613
|
+
const web = refuseAt("package", () => {
|
|
22614
|
+
if (!sourceBuild?.deferActions)
|
|
22615
|
+
loadAgentDefinitions();
|
|
22616
|
+
return loadWebAssets(sourceBuild?.webDirectory);
|
|
22617
|
+
});
|
|
22101
22618
|
const build2 = {
|
|
22102
22619
|
contractHash,
|
|
22103
22620
|
assets: {
|
|
@@ -22124,12 +22641,17 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22124
22641
|
throw new ContractError("corrupt-selected");
|
|
22125
22642
|
history.assertWritable();
|
|
22126
22643
|
}
|
|
22644
|
+
const home = launch.HOME ?? launch.USERPROFILE ?? input.config.agentDir;
|
|
22645
|
+
const pi = refuseAt("pi-agent-directory", () => piSource(input.config.agentDir, home, input.config.cwd, launch));
|
|
22646
|
+
refuseAt("pi-auth", () => readPiAuth(pi));
|
|
22647
|
+
refuseAt("pi-models", () => readPiModels(pi));
|
|
22127
22648
|
const http = new ServiceHttpServer({
|
|
22128
22649
|
policy: input.config.http,
|
|
22129
22650
|
assets: web.assets
|
|
22130
22651
|
});
|
|
22131
22652
|
const endpoint2 = await http.listen();
|
|
22132
22653
|
let owner;
|
|
22654
|
+
let stage = "service";
|
|
22133
22655
|
try {
|
|
22134
22656
|
const resolved = {
|
|
22135
22657
|
...input.config,
|
|
@@ -22179,7 +22701,7 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22179
22701
|
owner,
|
|
22180
22702
|
accounting: writer.auth,
|
|
22181
22703
|
network,
|
|
22182
|
-
home
|
|
22704
|
+
home,
|
|
22183
22705
|
agentDir: input.config.agentDir,
|
|
22184
22706
|
cwd: input.config.cwd,
|
|
22185
22707
|
environment: launch
|
|
@@ -22320,9 +22842,11 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22320
22842
|
owner.quarantine();
|
|
22321
22843
|
throw new ContractError("quarantine");
|
|
22322
22844
|
}
|
|
22845
|
+
stage = "pi-runtime";
|
|
22323
22846
|
const loading = models.load();
|
|
22324
22847
|
owner.trackAuth(loading);
|
|
22325
22848
|
await loading;
|
|
22849
|
+
stage = "service";
|
|
22326
22850
|
lease.assert();
|
|
22327
22851
|
if (owner.state !== "idle")
|
|
22328
22852
|
throw new ContractError("quarantine");
|
|
@@ -22353,7 +22877,7 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22353
22877
|
} catch {}
|
|
22354
22878
|
} else
|
|
22355
22879
|
await http.close();
|
|
22356
|
-
throw new
|
|
22880
|
+
throw new StartupRefusal(error2 instanceof ContractError ? error2.code : "unavailable", stage, !!owner);
|
|
22357
22881
|
}
|
|
22358
22882
|
}
|
|
22359
22883
|
|
|
@@ -22375,7 +22899,9 @@ try {
|
|
|
22375
22899
|
const host = await createServiceHost(input);
|
|
22376
22900
|
process.stdout.write(`${JSON.stringify({ status: "ready", epoch: host.owner.lease.registration.epoch })}
|
|
22377
22901
|
`);
|
|
22378
|
-
} catch {
|
|
22902
|
+
} catch (error2) {
|
|
22903
|
+
process.stdout.write(`${JSON.stringify(startupReport(error2))}
|
|
22904
|
+
`);
|
|
22379
22905
|
process.stderr.write(`{"error":"service-host-refused"}
|
|
22380
22906
|
`);
|
|
22381
22907
|
process.exitCode = 1;
|