@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2653-next.71811ca
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 +129 -39
- package/README.zh-CN.md +128 -39
- package/dist/activation-ledger-topology.js +1 -1
- package/dist/archivist-record-entry.js +47 -26
- package/dist/auditor-dossier-tool.js +3 -1
- package/dist/compliance-transport.js +17 -26
- package/dist/doctor-contracts.js +1 -2
- package/dist/dossier-resolution.js +4 -8
- package/dist/engine-detour-tool.js +33 -8
- package/dist/engine-detour.js +1 -0
- package/dist/evidence-child-executor.js +277 -424
- package/dist/host-contracts.js +22 -0
- package/dist/institutional-resolution.js +88 -0
- package/dist/merger-contracts.js +1 -2
- package/dist/navigator-attendance.js +16 -0
- package/dist/notary-contracts.js +2 -3
- package/dist/package-contracts/fixer-output.js +1 -2
- package/dist/packaged-role-registry.js +102 -9
- package/dist/pi/in-process-session.js +671 -0
- package/dist/public-cli/config.js +7 -5
- package/dist/public-cli/main.js +3767 -4113
- package/dist/reviewer-child-executor.js +6 -1
- package/dist/session-opening-materials.js +10 -38
- package/dist/sitian-appender.js +146 -0
- package/dist/sitian-contracts.js +21 -0
- package/dist/sitian-facade.js +15 -0
- package/dist/sitian-reader.js +52 -0
- package/extensions/role-runtime.ts +9 -7
- package/package.json +5 -5
- package/resources/engines/cursor.md +12 -26
- package/resources/engines/hermes.md +2 -23
- package/resources/engines/opus.md +1 -16
- package/resources/navigator-route-playbook.md +2 -2
- package/scripts/build-package.mjs +0 -1
- package/souls/quality-law.md +1 -1
- package/src/activation-ledger-topology.ts +1 -1
- package/src/archivist-record-entry.ts +57 -45
- package/src/auditor-dossier-tool.ts +4 -2
- package/src/canonical-skill-binding.ts +37 -27
- package/src/collector-role.ts +11 -20
- package/src/collector-tool-schemas.ts +1 -4
- package/src/compliance-transport.ts +19 -44
- package/src/doctor-auditor.ts +1 -5
- package/src/doctor-contracts.ts +1 -4
- package/src/doctor-role.ts +31 -8
- package/src/dossier-resolution.ts +8 -8
- package/src/engine-detour-tool.ts +48 -19
- package/src/engine-detour.ts +3 -0
- package/src/evidence-child-executor.ts +277 -485
- package/src/factory-board.ts +3 -3
- package/src/gatekeeper-role.ts +21 -10
- package/src/host-contracts.ts +386 -0
- package/src/in-process-session.ts +1 -115
- package/src/institutional-resolution.ts +135 -0
- package/src/judge-auditor.ts +1 -5
- package/src/judge-role.ts +37 -51
- package/src/merger-contracts.ts +1 -4
- package/src/merger-role.ts +7 -11
- package/src/navigator-attendance.ts +20 -1
- package/src/notary-contracts.ts +8 -11
- package/src/notary-role.ts +7 -23
- package/src/notary-source-run.ts +4 -3
- package/src/package-contracts/fixer-output.ts +1 -4
- package/src/package-resources/method-skill-binding.ts +10 -34
- package/src/packaged-role-registry.ts +122 -12
- package/src/pi/adapter.ts +192 -0
- package/src/pi/durable-principal.ts +98 -0
- package/src/pi/in-process-session.ts +819 -0
- package/src/pi/known-failure.ts +77 -0
- package/src/pi/pi-normalization.ts +284 -0
- package/src/pi/role-turn-host.ts +459 -0
- package/src/public-cli/auto-resume.ts +42 -44
- package/src/public-cli/cli.ts +171 -257
- package/src/public-cli/coder-run.ts +115 -433
- package/src/public-cli/collector-run.ts +62 -314
- package/src/public-cli/config.ts +7 -5
- package/src/public-cli/doctor-run.ts +41 -52
- package/src/public-cli/fixer-run.ts +122 -427
- package/src/public-cli/invocation.ts +287 -96
- package/src/public-cli/judge-run.ts +92 -422
- package/src/public-cli/merger-run.ts +167 -429
- package/src/public-cli/notary-run.ts +41 -47
- package/src/public-cli/option-definitions.ts +136 -3
- package/src/public-cli/post-admission.ts +497 -0
- package/src/public-cli/public-run-credentials.ts +4 -4
- package/src/public-cli/reviewer-dispatch-rejection.ts +106 -0
- package/src/public-cli/reviewer-run.ts +132 -449
- package/src/public-cli/run-lifecycle.ts +339 -327
- package/src/public-cli/settlement.ts +251 -134
- package/src/public-cli/terminal.ts +3 -9
- package/src/public-cli/turn-request.ts +87 -0
- package/src/reviewer-child-executor.ts +9 -1
- package/src/reviewer-role.ts +11 -20
- package/src/role-runtime.ts +129 -78
- package/src/session-opening-materials.ts +25 -39
- package/src/sitian-appender.ts +171 -0
- package/src/sitian-contracts.ts +106 -0
- package/src/sitian-facade.ts +18 -0
- package/src/sitian-reader.ts +62 -0
- package/src/worker-role.ts +42 -40
- package/src/worker-submission-gates.ts +33 -0
- package/dist/archivist-role-run-coordinates.js +0 -28
- package/dist/package-contracts/terminating-infrastructure.js +0 -90
- package/resources/engines/sonnet.md +0 -27
- package/resources/engines/zcode.md +0 -63
- package/src/archivist-role-run-coordinates.ts +0 -45
- package/src/package-contracts/terminating-infrastructure.ts +0 -132
- package/src/public-cli/explicit-internal.ts +0 -427
- package/src/public-cli/one-shot-dispatch.ts +0 -300
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DurablePrincipal,
|
|
3
|
+
DurablePrincipalAuthority,
|
|
4
|
+
} from "../host-contracts.ts";
|
|
5
|
+
import { decodePiDurablePrincipal } from "../pi/durable-principal.ts";
|
|
1
6
|
/**
|
|
2
7
|
* Durable Role run lifecycle for public CLI (ADR 0052 / #11 / #108 / #416).
|
|
3
8
|
* States: admitted → running → resumable | terminal.
|
|
@@ -5,7 +10,7 @@
|
|
|
5
10
|
* any existing run with an available Pi session principal may be resumed; caller decides.
|
|
6
11
|
* Prose is never regex-classified as quota evidence.
|
|
7
12
|
*/
|
|
8
|
-
import { chmod,
|
|
13
|
+
import { chmod, open, readdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
9
14
|
import { join } from "node:path";
|
|
10
15
|
|
|
11
16
|
import {
|
|
@@ -24,6 +29,8 @@ export {
|
|
|
24
29
|
} from "../typed-provider-http.ts";
|
|
25
30
|
import type { FixerPhase } from "../package-contracts/fixer-output.ts";
|
|
26
31
|
import type { FixerPrerequisite } from "../package-contracts/fixer-packet.ts";
|
|
32
|
+
import { THINKING_LEVELS } from "./config.ts";
|
|
33
|
+
import type { PublicThinkingLevel } from "./registry.ts";
|
|
27
34
|
import {
|
|
28
35
|
recordEffectiveInvocationModel,
|
|
29
36
|
requireAuthorityRef,
|
|
@@ -155,102 +162,224 @@ export async function writeRoleRunState(
|
|
|
155
162
|
);
|
|
156
163
|
}
|
|
157
164
|
|
|
158
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Uninterpreted principal wire as stored on run-state.json.
|
|
167
|
+
* Legacy rows may omit sessionFile; only DurablePrincipalAuthority decodes it.
|
|
168
|
+
*/
|
|
169
|
+
type RoleRunPrincipalWire = {
|
|
170
|
+
readonly sessionDirectory: string;
|
|
171
|
+
readonly sessionFile?: string;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** Envelope I/O only — principal payload is carried uninterpreted. */
|
|
175
|
+
type RoleRunStateDisk = {
|
|
176
|
+
readonly runId: string;
|
|
177
|
+
readonly role: RoleRunRecord["role"];
|
|
178
|
+
readonly state: RoleRunState;
|
|
179
|
+
readonly bookKey: string;
|
|
180
|
+
readonly projectRoot: string;
|
|
181
|
+
readonly runDirectory: string;
|
|
182
|
+
readonly admittedRequestPath: string;
|
|
183
|
+
readonly principalWire: RoleRunPrincipalWire;
|
|
184
|
+
readonly phase?: CoderPhase | FixerPhase;
|
|
185
|
+
readonly resumable?: TypedHttp429Observation;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
async function readRoleRunStateDisk(
|
|
159
189
|
runDirectory: string,
|
|
160
|
-
): Promise<
|
|
190
|
+
): Promise<RoleRunStateDisk | undefined> {
|
|
161
191
|
let raw: unknown;
|
|
162
192
|
try {
|
|
163
193
|
raw = JSON.parse(await readFile(join(runDirectory, RUN_STATE_FILE), "utf8"));
|
|
164
194
|
} catch {
|
|
165
195
|
return undefined;
|
|
166
196
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
197
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const record = raw as Record<string, unknown>;
|
|
201
|
+
if (typeof record.runId !== "string" || record.runId.trim() === "") {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
if (
|
|
205
|
+
record.role !== "judge" &&
|
|
206
|
+
record.role !== "coder" &&
|
|
207
|
+
record.role !== "fixer" &&
|
|
208
|
+
record.role !== "collector" &&
|
|
209
|
+
record.role !== "doctor" &&
|
|
210
|
+
record.role !== "reviewer" &&
|
|
211
|
+
record.role !== "merger" &&
|
|
212
|
+
record.role !== "notary"
|
|
213
|
+
) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
if (
|
|
217
|
+
record.state !== "admitted" &&
|
|
218
|
+
record.state !== "running" &&
|
|
219
|
+
record.state !== "resumable" &&
|
|
220
|
+
record.state !== "terminal"
|
|
221
|
+
) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
if (typeof record.bookKey !== "string") return undefined;
|
|
225
|
+
if (typeof record.projectRoot !== "string") return undefined;
|
|
226
|
+
if (typeof record.sessionDirectory !== "string") return undefined;
|
|
227
|
+
if (typeof record.admittedRequestPath !== "string") return undefined;
|
|
228
|
+
const runDir =
|
|
229
|
+
typeof record.runDirectory === "string" && record.runDirectory.trim() !== ""
|
|
230
|
+
? record.runDirectory
|
|
231
|
+
: runDirectory;
|
|
232
|
+
// Principal wire stays uninterpreted — authority owns legacy sessionFile fallback.
|
|
233
|
+
const principalWire: RoleRunPrincipalWire = {
|
|
234
|
+
sessionDirectory: record.sessionDirectory,
|
|
235
|
+
...(typeof record.sessionFile === "string"
|
|
236
|
+
? { sessionFile: record.sessionFile }
|
|
237
|
+
: {}),
|
|
238
|
+
};
|
|
239
|
+
let resumable: TypedHttp429Observation | undefined;
|
|
240
|
+
if (record.resumable !== undefined && record.resumable !== null) {
|
|
186
241
|
if (
|
|
187
|
-
record.
|
|
188
|
-
record.
|
|
189
|
-
record.state !== "resumable" &&
|
|
190
|
-
record.state !== "terminal"
|
|
242
|
+
typeof record.resumable === "object" &&
|
|
243
|
+
!Array.isArray(record.resumable)
|
|
191
244
|
) {
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
if (typeof record.bookKey !== "string") return undefined;
|
|
195
|
-
if (typeof record.projectRoot !== "string") return undefined;
|
|
196
|
-
if (typeof record.sessionDirectory !== "string") return undefined;
|
|
197
|
-
if (typeof record.admittedRequestPath !== "string") return undefined;
|
|
198
|
-
const runDir =
|
|
199
|
-
typeof record.runDirectory === "string" && record.runDirectory.trim() !== ""
|
|
200
|
-
? record.runDirectory
|
|
201
|
-
: runDirectory;
|
|
202
|
-
// Legacy states predate sessionFile. Their persisted session directory is
|
|
203
|
-
// the authority for the historical principal, even if project topology moved.
|
|
204
|
-
const sessionFile =
|
|
205
|
-
typeof record.sessionFile === "string" && record.sessionFile.trim() !== ""
|
|
206
|
-
? record.sessionFile
|
|
207
|
-
: join(record.sessionDirectory, "session.jsonl");
|
|
208
|
-
let resumable: TypedHttp429Observation | undefined;
|
|
209
|
-
if (record.resumable !== undefined && record.resumable !== null) {
|
|
245
|
+
const r = record.resumable as Record<string, unknown>;
|
|
210
246
|
if (
|
|
211
|
-
|
|
212
|
-
|
|
247
|
+
r.httpStatus === 429 &&
|
|
248
|
+
typeof r.provider === "string" &&
|
|
249
|
+
isV1ResumableProvider(r.provider)
|
|
213
250
|
) {
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
r.httpStatus === 429 &&
|
|
217
|
-
typeof r.provider === "string" &&
|
|
218
|
-
isV1ResumableProvider(r.provider)
|
|
219
|
-
) {
|
|
220
|
-
resumable = { httpStatus: 429, provider: r.provider };
|
|
221
|
-
}
|
|
251
|
+
resumable = { httpStatus: 429, provider: r.provider };
|
|
222
252
|
}
|
|
223
253
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
254
|
+
}
|
|
255
|
+
const phase =
|
|
256
|
+
record.phase === "plan" || record.phase === "apply"
|
|
257
|
+
? record.phase
|
|
258
|
+
: undefined;
|
|
259
|
+
return {
|
|
260
|
+
runId: record.runId,
|
|
261
|
+
role: record.role,
|
|
262
|
+
state: record.state,
|
|
263
|
+
bookKey: record.bookKey,
|
|
264
|
+
projectRoot: record.projectRoot,
|
|
265
|
+
runDirectory: runDir,
|
|
266
|
+
admittedRequestPath: record.admittedRequestPath,
|
|
267
|
+
principalWire,
|
|
268
|
+
...(phase === undefined ? {} : { phase }),
|
|
269
|
+
...(resumable === undefined ? {} : { resumable }),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function writeRoleRunStateDisk(
|
|
274
|
+
runDirectory: string,
|
|
275
|
+
disk: RoleRunStateDisk,
|
|
276
|
+
): Promise<void> {
|
|
277
|
+
const payload = {
|
|
278
|
+
runId: disk.runId,
|
|
279
|
+
role: disk.role,
|
|
280
|
+
state: disk.state,
|
|
281
|
+
bookKey: disk.bookKey,
|
|
282
|
+
projectRoot: disk.projectRoot,
|
|
283
|
+
runDirectory: disk.runDirectory,
|
|
284
|
+
sessionDirectory: disk.principalWire.sessionDirectory,
|
|
285
|
+
...(disk.principalWire.sessionFile === undefined
|
|
286
|
+
? {}
|
|
287
|
+
: { sessionFile: disk.principalWire.sessionFile }),
|
|
288
|
+
admittedRequestPath: disk.admittedRequestPath,
|
|
289
|
+
...(disk.phase === undefined ? {} : { phase: disk.phase }),
|
|
290
|
+
...(disk.resumable === undefined ? {} : { resumable: disk.resumable }),
|
|
291
|
+
};
|
|
292
|
+
await writeFile(
|
|
293
|
+
join(runDirectory, RUN_STATE_FILE),
|
|
294
|
+
`${JSON.stringify(payload, null, 2)}\n`,
|
|
295
|
+
"utf8",
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** One authority.decode of the uninterpreted wire → record + opaque principal (frozen wire itself). */
|
|
300
|
+
function materializeRoleRunFromDisk(
|
|
301
|
+
disk: RoleRunStateDisk,
|
|
302
|
+
authority: DurablePrincipalAuthority,
|
|
303
|
+
): { readonly run: RoleRunRecord; readonly principal: DurablePrincipal } | undefined {
|
|
304
|
+
try {
|
|
305
|
+
const coordinates = authority.decode(disk.principalWire);
|
|
228
306
|
return {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
307
|
+
principal: disk.principalWire as unknown as DurablePrincipal,
|
|
308
|
+
run: {
|
|
309
|
+
runId: disk.runId,
|
|
310
|
+
role: disk.role,
|
|
311
|
+
state: disk.state,
|
|
312
|
+
bookKey: disk.bookKey,
|
|
313
|
+
projectRoot: disk.projectRoot,
|
|
314
|
+
sessionDirectory: coordinates.sessionDirectory,
|
|
315
|
+
sessionFile: coordinates.sessionFile,
|
|
316
|
+
runDirectory: disk.runDirectory,
|
|
317
|
+
admittedRequestPath: disk.admittedRequestPath,
|
|
318
|
+
...(disk.phase === undefined ? {} : { phase: disk.phase }),
|
|
319
|
+
...(disk.resumable === undefined ? {} : { resumable: disk.resumable }),
|
|
320
|
+
},
|
|
240
321
|
};
|
|
322
|
+
} catch {
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Read durable run-state and materialize principal coordinates through the
|
|
329
|
+
* injected host authority (legacy sessionFile fallback lives only in the codec).
|
|
330
|
+
*/
|
|
331
|
+
export async function readRoleRunState(
|
|
332
|
+
runDirectory: string,
|
|
333
|
+
authority: DurablePrincipalAuthority,
|
|
334
|
+
): Promise<RoleRunRecord | undefined> {
|
|
335
|
+
const disk = await readRoleRunStateDisk(runDirectory);
|
|
336
|
+
if (disk === undefined) return undefined;
|
|
337
|
+
return materializeRoleRunFromDisk(disk, authority)?.run;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Envelope identity only — no principal payload interpretation.
|
|
342
|
+
* Used by notary locator / role peek that never consume session coordinates.
|
|
343
|
+
*/
|
|
344
|
+
export async function readRoleRunIdentity(
|
|
345
|
+
runDirectory: string,
|
|
346
|
+
): Promise<
|
|
347
|
+
| {
|
|
348
|
+
readonly runId: string;
|
|
349
|
+
readonly role: RoleRunRecord["role"];
|
|
350
|
+
readonly bookKey: string;
|
|
351
|
+
readonly runDirectory: string;
|
|
352
|
+
readonly state: RoleRunState;
|
|
353
|
+
}
|
|
354
|
+
| undefined
|
|
355
|
+
> {
|
|
356
|
+
const disk = await readRoleRunStateDisk(runDirectory);
|
|
357
|
+
if (disk === undefined) return undefined;
|
|
358
|
+
return {
|
|
359
|
+
runId: disk.runId,
|
|
360
|
+
role: disk.role,
|
|
361
|
+
bookKey: disk.bookKey,
|
|
362
|
+
runDirectory: disk.runDirectory,
|
|
363
|
+
state: disk.state,
|
|
364
|
+
};
|
|
241
365
|
}
|
|
242
366
|
|
|
243
367
|
export async function markRunAdmitted(
|
|
244
368
|
admitted: AdmittedRoleInvocation,
|
|
369
|
+
authority: DurablePrincipalAuthority,
|
|
245
370
|
): Promise<void> {
|
|
371
|
+
const { sessionDirectory, sessionFile } = decodePiDurablePrincipal(
|
|
372
|
+
authority,
|
|
373
|
+
admitted.principal,
|
|
374
|
+
);
|
|
246
375
|
await writeRoleRunState(admitted.runDirectory, {
|
|
247
376
|
runId: admitted.runId,
|
|
248
377
|
role: admitted.role,
|
|
249
378
|
state: "admitted",
|
|
250
379
|
bookKey: admitted.bookKey,
|
|
251
380
|
projectRoot: admitted.projectRoot,
|
|
252
|
-
sessionDirectory
|
|
253
|
-
sessionFile
|
|
381
|
+
sessionDirectory,
|
|
382
|
+
sessionFile,
|
|
254
383
|
admittedRequestPath: admitted.admittedRequestPath,
|
|
255
384
|
...(
|
|
256
385
|
admitted.role === "coder" || admitted.role === "fixer"
|
|
@@ -273,27 +402,25 @@ export async function markRunRunning(
|
|
|
273
402
|
effectiveModel?: InvocationEffectiveModel,
|
|
274
403
|
effectiveEngine?: string,
|
|
275
404
|
): Promise<void> {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
const current = await readRoleRunState(runDirectory);
|
|
405
|
+
await recordEffectiveInvocationModel(
|
|
406
|
+
runDirectory,
|
|
407
|
+
effectiveModel,
|
|
408
|
+
effectiveEngine,
|
|
409
|
+
);
|
|
410
|
+
const current = await readRoleRunStateDisk(runDirectory);
|
|
284
411
|
if (current === undefined) {
|
|
285
412
|
throw new Error("cannot mark running: run state missing");
|
|
286
413
|
}
|
|
287
|
-
// Omit resumable while a writer is active.
|
|
288
|
-
await
|
|
414
|
+
// Omit resumable while a writer is active. Principal wire is passed through uninterpreted.
|
|
415
|
+
await writeRoleRunStateDisk(runDirectory, {
|
|
289
416
|
runId: current.runId,
|
|
290
417
|
role: current.role,
|
|
291
418
|
state: "running",
|
|
292
419
|
bookKey: current.bookKey,
|
|
293
420
|
projectRoot: current.projectRoot,
|
|
294
|
-
|
|
295
|
-
sessionFile: current.sessionFile,
|
|
421
|
+
runDirectory: current.runDirectory,
|
|
296
422
|
admittedRequestPath: current.admittedRequestPath,
|
|
423
|
+
principalWire: current.principalWire,
|
|
297
424
|
...(current.phase === undefined ? {} : { phase: current.phase }),
|
|
298
425
|
});
|
|
299
426
|
}
|
|
@@ -303,11 +430,11 @@ export async function markRunResumable(
|
|
|
303
430
|
runDirectory: string,
|
|
304
431
|
observation: TypedHttp429Observation,
|
|
305
432
|
): Promise<void> {
|
|
306
|
-
const current = await
|
|
433
|
+
const current = await readRoleRunStateDisk(runDirectory);
|
|
307
434
|
if (current === undefined) {
|
|
308
435
|
throw new Error("cannot mark resumable: run state missing");
|
|
309
436
|
}
|
|
310
|
-
await
|
|
437
|
+
await writeRoleRunStateDisk(runDirectory, {
|
|
311
438
|
...current,
|
|
312
439
|
state: "resumable",
|
|
313
440
|
resumable: observation,
|
|
@@ -315,37 +442,32 @@ export async function markRunResumable(
|
|
|
315
442
|
}
|
|
316
443
|
|
|
317
444
|
export async function markRunTerminal(runDirectory: string): Promise<void> {
|
|
318
|
-
const current = await
|
|
445
|
+
const current = await readRoleRunStateDisk(runDirectory);
|
|
319
446
|
if (current === undefined) {
|
|
320
447
|
throw new Error("cannot mark terminal: run state missing");
|
|
321
448
|
}
|
|
322
|
-
await
|
|
449
|
+
await writeRoleRunStateDisk(runDirectory, {
|
|
323
450
|
runId: current.runId,
|
|
324
451
|
role: current.role,
|
|
325
452
|
state: "terminal",
|
|
326
453
|
bookKey: current.bookKey,
|
|
327
454
|
projectRoot: current.projectRoot,
|
|
328
|
-
|
|
329
|
-
sessionFile: current.sessionFile,
|
|
455
|
+
runDirectory: current.runDirectory,
|
|
330
456
|
admittedRequestPath: current.admittedRequestPath,
|
|
457
|
+
principalWire: current.principalWire,
|
|
331
458
|
...(current.phase === undefined ? {} : { phase: current.phase }),
|
|
332
459
|
});
|
|
333
460
|
}
|
|
334
461
|
|
|
335
462
|
/**
|
|
336
|
-
* True when the
|
|
463
|
+
* True when the host authority reports the durable principal available.
|
|
337
464
|
* Resume must reopen this exact principal; directory-latest is not identity.
|
|
338
465
|
*/
|
|
339
|
-
export async function
|
|
340
|
-
|
|
466
|
+
export async function isDurablePrincipalAvailable(
|
|
467
|
+
principal: DurablePrincipal,
|
|
468
|
+
authority: DurablePrincipalAuthority,
|
|
341
469
|
): Promise<boolean> {
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
const st = await lstat(sessionFile);
|
|
345
|
-
return st.isFile() && !st.isSymbolicLink();
|
|
346
|
-
} catch {
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
470
|
+
return authority.isAvailable(principal);
|
|
349
471
|
}
|
|
350
472
|
|
|
351
473
|
export class RunWriterLeaseHeldError extends Error {
|
|
@@ -382,151 +504,14 @@ export function describeErrorIdentity(error: unknown): string {
|
|
|
382
504
|
return `${name}${code}${message}`;
|
|
383
505
|
}
|
|
384
506
|
|
|
385
|
-
function errorCodeOf(error: unknown): unknown {
|
|
386
|
-
return (error as { code?: unknown }).code;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
507
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*/
|
|
393
|
-
function isProcessAlive(pid: number): boolean {
|
|
394
|
-
try {
|
|
395
|
-
process.kill(pid, 0);
|
|
396
|
-
return true;
|
|
397
|
-
} catch (error) {
|
|
398
|
-
return errorCodeOf(error) !== "ESRCH";
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
type WriterLockAutopsy =
|
|
403
|
-
| { verdict: "absent"; readFailure?: unknown }
|
|
404
|
-
| { verdict: "dead"; pid: number }
|
|
405
|
-
| { verdict: "alive"; pid: number };
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Holder autopsy for an existing writer.lock (#552). "absent" covers no file,
|
|
409
|
-
* no parseable pid (a live creator mid-acquisition reads as empty, and so does
|
|
410
|
-
* the crash-window leftover), and unreadable files — absent alone never
|
|
411
|
-
* authorizes reclaim; only a "dead" verdict does. A non-ENOENT read failure
|
|
412
|
-
* still decides "absent" but rides along as readFailure so the true cause can
|
|
413
|
-
* land in the cleanup sink instead of being laundered away.
|
|
414
|
-
*/
|
|
415
|
-
async function autopsyWriterLock(lockPath: string): Promise<WriterLockAutopsy> {
|
|
416
|
-
let content: string;
|
|
417
|
-
try {
|
|
418
|
-
content = await readFile(lockPath, "utf8");
|
|
419
|
-
} catch (error) {
|
|
420
|
-
if (errorCodeOf(error) === "ENOENT") return { verdict: "absent" };
|
|
421
|
-
return { verdict: "absent", readFailure: error };
|
|
422
|
-
}
|
|
423
|
-
const pid = Number.parseInt(content.trim(), 10);
|
|
424
|
-
if (!Number.isInteger(pid) || pid <= 0) return { verdict: "absent" };
|
|
425
|
-
return isProcessAlive(pid) ? { verdict: "alive", pid } : { verdict: "dead", pid };
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function describeAutopsy(autopsy: WriterLockAutopsy): string {
|
|
429
|
-
switch (autopsy.verdict) {
|
|
430
|
-
case "alive":
|
|
431
|
-
return `live pid ${autopsy.pid}`;
|
|
432
|
-
case "dead":
|
|
433
|
-
return `dead pid ${autopsy.pid}`;
|
|
434
|
-
case "absent":
|
|
435
|
-
return autopsy.readFailure !== undefined
|
|
436
|
-
? "unreadable lock"
|
|
437
|
-
: "absent or unparseable holder";
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Remove one lock whose re-read autopsy is still a verified-dead holder, or
|
|
443
|
-
* leave it for the next round otherwise. The pre-unlink re-read guard means a
|
|
444
|
-
* concurrent writer that re-locked between the autopsy and the unlink cannot
|
|
445
|
-
* have its live lock stolen. Residual race: a writer can still re-lock between
|
|
446
|
-
* the re-read and the unlink itself; POSIX offers no compare-and-delete, and
|
|
447
|
-
* this narrows the window to a single syscall pair.
|
|
448
|
-
*/
|
|
449
|
-
async function reclaimStaleWriterLock(lockPath: string, runDirectory: string): Promise<void> {
|
|
450
|
-
const current = await autopsyWriterLock(lockPath);
|
|
451
|
-
if (current.verdict !== "dead") return;
|
|
452
|
-
try {
|
|
453
|
-
await unlink(lockPath);
|
|
454
|
-
} catch (error) {
|
|
455
|
-
if (errorCodeOf(error) === "ENOENT") return;
|
|
456
|
-
if (errorCodeOf(error) !== "EACCES") throw error;
|
|
457
|
-
await chmod(runDirectory, 0o755);
|
|
458
|
-
await unlink(lockPath);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
async function createWriterLease(
|
|
463
|
-
lockPath: string,
|
|
464
|
-
runDirectory: string,
|
|
465
|
-
reportCleanupFailure: (error: unknown) => void,
|
|
466
|
-
): Promise<RunWriterLease> {
|
|
467
|
-
const handle = await open(lockPath, "wx");
|
|
468
|
-
try {
|
|
469
|
-
await handle.writeFile(`${process.pid}\n`, "utf8");
|
|
470
|
-
} catch (error) {
|
|
471
|
-
await handle.close().catch(() => undefined);
|
|
472
|
-
await unlink(lockPath).catch(() => undefined);
|
|
473
|
-
throw error;
|
|
474
|
-
}
|
|
475
|
-
let released = false;
|
|
476
|
-
return {
|
|
477
|
-
lockPath,
|
|
478
|
-
async release() {
|
|
479
|
-
if (released) return;
|
|
480
|
-
released = true;
|
|
481
|
-
await handle.close().catch(() => undefined);
|
|
482
|
-
try {
|
|
483
|
-
await unlink(lockPath);
|
|
484
|
-
} catch (error) {
|
|
485
|
-
if (errorCodeOf(error) === "EACCES") {
|
|
486
|
-
try {
|
|
487
|
-
await chmod(runDirectory, 0o755);
|
|
488
|
-
await unlink(lockPath);
|
|
489
|
-
} catch (retryError) {
|
|
490
|
-
// best-effort cleanup: a stale lock left here is reclaimed by the
|
|
491
|
-
// next acquire's holder autopsy, but the true chmod/unlink cause
|
|
492
|
-
// must be recorded, not swallowed.
|
|
493
|
-
reportCleanupFailure(retryError);
|
|
494
|
-
}
|
|
495
|
-
} else {
|
|
496
|
-
// non-EACCES unlink failure is best-effort settlement cleanup; record true cause.
|
|
497
|
-
reportCleanupFailure(error);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
},
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const WRITER_LEASE_RECLAIM_ROUNDS = 3;
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Acquire the one-writer lease for a Role run. Exclusive create — no second
|
|
508
|
-
* writer; a concurrent acquire rejects without dispatch.
|
|
509
|
-
*
|
|
510
|
-
* A contested lock gets a holder autopsy before rejection (#552): only a
|
|
511
|
-
* verified-dead holder pid — parseable pid, signal-0 ESRCH, and still dead on
|
|
512
|
-
* the pre-unlink re-read — authorizes reclaim, because no writer is left to
|
|
513
|
-
* release the lock; acquire then retries the create. An empty, unparseable, or
|
|
514
|
-
* unreadable lock proves no dead holder (a live creator is mid-acquisition
|
|
515
|
-
* between the exclusive create and its pid write), so it rejects as
|
|
516
|
-
* RunWriterLeaseHeldError naming the path and the lock stays on disk — a
|
|
517
|
-
* crash-window empty lock blocking a resume is that refusal's known residue,
|
|
518
|
-
* not safely fixable by unlink here. A live holder rejects the same typed
|
|
519
|
-
* error naming the pid and path. A pid recycled by an unrelated process reads
|
|
520
|
-
* as alive — that degrades to the same typed rejection, never worse than the
|
|
521
|
-
* pre-#552 behavior. Reclaim rounds are bounded by
|
|
522
|
-
* WRITER_LEASE_RECLAIM_ROUNDS; a lock that stays contested (e.g. a reclaim
|
|
523
|
-
* race repeatedly lost) surfaces the same typed error instead of spinning.
|
|
508
|
+
* Acquire the one-writer lease for a Role run. Concurrent acquire rejects
|
|
509
|
+
* without dispatch. Exclusive create — no second writer.
|
|
524
510
|
*
|
|
525
511
|
* `onCleanupFailure` receives a non-terminal diagnostic line when release-time
|
|
526
|
-
* lock cleanup fails
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
* swallowing is forbidden.
|
|
512
|
+
* lock cleanup fails. Release stays best-effort (a stale lock resurfaces as
|
|
513
|
+
* RunWriterLeaseHeldError on next acquire), but the true error identity must
|
|
514
|
+
* still land somewhere observable — silent swallowing is forbidden.
|
|
530
515
|
*/
|
|
531
516
|
export async function acquireRunWriterLease(
|
|
532
517
|
runDirectory: string,
|
|
@@ -538,57 +523,58 @@ export async function acquireRunWriterLease(
|
|
|
538
523
|
// cause has already been handed to the sink as its argument.
|
|
539
524
|
try {
|
|
540
525
|
onCleanupFailure?.(
|
|
541
|
-
`writer lease lock cleanup failed (best-effort continue; stale lock
|
|
526
|
+
`writer lease lock cleanup failed (best-effort continue; stale lock resurfaces as lease-held on next acquire) at ${join(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`,
|
|
542
527
|
);
|
|
543
528
|
} catch {
|
|
544
529
|
// diagnostic-sink failure is itself best-effort; never break release().
|
|
545
530
|
}
|
|
546
531
|
};
|
|
547
532
|
const lockPath = join(runDirectory, WRITER_LOCK_FILE);
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
// fresh create attempt, including the budget's last one (the previous shape
|
|
551
|
-
// exited after a final-round reclaim with the path already clear).
|
|
552
|
-
for (let reclaimsLeft = WRITER_LEASE_RECLAIM_ROUNDS; ; reclaimsLeft -= 1) {
|
|
533
|
+
try {
|
|
534
|
+
const handle = await open(lockPath, "wx");
|
|
553
535
|
try {
|
|
554
|
-
|
|
536
|
+
await handle.writeFile(`${process.pid}\n`, "utf8");
|
|
555
537
|
} catch (error) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (lastAutopsy.verdict === "absent" && lastAutopsy.readFailure !== undefined) {
|
|
560
|
-
// 失败诚实: a lock we could not read must land its true read cause
|
|
561
|
-
// somewhere observable — recorded, but never treated as proof of death.
|
|
562
|
-
reportCleanupFailure(lastAutopsy.readFailure);
|
|
563
|
-
}
|
|
564
|
-
if (lastAutopsy.verdict === "alive") {
|
|
565
|
-
throw new RunWriterLeaseHeldError(
|
|
566
|
-
`role run writer lease is already held by live pid ${lastAutopsy.pid} at ${lockPath}`,
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
if (lastAutopsy.verdict === "absent") {
|
|
570
|
-
// #552 mechanical criterion: stale = holder pid verified dead. An
|
|
571
|
-
// empty lock (a live creator is mid-acquisition before its pid write)
|
|
572
|
-
// or an unreadable one proves no dead holder — unlinking here could
|
|
573
|
-
// steal a live writer's lock, so reject typed and leave the lock.
|
|
574
|
-
throw new RunWriterLeaseHeldError(
|
|
575
|
-
lastAutopsy.readFailure !== undefined
|
|
576
|
-
? `role run writer lease lock is unreadable at ${lockPath}: ${describeErrorIdentity(lastAutopsy.readFailure)}; holder liveness unverifiable, lock left in place`
|
|
577
|
-
: `role run writer lease lock at ${lockPath} has no verifiable holder pid (empty or unparseable); holder liveness unverifiable, lock left in place`,
|
|
578
|
-
);
|
|
538
|
+
await handle.close().catch(() => undefined);
|
|
539
|
+
await unlink(lockPath).catch(() => undefined);
|
|
540
|
+
throw error;
|
|
579
541
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
542
|
+
let released = false;
|
|
543
|
+
return {
|
|
544
|
+
lockPath,
|
|
545
|
+
async release() {
|
|
546
|
+
if (released) return;
|
|
547
|
+
released = true;
|
|
548
|
+
await handle.close().catch(() => undefined);
|
|
549
|
+
try {
|
|
550
|
+
await unlink(lockPath);
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if ((error as { code?: unknown }).code === "EACCES") {
|
|
553
|
+
try {
|
|
554
|
+
await chmod(runDirectory, 0o755);
|
|
555
|
+
await unlink(lockPath);
|
|
556
|
+
} catch (retryError) {
|
|
557
|
+
// best-effort cleanup: stale lock will surface as lease-held on next acquire (exit 2),
|
|
558
|
+
// but the true chmod/unlink cause must be recorded, not swallowed.
|
|
559
|
+
reportCleanupFailure(retryError);
|
|
560
|
+
}
|
|
561
|
+
} else {
|
|
562
|
+
// non-EACCES unlink failure is best-effort settlement cleanup; record true cause.
|
|
563
|
+
reportCleanupFailure(error);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (
|
|
570
|
+
error instanceof Error &&
|
|
571
|
+
"code" in error &&
|
|
572
|
+
(error as { code?: unknown }).code === "EEXIST"
|
|
573
|
+
) {
|
|
574
|
+
throw new RunWriterLeaseHeldError();
|
|
587
575
|
}
|
|
576
|
+
throw error;
|
|
588
577
|
}
|
|
589
|
-
throw new RunWriterLeaseHeldError(
|
|
590
|
-
`role run writer lease stayed contested at ${lockPath} after ${WRITER_LEASE_RECLAIM_ROUNDS} reclaims (last autopsy: ${describeAutopsy(lastAutopsy)})`,
|
|
591
|
-
);
|
|
592
578
|
}
|
|
593
579
|
|
|
594
580
|
/**
|
|
@@ -640,6 +626,8 @@ type LoadedAdmittedRequestFields = {
|
|
|
640
626
|
readonly derived?: DerivedMergerEnvelope;
|
|
641
627
|
readonly correlationId?: string;
|
|
642
628
|
readonly ticketNumber?: number;
|
|
629
|
+
/** Effective model restored from the invocation identity page on resume. */
|
|
630
|
+
readonly model?: InvocationEffectiveModel;
|
|
643
631
|
};
|
|
644
632
|
|
|
645
633
|
/** Restore optional correlation + typed ticketNumber from a durable admitted page. */
|
|
@@ -675,8 +663,10 @@ function restoredTicketFields(fields: LoadedAdmittedRequestFields): {
|
|
|
675
663
|
async function loadResumableRunRecord(
|
|
676
664
|
home: string,
|
|
677
665
|
runId: string,
|
|
666
|
+
authority: DurablePrincipalAuthority,
|
|
678
667
|
): Promise<{
|
|
679
668
|
readonly run: RoleRunRecord;
|
|
669
|
+
readonly principal: DurablePrincipal;
|
|
680
670
|
readonly observation?: TypedHttp429Observation;
|
|
681
671
|
readonly admittedFields: LoadedAdmittedRequestFields;
|
|
682
672
|
}> {
|
|
@@ -684,14 +674,19 @@ async function loadResumableRunRecord(
|
|
|
684
674
|
if (runDirectory === undefined) {
|
|
685
675
|
throw new CliUsageError(`unknown role run id: ${runId}`);
|
|
686
676
|
}
|
|
687
|
-
const
|
|
688
|
-
if (
|
|
677
|
+
const disk = await readRoleRunStateDisk(runDirectory);
|
|
678
|
+
if (disk === undefined) {
|
|
689
679
|
throw new CliUsageError(`unknown role run id: ${runId}`);
|
|
690
680
|
}
|
|
691
681
|
// #416: removed terminal/resumable gates per owner decision "根本不要有限制" (2026-08-22).
|
|
692
682
|
// Only the exact Pi session principal check remains as honest failure.
|
|
693
|
-
//
|
|
694
|
-
|
|
683
|
+
// One authority.decode of the uninterpreted wire yields both principal and record.
|
|
684
|
+
const materialized = materializeRoleRunFromDisk(disk, authority);
|
|
685
|
+
if (materialized === undefined) {
|
|
686
|
+
throw new CliUsageError(`unknown role run id: ${runId}`);
|
|
687
|
+
}
|
|
688
|
+
const { run, principal } = materialized;
|
|
689
|
+
if (!(await isDurablePrincipalAvailable(principal, authority))) {
|
|
695
690
|
throw new CliUsageError(
|
|
696
691
|
`role run Pi session principal is unavailable: ${runId}`,
|
|
697
692
|
);
|
|
@@ -805,39 +800,50 @@ async function loadResumableRunRecord(
|
|
|
805
800
|
{ cause: error },
|
|
806
801
|
);
|
|
807
802
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
803
|
+
let model: InvocationEffectiveModel | undefined;
|
|
804
|
+
try {
|
|
805
|
+
const invocationRaw: unknown = JSON.parse(
|
|
806
|
+
await readFile(join(run.runDirectory, "invocation.json"), "utf8"),
|
|
807
|
+
);
|
|
808
|
+
if (
|
|
809
|
+
invocationRaw !== null &&
|
|
810
|
+
typeof invocationRaw === "object" &&
|
|
811
|
+
!Array.isArray(invocationRaw)
|
|
812
|
+
) {
|
|
813
|
+
const rec = invocationRaw as Record<string, unknown>;
|
|
814
|
+
if (typeof rec.provider === "string" && typeof rec.model === "string") {
|
|
815
|
+
model = {
|
|
816
|
+
provider: rec.provider,
|
|
817
|
+
model: rec.model,
|
|
818
|
+
...(typeof rec.thinking === "string" &&
|
|
819
|
+
THINKING_LEVELS.has(rec.thinking as PublicThinkingLevel)
|
|
820
|
+
? { thinking: rec.thinking as PublicThinkingLevel }
|
|
821
|
+
: {}),
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (correlationId === undefined || ticketNumber === undefined) {
|
|
825
|
+
const fromInvocation = parsePersistedTicketIdentity(rec);
|
|
821
826
|
if (correlationId === undefined) correlationId = fromInvocation.correlationId;
|
|
822
827
|
if (ticketNumber === undefined) ticketNumber = fromInvocation.ticketNumber;
|
|
823
828
|
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
)
|
|
831
|
-
)
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
829
|
+
}
|
|
830
|
+
} catch (error) {
|
|
831
|
+
if (
|
|
832
|
+
!(
|
|
833
|
+
error instanceof Error &&
|
|
834
|
+
"code" in error &&
|
|
835
|
+
(error as { code?: unknown }).code === "ENOENT"
|
|
836
|
+
)
|
|
837
|
+
) {
|
|
838
|
+
throw new CliUsageError(
|
|
839
|
+
`role run invocation identity is unreadable: ${runId}`,
|
|
840
|
+
{ cause: error },
|
|
841
|
+
);
|
|
837
842
|
}
|
|
838
843
|
}
|
|
839
844
|
return {
|
|
840
845
|
run,
|
|
846
|
+
principal,
|
|
841
847
|
...(run.resumable === undefined ? {} : { observation: run.resumable }),
|
|
842
848
|
admittedFields: {
|
|
843
849
|
instruction,
|
|
@@ -854,6 +860,7 @@ async function loadResumableRunRecord(
|
|
|
854
860
|
...(derived === undefined ? {} : { derived }),
|
|
855
861
|
...(correlationId === undefined ? {} : { correlationId }),
|
|
856
862
|
...(ticketNumber === undefined ? {} : { ticketNumber }),
|
|
863
|
+
...(model === undefined ? {} : { model }),
|
|
857
864
|
},
|
|
858
865
|
};
|
|
859
866
|
}
|
|
@@ -890,8 +897,9 @@ export type LoadedResumableReviewerRun = {
|
|
|
890
897
|
export async function loadResumableJudgeRun(
|
|
891
898
|
home: string,
|
|
892
899
|
runId: string,
|
|
900
|
+
authority: DurablePrincipalAuthority,
|
|
893
901
|
): Promise<LoadedResumableJudgeRun> {
|
|
894
|
-
const loaded = await loadResumableRunRecord(home, runId);
|
|
902
|
+
const loaded = await loadResumableRunRecord(home, runId, authority);
|
|
895
903
|
if (loaded.run.role !== "judge") {
|
|
896
904
|
throw new CliUsageError(
|
|
897
905
|
`role run ${runId} belongs to ${loaded.run.role}, not judge`,
|
|
@@ -906,9 +914,9 @@ export async function loadResumableJudgeRun(
|
|
|
906
914
|
instructionEmpty: loaded.admittedFields.instructionEmpty,
|
|
907
915
|
attachments: loaded.admittedFields.attachments,
|
|
908
916
|
runDirectory: loaded.run.runDirectory,
|
|
909
|
-
|
|
910
|
-
sessionFile: loaded.run.sessionFile,
|
|
917
|
+
principal: loaded.principal,
|
|
911
918
|
admittedRequestPath: loaded.run.admittedRequestPath,
|
|
919
|
+
...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
|
|
912
920
|
...restoredTicketFields(loaded.admittedFields),
|
|
913
921
|
};
|
|
914
922
|
return {
|
|
@@ -925,8 +933,9 @@ export async function loadResumableJudgeRun(
|
|
|
925
933
|
export async function loadResumableCoderRun(
|
|
926
934
|
home: string,
|
|
927
935
|
runId: string,
|
|
936
|
+
authority: DurablePrincipalAuthority,
|
|
928
937
|
): Promise<LoadedResumableCoderRun> {
|
|
929
|
-
const loaded = await loadResumableRunRecord(home, runId);
|
|
938
|
+
const loaded = await loadResumableRunRecord(home, runId, authority);
|
|
930
939
|
if (loaded.run.role !== "coder") {
|
|
931
940
|
throw new CliUsageError(
|
|
932
941
|
`role run ${runId} belongs to ${loaded.run.role}, not coder`,
|
|
@@ -959,10 +968,10 @@ export async function loadResumableCoderRun(
|
|
|
959
968
|
instructionEmpty: false,
|
|
960
969
|
attachments: loaded.admittedFields.attachments,
|
|
961
970
|
runDirectory: loaded.run.runDirectory,
|
|
962
|
-
|
|
963
|
-
sessionFile: loaded.run.sessionFile,
|
|
971
|
+
principal: loaded.principal,
|
|
964
972
|
admittedRequestPath: loaded.run.admittedRequestPath,
|
|
965
973
|
taskPath,
|
|
974
|
+
...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
|
|
966
975
|
...restoredTicketFields(loaded.admittedFields),
|
|
967
976
|
};
|
|
968
977
|
return {
|
|
@@ -979,8 +988,9 @@ export async function loadResumableCoderRun(
|
|
|
979
988
|
export async function loadResumableFixerRun(
|
|
980
989
|
home: string,
|
|
981
990
|
runId: string,
|
|
991
|
+
authority: DurablePrincipalAuthority,
|
|
982
992
|
): Promise<LoadedResumableFixerRun> {
|
|
983
|
-
const loaded = await loadResumableRunRecord(home, runId);
|
|
993
|
+
const loaded = await loadResumableRunRecord(home, runId, authority);
|
|
984
994
|
if (loaded.run.role !== "fixer") {
|
|
985
995
|
throw new CliUsageError(
|
|
986
996
|
`role run ${runId} belongs to ${loaded.run.role}, not fixer`,
|
|
@@ -1014,14 +1024,14 @@ export async function loadResumableFixerRun(
|
|
|
1014
1024
|
instructionEmpty: false,
|
|
1015
1025
|
attachments: loaded.admittedFields.attachments,
|
|
1016
1026
|
runDirectory: loaded.run.runDirectory,
|
|
1017
|
-
|
|
1018
|
-
sessionFile: loaded.run.sessionFile,
|
|
1027
|
+
principal: loaded.principal,
|
|
1019
1028
|
admittedRequestPath: loaded.run.admittedRequestPath,
|
|
1020
1029
|
packetPath,
|
|
1021
1030
|
...(loaded.admittedFields.prerequisitesPath === undefined
|
|
1022
1031
|
? {}
|
|
1023
1032
|
: { prerequisitesPath: loaded.admittedFields.prerequisitesPath }),
|
|
1024
1033
|
prerequisites,
|
|
1034
|
+
...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
|
|
1025
1035
|
...restoredTicketFields(loaded.admittedFields),
|
|
1026
1036
|
};
|
|
1027
1037
|
return {
|
|
@@ -1042,8 +1052,9 @@ export async function loadResumableFixerRun(
|
|
|
1042
1052
|
export async function loadResumableReviewerRun(
|
|
1043
1053
|
home: string,
|
|
1044
1054
|
runId: string,
|
|
1055
|
+
authority: DurablePrincipalAuthority,
|
|
1045
1056
|
): Promise<LoadedResumableReviewerRun> {
|
|
1046
|
-
const loaded = await loadResumableRunRecord(home, runId);
|
|
1057
|
+
const loaded = await loadResumableRunRecord(home, runId, authority);
|
|
1047
1058
|
if (loaded.run.role !== "reviewer") {
|
|
1048
1059
|
throw new CliUsageError(
|
|
1049
1060
|
`role run ${runId} belongs to ${loaded.run.role}, not reviewer`,
|
|
@@ -1064,11 +1075,11 @@ export async function loadResumableReviewerRun(
|
|
|
1064
1075
|
instructionEmpty: loaded.admittedFields.instructionEmpty,
|
|
1065
1076
|
attachments: loaded.admittedFields.attachments,
|
|
1066
1077
|
runDirectory: loaded.run.runDirectory,
|
|
1067
|
-
|
|
1068
|
-
sessionFile: loaded.run.sessionFile,
|
|
1078
|
+
principal: loaded.principal,
|
|
1069
1079
|
admittedRequestPath: loaded.run.admittedRequestPath,
|
|
1070
1080
|
baseRevision,
|
|
1071
1081
|
authorityRefs: Object.freeze([...(loaded.admittedFields.authorityRefs ?? [])]),
|
|
1082
|
+
...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
|
|
1072
1083
|
...restoredTicketFields(loaded.admittedFields),
|
|
1073
1084
|
};
|
|
1074
1085
|
return {
|
|
@@ -1091,8 +1102,9 @@ export type LoadedResumableMergerRun = {
|
|
|
1091
1102
|
export async function loadResumableMergerRun(
|
|
1092
1103
|
home: string,
|
|
1093
1104
|
runId: string,
|
|
1105
|
+
authority: DurablePrincipalAuthority,
|
|
1094
1106
|
): Promise<LoadedResumableMergerRun> {
|
|
1095
|
-
const loaded = await loadResumableRunRecord(home, runId);
|
|
1107
|
+
const loaded = await loadResumableRunRecord(home, runId, authority);
|
|
1096
1108
|
if (loaded.run.role !== "merger") {
|
|
1097
1109
|
throw new CliUsageError(
|
|
1098
1110
|
`role run ${runId} belongs to ${loaded.run.role}, not merger`,
|
|
@@ -1124,11 +1136,11 @@ export async function loadResumableMergerRun(
|
|
|
1124
1136
|
instructionEmpty: false,
|
|
1125
1137
|
attachments: loaded.admittedFields.attachments,
|
|
1126
1138
|
runDirectory: loaded.run.runDirectory,
|
|
1127
|
-
|
|
1128
|
-
sessionFile: loaded.run.sessionFile,
|
|
1139
|
+
principal: loaded.principal,
|
|
1129
1140
|
admittedRequestPath: loaded.run.admittedRequestPath,
|
|
1130
1141
|
mergerInputPath,
|
|
1131
1142
|
derived,
|
|
1143
|
+
...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
|
|
1132
1144
|
...restoredTicketFields(loaded.admittedFields),
|
|
1133
1145
|
};
|
|
1134
1146
|
return {
|
|
@@ -1154,6 +1166,6 @@ export async function peekRoleRunRole(
|
|
|
1154
1166
|
> {
|
|
1155
1167
|
const runDirectory = await findRunDirectoryById(home, runId);
|
|
1156
1168
|
if (runDirectory === undefined) return undefined;
|
|
1157
|
-
const run = await
|
|
1169
|
+
const run = await readRoleRunIdentity(runDirectory);
|
|
1158
1170
|
return run?.role;
|
|
1159
1171
|
}
|