@akagilnc/pi-workflow-roles 0.1.5357 → 0.1.5368

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 (43) hide show
  1. package/dist/acp-host/production-host.js +5353 -5480
  2. package/dist/doctor-contracts.js +22 -34
  3. package/dist/headless-host/production-host.js +1215 -1342
  4. package/dist/merger-contracts.js +11 -6
  5. package/dist/migrate-book-topology.js +57 -101
  6. package/dist/open-tool-schema.js +1 -25
  7. package/dist/package-contracts/fixer-output.js +21 -35
  8. package/dist/public-cli/auto-resume.js +1 -1
  9. package/dist/public-cli/cli.js +57 -56
  10. package/dist/public-cli/config.js +4 -0
  11. package/dist/public-cli/instruction-seat-run.js +65 -40
  12. package/dist/public-cli/invocation.js +132 -128
  13. package/dist/public-cli/main.js +449 -506
  14. package/dist/public-cli/post-admission.js +85 -86
  15. package/dist/public-cli/role-seat-runtime.js +29 -0
  16. package/dist/public-cli/run-lifecycle.js +6 -6
  17. package/dist/public-cli/turn-request.js +14 -0
  18. package/dist/public-role-summons.js +20 -33
  19. package/dist/role-run-placement.js +4 -0
  20. package/package.json +1 -1
  21. package/src/book-topology-migration-placement.ts +3 -5
  22. package/src/diarist-role.ts +12 -49
  23. package/src/doctor-contracts.ts +38 -49
  24. package/src/gleaner-left-role.ts +10 -11
  25. package/src/merger-contracts.ts +19 -12
  26. package/src/open-tool-schema.ts +8 -32
  27. package/src/package-contracts/fixer-output.ts +49 -52
  28. package/src/public-cli/auto-resume.ts +1 -1
  29. package/src/public-cli/cli.ts +67 -73
  30. package/src/public-cli/config.ts +8 -0
  31. package/src/public-cli/instruction-seat-run.ts +82 -55
  32. package/src/public-cli/invocation.ts +148 -162
  33. package/src/public-cli/post-admission.ts +110 -100
  34. package/src/public-cli/role-seat-runtime.ts +72 -0
  35. package/src/public-cli/run-lifecycle.ts +7 -26
  36. package/src/public-cli/terminal.ts +2 -17
  37. package/src/public-cli/turn-request.ts +26 -0
  38. package/src/public-role-summons.ts +20 -45
  39. package/src/reviewer-role.ts +16 -35
  40. package/src/role-run-placement.ts +5 -0
  41. package/src/secretariat-role.ts +16 -12
  42. package/src/worker-role.ts +14 -29
  43. package/src/worker-submission-gates.ts +14 -3
@@ -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,
@@ -445,39 +446,46 @@ export async function recordEffectiveInvocationModel(
445
446
  host?: string,
446
447
  engineModel?: string | null,
447
448
  ): 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;
449
+ await updateDurableJsonPage(join(runDirectory, "invocation.json"), (current) => {
450
+ const next: Record<string, unknown> = { ...current };
451
+ if (model !== undefined) {
452
+ next.provider = model.provider;
453
+ next.model = model.model;
454
+ if (model.thinking === undefined) {
455
+ delete next.thinking;
456
+ } else {
457
+ next.thinking = model.thinking;
458
+ }
461
459
  }
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
- );
460
+ if (engine === null) {
461
+ delete next.engine;
462
+ } else if (engine !== undefined) {
463
+ next.engine = engine;
464
+ }
465
+ if (engineModel === null) {
466
+ delete next.engineModel;
467
+ } else if (engineModel !== undefined) {
468
+ next.engineModel = engineModel;
469
+ }
470
+ if (host !== undefined) {
471
+ next.host = host;
472
+ }
473
+ return next;
474
+ });
475
+ }
476
+
477
+ /**
478
+ * Read one durable JSON object, let the caller keep its field rules, and write
479
+ * the page back. `undefined` means the caller decided not to change the page.
480
+ */
481
+ async function updateDurableJsonPage(
482
+ path: string,
483
+ update: (current: Record<string, unknown>) => Record<string, unknown> | undefined,
484
+ ): Promise<void> {
485
+ const current = JSON.parse(await readFile(path, "utf8")) as Record<string, unknown>;
486
+ const next = update(current);
487
+ if (next === undefined) return;
488
+ await writeFile(path, `${JSON.stringify(next, null, 2)}\n`, "utf8");
481
489
  }
482
490
 
483
491
  /** Merge observed launch-time fields into the single existing invocation.json identity page. */
@@ -485,16 +493,10 @@ async function mergeInvocationIdentityPage(
485
493
  runDirectory: string,
486
494
  fields: Record<string, unknown>,
487
495
  ): 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
- );
496
+ await updateDurableJsonPage(join(runDirectory, "invocation.json"), (current) => ({
497
+ ...current,
498
+ ...fields,
499
+ }));
498
500
  }
499
501
 
500
502
  /**
@@ -509,27 +511,20 @@ export async function persistAdmittedSourceRunPath(
509
511
  if (sourceRunPath.trim() === "") {
510
512
  throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
511
513
  }
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({
514
+ await updateDurableJsonPage(admitted.admittedRequestPath, (current) => {
515
+ if (typeof current.sourceRunPath === "string" && current.sourceRunPath !== sourceRunPath) {
516
+ throw new Error(
517
+ `persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`,
518
+ );
519
+ }
520
+ const sameSubject = auditorSubject === undefined || current.auditorSubject === auditorSubject;
521
+ if (current.sourceRunPath === sourceRunPath && sameSubject) return undefined;
522
+ return {
527
523
  ...current,
528
524
  sourceRunPath,
529
525
  ...(auditorSubject === undefined ? {} : { auditorSubject }),
530
- }, null, 2)}\n`,
531
- "utf8",
532
- );
526
+ };
527
+ });
533
528
  }
534
529
 
535
530
  /**
@@ -551,25 +546,21 @@ export async function recordAdmittedCorrelation(
551
546
  admitted: AdmittedRoleInvocation,
552
547
  correlationId: string,
553
548
  ): 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
- );
549
+ let correlationIds: string[] = [];
550
+ await updateDurableJsonPage(admitted.admittedRequestPath, (current) => {
551
+ const prior = [
552
+ ...(Array.isArray(current.correlationIds)
553
+ ? current.correlationIds.filter((value): value is string =>
554
+ typeof value === "string" && value.trim() !== ""
555
+ )
556
+ : []),
557
+ ...(typeof current.correlationId === "string" && current.correlationId.trim() !== ""
558
+ ? [current.correlationId]
559
+ : []),
560
+ ];
561
+ correlationIds = [...new Set([...prior, correlationId])];
562
+ return { ...current, correlationId, correlationIds };
563
+ });
573
564
  await mergeInvocationIdentityPage(admitted.runDirectory, {
574
565
  correlationId,
575
566
  correlationIds,
@@ -588,7 +579,7 @@ export async function relocateAdmittedRunToTicket(
588
579
  authority: DurablePrincipalAuthority,
589
580
  heldLease?: { relocate(runDirectory: string): void },
590
581
  ): Promise<RunDirectoryRelocation | undefined> {
591
- if (admitted.ticketNumber === undefined || !admitted.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) return undefined;
582
+ if (admitted.ticketNumber === undefined || !isUnboundRunDirectory(admitted.runDirectory)) return undefined;
592
583
  const oldRunDirectory = admitted.runDirectory;
593
584
  const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(oldRunDirectory));
594
585
  const target = roleRunPlacement(ledgerHome, {
@@ -670,12 +661,13 @@ export async function recordChildDiaristRun(
670
661
  parent: AdmittedRoleInvocation,
671
662
  childRunId: string,
672
663
  ): 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");
664
+ await updateDurableJsonPage(parent.admittedRequestPath, (page) => {
665
+ const existing = Array.isArray(page.childDiaristRunIds)
666
+ ? page.childDiaristRunIds.filter((runId): runId is string => typeof runId === "string")
667
+ : [];
668
+ const childDiaristRunIds = existing.includes(childRunId) ? existing : [...existing, childRunId];
669
+ return { ...page, childDiaristRunIds };
670
+ });
679
671
  }
680
672
 
681
673
  /**
@@ -692,16 +684,15 @@ export async function bindTicketNumberOnRunDirectory(
692
684
  "bindTicketNumberOnRunDirectory",
693
685
  );
694
686
  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
- );
687
+ let unchanged = false;
688
+ await updateDurableJsonPage(admittedPath, (admitted) => {
689
+ if (admitted.ticketNumber === ticketNumber) {
690
+ unchanged = true;
691
+ return undefined;
692
+ }
693
+ return { ...admitted, ticketNumber };
694
+ });
695
+ if (unchanged) return;
705
696
  await mergeInvocationIdentityPage(runDirectory, { ticketNumber });
706
697
  }
707
698
 
@@ -1097,20 +1088,34 @@ function requireOptionPath(
1097
1088
  * next option), and a leading `-` (read as the next Skill option). Not a
1098
1089
  * general free-text gate — only the projection admission seam.
1099
1090
  */
1091
+ function skillArgTokenFault(
1092
+ value: string | undefined,
1093
+ ): "empty" | "whitespace" | "optionLike" | undefined {
1094
+ if (value === undefined || value.trim() === "") return "empty";
1095
+ if (/\s/.test(value)) return "whitespace";
1096
+ if (value.startsWith("-")) return "optionLike";
1097
+ return undefined;
1098
+ }
1099
+
1100
1100
  function requireSkillArgToken(
1101
1101
  value: string | undefined,
1102
1102
  messages: { empty: string; whitespace: string; optionLike: string },
1103
1103
  ): 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;
1104
+ const fault = skillArgTokenFault(value);
1105
+ if (fault !== undefined) throw new CliUsageError(messages[fault]);
1106
+ return value as string;
1107
+ }
1108
+
1109
+ /**
1110
+ * Durable reviewer base on resume. Same token rule as fresh `--base`,
1111
+ * with the resume-owned missing/damaged split.
1112
+ */
1113
+ export function admittedReviewerBaseFault(
1114
+ value: string | undefined,
1115
+ ): "missing" | "damaged" | undefined {
1116
+ const fault = skillArgTokenFault(value);
1117
+ if (fault === undefined) return undefined;
1118
+ return fault === "empty" ? "missing" : "damaged";
1114
1119
  }
1115
1120
 
1116
1121
  /**
@@ -1420,6 +1425,11 @@ export type PublicSeatParse = {
1420
1425
  * Sole admit call. Kind comes from the composition-root record.
1421
1426
  * Placement stays ticketAdmissionFields. Seat field checks live in this function only.
1422
1427
  */
1428
+ type RoleForAdmission<K extends PublicRoleRecord["admission"]> = Extract<
1429
+ PublicRoleRecord,
1430
+ { readonly admission: K }
1431
+ >["role"];
1432
+
1423
1433
  export function admitPublicRole<R extends PackagedRole>(
1424
1434
  role: R,
1425
1435
  parsed: PublicSeatParse,
@@ -1427,6 +1437,8 @@ export function admitPublicRole<R extends PackagedRole>(
1427
1437
  override?: {
1428
1438
  readonly assertedTicketNumber?: number;
1429
1439
  readonly deferPersistence?: boolean;
1440
+ /** Locator already resolved on the new-summon path. Absent callers still resolve. */
1441
+ readonly resolvedSourceRun?: NotarySourceRunLocator;
1430
1442
  },
1431
1443
  ): Promise<Extract<AdmittedRoleInvocation, { readonly role: R }>>;
1432
1444
  export async function admitPublicRole(
@@ -1436,6 +1448,8 @@ export async function admitPublicRole(
1436
1448
  override?: {
1437
1449
  readonly assertedTicketNumber?: number;
1438
1450
  readonly deferPersistence?: boolean;
1451
+ /** Locator already resolved on the new-summon path. Absent callers still resolve. */
1452
+ readonly resolvedSourceRun?: NotarySourceRunLocator;
1439
1453
  },
1440
1454
  ): Promise<AdmittedRoleInvocation> {
1441
1455
  const record = packagedRoleMetadata(role);
@@ -1454,7 +1468,7 @@ export async function admitPublicRole(
1454
1468
  switch (record.admission) {
1455
1469
  case "instruction":
1456
1470
  return admitStandardMaterialInvocation(
1457
- role as "judge" | "inspector" | "gatekeeper" | "navigator" | "auditor" | "diarist" | "secretariat",
1471
+ role as RoleForAdmission<"instruction">,
1458
1472
  {
1459
1473
  ...shared,
1460
1474
  instruction,
@@ -1493,9 +1507,6 @@ export async function admitPublicRole(
1493
1507
  });
1494
1508
  }
1495
1509
  case "worker-packet": {
1496
- if (parsed.project !== undefined) {
1497
- requireOptionPath("--project", parsed.project);
1498
- }
1499
1510
  if (instruction.trim() === "") {
1500
1511
  throw new CliUsageError("fixer requires a nonblank repair instruction");
1501
1512
  }
@@ -1553,18 +1564,7 @@ export async function admitPublicRole(
1553
1564
  });
1554
1565
  }
1555
1566
  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
- }
1567
+ const explicitPrNumber = parsed.prNumber;
1568
1568
  const projectRoot = resolve(parsed.project ?? shared.cwd);
1569
1569
  let repository: CollectorRepository;
1570
1570
  if (parsed.repo !== undefined) {
@@ -1615,19 +1615,12 @@ export async function admitPublicRole(
1615
1615
  return { ...admittedCollector, repository };
1616
1616
  }
1617
1617
  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
- ) {
1618
+ if (parsed.issueNumber === undefined) {
1627
1619
  throw new CliUsageError(
1628
- `doctor --issue must be a positive integer, got ${issueNumber}`,
1620
+ `doctor --issue must be a positive integer, got ${Number.NaN}`,
1629
1621
  );
1630
1622
  }
1623
+ const issueNumber = parsed.issueNumber;
1631
1624
  let frozenAttachments: readonly FrozenAttachment[] = [];
1632
1625
  const admittedDoctor = await admitStandardMaterialInvocation("doctor", {
1633
1626
  ...shared,
@@ -1683,22 +1676,21 @@ export async function admitPublicRole(
1683
1676
  return { ...admittedDoctor, attachments: frozenAttachments };
1684
1677
  }
1685
1678
  case "source-locator": {
1686
- if (parsed.project !== undefined) {
1687
- requireOptionPath("--project", parsed.project);
1688
- }
1689
1679
  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 });
1680
+ let sourceRun = override?.resolvedSourceRun;
1681
+ if (sourceRun === undefined) {
1682
+ try {
1683
+ sourceRun = await resolveNotarySourceRunLocator({
1684
+ projectRoot,
1685
+ sourceRun: parsed.sourceRun ?? "",
1686
+ home: shared.home,
1687
+ });
1688
+ } catch (error) {
1689
+ if (error instanceof NotarySourceRunError) {
1690
+ throw new CliUsageError(error.message, { cause: error });
1691
+ }
1692
+ throw error;
1700
1693
  }
1701
- throw error;
1702
1694
  }
1703
1695
  // Board ticket on the source run wins; otherwise the summons ticket. Code does not infer one.
1704
1696
  const inheritedTicketNumber = await readBoardTicketNumber(sourceRun.runDirectory);
@@ -1719,9 +1711,6 @@ export async function admitPublicRole(
1719
1711
  });
1720
1712
  }
1721
1713
  case "gleaner": {
1722
- if (parsed.project !== undefined) {
1723
- requireOptionPath("--project", parsed.project);
1724
- }
1725
1714
  const baseRevision = parsed.baseRevision ?? "";
1726
1715
  if (baseRevision.trim() === "") {
1727
1716
  throw new CliUsageError("--base requires a nonempty revision");
@@ -1736,16 +1725,19 @@ export async function admitPublicRole(
1736
1725
  });
1737
1726
  }
1738
1727
  case "review-basis": {
1739
- const lens = requireReviewerLens(parsed.lens);
1740
- if (parsed.project !== undefined) {
1741
- requireOptionPath("--project", parsed.project);
1728
+ if (parsed.lens === undefined) {
1729
+ throw new CliUsageError("--lens requires completeness or correctness");
1730
+ }
1731
+ if (parsed.baseRevision === undefined) {
1732
+ throw new CliUsageError("--base requires a nonempty revision");
1742
1733
  }
1743
- const baseRevision = requireReviewerBaseRevision(parsed.baseRevision);
1744
1734
  const rawRefs = parsed.authorityRefs ?? [];
1745
1735
  if (rawRefs.length === 0) {
1746
1736
  throw new CliUsageError("reviewer requires --authority-ref <ref>");
1747
1737
  }
1748
- const authorityRefs = Object.freeze(rawRefs.map((ref) => requireAuthorityRef(ref)));
1738
+ const lens = parsed.lens;
1739
+ const baseRevision = parsed.baseRevision;
1740
+ const authorityRefs = Object.freeze([...rawRefs]);
1749
1741
  const admittedReviewer = await admitStandardMaterialInvocation("reviewer", {
1750
1742
  ...shared,
1751
1743
  instruction,
@@ -1760,9 +1752,6 @@ export async function admitPublicRole(
1760
1752
  return { ...admittedReviewer, authorityRefs };
1761
1753
  }
1762
1754
  case "merge-envelope": {
1763
- if (parsed.project !== undefined) {
1764
- requireOptionPath("--project", parsed.project);
1765
- }
1766
1755
  if (instruction.trim() === "") {
1767
1756
  throw new CliUsageError("merger requires a nonblank task instruction");
1768
1757
  }
@@ -1885,9 +1874,6 @@ async function placeRoleAdmission(options: {
1885
1874
  /** Countersign reserves coordinates, then materializes after identity lookup. */
1886
1875
  readonly materialize?: boolean;
1887
1876
  }): Promise<PlacedRoleAdmission> {
1888
- if (options.project !== undefined) {
1889
- requireOptionPath("--project", options.project);
1890
- }
1891
1877
  const projectRoot = resolve(options.project ?? options.cwd);
1892
1878
  const runId = options.runId ?? (options.createRunId ?? uuidv7)();
1893
1879
  const ticketFields = ticketAdmissionFields(options.assertedTicketNumber);
@@ -1988,7 +1974,7 @@ async function persistPlacedAdmission(
1988
1974
  * doctor resolves the case, then freezes attachments; merger writes merger-input.json).
1989
1975
  */
1990
1976
  async function admitStandardMaterialInvocation<
1991
- R extends "judge" | "inspector" | "gatekeeper" | "navigator" | "auditor" | "diarist" | "secretariat" | "countersign" | "gleaner-left" | "reviewer" | "notary" | "coder" | "fixer" | "collector" | "doctor" | "merger",
1977
+ R extends PackagedRole,
1992
1978
  Extra extends object = {},
1993
1979
  >(
1994
1980
  role: R,