@akagilnc/pi-workflow-roles 0.1.4802 → 0.1.4870

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 (142) hide show
  1. package/dist/acp-host/production-host.js +19434 -18218
  2. package/dist/analyst-book-key.js +63 -0
  3. package/dist/analyst-cohort.js +167 -0
  4. package/dist/analyst-entry.js +320 -0
  5. package/dist/analyst-index.js +247 -0
  6. package/dist/analyst-ledger.js +561 -0
  7. package/dist/analyst-median.js +16 -0
  8. package/dist/analyst-metric-families/acceptance-success-rework.js +249 -0
  9. package/dist/analyst-metric-families/b2-frame-buckets-actions.js +189 -0
  10. package/dist/analyst-metric-families/gate-cycles.js +77 -0
  11. package/dist/analyst-metric-families/leg-wall-clock.js +56 -0
  12. package/dist/analyst-metric-families/round-timeline.js +126 -0
  13. package/dist/analyst-metric-families.js +23 -0
  14. package/dist/analyst-metric-family.js +11 -0
  15. package/dist/analyst-model-groups.js +135 -0
  16. package/dist/analyst-page.js +182 -0
  17. package/dist/atomic-write.js +23 -0
  18. package/dist/auditor-soul.js +1 -1
  19. package/dist/countersign-contracts.js +0 -2
  20. package/dist/diarist-contracts.js +0 -1
  21. package/dist/doctor-contracts.js +2 -2
  22. package/dist/gatekeeper-role.js +15 -16
  23. package/dist/gleaner-left-contracts.js +0 -2
  24. package/dist/headless-host/production-host.js +16967 -15749
  25. package/dist/host-contracts.js +0 -4
  26. package/dist/inspector-contracts.js +0 -2
  27. package/dist/institutional-resolution.js +4 -2
  28. package/dist/merger-contracts.js +0 -2
  29. package/dist/migrate-book-topology.js +3908 -3089
  30. package/dist/navigator-public-session.js +4 -3
  31. package/dist/notary-contracts.js +0 -2
  32. package/dist/package-contracts/auditor-output.js +0 -1
  33. package/dist/package-contracts/collector-output.js +0 -2
  34. package/dist/package-contracts/fixer-output.js +0 -2
  35. package/dist/package-contracts/gatekeeper-output.js +0 -2
  36. package/dist/package-contracts/judge-output.js +0 -2
  37. package/dist/package-contracts/navigator-output.js +0 -2
  38. package/dist/package-contracts/reviewer-output.js +0 -2
  39. package/dist/package-contracts/terminating-tools.js +32 -92
  40. package/dist/package-contracts/worker-output.js +0 -4
  41. package/dist/packaged-role-registry.js +518 -36
  42. package/dist/pi/role-turn-host.js +2 -1
  43. package/dist/public-cli/analyst-run.js +185 -0
  44. package/dist/public-cli/cli.js +885 -0
  45. package/dist/public-cli/config.js +12 -9
  46. package/dist/public-cli/countersign-run.js +4 -311
  47. package/dist/public-cli/instruction-seat-run.js +628 -158
  48. package/dist/public-cli/invocation.js +806 -1550
  49. package/dist/public-cli/main.js +6962 -9882
  50. package/dist/public-cli/option-definitions.js +51 -99
  51. package/dist/public-cli/post-admission.js +30 -2
  52. package/dist/public-cli/run-lifecycle.js +234 -353
  53. package/dist/public-cli/seat-ticket-binding.js +6 -0
  54. package/dist/public-cli/settlement.js +261 -675
  55. package/dist/public-cli/turn-request.js +76 -0
  56. package/dist/public-role-summons.js +38 -112
  57. package/dist/role-activation-flags.js +23 -45
  58. package/dist/secretariat-contracts.js +0 -2
  59. package/dist/session-identity.js +17 -8
  60. package/dist/session-opening-materials.js +3 -2
  61. package/extensions/role-runtime.ts +6 -80
  62. package/package.json +1 -1
  63. package/src/acp-host/role-turn-host.ts +4 -1
  64. package/src/analyst-metric-families/acceptance-success-rework.ts +3 -2
  65. package/src/auditor-role.ts +1 -2
  66. package/src/auditor-soul.ts +1 -1
  67. package/src/collector-role.ts +2 -2
  68. package/src/countersign-contracts.ts +0 -1
  69. package/src/countersign-role.ts +1 -1
  70. package/src/diarist-contracts.ts +0 -1
  71. package/src/diarist-role.ts +0 -1
  72. package/src/doctor-auditor.ts +1 -2
  73. package/src/doctor-contracts.ts +1 -1
  74. package/src/doctor-role.ts +3 -2
  75. package/src/external-host-turn-loop.ts +1 -1
  76. package/src/factory-board.ts +10 -7
  77. package/src/gatekeeper-role.ts +16 -10
  78. package/src/gleaner-left-contracts.ts +0 -1
  79. package/src/gleaner-left-role.ts +0 -1
  80. package/src/headless-host/role-turn-host.ts +7 -1
  81. package/src/host-contracts.ts +10 -6
  82. package/src/inspector-contracts.ts +0 -1
  83. package/src/inspector-role.ts +0 -2
  84. package/src/institutional-resolution.ts +6 -4
  85. package/src/judge-role.ts +3 -3
  86. package/src/merger-contracts.ts +0 -1
  87. package/src/merger-role.ts +2 -2
  88. package/src/navigator-public-session.ts +7 -6
  89. package/src/navigator-work-context.ts +6 -8
  90. package/src/notary-contracts.ts +0 -1
  91. package/src/notary-role.ts +2 -3
  92. package/src/package-contracts/auditor-output.ts +0 -1
  93. package/src/package-contracts/collector-output.ts +0 -1
  94. package/src/package-contracts/fixer-output.ts +0 -1
  95. package/src/package-contracts/gatekeeper-output.ts +0 -1
  96. package/src/package-contracts/judge-output.ts +0 -1
  97. package/src/package-contracts/navigator-output.ts +0 -1
  98. package/src/package-contracts/reviewer-output.ts +0 -1
  99. package/src/package-contracts/terminating-tools.ts +27 -99
  100. package/src/package-contracts/worker-output.ts +0 -2
  101. package/src/packaged-role-registry.ts +604 -35
  102. package/src/pi/role-turn-host.ts +2 -1
  103. package/src/public-cli/cli.ts +64 -259
  104. package/src/public-cli/config.ts +18 -10
  105. package/src/public-cli/countersign-run.ts +14 -449
  106. package/src/public-cli/instruction-seat-run.ts +758 -277
  107. package/src/public-cli/invocation.ts +1097 -2051
  108. package/src/public-cli/option-definitions.ts +62 -127
  109. package/src/public-cli/post-admission.ts +47 -2
  110. package/src/public-cli/run-lifecycle.ts +303 -613
  111. package/src/public-cli/seat-ticket-binding.ts +6 -0
  112. package/src/public-cli/settlement.ts +399 -1185
  113. package/src/public-cli/turn-request.ts +92 -1
  114. package/src/public-role-summons.ts +44 -128
  115. package/src/reviewer-role.ts +3 -2
  116. package/src/role-activation-flags.ts +20 -41
  117. package/src/role-runtime-dependencies.ts +25 -26
  118. package/src/role-runtime.ts +121 -232
  119. package/src/secretariat-contracts.ts +0 -1
  120. package/src/secretariat-role.ts +0 -2
  121. package/src/session-identity.ts +23 -8
  122. package/src/session-opening-materials.ts +2 -1
  123. package/src/worker-role.ts +3 -4
  124. package/dist/doctor-auditor.js +0 -25
  125. package/dist/public-cli/diarist-run.js +0 -130
  126. package/dist/public-cli/doctor-run.js +0 -87
  127. package/dist/public-cli/inspector-run.js +0 -151
  128. package/dist/public-cli/judge-run.js +0 -114
  129. package/dist/public-cli/notary-run.js +0 -174
  130. package/dist/public-cli/reviewer-run.js +0 -339
  131. package/src/public-cli/coder-run.ts +0 -255
  132. package/src/public-cli/collector-run.ts +0 -189
  133. package/src/public-cli/diarist-run.ts +0 -233
  134. package/src/public-cli/doctor-run.ts +0 -167
  135. package/src/public-cli/fixer-run.ts +0 -265
  136. package/src/public-cli/gleaner-left-run.ts +0 -167
  137. package/src/public-cli/inspector-run.ts +0 -261
  138. package/src/public-cli/judge-run.ts +0 -196
  139. package/src/public-cli/merger-run.ts +0 -294
  140. package/src/public-cli/notary-run.ts +0 -279
  141. package/src/public-cli/reviewer-run.ts +0 -480
  142. package/src/public-cli/secretariat-run.ts +0 -265
@@ -47,6 +47,11 @@ import {
47
47
  pickEngineAxis,
48
48
  } from "../package-resources/engine-material.ts";
49
49
  import type { PublicThinkingLevel } from "./registry.ts";
50
+ import {
51
+ packagedResumeSourcePath,
52
+ packagedRoleMetadata,
53
+ type PackagedRole,
54
+ } from "../packaged-role-registry.ts";
50
55
  import {
51
56
  recordEffectiveInvocationModel,
52
57
  requireAuthorityRef,
@@ -54,15 +59,11 @@ import {
54
59
  type AdmittedCoderInvocation,
55
60
  type AdmittedCountersignInvocation,
56
61
  type AdmittedCollectorInvocation,
57
- type AdmittedDiaristInvocation,
58
62
  type AdmittedDoctorInvocation,
59
63
  type AdmittedInspectorInvocation,
60
64
  type AdmittedNotaryInvocation,
61
65
  type AdmittedFixerInvocation,
62
66
  type AdmittedGleanerLeftInvocation,
63
- type AdmittedGatekeeperInvocation,
64
- type AdmittedJudgeInvocation,
65
- type AdmittedNavigatorInvocation,
66
67
  type AdmittedMergerInvocation,
67
68
  type AdmittedReviewerInvocation,
68
69
  type AdmittedRoleInvocation,
@@ -94,23 +95,7 @@ export const AUTO_RESUME_LIMIT = 2 as const;
94
95
 
95
96
  export type RoleRunRecord = {
96
97
  readonly runId: string;
97
- readonly role:
98
- | "judge"
99
- | "coder"
100
- | "fixer"
101
- | "collector"
102
- | "doctor"
103
- | "reviewer"
104
- | "merger"
105
- | "notary"
106
- | "countersign"
107
- | "gleaner-left"
108
- | "inspector"
109
- | "gatekeeper"
110
- | "navigator"
111
- | "auditor"
112
- | "diarist"
113
- | "secretariat";
98
+ readonly role: PackagedRole;
114
99
  readonly state: RoleRunState;
115
100
  readonly bookKey: string;
116
101
  readonly projectRoot: string;
@@ -350,24 +335,8 @@ async function readRoleRunStateDisk(
350
335
  if (typeof record.runId !== "string" || record.runId.trim() === "") {
351
336
  return undefined;
352
337
  }
353
- if (
354
- record.role !== "judge" &&
355
- record.role !== "coder" &&
356
- record.role !== "fixer" &&
357
- record.role !== "collector" &&
358
- record.role !== "doctor" &&
359
- record.role !== "reviewer" &&
360
- record.role !== "merger" &&
361
- record.role !== "notary" &&
362
- record.role !== "countersign" &&
363
- record.role !== "gleaner-left" &&
364
- record.role !== "inspector" &&
365
- record.role !== "gatekeeper" &&
366
- record.role !== "navigator" &&
367
- record.role !== "auditor" &&
368
- record.role !== "diarist" &&
369
- record.role !== "secretariat"
370
- ) {
338
+ const role = typeof record.role === "string" ? packagedRoleMetadata(record.role)?.role : undefined;
339
+ if (role === undefined) {
371
340
  return undefined;
372
341
  }
373
342
  if (
@@ -421,7 +390,7 @@ async function readRoleRunStateDisk(
421
390
  const currentCourt = parseCurrentCourtState(record.currentCourt);
422
391
  return {
423
392
  runId: record.runId,
424
- role: record.role,
393
+ role,
425
394
  state: record.state,
426
395
  bookKey: record.bookKey,
427
396
  projectRoot: record.projectRoot,
@@ -538,6 +507,10 @@ export async function markRunAdmitted(
538
507
  authority: DurablePrincipalAuthority,
539
508
  ): Promise<void> {
540
509
  const { sessionDirectory, sessionFile } = authority.decode(admitted.principal);
510
+ const record = packagedRoleMetadata(admitted.role);
511
+ const workerPhase = record !== undefined && "worker" in record && record.worker === true && "phase" in admitted
512
+ ? admitted.phase
513
+ : undefined;
541
514
  await writeRoleRunState(admitted.runDirectory, {
542
515
  runId: admitted.runId,
543
516
  role: admitted.role,
@@ -547,11 +520,7 @@ export async function markRunAdmitted(
547
520
  sessionDirectory,
548
521
  sessionFile,
549
522
  admittedRequestPath: admitted.admittedRequestPath,
550
- ...(
551
- admitted.role === "coder" || admitted.role === "fixer"
552
- ? { phase: admitted.phase }
553
- : {}
554
- ),
523
+ ...(workerPhase === undefined ? {} : { phase: workerPhase }),
555
524
  });
556
525
  }
557
526
 
@@ -1141,10 +1110,11 @@ async function runHasFormedSessionPrincipal(runDirectory: string): Promise<boole
1141
1110
  /**
1142
1111
  * Locate the latest retained run for one seat under a book (#637 / #747).
1143
1112
  * Same walk surface as findRunDirectoryById (listBookRunDirectories). Match by
1144
- * parent run path (officer / gate seats, #747 / #987). runId is UUIDv7 —
1145
- * lexicographic max is latest among runs that formed a session principal. No
1146
- * parallel index. Public ticket-number selection of a prior run was deleted
1147
- * (#987 Result 7); callers use explicit `ak-role resume <runId>`.
1113
+ * parent run path (officer / gate seats, #747 / #987). A typed ticket number
1114
+ * does not select a run. runId is UUIDv7 — lexicographic max is latest among
1115
+ * runs that formed a session principal. No parallel index. Public ticket-number
1116
+ * selection of a prior run was deleted (#987 Result 7); callers use explicit
1117
+ * `ak-role resume <runId>`.
1148
1118
  * Only a truly missing book directory means no history; damage/permission errors propagate.
1149
1119
  */
1150
1120
  export async function findLatestRunIdForSeatTicket(input: {
@@ -1679,68 +1649,9 @@ async function loadResumableRunRecord(
1679
1649
  };
1680
1650
  }
1681
1651
 
1682
- export type LoadedResumableJudgeRun = {
1683
- readonly admitted: AdmittedJudgeInvocation;
1684
- readonly run: RoleRunRecord;
1685
- /** @deprecated #416: resumable observation no longer gates resume; may be undefined. */
1686
- readonly observation?: TypedHttp429Observation;
1687
- };
1688
-
1689
- export type LoadedResumableCoderRun = {
1690
- readonly admitted: AdmittedCoderInvocation;
1691
- readonly run: RoleRunRecord;
1692
- readonly observation?: TypedHttp429Observation;
1693
- };
1694
-
1695
- export type LoadedResumableFixerRun = {
1696
- readonly admitted: AdmittedFixerInvocation;
1697
- readonly run: RoleRunRecord;
1698
- readonly observation?: TypedHttp429Observation;
1699
- };
1700
-
1701
- export type LoadedResumableReviewerRun = {
1702
- readonly admitted: AdmittedReviewerInvocation;
1703
- readonly run: RoleRunRecord;
1704
- readonly observation?: TypedHttp429Observation;
1705
- };
1706
-
1707
- export type LoadedResumableCountersignRun = {
1708
- readonly admitted: AdmittedCountersignInvocation;
1709
- readonly run: RoleRunRecord;
1710
- readonly observation?: TypedHttp429Observation;
1711
- };
1712
-
1713
- export type LoadedResumableGleanerLeftRun = {
1714
- readonly admitted: AdmittedGleanerLeftInvocation;
1715
- readonly run: RoleRunRecord;
1716
- readonly observation?: TypedHttp429Observation;
1717
- };
1718
-
1719
- export type LoadedResumableDiaristRun = {
1720
- readonly admitted: AdmittedDiaristInvocation;
1721
- readonly run: RoleRunRecord;
1722
- readonly observation?: TypedHttp429Observation;
1723
- };
1724
-
1725
- export type LoadedResumableSecretariatRun = {
1726
- readonly admitted: import("./invocation.ts").AdmittedSecretariatInvocation;
1727
- readonly run: RoleRunRecord;
1728
- readonly observation?: TypedHttp429Observation;
1729
- };
1730
-
1731
- export type LoadedResumableInstructionSeatRun = {
1732
- readonly admitted:
1733
- | AdmittedGatekeeperInvocation
1734
- | AdmittedNavigatorInvocation
1735
- | import("./invocation.ts").AdmittedAuditorInvocation
1736
- readonly run: RoleRunRecord;
1737
- readonly observation?: TypedHttp429Observation;
1738
- };
1739
-
1740
1652
  /**
1741
- * Base admitted projection shared by every seat loader: durable identity +
1742
- * instruction/attachments + principal + restored ticket identity + model (#633).
1743
- * Seat loaders add only their seat-specific fields on top.
1653
+ * Base admitted projection: durable identity, instruction, attachments, principal, ticket, and model.
1654
+ * Seat restore adds only that seat's fields.
1744
1655
  */
1745
1656
  function resumedBaseAdmitted(loaded: {
1746
1657
  readonly run: RoleRunRecord;
@@ -1762,7 +1673,7 @@ function resumedBaseAdmitted(loaded: {
1762
1673
  };
1763
1674
  }
1764
1675
 
1765
- /** Loaded-run envelope projection; seat loaders add only their seat fields. */
1676
+ /** Loaded-run envelope. */
1766
1677
  function seatLoadedResult<R extends AdmittedRoleInvocation>(
1767
1678
  loaded: {
1768
1679
  readonly run: RoleRunRecord;
@@ -1777,508 +1688,6 @@ function seatLoadedResult<R extends AdmittedRoleInvocation>(
1777
1688
  };
1778
1689
  }
1779
1690
 
1780
- /**
1781
- * Load a resumable Judge run for resume. Rejects unknown, terminal,
1782
- * non-resumable, and non-Judge IDs without replaying dispatch.
1783
- */
1784
- export async function loadResumableJudgeRun(
1785
- home: string,
1786
- runId: string,
1787
- authority: DurablePrincipalAuthority,
1788
- ): Promise<LoadedResumableJudgeRun> {
1789
- const loaded = await loadResumableRunRecord(home, runId, authority);
1790
- if (loaded.run.role !== "judge") {
1791
- throw new CliUsageError(
1792
- `role run ${runId} belongs to ${loaded.run.role}, not judge`,
1793
- );
1794
- }
1795
- const admitted: AdmittedJudgeInvocation = {
1796
- role: "judge",
1797
- ...resumedBaseAdmitted(loaded),
1798
- };
1799
- return seatLoadedResult(loaded, admitted);
1800
- }
1801
-
1802
- /**
1803
- * Load a resumable Coder run for resume. Phase and task path are restored from
1804
- * the admitted request so continuation stays role-correct (#109).
1805
- */
1806
- export async function loadResumableCoderRun(
1807
- home: string,
1808
- runId: string,
1809
- authority: DurablePrincipalAuthority,
1810
- ): Promise<LoadedResumableCoderRun> {
1811
- const loaded = await loadResumableRunRecord(home, runId, authority);
1812
- if (loaded.run.role !== "coder") {
1813
- throw new CliUsageError(
1814
- `role run ${runId} belongs to ${loaded.run.role}, not coder`,
1815
- );
1816
- }
1817
- const phase = loaded.admittedFields.phase ?? loaded.run.phase;
1818
- if (phase !== "plan" && phase !== "apply") {
1819
- throw new CliUsageError(
1820
- `role run admitted coder phase is missing: ${runId}`,
1821
- );
1822
- }
1823
- const taskPath = loaded.admittedFields.taskPath;
1824
- if (taskPath === undefined) {
1825
- throw new CliUsageError(
1826
- `role run admitted coder task path is missing: ${runId}`,
1827
- );
1828
- }
1829
- if (loaded.admittedFields.instruction.trim() === "") {
1830
- throw new CliUsageError(
1831
- `role run admitted coder task is blank: ${runId}`,
1832
- );
1833
- }
1834
- const admitted: AdmittedCoderInvocation = {
1835
- role: "coder",
1836
- phase,
1837
- ...resumedBaseAdmitted(loaded),
1838
- instructionEmpty: false,
1839
- taskPath,
1840
- };
1841
- return seatLoadedResult(loaded, admitted);
1842
- }
1843
-
1844
- /**
1845
- * Load a resumable Fixer run for resume. Phase, packet, and prerequisites are
1846
- * restored from the admitted request so continuation stays role-correct (#110).
1847
- */
1848
- export async function loadResumableFixerRun(
1849
- home: string,
1850
- runId: string,
1851
- authority: DurablePrincipalAuthority,
1852
- ): Promise<LoadedResumableFixerRun> {
1853
- const loaded = await loadResumableRunRecord(home, runId, authority);
1854
- if (loaded.run.role !== "fixer") {
1855
- throw new CliUsageError(
1856
- `role run ${runId} belongs to ${loaded.run.role}, not fixer`,
1857
- );
1858
- }
1859
- const phase = loaded.admittedFields.phase ?? loaded.run.phase;
1860
- if (phase !== "plan" && phase !== "apply") {
1861
- throw new CliUsageError(
1862
- `role run admitted fixer phase is missing: ${runId}`,
1863
- );
1864
- }
1865
- const packetPath = loaded.admittedFields.packetPath;
1866
- if (packetPath === undefined) {
1867
- throw new CliUsageError(
1868
- `role run admitted fixer packet path is missing: ${runId}`,
1869
- );
1870
- }
1871
- if (loaded.admittedFields.instruction.trim() === "") {
1872
- throw new CliUsageError(
1873
- `role run admitted fixer instruction is blank: ${runId}`,
1874
- );
1875
- }
1876
- const prerequisites = loaded.admittedFields.prerequisites ?? Object.freeze([]);
1877
- const admitted: AdmittedFixerInvocation = {
1878
- role: "fixer",
1879
- phase,
1880
- ...resumedBaseAdmitted(loaded),
1881
- instructionEmpty: false,
1882
- packetPath,
1883
- ...(loaded.admittedFields.prerequisitesPath === undefined
1884
- ? {}
1885
- : { prerequisitesPath: loaded.admittedFields.prerequisitesPath }),
1886
- prerequisites,
1887
- };
1888
- return seatLoadedResult(loaded, admitted);
1889
- }
1890
-
1891
- /**
1892
- * Peek the durable role of a run id without enforcing resumable state.
1893
- * Used by public resume dispatch to pick the role-correct seat and path.
1894
- */
1895
- /**
1896
- * Load a resumable Reviewer run for resume. Fixed base is restored from the
1897
- * admitted request; caller instruction remains optional provenance only.
1898
- */
1899
- export async function loadResumableReviewerRun(
1900
- home: string,
1901
- runId: string,
1902
- authority: DurablePrincipalAuthority,
1903
- ): Promise<LoadedResumableReviewerRun> {
1904
- const loaded = await loadResumableRunRecord(home, runId, authority);
1905
- if (loaded.run.role !== "reviewer") {
1906
- throw new CliUsageError(
1907
- `role run ${runId} belongs to ${loaded.run.role}, not reviewer`,
1908
- );
1909
- }
1910
- // Durable restore keeps run identity — never rebrand record damage as fresh --base/--lens.
1911
- const rawBase = loaded.admittedFields.baseRevision;
1912
- if (rawBase === undefined || rawBase.trim() === "") {
1913
- throw new CliUsageError(
1914
- `role run admitted reviewer base revision is missing: ${runId}`,
1915
- );
1916
- }
1917
- if (/\s/.test(rawBase) || rawBase.startsWith("-")) {
1918
- throw new CliUsageError(
1919
- `role run admitted reviewer base revision is damaged: ${runId}`,
1920
- );
1921
- }
1922
- const baseRevision = rawBase;
1923
- const lens = loaded.admittedFields.lens;
1924
- if (!isReviewerLens(lens)) {
1925
- throw new CliUsageError(
1926
- `role run admitted reviewer lens is missing: ${runId}`,
1927
- );
1928
- }
1929
- const authorityRefs = Object.freeze([
1930
- ...(loaded.admittedFields.authorityRefs ?? []),
1931
- ]);
1932
- if (authorityRefs.length === 0) {
1933
- throw new CliUsageError(
1934
- `role run admitted reviewer authority refs are missing: ${runId}`,
1935
- );
1936
- }
1937
- const admitted: AdmittedReviewerInvocation = {
1938
- role: "reviewer",
1939
- ...resumedBaseAdmitted(loaded),
1940
- baseRevision,
1941
- lens,
1942
- authorityRefs,
1943
- };
1944
- return seatLoadedResult(loaded, admitted);
1945
- }
1946
-
1947
- /**
1948
- * Load a resumable Countersign run for resume (#599). Ticket binding and
1949
- * attachments restore from the admitted request.
1950
- */
1951
- export async function loadResumableCountersignRun(
1952
- home: string,
1953
- runId: string,
1954
- authority: DurablePrincipalAuthority,
1955
- ): Promise<LoadedResumableCountersignRun> {
1956
- const loaded = await loadResumableRunRecord(home, runId, authority);
1957
- if (loaded.run.role !== "countersign") {
1958
- throw new CliUsageError(
1959
- `role run ${runId} belongs to ${loaded.run.role}, not countersign`,
1960
- );
1961
- }
1962
- const base = resumedBaseAdmitted(loaded);
1963
- const admitted: AdmittedCountersignInvocation = {
1964
- role: "countersign",
1965
- ...base,
1966
- ...(base.courtTicketNumbers === undefined
1967
- ? {}
1968
- : { courtTicketNumbers: base.courtTicketNumbers }),
1969
- ...(loaded.admittedFields.courtTicketNumbersDamage === undefined
1970
- ? {}
1971
- : {
1972
- courtTicketNumbersDamage:
1973
- loaded.admittedFields.courtTicketNumbersDamage,
1974
- }),
1975
- };
1976
- return seatLoadedResult(loaded, admitted);
1977
- }
1978
-
1979
- /**
1980
- * Load a resumable Gleaner-Left run for resume (#599). Fixed base restores from
1981
- * the admitted request so continuation stays comparison-correct.
1982
- */
1983
- export async function loadResumableInstructionSeatRun(
1984
- home: string,
1985
- runId: string,
1986
- authority: DurablePrincipalAuthority,
1987
- ): Promise<LoadedResumableInstructionSeatRun> {
1988
- const loaded = await loadResumableRunRecord(home, runId, authority);
1989
- if (
1990
- loaded.run.role !== "gatekeeper"
1991
- && loaded.run.role !== "navigator"
1992
- && loaded.run.role !== "auditor"
1993
- ) {
1994
- throw new CliUsageError(
1995
- `role run ${runId} belongs to ${loaded.run.role}, not an instruction seat`,
1996
- );
1997
- }
1998
- const admitted = {
1999
- role: loaded.run.role,
2000
- ...resumedBaseAdmitted(loaded),
2001
- } as
2002
- | AdmittedGatekeeperInvocation
2003
- | AdmittedNavigatorInvocation
2004
- | import("./invocation.ts").AdmittedAuditorInvocation
2005
- return seatLoadedResult(loaded, admitted);
2006
- }
2007
-
2008
- export async function loadResumableGleanerLeftRun(
2009
- home: string,
2010
- runId: string,
2011
- authority: DurablePrincipalAuthority,
2012
- ): Promise<LoadedResumableGleanerLeftRun> {
2013
- const loaded = await loadResumableRunRecord(home, runId, authority);
2014
- if (loaded.run.role !== "gleaner-left") {
2015
- throw new CliUsageError(
2016
- `role run ${runId} belongs to ${loaded.run.role}, not gleaner-left`,
2017
- );
2018
- }
2019
- const baseRevision = loaded.admittedFields.baseRevision;
2020
- if (baseRevision === undefined || baseRevision.trim() === "") {
2021
- throw new CliUsageError(
2022
- `role run admitted gleaner-left base revision is missing: ${runId}`,
2023
- );
2024
- }
2025
- const admitted: AdmittedGleanerLeftInvocation = {
2026
- role: "gleaner-left",
2027
- ...resumedBaseAdmitted(loaded),
2028
- baseRevision,
2029
- };
2030
- return seatLoadedResult(loaded, admitted);
2031
- }
2032
-
2033
- export async function loadResumableDiaristRun(
2034
- home: string,
2035
- runId: string,
2036
- authority: DurablePrincipalAuthority,
2037
- ): Promise<LoadedResumableDiaristRun> {
2038
- const loaded = await loadResumableRunRecord(home, runId, authority);
2039
- if (loaded.run.role !== "diarist") {
2040
- throw new CliUsageError(
2041
- `role run ${runId} belongs to ${loaded.run.role}, not diarist`,
2042
- );
2043
- }
2044
- const admitted: AdmittedDiaristInvocation = {
2045
- role: "diarist",
2046
- ...resumedBaseAdmitted(loaded),
2047
- };
2048
- return seatLoadedResult(loaded, admitted);
2049
- }
2050
-
2051
- export async function loadResumableSecretariatRun(
2052
- home: string,
2053
- runId: string,
2054
- authority: DurablePrincipalAuthority,
2055
- ): Promise<LoadedResumableSecretariatRun> {
2056
- const loaded = await loadResumableRunRecord(home, runId, authority);
2057
- if (loaded.run.role !== "secretariat") {
2058
- throw new CliUsageError(
2059
- `role run ${runId} belongs to ${loaded.run.role}, not secretariat`,
2060
- );
2061
- }
2062
- const admitted: import("./invocation.ts").AdmittedSecretariatInvocation = {
2063
- role: "secretariat",
2064
- ...resumedBaseAdmitted(loaded),
2065
- };
2066
- return seatLoadedResult(loaded, admitted);
2067
- }
2068
-
2069
- export type LoadedResumableMergerRun = {
2070
- readonly admitted: AdmittedMergerInvocation;
2071
- readonly run: RoleRunRecord;
2072
- readonly observation?: TypedHttp429Observation;
2073
- };
2074
-
2075
- /**
2076
- * Load a resumable Merger run for resume. Derived envelope + internal input path
2077
- * are restored from the admitted request (#114).
2078
- */
2079
- export async function loadResumableMergerRun(
2080
- home: string,
2081
- runId: string,
2082
- authority: DurablePrincipalAuthority,
2083
- ): Promise<LoadedResumableMergerRun> {
2084
- const loaded = await loadResumableRunRecord(home, runId, authority);
2085
- if (loaded.run.role !== "merger") {
2086
- throw new CliUsageError(
2087
- `role run ${runId} belongs to ${loaded.run.role}, not merger`,
2088
- );
2089
- }
2090
- const mergerInputPath = loaded.admittedFields.mergerInputPath;
2091
- if (mergerInputPath === undefined) {
2092
- throw new CliUsageError(
2093
- `role run admitted merger input path is missing: ${runId}`,
2094
- );
2095
- }
2096
- const derived = loaded.admittedFields.derived;
2097
- if (derived === undefined) {
2098
- throw new CliUsageError(
2099
- `role run admitted merger envelope is missing: ${runId}`,
2100
- );
2101
- }
2102
- if (loaded.admittedFields.instruction.trim() === "") {
2103
- throw new CliUsageError(
2104
- `role run admitted merger task is blank: ${runId}`,
2105
- );
2106
- }
2107
- const admitted: AdmittedMergerInvocation = {
2108
- role: "merger",
2109
- ...resumedBaseAdmitted(loaded),
2110
- instructionEmpty: false,
2111
- mergerInputPath,
2112
- derived,
2113
- };
2114
- return seatLoadedResult(loaded, admitted);
2115
- }
2116
-
2117
- export type LoadedResumableCollectorRun = {
2118
- readonly admitted: AdmittedCollectorInvocation;
2119
- readonly run: RoleRunRecord;
2120
- readonly observation?: TypedHttp429Observation;
2121
- };
2122
-
2123
- /**
2124
- * Load a resumable Collector run for resume (#633). Admitted repository/PR
2125
- * identity restores from the durable admitted request so the continuation
2126
- * stays binding-correct.
2127
- */
2128
- export async function loadResumableCollectorRun(
2129
- home: string,
2130
- runId: string,
2131
- authority: DurablePrincipalAuthority,
2132
- ): Promise<LoadedResumableCollectorRun> {
2133
- const loaded = await loadResumableRunRecord(home, runId, authority);
2134
- if (loaded.run.role !== "collector") {
2135
- throw new CliUsageError(
2136
- `role run ${runId} belongs to ${loaded.run.role}, not collector`,
2137
- );
2138
- }
2139
- const { prNumber, repository, repositoryDisplay, manifestDigest } = loaded.admittedFields;
2140
- // #676 A: prNumber may be unbound at admission (role bind-target). Repository is required.
2141
- if (
2142
- repository === undefined ||
2143
- repositoryDisplay === undefined ||
2144
- manifestDigest === undefined
2145
- ) {
2146
- throw new CliUsageError(
2147
- `role run admitted collector repository identity is missing: ${runId}`,
2148
- );
2149
- }
2150
- let parsedRepository;
2151
- try {
2152
- parsedRepository = parseCollectorRepository(repositoryDisplay);
2153
- } catch (error) {
2154
- const detail = error instanceof Error ? error.message : String(error);
2155
- throw new CliUsageError(detail, { cause: error });
2156
- }
2157
- if (parsedRepository.canonical !== repository) {
2158
- throw new CliUsageError(
2159
- `role run admitted collector repository does not match its canonical form: ${runId}`,
2160
- );
2161
- }
2162
- const admitted: AdmittedCollectorInvocation = {
2163
- role: "collector",
2164
- ...resumedBaseAdmitted(loaded),
2165
- ...(prNumber === undefined ? {} : { prNumber }),
2166
- repository: parsedRepository,
2167
- ...(loaded.admittedFields.requestManifestPath === undefined
2168
- ? {}
2169
- : { requestManifestPath: loaded.admittedFields.requestManifestPath }),
2170
- ...(loaded.admittedFields.waitWindowMs === undefined
2171
- ? {}
2172
- : { waitWindowMs: loaded.admittedFields.waitWindowMs }),
2173
- manifestDigest,
2174
- };
2175
- return seatLoadedResult(loaded, admitted);
2176
- }
2177
-
2178
- export type LoadedResumableDoctorRun = {
2179
- readonly admitted: AdmittedDoctorInvocation;
2180
- readonly run: RoleRunRecord;
2181
- readonly observation?: TypedHttp429Observation;
2182
- };
2183
-
2184
- /**
2185
- * Load a resumable Doctor run for resume (#633). Issue + retained case
2186
- * identity restore from the durable admitted request.
2187
- */
2188
- export async function loadResumableDoctorRun(
2189
- home: string,
2190
- runId: string,
2191
- authority: DurablePrincipalAuthority,
2192
- ): Promise<LoadedResumableDoctorRun> {
2193
- const loaded = await loadResumableRunRecord(home, runId, authority);
2194
- if (loaded.run.role !== "doctor") {
2195
- throw new CliUsageError(
2196
- `role run ${runId} belongs to ${loaded.run.role}, not doctor`,
2197
- );
2198
- }
2199
- const { issueNumber, caseRunsPath, caseIdentity } = loaded.admittedFields;
2200
- if (
2201
- issueNumber === undefined ||
2202
- caseRunsPath === undefined ||
2203
- caseIdentity === undefined
2204
- ) {
2205
- throw new CliUsageError(
2206
- `role run admitted doctor case identity is missing: ${runId}`,
2207
- );
2208
- }
2209
- const admitted: AdmittedDoctorInvocation = {
2210
- role: "doctor",
2211
- ...resumedBaseAdmitted(loaded),
2212
- issueNumber,
2213
- caseRunsPath,
2214
- caseIdentity,
2215
- };
2216
- return seatLoadedResult(loaded, admitted);
2217
- }
2218
-
2219
- export type LoadedResumableNotaryRun = {
2220
- readonly admitted: AdmittedNotaryInvocation;
2221
- readonly run: RoleRunRecord;
2222
- readonly observation?: TypedHttp429Observation;
2223
- };
2224
-
2225
- /**
2226
- * Load a resumable Notary run for resume (#633). Source-run locator restores
2227
- * from the durable admitted request; the locator is not re-resolved.
2228
- */
2229
- export async function loadResumableNotaryRun(
2230
- home: string,
2231
- runId: string,
2232
- authority: DurablePrincipalAuthority,
2233
- ): Promise<LoadedResumableNotaryRun> {
2234
- const loaded = await loadResumableRunRecord(home, runId, authority);
2235
- if (loaded.run.role !== "notary") {
2236
- throw new CliUsageError(
2237
- `role run ${runId} belongs to ${loaded.run.role}, not notary`,
2238
- );
2239
- }
2240
- const { sourceRunPath, sourceRun } = loaded.admittedFields;
2241
- if (sourceRunPath === undefined || sourceRun === undefined) {
2242
- throw new CliUsageError(
2243
- `role run admitted notary source-run locator is missing: ${runId}`,
2244
- );
2245
- }
2246
- const admitted: AdmittedNotaryInvocation = {
2247
- role: "notary",
2248
- ...resumedBaseAdmitted(loaded),
2249
- sourceRunPath,
2250
- sourceRun,
2251
- };
2252
- return seatLoadedResult(loaded, admitted);
2253
- }
2254
-
2255
- export type LoadedResumableInspectorRun = {
2256
- readonly admitted: AdmittedInspectorInvocation;
2257
- readonly run: RoleRunRecord;
2258
- readonly observation?: TypedHttp429Observation;
2259
- };
2260
-
2261
- /** Load a resumable Inspector run for resume (#633); base admitted identity only. */
2262
- export async function loadResumableInspectorRun(
2263
- home: string,
2264
- runId: string,
2265
- authority: DurablePrincipalAuthority,
2266
- ): Promise<LoadedResumableInspectorRun> {
2267
- const loaded = await loadResumableRunRecord(home, runId, authority);
2268
- if (loaded.run.role !== "inspector") {
2269
- throw new CliUsageError(
2270
- `role run ${runId} belongs to ${loaded.run.role}, not inspector`,
2271
- );
2272
- }
2273
- const admitted: AdmittedInspectorInvocation = {
2274
- role: "inspector",
2275
- ...resumedBaseAdmitted(loaded),
2276
- ...(loaded.admittedFields.sourceRunPath === undefined
2277
- ? {}
2278
- : { sourceRunPath: loaded.admittedFields.sourceRunPath }),
2279
- };
2280
- return seatLoadedResult(loaded, admitted);
2281
- }
2282
1691
 
2283
1692
  export async function peekRoleRunRole(
2284
1693
  home: string,
@@ -2307,3 +1716,284 @@ export async function peekRoleRunRole(
2307
1716
  const run = await readRoleRunIdentity(runDirectory);
2308
1717
  return run?.role;
2309
1718
  }
1719
+
1720
+
1721
+ export type LoadedResumablePublicRole = {
1722
+ readonly admitted: AdmittedRoleInvocation;
1723
+ readonly run: RoleRunRecord;
1724
+ readonly observation?: TypedHttp429Observation;
1725
+ };
1726
+
1727
+ /**
1728
+ * One resume load. The caller supplies the package run id; the disk role
1729
+ * selects the existing restore checks. The stored native host session id is
1730
+ * read on the public explicit resume seam, not from this load.
1731
+ */
1732
+ export async function loadResumablePublicRole(
1733
+ home: string,
1734
+ runId: string,
1735
+ authority: DurablePrincipalAuthority,
1736
+ ): Promise<LoadedResumablePublicRole> {
1737
+ const loaded = await loadResumableRunRecord(home, runId, authority);
1738
+ return seatLoadedResult(loaded, admitResumedRole(loaded));
1739
+ }
1740
+
1741
+ /**
1742
+ * One resume restore. Admission kind comes from the composition-root record;
1743
+ * seat-only required fields stay on that kind. Instruction seats share one face.
1744
+ */
1745
+ function admitResumedRole(loaded: {
1746
+ readonly run: RoleRunRecord;
1747
+ readonly principal: DurablePrincipal;
1748
+ readonly observation?: TypedHttp429Observation;
1749
+ readonly admittedFields: LoadedAdmittedRequestFields;
1750
+ }): AdmittedRoleInvocation {
1751
+ const role = loaded.run.role;
1752
+ const runId = loaded.run.runId;
1753
+ const record = packagedRoleMetadata(role);
1754
+ if (record === undefined) {
1755
+ throw new CliUsageError(`unknown role: ${role}`);
1756
+ }
1757
+ const fields = loaded.admittedFields;
1758
+ const base = resumedBaseAdmitted(loaded);
1759
+ switch (record.admission) {
1760
+ case "instruction": {
1761
+ if (packagedResumeSourcePath(role)) {
1762
+ const admitted: AdmittedInspectorInvocation = {
1763
+ role: "inspector",
1764
+ ...base,
1765
+ ...(fields.sourceRunPath === undefined
1766
+ ? {}
1767
+ : { sourceRunPath: fields.sourceRunPath }),
1768
+ };
1769
+ return admitted;
1770
+ }
1771
+ return {
1772
+ role,
1773
+ ...base,
1774
+ } as AdmittedRoleInvocation;
1775
+ }
1776
+ case "court-materials": {
1777
+ const admitted: AdmittedCountersignInvocation = {
1778
+ role: "countersign",
1779
+ ...base,
1780
+ ...(base.courtTicketNumbers === undefined
1781
+ ? {}
1782
+ : { courtTicketNumbers: base.courtTicketNumbers }),
1783
+ ...(fields.courtTicketNumbersDamage === undefined
1784
+ ? {}
1785
+ : { courtTicketNumbersDamage: fields.courtTicketNumbersDamage }),
1786
+ };
1787
+ return admitted;
1788
+ }
1789
+ case "worker-task": {
1790
+ const phase = resumedWorkerPhase(fields.phase ?? loaded.run.phase, record.phases, role, runId);
1791
+ const taskPath = fields.taskPath;
1792
+ if (taskPath === undefined) {
1793
+ throw new CliUsageError(
1794
+ `role run admitted coder task path is missing: ${runId}`,
1795
+ );
1796
+ }
1797
+ if (fields.instruction.trim() === "") {
1798
+ throw new CliUsageError(
1799
+ `role run admitted coder task is blank: ${runId}`,
1800
+ );
1801
+ }
1802
+ const admitted: AdmittedCoderInvocation = {
1803
+ role: "coder",
1804
+ phase,
1805
+ ...base,
1806
+ instructionEmpty: false,
1807
+ taskPath,
1808
+ };
1809
+ return admitted;
1810
+ }
1811
+ case "worker-packet": {
1812
+ const phase = resumedWorkerPhase(fields.phase ?? loaded.run.phase, record.phases, role, runId);
1813
+ const packetPath = fields.packetPath;
1814
+ if (packetPath === undefined) {
1815
+ throw new CliUsageError(
1816
+ `role run admitted fixer packet path is missing: ${runId}`,
1817
+ );
1818
+ }
1819
+ if (fields.instruction.trim() === "") {
1820
+ throw new CliUsageError(
1821
+ `role run admitted fixer instruction is blank: ${runId}`,
1822
+ );
1823
+ }
1824
+ const prerequisites = fields.prerequisites ?? Object.freeze([]);
1825
+ const admitted: AdmittedFixerInvocation = {
1826
+ role: "fixer",
1827
+ phase,
1828
+ ...base,
1829
+ instructionEmpty: false,
1830
+ packetPath,
1831
+ ...(fields.prerequisitesPath === undefined
1832
+ ? {}
1833
+ : { prerequisitesPath: fields.prerequisitesPath }),
1834
+ prerequisites,
1835
+ };
1836
+ return admitted;
1837
+ }
1838
+ case "review-basis": {
1839
+ const rawBase = fields.baseRevision;
1840
+ if (rawBase === undefined || rawBase.trim() === "") {
1841
+ throw new CliUsageError(
1842
+ `role run admitted reviewer base revision is missing: ${runId}`,
1843
+ );
1844
+ }
1845
+ if (/\s/.test(rawBase) || rawBase.startsWith("-")) {
1846
+ throw new CliUsageError(
1847
+ `role run admitted reviewer base revision is damaged: ${runId}`,
1848
+ );
1849
+ }
1850
+ const lens = fields.lens;
1851
+ if (!isReviewerLens(lens)) {
1852
+ throw new CliUsageError(
1853
+ `role run admitted reviewer lens is missing: ${runId}`,
1854
+ );
1855
+ }
1856
+ const authorityRefs = Object.freeze([...(fields.authorityRefs ?? [])]);
1857
+ if (authorityRefs.length === 0) {
1858
+ throw new CliUsageError(
1859
+ `role run admitted reviewer authority refs are missing: ${runId}`,
1860
+ );
1861
+ }
1862
+ const admitted: AdmittedReviewerInvocation = {
1863
+ role: "reviewer",
1864
+ ...base,
1865
+ baseRevision: rawBase,
1866
+ lens,
1867
+ authorityRefs,
1868
+ };
1869
+ return admitted;
1870
+ }
1871
+ case "gleaner": {
1872
+ const baseRevision = fields.baseRevision;
1873
+ if (baseRevision === undefined || baseRevision.trim() === "") {
1874
+ throw new CliUsageError(
1875
+ `role run admitted gleaner-left base revision is missing: ${runId}`,
1876
+ );
1877
+ }
1878
+ const admitted: AdmittedGleanerLeftInvocation = {
1879
+ role: "gleaner-left",
1880
+ ...base,
1881
+ baseRevision,
1882
+ };
1883
+ return admitted;
1884
+ }
1885
+ case "merge-envelope": {
1886
+ const mergerInputPath = fields.mergerInputPath;
1887
+ if (mergerInputPath === undefined) {
1888
+ throw new CliUsageError(
1889
+ `role run admitted merger input path is missing: ${runId}`,
1890
+ );
1891
+ }
1892
+ const derived = fields.derived;
1893
+ if (derived === undefined) {
1894
+ throw new CliUsageError(
1895
+ `role run admitted merger envelope is missing: ${runId}`,
1896
+ );
1897
+ }
1898
+ if (fields.instruction.trim() === "") {
1899
+ throw new CliUsageError(
1900
+ `role run admitted merger task is blank: ${runId}`,
1901
+ );
1902
+ }
1903
+ const admitted: AdmittedMergerInvocation = {
1904
+ role: "merger",
1905
+ ...base,
1906
+ instructionEmpty: false,
1907
+ mergerInputPath,
1908
+ derived,
1909
+ };
1910
+ return admitted;
1911
+ }
1912
+ case "collect-target": {
1913
+ const { prNumber, repository, repositoryDisplay, manifestDigest } = fields;
1914
+ if (
1915
+ repository === undefined ||
1916
+ repositoryDisplay === undefined ||
1917
+ manifestDigest === undefined
1918
+ ) {
1919
+ throw new CliUsageError(
1920
+ `role run admitted collector repository identity is missing: ${runId}`,
1921
+ );
1922
+ }
1923
+ let parsedRepository;
1924
+ try {
1925
+ parsedRepository = parseCollectorRepository(repositoryDisplay);
1926
+ } catch (error) {
1927
+ const detail = error instanceof Error ? error.message : String(error);
1928
+ throw new CliUsageError(detail, { cause: error });
1929
+ }
1930
+ if (parsedRepository.canonical !== repository) {
1931
+ throw new CliUsageError(
1932
+ `role run admitted collector repository does not match its canonical form: ${runId}`,
1933
+ );
1934
+ }
1935
+ const admitted: AdmittedCollectorInvocation = {
1936
+ role: "collector",
1937
+ ...base,
1938
+ ...(prNumber === undefined ? {} : { prNumber }),
1939
+ repository: parsedRepository,
1940
+ ...(fields.requestManifestPath === undefined
1941
+ ? {}
1942
+ : { requestManifestPath: fields.requestManifestPath }),
1943
+ ...(fields.waitWindowMs === undefined ? {} : { waitWindowMs: fields.waitWindowMs }),
1944
+ manifestDigest,
1945
+ };
1946
+ return admitted;
1947
+ }
1948
+ case "case-identity": {
1949
+ const { issueNumber, caseRunsPath, caseIdentity } = fields;
1950
+ if (
1951
+ issueNumber === undefined ||
1952
+ caseRunsPath === undefined ||
1953
+ caseIdentity === undefined
1954
+ ) {
1955
+ throw new CliUsageError(
1956
+ `role run admitted doctor case identity is missing: ${runId}`,
1957
+ );
1958
+ }
1959
+ const admitted: AdmittedDoctorInvocation = {
1960
+ role: "doctor",
1961
+ ...base,
1962
+ issueNumber,
1963
+ caseRunsPath,
1964
+ caseIdentity,
1965
+ };
1966
+ return admitted;
1967
+ }
1968
+ case "source-locator": {
1969
+ const { sourceRunPath, sourceRun } = fields;
1970
+ if (sourceRunPath === undefined || sourceRun === undefined) {
1971
+ throw new CliUsageError(
1972
+ `role run admitted notary source-run locator is missing: ${runId}`,
1973
+ );
1974
+ }
1975
+ const admitted: AdmittedNotaryInvocation = {
1976
+ role: "notary",
1977
+ ...base,
1978
+ sourceRunPath,
1979
+ sourceRun,
1980
+ };
1981
+ return admitted;
1982
+ }
1983
+ }
1984
+ }
1985
+
1986
+ /** Worker phase on resume: value must belong to the registry phase set. */
1987
+ function resumedWorkerPhase(
1988
+ phase: string | undefined,
1989
+ phases: readonly (string | null)[],
1990
+ role: string,
1991
+ runId: string,
1992
+ ): "plan" | "apply" {
1993
+ if (phase === "plan" || phase === "apply") {
1994
+ if (phases.some((item) => item === phase)) {
1995
+ return phase;
1996
+ }
1997
+ }
1998
+ throw new CliUsageError(`role run admitted ${role} phase is missing: ${runId}`);
1999
+ }