@akagilnc/pi-workflow-roles 0.1.3280 → 0.1.3353

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.
Files changed (41) hide show
  1. package/README.md +5 -5
  2. package/README.zh-CN.md +5 -5
  3. package/dist/audit-escalation.js +31 -4
  4. package/dist/gatekeeper-role.js +25 -4
  5. package/dist/grok/production-host.js +1167 -862
  6. package/dist/inspector-contracts.js +5 -2
  7. package/dist/notary-contracts.js +16 -5
  8. package/dist/packaged-role-registry.js +0 -7
  9. package/dist/public-cli/main.js +867 -757
  10. package/package.json +1 -1
  11. package/src/audit-escalation.ts +52 -8
  12. package/src/collector-ledger.ts +309 -74
  13. package/src/collector-receipt.ts +0 -1
  14. package/src/collector-role.ts +21 -14
  15. package/src/diarist-mechanical.ts +16 -5
  16. package/src/gatekeeper-role.ts +33 -4
  17. package/src/grok/role-envelope.ts +68 -52
  18. package/src/inspector-contracts.ts +6 -3
  19. package/src/inspector-role.ts +5 -2
  20. package/src/notary-contracts.ts +20 -5
  21. package/src/notary-role.ts +2 -2
  22. package/src/packaged-role-registry.ts +0 -12
  23. package/src/public-cli/cli.ts +50 -125
  24. package/src/public-cli/coder-run.ts +5 -19
  25. package/src/public-cli/collector-run.ts +65 -21
  26. package/src/public-cli/countersign-run.ts +14 -40
  27. package/src/public-cli/doctor-run.ts +50 -6
  28. package/src/public-cli/fixer-run.ts +5 -19
  29. package/src/public-cli/gleaner-left-run.ts +14 -40
  30. package/src/public-cli/inspector-run.ts +51 -7
  31. package/src/public-cli/instruction-seat-run.ts +27 -0
  32. package/src/public-cli/judge-run.ts +11 -36
  33. package/src/public-cli/merger-run.ts +5 -19
  34. package/src/public-cli/notary-run.ts +54 -5
  35. package/src/public-cli/option-definitions.ts +3 -3
  36. package/src/public-cli/post-admission.ts +76 -0
  37. package/src/public-cli/reviewer-run.ts +5 -19
  38. package/src/public-cli/run-lifecycle.ts +342 -107
  39. package/src/public-cli/settlement.ts +134 -71
  40. package/src/role-runtime.ts +18 -1
  41. package/src/submission-ledger.ts +55 -5
@@ -28,6 +28,9 @@ export {
28
28
  } from "../typed-provider-http.ts";
29
29
  import type { FixerPhase } from "../package-contracts/fixer-output.ts";
30
30
  import type { FixerPrerequisite } from "../package-contracts/fixer-packet.ts";
31
+ import { parseCollectorRepository } from "../collector-config.ts";
32
+ import type { DoctorCaseIdentity } from "../doctor-contracts.ts";
33
+ import type { NotarySourceRunLocator } from "../notary-contracts.ts";
31
34
  import {
32
35
  appendEngineSessionMaterial,
33
36
  engineSessionMaterialFromOptions,
@@ -40,9 +43,15 @@ import {
40
43
  requireAuthorityRef,
41
44
  type AdmittedCoderInvocation,
42
45
  type AdmittedCountersignInvocation,
46
+ type AdmittedCollectorInvocation,
47
+ type AdmittedDoctorInvocation,
48
+ type AdmittedInspectorInvocation,
49
+ type AdmittedNotaryInvocation,
43
50
  type AdmittedFixerInvocation,
44
51
  type AdmittedGleanerLeftInvocation,
52
+ type AdmittedGatekeeperInvocation,
45
53
  type AdmittedJudgeInvocation,
54
+ type AdmittedNavigatorInvocation,
46
55
  type AdmittedMergerInvocation,
47
56
  type AdmittedReviewerInvocation,
48
57
  type AdmittedRoleInvocation,
@@ -849,6 +858,19 @@ type LoadedAdmittedRequestFields = {
849
858
  readonly derived?: DerivedMergerEnvelope;
850
859
  readonly correlationId?: string;
851
860
  readonly ticketNumber?: number;
861
+ /** Collector — admitted repository/PR identity restored on resume (#633). */
862
+ readonly prNumber?: number;
863
+ readonly repository?: string;
864
+ readonly repositoryDisplay?: string;
865
+ readonly requestManifestPath?: string;
866
+ readonly manifestDigest?: string;
867
+ /** Doctor — admitted single-case identity restored on resume (#633). */
868
+ readonly issueNumber?: number;
869
+ readonly caseRunsPath?: string;
870
+ readonly caseIdentity?: DoctorCaseIdentity;
871
+ /** Notary — admitted source-run locator restored on resume (#633). */
872
+ readonly sourceRunPath?: string;
873
+ readonly sourceRun?: NotarySourceRunLocator;
852
874
  /** Effective model restored from the invocation identity page on resume. */
853
875
  readonly model?: InvocationEffectiveModel;
854
876
  };
@@ -929,6 +951,16 @@ async function loadResumableRunRecord(
929
951
  let derived: DerivedMergerEnvelope | undefined;
930
952
  let correlationId: string | undefined;
931
953
  let ticketNumber: number | undefined;
954
+ let prNumber: number | undefined;
955
+ let repository: string | undefined;
956
+ let repositoryDisplay: string | undefined;
957
+ let requestManifestPath: string | undefined;
958
+ let manifestDigest: string | undefined;
959
+ let issueNumber: number | undefined;
960
+ let caseRunsPath: string | undefined;
961
+ let caseIdentity: DoctorCaseIdentity | undefined;
962
+ let sourceRunPath: string | undefined;
963
+ let sourceRun: NotarySourceRunLocator | undefined;
932
964
  try {
933
965
  const raw: unknown = JSON.parse(
934
966
  await readFile(run.admittedRequestPath, "utf8"),
@@ -968,6 +1000,70 @@ async function loadResumableRunRecord(
968
1000
  ) {
969
1001
  baseRevision = record.baseRevision;
970
1002
  }
1003
+ // Collector — admitted repository/PR identity (#633 resume).
1004
+ if (typeof record.prNumber === "number" && Number.isSafeInteger(record.prNumber) && record.prNumber >= 1) {
1005
+ prNumber = record.prNumber;
1006
+ }
1007
+ if (typeof record.repository === "string" && record.repository.trim() !== "") {
1008
+ repository = record.repository;
1009
+ }
1010
+ if (typeof record.repositoryDisplay === "string" && record.repositoryDisplay.trim() !== "") {
1011
+ repositoryDisplay = record.repositoryDisplay;
1012
+ }
1013
+ if (typeof record.requestManifestPath === "string" && record.requestManifestPath.trim() !== "") {
1014
+ requestManifestPath = record.requestManifestPath;
1015
+ }
1016
+ if (typeof record.manifestDigest === "string" && record.manifestDigest.trim() !== "") {
1017
+ manifestDigest = record.manifestDigest;
1018
+ }
1019
+ // Doctor — admitted single-case identity (#633 resume).
1020
+ if (typeof record.issueNumber === "number" && Number.isSafeInteger(record.issueNumber) && record.issueNumber >= 1) {
1021
+ issueNumber = record.issueNumber;
1022
+ }
1023
+ if (typeof record.caseRunsPath === "string" && record.caseRunsPath.trim() !== "") {
1024
+ caseRunsPath = record.caseRunsPath;
1025
+ }
1026
+ if (
1027
+ record.caseIdentity !== null &&
1028
+ typeof record.caseIdentity === "object" &&
1029
+ !Array.isArray(record.caseIdentity)
1030
+ ) {
1031
+ const ci = record.caseIdentity as Record<string, unknown>;
1032
+ if (
1033
+ typeof ci.issueNumber === "number" &&
1034
+ Number.isSafeInteger(ci.issueNumber) &&
1035
+ ci.issueNumber >= 1 &&
1036
+ typeof ci.runsPath === "string" &&
1037
+ ci.runsPath.trim() !== ""
1038
+ ) {
1039
+ caseIdentity = { issueNumber: ci.issueNumber, runsPath: ci.runsPath };
1040
+ }
1041
+ }
1042
+ // Notary — admitted source-run locator identity (#633 resume).
1043
+ if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
1044
+ sourceRunPath = record.sourceRunPath;
1045
+ }
1046
+ if (
1047
+ record.sourceRun !== null &&
1048
+ typeof record.sourceRun === "object" &&
1049
+ !Array.isArray(record.sourceRun)
1050
+ ) {
1051
+ const sr = record.sourceRun as Record<string, unknown>;
1052
+ if (
1053
+ typeof sr.runId === "string" &&
1054
+ sr.runId.trim() !== "" &&
1055
+ typeof sr.role === "string" &&
1056
+ sr.role.trim() !== "" &&
1057
+ typeof sr.runDirectory === "string" &&
1058
+ sr.runDirectory.trim() !== ""
1059
+ ) {
1060
+ sourceRun = {
1061
+ runId: sr.runId,
1062
+ role: sr.role,
1063
+ runDirectory: sr.runDirectory,
1064
+ };
1065
+ }
1066
+ }
971
1067
  if (Array.isArray(record.authorityRefs)) {
972
1068
  // Reuse unique --authority-ref grammar; blank/inline prose must not resume as authority.
973
1069
  authorityRefs = Object.freeze(
@@ -1083,6 +1179,16 @@ async function loadResumableRunRecord(
1083
1179
  ...(derived === undefined ? {} : { derived }),
1084
1180
  ...(correlationId === undefined ? {} : { correlationId }),
1085
1181
  ...(ticketNumber === undefined ? {} : { ticketNumber }),
1182
+ ...(prNumber === undefined ? {} : { prNumber }),
1183
+ ...(repository === undefined ? {} : { repository }),
1184
+ ...(repositoryDisplay === undefined ? {} : { repositoryDisplay }),
1185
+ ...(requestManifestPath === undefined ? {} : { requestManifestPath }),
1186
+ ...(manifestDigest === undefined ? {} : { manifestDigest }),
1187
+ ...(issueNumber === undefined ? {} : { issueNumber }),
1188
+ ...(caseRunsPath === undefined ? {} : { caseRunsPath }),
1189
+ ...(caseIdentity === undefined ? {} : { caseIdentity }),
1190
+ ...(sourceRunPath === undefined ? {} : { sourceRunPath }),
1191
+ ...(sourceRun === undefined ? {} : { sourceRun }),
1086
1192
  ...(model === undefined ? {} : { model }),
1087
1193
  },
1088
1194
  };
@@ -1125,23 +1231,23 @@ export type LoadedResumableGleanerLeftRun = {
1125
1231
  readonly observation?: TypedHttp429Observation;
1126
1232
  };
1127
1233
 
1234
+ export type LoadedResumableInstructionSeatRun = {
1235
+ readonly admitted: AdmittedGatekeeperInvocation | AdmittedNavigatorInvocation;
1236
+ readonly run: RoleRunRecord;
1237
+ readonly observation?: TypedHttp429Observation;
1238
+ };
1239
+
1128
1240
  /**
1129
- * Load a resumable Judge run for resume. Rejects unknown, terminal,
1130
- * non-resumable, and non-Judge IDs without replaying dispatch.
1241
+ * Base admitted projection shared by every seat loader: durable identity +
1242
+ * instruction/attachments + principal + restored ticket identity + model (#633).
1243
+ * Seat loaders add only their seat-specific fields on top.
1131
1244
  */
1132
- export async function loadResumableJudgeRun(
1133
- home: string,
1134
- runId: string,
1135
- authority: DurablePrincipalAuthority,
1136
- ): Promise<LoadedResumableJudgeRun> {
1137
- const loaded = await loadResumableRunRecord(home, runId, authority);
1138
- if (loaded.run.role !== "judge") {
1139
- throw new CliUsageError(
1140
- `role run ${runId} belongs to ${loaded.run.role}, not judge`,
1141
- );
1142
- }
1143
- const admitted: AdmittedJudgeInvocation = {
1144
- role: "judge",
1245
+ function resumedBaseAdmitted(loaded: {
1246
+ readonly run: RoleRunRecord;
1247
+ readonly principal: DurablePrincipal;
1248
+ readonly admittedFields: LoadedAdmittedRequestFields;
1249
+ }) {
1250
+ return {
1145
1251
  runId: loaded.run.runId,
1146
1252
  bookKey: loaded.run.bookKey,
1147
1253
  projectRoot: loaded.run.projectRoot,
@@ -1154,6 +1260,16 @@ export async function loadResumableJudgeRun(
1154
1260
  ...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
1155
1261
  ...restoredTicketFields(loaded.admittedFields),
1156
1262
  };
1263
+ }
1264
+
1265
+ /** Loaded-run envelope projection; seat loaders add only their seat fields. */
1266
+ function seatLoadedResult<R extends AdmittedRoleInvocation>(
1267
+ loaded: {
1268
+ readonly run: RoleRunRecord;
1269
+ readonly observation?: TypedHttp429Observation;
1270
+ },
1271
+ admitted: R,
1272
+ ): { admitted: R; run: RoleRunRecord; observation?: TypedHttp429Observation } {
1157
1273
  return {
1158
1274
  admitted,
1159
1275
  run: loaded.run,
@@ -1161,6 +1277,28 @@ export async function loadResumableJudgeRun(
1161
1277
  };
1162
1278
  }
1163
1279
 
1280
+ /**
1281
+ * Load a resumable Judge run for resume. Rejects unknown, terminal,
1282
+ * non-resumable, and non-Judge IDs without replaying dispatch.
1283
+ */
1284
+ export async function loadResumableJudgeRun(
1285
+ home: string,
1286
+ runId: string,
1287
+ authority: DurablePrincipalAuthority,
1288
+ ): Promise<LoadedResumableJudgeRun> {
1289
+ const loaded = await loadResumableRunRecord(home, runId, authority);
1290
+ if (loaded.run.role !== "judge") {
1291
+ throw new CliUsageError(
1292
+ `role run ${runId} belongs to ${loaded.run.role}, not judge`,
1293
+ );
1294
+ }
1295
+ const admitted: AdmittedJudgeInvocation = {
1296
+ role: "judge",
1297
+ ...resumedBaseAdmitted(loaded),
1298
+ };
1299
+ return seatLoadedResult(loaded, admitted);
1300
+ }
1301
+
1164
1302
  /**
1165
1303
  * Load a resumable Coder run for resume. Phase and task path are restored from
1166
1304
  * the admitted request so continuation stays role-correct (#109).
@@ -1196,24 +1334,11 @@ export async function loadResumableCoderRun(
1196
1334
  const admitted: AdmittedCoderInvocation = {
1197
1335
  role: "coder",
1198
1336
  phase,
1199
- runId: loaded.run.runId,
1200
- bookKey: loaded.run.bookKey,
1201
- projectRoot: loaded.run.projectRoot,
1202
- instruction: loaded.admittedFields.instruction,
1337
+ ...resumedBaseAdmitted(loaded),
1203
1338
  instructionEmpty: false,
1204
- attachments: loaded.admittedFields.attachments,
1205
- runDirectory: loaded.run.runDirectory,
1206
- principal: loaded.principal,
1207
- admittedRequestPath: loaded.run.admittedRequestPath,
1208
1339
  taskPath,
1209
- ...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
1210
- ...restoredTicketFields(loaded.admittedFields),
1211
- };
1212
- return {
1213
- admitted,
1214
- run: loaded.run,
1215
- ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
1216
1340
  };
1341
+ return seatLoadedResult(loaded, admitted);
1217
1342
  }
1218
1343
 
1219
1344
  /**
@@ -1252,28 +1377,15 @@ export async function loadResumableFixerRun(
1252
1377
  const admitted: AdmittedFixerInvocation = {
1253
1378
  role: "fixer",
1254
1379
  phase,
1255
- runId: loaded.run.runId,
1256
- bookKey: loaded.run.bookKey,
1257
- projectRoot: loaded.run.projectRoot,
1258
- instruction: loaded.admittedFields.instruction,
1380
+ ...resumedBaseAdmitted(loaded),
1259
1381
  instructionEmpty: false,
1260
- attachments: loaded.admittedFields.attachments,
1261
- runDirectory: loaded.run.runDirectory,
1262
- principal: loaded.principal,
1263
- admittedRequestPath: loaded.run.admittedRequestPath,
1264
1382
  packetPath,
1265
1383
  ...(loaded.admittedFields.prerequisitesPath === undefined
1266
1384
  ? {}
1267
1385
  : { prerequisitesPath: loaded.admittedFields.prerequisitesPath }),
1268
1386
  prerequisites,
1269
- ...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
1270
- ...restoredTicketFields(loaded.admittedFields),
1271
- };
1272
- return {
1273
- admitted,
1274
- run: loaded.run,
1275
- ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
1276
1387
  };
1388
+ return seatLoadedResult(loaded, admitted);
1277
1389
  }
1278
1390
 
1279
1391
  /**
@@ -1303,25 +1415,11 @@ export async function loadResumableReviewerRun(
1303
1415
  }
1304
1416
  const admitted: AdmittedReviewerInvocation = {
1305
1417
  role: "reviewer",
1306
- runId: loaded.run.runId,
1307
- bookKey: loaded.run.bookKey,
1308
- projectRoot: loaded.run.projectRoot,
1309
- instruction: loaded.admittedFields.instruction,
1310
- instructionEmpty: loaded.admittedFields.instructionEmpty,
1311
- attachments: loaded.admittedFields.attachments,
1312
- runDirectory: loaded.run.runDirectory,
1313
- principal: loaded.principal,
1314
- admittedRequestPath: loaded.run.admittedRequestPath,
1418
+ ...resumedBaseAdmitted(loaded),
1315
1419
  baseRevision,
1316
1420
  authorityRefs: Object.freeze([...(loaded.admittedFields.authorityRefs ?? [])]),
1317
- ...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
1318
- ...restoredTicketFields(loaded.admittedFields),
1319
- };
1320
- return {
1321
- admitted,
1322
- run: loaded.run,
1323
- ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
1324
1421
  };
1422
+ return seatLoadedResult(loaded, admitted);
1325
1423
  }
1326
1424
 
1327
1425
  /**
@@ -1341,29 +1439,33 @@ export async function loadResumableCountersignRun(
1341
1439
  }
1342
1440
  const admitted: AdmittedCountersignInvocation = {
1343
1441
  role: "countersign",
1344
- runId: loaded.run.runId,
1345
- bookKey: loaded.run.bookKey,
1346
- projectRoot: loaded.run.projectRoot,
1347
- instruction: loaded.admittedFields.instruction,
1348
- instructionEmpty: loaded.admittedFields.instructionEmpty,
1349
- attachments: loaded.admittedFields.attachments,
1350
- runDirectory: loaded.run.runDirectory,
1351
- principal: loaded.principal,
1352
- admittedRequestPath: loaded.run.admittedRequestPath,
1353
- ...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
1354
- ...restoredTicketFields(loaded.admittedFields),
1355
- };
1356
- return {
1357
- admitted,
1358
- run: loaded.run,
1359
- ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
1442
+ ...resumedBaseAdmitted(loaded),
1360
1443
  };
1444
+ return seatLoadedResult(loaded, admitted);
1361
1445
  }
1362
1446
 
1363
1447
  /**
1364
1448
  * Load a resumable Gleaner-Left run for resume (#599). Fixed base restores from
1365
1449
  * the admitted request so continuation stays comparison-correct.
1366
1450
  */
1451
+ export async function loadResumableInstructionSeatRun(
1452
+ home: string,
1453
+ runId: string,
1454
+ authority: DurablePrincipalAuthority,
1455
+ ): Promise<LoadedResumableInstructionSeatRun> {
1456
+ const loaded = await loadResumableRunRecord(home, runId, authority);
1457
+ if (loaded.run.role !== "gatekeeper" && loaded.run.role !== "navigator") {
1458
+ throw new CliUsageError(
1459
+ `role run ${runId} belongs to ${loaded.run.role}, not an instruction seat`,
1460
+ );
1461
+ }
1462
+ const admitted = {
1463
+ role: loaded.run.role,
1464
+ ...resumedBaseAdmitted(loaded),
1465
+ } as AdmittedGatekeeperInvocation | AdmittedNavigatorInvocation;
1466
+ return seatLoadedResult(loaded, admitted);
1467
+ }
1468
+
1367
1469
  export async function loadResumableGleanerLeftRun(
1368
1470
  home: string,
1369
1471
  runId: string,
@@ -1383,24 +1485,10 @@ export async function loadResumableGleanerLeftRun(
1383
1485
  }
1384
1486
  const admitted: AdmittedGleanerLeftInvocation = {
1385
1487
  role: "gleaner-left",
1386
- runId: loaded.run.runId,
1387
- bookKey: loaded.run.bookKey,
1388
- projectRoot: loaded.run.projectRoot,
1389
- instruction: loaded.admittedFields.instruction,
1390
- instructionEmpty: loaded.admittedFields.instructionEmpty,
1391
- attachments: loaded.admittedFields.attachments,
1392
- runDirectory: loaded.run.runDirectory,
1393
- principal: loaded.principal,
1394
- admittedRequestPath: loaded.run.admittedRequestPath,
1488
+ ...resumedBaseAdmitted(loaded),
1395
1489
  baseRevision,
1396
- ...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
1397
- ...restoredTicketFields(loaded.admittedFields),
1398
- };
1399
- return {
1400
- admitted,
1401
- run: loaded.run,
1402
- ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
1403
1490
  };
1491
+ return seatLoadedResult(loaded, admitted);
1404
1492
  }
1405
1493
 
1406
1494
  export type LoadedResumableMergerRun = {
@@ -1443,25 +1531,172 @@ export async function loadResumableMergerRun(
1443
1531
  }
1444
1532
  const admitted: AdmittedMergerInvocation = {
1445
1533
  role: "merger",
1446
- runId: loaded.run.runId,
1447
- bookKey: loaded.run.bookKey,
1448
- projectRoot: loaded.run.projectRoot,
1449
- instruction: loaded.admittedFields.instruction,
1534
+ ...resumedBaseAdmitted(loaded),
1450
1535
  instructionEmpty: false,
1451
- attachments: loaded.admittedFields.attachments,
1452
- runDirectory: loaded.run.runDirectory,
1453
- principal: loaded.principal,
1454
- admittedRequestPath: loaded.run.admittedRequestPath,
1455
1536
  mergerInputPath,
1456
1537
  derived,
1457
- ...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
1458
- ...restoredTicketFields(loaded.admittedFields),
1459
1538
  };
1460
- return {
1461
- admitted,
1462
- run: loaded.run,
1463
- ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
1539
+ return seatLoadedResult(loaded, admitted);
1540
+ }
1541
+
1542
+ export type LoadedResumableCollectorRun = {
1543
+ readonly admitted: AdmittedCollectorInvocation;
1544
+ readonly run: RoleRunRecord;
1545
+ readonly observation?: TypedHttp429Observation;
1546
+ };
1547
+
1548
+ /**
1549
+ * Load a resumable Collector run for resume (#633). Admitted repository/PR
1550
+ * identity restores from the durable admitted request so the continuation
1551
+ * stays binding-correct.
1552
+ */
1553
+ export async function loadResumableCollectorRun(
1554
+ home: string,
1555
+ runId: string,
1556
+ authority: DurablePrincipalAuthority,
1557
+ ): Promise<LoadedResumableCollectorRun> {
1558
+ const loaded = await loadResumableRunRecord(home, runId, authority);
1559
+ if (loaded.run.role !== "collector") {
1560
+ throw new CliUsageError(
1561
+ `role run ${runId} belongs to ${loaded.run.role}, not collector`,
1562
+ );
1563
+ }
1564
+ const { prNumber, repository, repositoryDisplay, manifestDigest } = loaded.admittedFields;
1565
+ if (
1566
+ prNumber === undefined ||
1567
+ repository === undefined ||
1568
+ repositoryDisplay === undefined ||
1569
+ manifestDigest === undefined
1570
+ ) {
1571
+ throw new CliUsageError(
1572
+ `role run admitted collector repository identity is missing: ${runId}`,
1573
+ );
1574
+ }
1575
+ let parsedRepository;
1576
+ try {
1577
+ parsedRepository = parseCollectorRepository(repositoryDisplay);
1578
+ } catch (error) {
1579
+ const detail = error instanceof Error ? error.message : String(error);
1580
+ throw new CliUsageError(detail, { cause: error });
1581
+ }
1582
+ if (parsedRepository.canonical !== repository) {
1583
+ throw new CliUsageError(
1584
+ `role run admitted collector repository does not match its canonical form: ${runId}`,
1585
+ );
1586
+ }
1587
+ const admitted: AdmittedCollectorInvocation = {
1588
+ role: "collector",
1589
+ ...resumedBaseAdmitted(loaded),
1590
+ prNumber,
1591
+ repository: parsedRepository,
1592
+ ...(loaded.admittedFields.requestManifestPath === undefined
1593
+ ? {}
1594
+ : { requestManifestPath: loaded.admittedFields.requestManifestPath }),
1595
+ manifestDigest,
1596
+ };
1597
+ return seatLoadedResult(loaded, admitted);
1598
+ }
1599
+
1600
+ export type LoadedResumableDoctorRun = {
1601
+ readonly admitted: AdmittedDoctorInvocation;
1602
+ readonly run: RoleRunRecord;
1603
+ readonly observation?: TypedHttp429Observation;
1604
+ };
1605
+
1606
+ /**
1607
+ * Load a resumable Doctor run for resume (#633). Issue + retained case
1608
+ * identity restore from the durable admitted request.
1609
+ */
1610
+ export async function loadResumableDoctorRun(
1611
+ home: string,
1612
+ runId: string,
1613
+ authority: DurablePrincipalAuthority,
1614
+ ): Promise<LoadedResumableDoctorRun> {
1615
+ const loaded = await loadResumableRunRecord(home, runId, authority);
1616
+ if (loaded.run.role !== "doctor") {
1617
+ throw new CliUsageError(
1618
+ `role run ${runId} belongs to ${loaded.run.role}, not doctor`,
1619
+ );
1620
+ }
1621
+ const { issueNumber, caseRunsPath, caseIdentity } = loaded.admittedFields;
1622
+ if (
1623
+ issueNumber === undefined ||
1624
+ caseRunsPath === undefined ||
1625
+ caseIdentity === undefined
1626
+ ) {
1627
+ throw new CliUsageError(
1628
+ `role run admitted doctor case identity is missing: ${runId}`,
1629
+ );
1630
+ }
1631
+ const admitted: AdmittedDoctorInvocation = {
1632
+ role: "doctor",
1633
+ ...resumedBaseAdmitted(loaded),
1634
+ issueNumber,
1635
+ caseRunsPath,
1636
+ caseIdentity,
1637
+ };
1638
+ return seatLoadedResult(loaded, admitted);
1639
+ }
1640
+
1641
+ export type LoadedResumableNotaryRun = {
1642
+ readonly admitted: AdmittedNotaryInvocation;
1643
+ readonly run: RoleRunRecord;
1644
+ readonly observation?: TypedHttp429Observation;
1645
+ };
1646
+
1647
+ /**
1648
+ * Load a resumable Notary run for resume (#633). Source-run locator restores
1649
+ * from the durable admitted request; the locator is not re-resolved.
1650
+ */
1651
+ export async function loadResumableNotaryRun(
1652
+ home: string,
1653
+ runId: string,
1654
+ authority: DurablePrincipalAuthority,
1655
+ ): Promise<LoadedResumableNotaryRun> {
1656
+ const loaded = await loadResumableRunRecord(home, runId, authority);
1657
+ if (loaded.run.role !== "notary") {
1658
+ throw new CliUsageError(
1659
+ `role run ${runId} belongs to ${loaded.run.role}, not notary`,
1660
+ );
1661
+ }
1662
+ const { sourceRunPath, sourceRun } = loaded.admittedFields;
1663
+ if (sourceRunPath === undefined || sourceRun === undefined) {
1664
+ throw new CliUsageError(
1665
+ `role run admitted notary source-run locator is missing: ${runId}`,
1666
+ );
1667
+ }
1668
+ const admitted: AdmittedNotaryInvocation = {
1669
+ role: "notary",
1670
+ ...resumedBaseAdmitted(loaded),
1671
+ sourceRunPath,
1672
+ sourceRun,
1673
+ };
1674
+ return seatLoadedResult(loaded, admitted);
1675
+ }
1676
+
1677
+ export type LoadedResumableInspectorRun = {
1678
+ readonly admitted: AdmittedInspectorInvocation;
1679
+ readonly run: RoleRunRecord;
1680
+ readonly observation?: TypedHttp429Observation;
1681
+ };
1682
+
1683
+ /** Load a resumable Inspector run for resume (#633); base admitted identity only. */
1684
+ export async function loadResumableInspectorRun(
1685
+ home: string,
1686
+ runId: string,
1687
+ authority: DurablePrincipalAuthority,
1688
+ ): Promise<LoadedResumableInspectorRun> {
1689
+ const loaded = await loadResumableRunRecord(home, runId, authority);
1690
+ if (loaded.run.role !== "inspector") {
1691
+ throw new CliUsageError(
1692
+ `role run ${runId} belongs to ${loaded.run.role}, not inspector`,
1693
+ );
1694
+ }
1695
+ const admitted: AdmittedInspectorInvocation = {
1696
+ role: "inspector",
1697
+ ...resumedBaseAdmitted(loaded),
1464
1698
  };
1699
+ return seatLoadedResult(loaded, admitted);
1465
1700
  }
1466
1701
 
1467
1702
  export async function peekRoleRunRole(