@akagilnc/pi-workflow-roles 0.1.5365 → 0.1.5375

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 (170) hide show
  1. package/dist/acp-host/production-host.js +5585 -5655
  2. package/dist/activation-ledger-git.js +2 -2
  3. package/dist/activation-ledger-topology.js +1 -7
  4. package/dist/analyst-entry.js +4 -8
  5. package/dist/analyst-gate-cycles-read.js +3 -11
  6. package/dist/analyst-index.js +3 -9
  7. package/dist/analyst-ledger.js +1 -11
  8. package/dist/analyst-metric-families/acceptance-success-rework.js +1 -3
  9. package/dist/analyst-metric-families/round-timeline.js +1 -3
  10. package/dist/analyst-page.js +2 -2
  11. package/dist/archivist-record-topology.js +2 -2
  12. package/dist/audit-escalation.js +2 -1
  13. package/dist/auditor-soul.js +2 -1
  14. package/dist/collector-config.js +6 -8
  15. package/dist/compliance-transport.js +2 -1
  16. package/dist/countersign-contracts.js +2 -1
  17. package/dist/diarist-contracts.js +3 -2
  18. package/dist/doctor-contracts.js +1 -3
  19. package/dist/doctor-evidence.js +7 -8
  20. package/dist/dossier-resolution.js +1 -3
  21. package/dist/engine-detour-usage.js +1 -3
  22. package/dist/gatekeeper-role.js +2 -9
  23. package/dist/gleaner-left-contracts.js +2 -1
  24. package/dist/headless-host/description.js +14 -17
  25. package/dist/headless-host/production-host.js +1665 -1737
  26. package/dist/host-session-record.js +3 -2
  27. package/dist/inspector-contracts.js +2 -1
  28. package/dist/ledger-session-read.js +1 -3
  29. package/dist/merger-contracts.js +9 -9
  30. package/dist/migrate-book-topology.js +348 -314
  31. package/dist/navigator-attendance.js +9 -11
  32. package/dist/navigator-invocation-identity.js +3 -2
  33. package/dist/navigator-public-session.js +3 -5
  34. package/dist/navigator-session-contracts.js +8 -10
  35. package/dist/navigator-work-base.js +5 -8
  36. package/dist/notary-contracts.js +3 -5
  37. package/dist/package-contracts/auditor-output.js +1 -3
  38. package/dist/package-contracts/fixer-packet.js +3 -6
  39. package/dist/package-contracts/gatekeeper-output.js +1 -3
  40. package/dist/package-contracts/navigator-output.js +8 -8
  41. package/dist/package-contracts/terminating-infrastructure.js +5 -9
  42. package/dist/package-contracts/terminating-tools.js +4 -3
  43. package/dist/public-cli/analyst-run.js +3 -2
  44. package/dist/public-cli/auto-resume.js +4 -9
  45. package/dist/public-cli/cli.js +66 -64
  46. package/dist/public-cli/config.js +12 -11
  47. package/dist/public-cli/countersign-run.js +1 -3
  48. package/dist/public-cli/host-providers.js +7 -14
  49. package/dist/public-cli/instruction-seat-run.js +69 -43
  50. package/dist/public-cli/invocation.js +141 -136
  51. package/dist/public-cli/main.js +1919 -1968
  52. package/dist/public-cli/post-admission.js +89 -89
  53. package/dist/public-cli/role-seat-runtime.js +29 -0
  54. package/dist/public-cli/run-lifecycle.js +20 -32
  55. package/dist/public-cli/settlement.js +10 -26
  56. package/dist/public-cli/turn-request.js +14 -0
  57. package/dist/public-role-summons.js +23 -35
  58. package/dist/receipt-delivery-policy.js +1 -3
  59. package/dist/role-run-placement.js +7 -5
  60. package/dist/role-run-relocation.js +12 -19
  61. package/dist/run-terminal-artifacts.js +1 -11
  62. package/dist/run-ticket-number.js +2 -4
  63. package/dist/session-assistant-usage.js +2 -1
  64. package/dist/session-dialogue.js +1 -14
  65. package/dist/sitian-reader.js +2 -4
  66. package/dist/submission-correctable-error.js +2 -1
  67. package/dist/submission-errors.js +2 -1
  68. package/dist/submission-gate.js +2 -1
  69. package/dist/submission-ledger.js +4 -5
  70. package/dist/ticket-provenance-contracts.js +1 -3
  71. package/dist/ticket-provenance.js +2 -1
  72. package/dist/typed-provider-http.js +4 -7
  73. package/dist/unknown-value.js +28 -0
  74. package/dist/user-dialogue-stdin.js +2 -1
  75. package/package.json +1 -1
  76. package/src/acp-host/role-turn-host.ts +4 -2
  77. package/src/activation-ledger-git.ts +3 -2
  78. package/src/activation-ledger-topology.ts +4 -12
  79. package/src/analyst-entry.ts +3 -11
  80. package/src/analyst-gate-cycles-read.ts +3 -15
  81. package/src/analyst-index.ts +3 -13
  82. package/src/analyst-ledger.ts +2 -16
  83. package/src/analyst-metric-families/acceptance-success-rework.ts +2 -4
  84. package/src/analyst-metric-families/round-timeline.ts +2 -4
  85. package/src/analyst-page.ts +2 -2
  86. package/src/archivist-record-topology.ts +2 -2
  87. package/src/audit-escalation.ts +2 -1
  88. package/src/auditor-soul.ts +3 -1
  89. package/src/book-topology-migration-placement.ts +8 -21
  90. package/src/book-topology-migrators-t11.ts +16 -15
  91. package/src/book-topology-mixed-volume-migrators.ts +3 -6
  92. package/src/book-topology-record-class-migrators.ts +7 -10
  93. package/src/book-topology-runs-migrator.ts +4 -3
  94. package/src/collector-config.ts +7 -9
  95. package/src/compliance-transport.ts +2 -1
  96. package/src/countersign-contracts.ts +2 -1
  97. package/src/diarist-contracts.ts +3 -2
  98. package/src/doctor-contracts.ts +3 -1
  99. package/src/doctor-evidence.ts +8 -7
  100. package/src/doctor-role.ts +4 -2
  101. package/src/dossier-resolution.ts +2 -4
  102. package/src/engine-detour-tool.ts +0 -2
  103. package/src/engine-detour-usage.ts +2 -4
  104. package/src/external-host-turn-loop.ts +3 -1
  105. package/src/factory-board.ts +1 -8
  106. package/src/gatekeeper-role.ts +2 -10
  107. package/src/gleaner-left-contracts.ts +3 -1
  108. package/src/headless-host/description.ts +15 -18
  109. package/src/headless-host/role-turn-host.ts +10 -9
  110. package/src/host-session-record.ts +4 -2
  111. package/src/inspector-contracts.ts +2 -1
  112. package/src/ledger-session-read.ts +2 -5
  113. package/src/merger-contracts.ts +10 -9
  114. package/src/navigator-attendance.ts +10 -12
  115. package/src/navigator-invocation-identity.ts +3 -2
  116. package/src/navigator-public-session.ts +4 -6
  117. package/src/navigator-session-contracts.ts +8 -11
  118. package/src/navigator-work-base.ts +5 -8
  119. package/src/navigator-work-context.ts +2 -1
  120. package/src/notary-contracts.ts +4 -6
  121. package/src/oauth-keepalive.ts +5 -18
  122. package/src/package-contracts/auditor-output.ts +2 -4
  123. package/src/package-contracts/fixer-packet.ts +4 -6
  124. package/src/package-contracts/gatekeeper-output.ts +2 -4
  125. package/src/package-contracts/judge-output.ts +0 -1
  126. package/src/package-contracts/navigator-output.ts +8 -8
  127. package/src/package-contracts/terminating-infrastructure.ts +6 -20
  128. package/src/package-contracts/terminating-tools.ts +8 -12
  129. package/src/pi/adapter.ts +0 -1
  130. package/src/pi/role-turn-host.ts +0 -1
  131. package/src/public-cli/analyst-run.ts +4 -2
  132. package/src/public-cli/auto-resume.ts +5 -12
  133. package/src/public-cli/cli.ts +77 -83
  134. package/src/public-cli/config.ts +16 -27
  135. package/src/public-cli/countersign-run.ts +2 -4
  136. package/src/public-cli/host-providers.ts +10 -27
  137. package/src/public-cli/instruction-seat-run.ts +87 -58
  138. package/src/public-cli/invocation.ts +159 -172
  139. package/src/public-cli/post-admission.ts +115 -103
  140. package/src/public-cli/role-seat-runtime.ts +72 -0
  141. package/src/public-cli/run-lifecycle.ts +27 -67
  142. package/src/public-cli/settlement.ts +11 -33
  143. package/src/public-cli/terminal.ts +2 -17
  144. package/src/public-cli/turn-request.ts +26 -0
  145. package/src/public-role-summons.ts +24 -48
  146. package/src/receipt-delivery-policy.ts +1 -4
  147. package/src/reviewer-role.ts +0 -1
  148. package/src/role-envelope.ts +5 -3
  149. package/src/role-run-placement.ts +9 -10
  150. package/src/role-run-relocation.ts +13 -23
  151. package/src/role-runtime.ts +9 -15
  152. package/src/run-terminal-artifacts.ts +2 -16
  153. package/src/run-ticket-number.ts +4 -10
  154. package/src/session-assistant-usage.ts +3 -1
  155. package/src/session-dialogue.ts +1 -4
  156. package/src/session-identity.ts +0 -1
  157. package/src/sitian-reader.ts +2 -4
  158. package/src/submission-correctable-error.ts +3 -1
  159. package/src/submission-errors.ts +2 -1
  160. package/src/submission-gate.ts +3 -1
  161. package/src/submission-ledger.ts +5 -6
  162. package/src/ticket-provenance-contracts.ts +1 -4
  163. package/src/ticket-provenance.ts +3 -1
  164. package/src/ticket-snapshot.ts +2 -4
  165. package/src/ticket-trajectory.ts +1 -8
  166. package/src/typed-provider-http.ts +5 -15
  167. package/src/unknown-value.ts +34 -0
  168. package/src/user-dialogue-stdin.ts +3 -1
  169. package/src/worker-role.ts +2 -5
  170. package/src/worker-submission-gates.ts +5 -7
@@ -27,6 +27,7 @@ import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
27
27
  import {
28
28
  ensureRoleRunDirectory,
29
29
  ensureRoleRunPlacement,
30
+ isUnboundRunDirectory,
30
31
  listBookRunDirectories,
31
32
  roleRunArtifactsDirectory,
32
33
  roleRunPlacement,
@@ -36,7 +37,7 @@ import type {
36
37
  DurablePrincipal,
37
38
  DurablePrincipalAuthority,
38
39
  } from "../host-contracts.ts";
39
- import type { PackagedRole } from "../packaged-role-registry.ts";
40
+ import type { PackagedRole, PublicRoleRecord } from "../packaged-role-registry.ts";
40
41
  import {
41
42
  packagedAdmittedSubject,
42
43
  packagedArgvResult,
@@ -109,6 +110,8 @@ import {
109
110
  } from "./option-definitions.ts";
110
111
  import type { PublicThinkingLevel } from "./registry.ts";
111
112
 
113
+ import { errorText, isRecord } from "../unknown-value.ts";
114
+
112
115
  export type FrozenAttachment = {
113
116
  /** Original caller path retained only as provenance. */
114
117
  readonly provenancePath: string;
@@ -445,39 +448,46 @@ export async function recordEffectiveInvocationModel(
445
448
  host?: string,
446
449
  engineModel?: string | null,
447
450
  ): Promise<void> {
448
- const ledgerPath = join(runDirectory, "invocation.json");
449
- const current = JSON.parse(await readFile(ledgerPath, "utf8")) as Record<
450
- string,
451
- unknown
452
- >;
453
- const next: Record<string, unknown> = { ...current };
454
- if (model !== undefined) {
455
- next.provider = model.provider;
456
- next.model = model.model;
457
- if (model.thinking === undefined) {
458
- delete next.thinking;
459
- } else {
460
- next.thinking = model.thinking;
451
+ await updateDurableJsonPage(join(runDirectory, "invocation.json"), (current) => {
452
+ const next: Record<string, unknown> = { ...current };
453
+ if (model !== undefined) {
454
+ next.provider = model.provider;
455
+ next.model = model.model;
456
+ if (model.thinking === undefined) {
457
+ delete next.thinking;
458
+ } else {
459
+ next.thinking = model.thinking;
460
+ }
461
461
  }
462
- }
463
- if (engine === null) {
464
- delete next.engine;
465
- } else if (engine !== undefined) {
466
- next.engine = engine;
467
- }
468
- if (engineModel === null) {
469
- delete next.engineModel;
470
- } else if (engineModel !== undefined) {
471
- next.engineModel = engineModel;
472
- }
473
- if (host !== undefined) {
474
- next.host = host;
475
- }
476
- await writeFile(
477
- ledgerPath,
478
- `${JSON.stringify(next, null, 2)}\n`,
479
- "utf8",
480
- );
462
+ if (engine === null) {
463
+ delete next.engine;
464
+ } else if (engine !== undefined) {
465
+ next.engine = engine;
466
+ }
467
+ if (engineModel === null) {
468
+ delete next.engineModel;
469
+ } else if (engineModel !== undefined) {
470
+ next.engineModel = engineModel;
471
+ }
472
+ if (host !== undefined) {
473
+ next.host = host;
474
+ }
475
+ return next;
476
+ });
477
+ }
478
+
479
+ /**
480
+ * Read one durable JSON object, let the caller keep its field rules, and write
481
+ * the page back. `undefined` means the caller decided not to change the page.
482
+ */
483
+ async function updateDurableJsonPage(
484
+ path: string,
485
+ update: (current: Record<string, unknown>) => Record<string, unknown> | undefined,
486
+ ): Promise<void> {
487
+ const current = JSON.parse(await readFile(path, "utf8")) as Record<string, unknown>;
488
+ const next = update(current);
489
+ if (next === undefined) return;
490
+ await writeFile(path, `${JSON.stringify(next, null, 2)}\n`, "utf8");
481
491
  }
482
492
 
483
493
  /** Merge observed launch-time fields into the single existing invocation.json identity page. */
@@ -485,16 +495,10 @@ async function mergeInvocationIdentityPage(
485
495
  runDirectory: string,
486
496
  fields: Record<string, unknown>,
487
497
  ): Promise<void> {
488
- const ledgerPath = join(runDirectory, "invocation.json");
489
- const current = JSON.parse(await readFile(ledgerPath, "utf8")) as Record<string, unknown>;
490
- await writeFile(
491
- ledgerPath,
492
- `${JSON.stringify({
493
- ...current,
494
- ...fields,
495
- }, null, 2)}\n`,
496
- "utf8",
497
- );
498
+ await updateDurableJsonPage(join(runDirectory, "invocation.json"), (current) => ({
499
+ ...current,
500
+ ...fields,
501
+ }));
498
502
  }
499
503
 
500
504
  /**
@@ -509,27 +513,20 @@ export async function persistAdmittedSourceRunPath(
509
513
  if (sourceRunPath.trim() === "") {
510
514
  throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
511
515
  }
512
- const admittedPath = admitted.admittedRequestPath;
513
- const current = JSON.parse(await readFile(admittedPath, "utf8")) as Record<
514
- string,
515
- unknown
516
- >;
517
- if (typeof current.sourceRunPath === "string" && current.sourceRunPath !== sourceRunPath) {
518
- throw new Error(
519
- `persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`,
520
- );
521
- }
522
- const sameSubject = auditorSubject === undefined || current.auditorSubject === auditorSubject;
523
- if (current.sourceRunPath === sourceRunPath && sameSubject) return;
524
- await writeFile(
525
- admittedPath,
526
- `${JSON.stringify({
516
+ await updateDurableJsonPage(admitted.admittedRequestPath, (current) => {
517
+ if (typeof current.sourceRunPath === "string" && current.sourceRunPath !== sourceRunPath) {
518
+ throw new Error(
519
+ `persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`,
520
+ );
521
+ }
522
+ const sameSubject = auditorSubject === undefined || current.auditorSubject === auditorSubject;
523
+ if (current.sourceRunPath === sourceRunPath && sameSubject) return undefined;
524
+ return {
527
525
  ...current,
528
526
  sourceRunPath,
529
527
  ...(auditorSubject === undefined ? {} : { auditorSubject }),
530
- }, null, 2)}\n`,
531
- "utf8",
532
- );
528
+ };
529
+ });
533
530
  }
534
531
 
535
532
  /**
@@ -551,25 +548,21 @@ export async function recordAdmittedCorrelation(
551
548
  admitted: AdmittedRoleInvocation,
552
549
  correlationId: string,
553
550
  ): Promise<void> {
554
- const current = JSON.parse(
555
- await readFile(admitted.admittedRequestPath, "utf8"),
556
- ) as Record<string, unknown>;
557
- const prior = [
558
- ...(Array.isArray(current.correlationIds)
559
- ? current.correlationIds.filter((value): value is string =>
560
- typeof value === "string" && value.trim() !== ""
561
- )
562
- : []),
563
- ...(typeof current.correlationId === "string" && current.correlationId.trim() !== ""
564
- ? [current.correlationId]
565
- : []),
566
- ];
567
- const correlationIds = [...new Set([...prior, correlationId])];
568
- await writeFile(
569
- admitted.admittedRequestPath,
570
- `${JSON.stringify({ ...current, correlationId, correlationIds }, null, 2)}\n`,
571
- "utf8",
572
- );
551
+ let correlationIds: string[] = [];
552
+ await updateDurableJsonPage(admitted.admittedRequestPath, (current) => {
553
+ const prior = [
554
+ ...(Array.isArray(current.correlationIds)
555
+ ? current.correlationIds.filter((value): value is string =>
556
+ typeof value === "string" && value.trim() !== ""
557
+ )
558
+ : []),
559
+ ...(typeof current.correlationId === "string" && current.correlationId.trim() !== ""
560
+ ? [current.correlationId]
561
+ : []),
562
+ ];
563
+ correlationIds = [...new Set([...prior, correlationId])];
564
+ return { ...current, correlationId, correlationIds };
565
+ });
573
566
  await mergeInvocationIdentityPage(admitted.runDirectory, {
574
567
  correlationId,
575
568
  correlationIds,
@@ -588,7 +581,7 @@ export async function relocateAdmittedRunToTicket(
588
581
  authority: DurablePrincipalAuthority,
589
582
  heldLease?: { relocate(runDirectory: string): void },
590
583
  ): Promise<RunDirectoryRelocation | undefined> {
591
- if (admitted.ticketNumber === undefined || !admitted.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) return undefined;
584
+ if (admitted.ticketNumber === undefined || !isUnboundRunDirectory(admitted.runDirectory)) return undefined;
592
585
  const oldRunDirectory = admitted.runDirectory;
593
586
  const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(oldRunDirectory));
594
587
  const target = roleRunPlacement(ledgerHome, {
@@ -670,12 +663,13 @@ export async function recordChildDiaristRun(
670
663
  parent: AdmittedRoleInvocation,
671
664
  childRunId: string,
672
665
  ): Promise<void> {
673
- const page = JSON.parse(await readFile(parent.admittedRequestPath, "utf8")) as Record<string, unknown>;
674
- const existing = Array.isArray(page.childDiaristRunIds)
675
- ? page.childDiaristRunIds.filter((runId): runId is string => typeof runId === "string")
676
- : [];
677
- const childDiaristRunIds = existing.includes(childRunId) ? existing : [...existing, childRunId];
678
- await writeFile(parent.admittedRequestPath, `${JSON.stringify({ ...page, childDiaristRunIds }, null, 2)}\n`, "utf8");
666
+ await updateDurableJsonPage(parent.admittedRequestPath, (page) => {
667
+ const existing = Array.isArray(page.childDiaristRunIds)
668
+ ? page.childDiaristRunIds.filter((runId): runId is string => typeof runId === "string")
669
+ : [];
670
+ const childDiaristRunIds = existing.includes(childRunId) ? existing : [...existing, childRunId];
671
+ return { ...page, childDiaristRunIds };
672
+ });
679
673
  }
680
674
 
681
675
  /**
@@ -692,16 +686,15 @@ export async function bindTicketNumberOnRunDirectory(
692
686
  "bindTicketNumberOnRunDirectory",
693
687
  );
694
688
  const admittedPath = join(runDirectory, "admitted-request.json");
695
- const admitted = JSON.parse(await readFile(admittedPath, "utf8")) as Record<
696
- string,
697
- unknown
698
- >;
699
- if (admitted.ticketNumber === ticketNumber) return;
700
- await writeFile(
701
- admittedPath,
702
- `${JSON.stringify({ ...admitted, ticketNumber }, null, 2)}\n`,
703
- "utf8",
704
- );
689
+ let unchanged = false;
690
+ await updateDurableJsonPage(admittedPath, (admitted) => {
691
+ if (admitted.ticketNumber === ticketNumber) {
692
+ unchanged = true;
693
+ return undefined;
694
+ }
695
+ return { ...admitted, ticketNumber };
696
+ });
697
+ if (unchanged) return;
705
698
  await mergeInvocationIdentityPage(runDirectory, { ticketNumber });
706
699
  }
707
700
 
@@ -1097,20 +1090,34 @@ function requireOptionPath(
1097
1090
  * next option), and a leading `-` (read as the next Skill option). Not a
1098
1091
  * general free-text gate — only the projection admission seam.
1099
1092
  */
1093
+ function skillArgTokenFault(
1094
+ value: string | undefined,
1095
+ ): "empty" | "whitespace" | "optionLike" | undefined {
1096
+ if (value === undefined || value.trim() === "") return "empty";
1097
+ if (/\s/.test(value)) return "whitespace";
1098
+ if (value.startsWith("-")) return "optionLike";
1099
+ return undefined;
1100
+ }
1101
+
1100
1102
  function requireSkillArgToken(
1101
1103
  value: string | undefined,
1102
1104
  messages: { empty: string; whitespace: string; optionLike: string },
1103
1105
  ): string {
1104
- if (value === undefined || value.trim() === "") {
1105
- throw new CliUsageError(messages.empty);
1106
- }
1107
- if (/\s/.test(value)) {
1108
- throw new CliUsageError(messages.whitespace);
1109
- }
1110
- if (value.startsWith("-")) {
1111
- throw new CliUsageError(messages.optionLike);
1112
- }
1113
- return value;
1106
+ const fault = skillArgTokenFault(value);
1107
+ if (fault !== undefined) throw new CliUsageError(messages[fault]);
1108
+ return value as string;
1109
+ }
1110
+
1111
+ /**
1112
+ * Durable reviewer base on resume. Same token rule as fresh `--base`,
1113
+ * with the resume-owned missing/damaged split.
1114
+ */
1115
+ export function admittedReviewerBaseFault(
1116
+ value: string | undefined,
1117
+ ): "missing" | "damaged" | undefined {
1118
+ const fault = skillArgTokenFault(value);
1119
+ if (fault === undefined) return undefined;
1120
+ return fault === "empty" ? "missing" : "damaged";
1114
1121
  }
1115
1122
 
1116
1123
  /**
@@ -1420,6 +1427,11 @@ export type PublicSeatParse = {
1420
1427
  * Sole admit call. Kind comes from the composition-root record.
1421
1428
  * Placement stays ticketAdmissionFields. Seat field checks live in this function only.
1422
1429
  */
1430
+ type RoleForAdmission<K extends PublicRoleRecord["admission"]> = Extract<
1431
+ PublicRoleRecord,
1432
+ { readonly admission: K }
1433
+ >["role"];
1434
+
1423
1435
  export function admitPublicRole<R extends PackagedRole>(
1424
1436
  role: R,
1425
1437
  parsed: PublicSeatParse,
@@ -1427,6 +1439,8 @@ export function admitPublicRole<R extends PackagedRole>(
1427
1439
  override?: {
1428
1440
  readonly assertedTicketNumber?: number;
1429
1441
  readonly deferPersistence?: boolean;
1442
+ /** Locator already resolved on the new-summon path. Absent callers still resolve. */
1443
+ readonly resolvedSourceRun?: NotarySourceRunLocator;
1430
1444
  },
1431
1445
  ): Promise<Extract<AdmittedRoleInvocation, { readonly role: R }>>;
1432
1446
  export async function admitPublicRole(
@@ -1436,6 +1450,8 @@ export async function admitPublicRole(
1436
1450
  override?: {
1437
1451
  readonly assertedTicketNumber?: number;
1438
1452
  readonly deferPersistence?: boolean;
1453
+ /** Locator already resolved on the new-summon path. Absent callers still resolve. */
1454
+ readonly resolvedSourceRun?: NotarySourceRunLocator;
1439
1455
  },
1440
1456
  ): Promise<AdmittedRoleInvocation> {
1441
1457
  const record = packagedRoleMetadata(role);
@@ -1454,7 +1470,7 @@ export async function admitPublicRole(
1454
1470
  switch (record.admission) {
1455
1471
  case "instruction":
1456
1472
  return admitStandardMaterialInvocation(
1457
- role as "judge" | "inspector" | "gatekeeper" | "navigator" | "auditor" | "diarist" | "secretariat",
1473
+ role as RoleForAdmission<"instruction">,
1458
1474
  {
1459
1475
  ...shared,
1460
1476
  instruction,
@@ -1493,9 +1509,6 @@ export async function admitPublicRole(
1493
1509
  });
1494
1510
  }
1495
1511
  case "worker-packet": {
1496
- if (parsed.project !== undefined) {
1497
- requireOptionPath("--project", parsed.project);
1498
- }
1499
1512
  if (instruction.trim() === "") {
1500
1513
  throw new CliUsageError("fixer requires a nonblank repair instruction");
1501
1514
  }
@@ -1553,25 +1566,14 @@ export async function admitPublicRole(
1553
1566
  });
1554
1567
  }
1555
1568
  case "collect-target": {
1556
- if (parsed.project !== undefined) {
1557
- requireOptionPath("--project", parsed.project);
1558
- }
1559
- let explicitPrNumber: number | undefined;
1560
- if (parsed.prNumber !== undefined) {
1561
- try {
1562
- explicitPrNumber = parseCollectorPrNumber(parsed.prNumber);
1563
- } catch (error) {
1564
- const detail = error instanceof Error ? error.message : String(error);
1565
- throw new CliUsageError(detail, { cause: error });
1566
- }
1567
- }
1569
+ const explicitPrNumber = parsed.prNumber;
1568
1570
  const projectRoot = resolve(parsed.project ?? shared.cwd);
1569
1571
  let repository: CollectorRepository;
1570
1572
  if (parsed.repo !== undefined) {
1571
1573
  try {
1572
1574
  repository = parseCollectorRepository(parsed.repo);
1573
1575
  } catch (error) {
1574
- const detail = error instanceof Error ? error.message : String(error);
1576
+ const detail = errorText(error);
1575
1577
  throw new CliUsageError(detail, { cause: error });
1576
1578
  }
1577
1579
  } else {
@@ -1585,7 +1587,7 @@ export async function admitPublicRole(
1585
1587
  manifestCanonicalJson = manifest.canonicalJson;
1586
1588
  } catch (error) {
1587
1589
  throw new CliUsageError(
1588
- error instanceof Error ? error.message : String(error),
1590
+ errorText(error),
1589
1591
  { cause: error },
1590
1592
  );
1591
1593
  }
@@ -1615,19 +1617,12 @@ export async function admitPublicRole(
1615
1617
  return { ...admittedCollector, repository };
1616
1618
  }
1617
1619
  case "case-identity": {
1618
- if (parsed.project !== undefined) {
1619
- requireOptionPath("--project", parsed.project);
1620
- }
1621
- const issueNumber = parsed.issueNumber ?? Number.NaN;
1622
- if (
1623
- !Number.isInteger(issueNumber) ||
1624
- issueNumber < 1 ||
1625
- !DOCTOR_ISSUE_NUMBER_PATTERN.test(String(issueNumber))
1626
- ) {
1620
+ if (parsed.issueNumber === undefined) {
1627
1621
  throw new CliUsageError(
1628
- `doctor --issue must be a positive integer, got ${issueNumber}`,
1622
+ `doctor --issue must be a positive integer, got ${Number.NaN}`,
1629
1623
  );
1630
1624
  }
1625
+ const issueNumber = parsed.issueNumber;
1631
1626
  let frozenAttachments: readonly FrozenAttachment[] = [];
1632
1627
  const admittedDoctor = await admitStandardMaterialInvocation("doctor", {
1633
1628
  ...shared,
@@ -1647,7 +1642,7 @@ export async function admitPublicRole(
1647
1642
  });
1648
1643
  } catch (error) {
1649
1644
  if (error instanceof CliUsageError) throw error;
1650
- const detail = error instanceof Error ? error.message : String(error);
1645
+ const detail = errorText(error);
1651
1646
  throw new CliUsageError(detail, { cause: error });
1652
1647
  }
1653
1648
  if (parsed.runs === undefined) {
@@ -1665,7 +1660,7 @@ export async function admitPublicRole(
1665
1660
  caseRunsPath = await realpath(caseRunsPath);
1666
1661
  } catch (error) {
1667
1662
  if (error instanceof CliUsageError) throw error;
1668
- const detail = error instanceof Error ? error.message : String(error);
1663
+ const detail = errorText(error);
1669
1664
  throw new CliUsageError(
1670
1665
  `doctor case could not be constructed from retained evidence: ${detail}`,
1671
1666
  { cause: error },
@@ -1683,22 +1678,21 @@ export async function admitPublicRole(
1683
1678
  return { ...admittedDoctor, attachments: frozenAttachments };
1684
1679
  }
1685
1680
  case "source-locator": {
1686
- if (parsed.project !== undefined) {
1687
- requireOptionPath("--project", parsed.project);
1688
- }
1689
1681
  const projectRoot = resolve(parsed.project ?? shared.cwd);
1690
- let sourceRun: NotarySourceRunLocator;
1691
- try {
1692
- sourceRun = await resolveNotarySourceRunLocator({
1693
- projectRoot,
1694
- sourceRun: parsed.sourceRun ?? "",
1695
- home: shared.home,
1696
- });
1697
- } catch (error) {
1698
- if (error instanceof NotarySourceRunError) {
1699
- throw new CliUsageError(error.message, { cause: error });
1682
+ let sourceRun = override?.resolvedSourceRun;
1683
+ if (sourceRun === undefined) {
1684
+ try {
1685
+ sourceRun = await resolveNotarySourceRunLocator({
1686
+ projectRoot,
1687
+ sourceRun: parsed.sourceRun ?? "",
1688
+ home: shared.home,
1689
+ });
1690
+ } catch (error) {
1691
+ if (error instanceof NotarySourceRunError) {
1692
+ throw new CliUsageError(error.message, { cause: error });
1693
+ }
1694
+ throw error;
1700
1695
  }
1701
- throw error;
1702
1696
  }
1703
1697
  // Board ticket on the source run wins; otherwise the summons ticket. Code does not infer one.
1704
1698
  const inheritedTicketNumber = await readBoardTicketNumber(sourceRun.runDirectory);
@@ -1719,9 +1713,6 @@ export async function admitPublicRole(
1719
1713
  });
1720
1714
  }
1721
1715
  case "gleaner": {
1722
- if (parsed.project !== undefined) {
1723
- requireOptionPath("--project", parsed.project);
1724
- }
1725
1716
  const baseRevision = parsed.baseRevision ?? "";
1726
1717
  if (baseRevision.trim() === "") {
1727
1718
  throw new CliUsageError("--base requires a nonempty revision");
@@ -1736,16 +1727,19 @@ export async function admitPublicRole(
1736
1727
  });
1737
1728
  }
1738
1729
  case "review-basis": {
1739
- const lens = requireReviewerLens(parsed.lens);
1740
- if (parsed.project !== undefined) {
1741
- requireOptionPath("--project", parsed.project);
1730
+ if (parsed.lens === undefined) {
1731
+ throw new CliUsageError("--lens requires completeness or correctness");
1732
+ }
1733
+ if (parsed.baseRevision === undefined) {
1734
+ throw new CliUsageError("--base requires a nonempty revision");
1742
1735
  }
1743
- const baseRevision = requireReviewerBaseRevision(parsed.baseRevision);
1744
1736
  const rawRefs = parsed.authorityRefs ?? [];
1745
1737
  if (rawRefs.length === 0) {
1746
1738
  throw new CliUsageError("reviewer requires --authority-ref <ref>");
1747
1739
  }
1748
- const authorityRefs = Object.freeze(rawRefs.map((ref) => requireAuthorityRef(ref)));
1740
+ const lens = parsed.lens;
1741
+ const baseRevision = parsed.baseRevision;
1742
+ const authorityRefs = Object.freeze([...rawRefs]);
1749
1743
  const admittedReviewer = await admitStandardMaterialInvocation("reviewer", {
1750
1744
  ...shared,
1751
1745
  instruction,
@@ -1760,9 +1754,6 @@ export async function admitPublicRole(
1760
1754
  return { ...admittedReviewer, authorityRefs };
1761
1755
  }
1762
1756
  case "merge-envelope": {
1763
- if (parsed.project !== undefined) {
1764
- requireOptionPath("--project", parsed.project);
1765
- }
1766
1757
  if (instruction.trim() === "") {
1767
1758
  throw new CliUsageError("merger requires a nonblank task instruction");
1768
1759
  }
@@ -1885,9 +1876,6 @@ async function placeRoleAdmission(options: {
1885
1876
  /** Countersign reserves coordinates, then materializes after identity lookup. */
1886
1877
  readonly materialize?: boolean;
1887
1878
  }): Promise<PlacedRoleAdmission> {
1888
- if (options.project !== undefined) {
1889
- requireOptionPath("--project", options.project);
1890
- }
1891
1879
  const projectRoot = resolve(options.project ?? options.cwd);
1892
1880
  const runId = options.runId ?? (options.createRunId ?? uuidv7)();
1893
1881
  const ticketFields = ticketAdmissionFields(options.assertedTicketNumber);
@@ -1988,7 +1976,7 @@ async function persistPlacedAdmission(
1988
1976
  * doctor resolves the case, then freezes attachments; merger writes merger-input.json).
1989
1977
  */
1990
1978
  async function admitStandardMaterialInvocation<
1991
- R extends "judge" | "inspector" | "gatekeeper" | "navigator" | "auditor" | "diarist" | "secretariat" | "countersign" | "gleaner-left" | "reviewer" | "notary" | "coder" | "fixer" | "collector" | "doctor" | "merger",
1979
+ R extends PackagedRole,
1992
1980
  Extra extends object = {},
1993
1981
  >(
1994
1982
  role: R,
@@ -2217,8 +2205,8 @@ export async function loadAdmittedJudgeRequest(
2217
2205
  const raw = JSON.parse(
2218
2206
  await readFile(join(runDirectory, "admitted-request.json"), "utf8"),
2219
2207
  ) as unknown;
2220
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return undefined;
2221
- const record = raw as Record<string, unknown>;
2208
+ if (!isRecord(raw)) return undefined;
2209
+ const record = raw;
2222
2210
  if (!packagedPublicInstructionSubject(record.role)) return undefined;
2223
2211
  if (typeof record.instruction !== "string") return undefined;
2224
2212
  if (typeof record.instructionEmpty !== "boolean") return undefined;
@@ -2260,7 +2248,7 @@ export type AdmitFixerInvocationOptions = {
2260
2248
 
2261
2249
  function parsePositivePrOption(raw: string | undefined): number {
2262
2250
  if (raw === undefined || raw.trim() === "") throw new CliUsageError("--pr requires a positive pull request number");
2263
- try { return parseCollectorPrNumber(raw); } catch (error) { throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error }); }
2251
+ try { return parseCollectorPrNumber(raw); } catch (error) { throw new CliUsageError(errorText(error), { cause: error }); }
2264
2252
  }
2265
2253
  function parseRepoOption(raw: string | undefined): string {
2266
2254
  if (raw === undefined || raw.trim() === "") throw new CliUsageError("--repo requires owner/repo");
@@ -2308,7 +2296,7 @@ export function resolveGitHubRemoteRepository(
2308
2296
  try {
2309
2297
  return parseCollectorRepository(ownerRepo);
2310
2298
  } catch (error) {
2311
- const detail = error instanceof Error ? error.message : String(error);
2299
+ const detail = errorText(error);
2312
2300
  throw new CliUsageError(detail, { cause: error });
2313
2301
  }
2314
2302
  }
@@ -2393,7 +2381,7 @@ export async function resolveDoctorCaseRunsPath(options: {
2393
2381
  try {
2394
2382
  real = await realpath(resolved);
2395
2383
  } catch (error) {
2396
- const detail = error instanceof Error ? error.message : String(error);
2384
+ const detail = errorText(error);
2397
2385
  throw new CliUsageError(
2398
2386
  `doctor --runs is not a readable retained runs root: ${detail}`,
2399
2387
  { cause: error },
@@ -2415,7 +2403,6 @@ export async function resolveDoctorCaseRunsPath(options: {
2415
2403
  return real;
2416
2404
  }
2417
2405
 
2418
-
2419
2406
  export type AdmitReviewerInvocationOptions = {
2420
2407
  home: string;
2421
2408
  principalAuthority: DurablePrincipalAuthority;