@akagilnc/pi-workflow-roles 0.1.5382 → 0.1.5462

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 (87) hide show
  1. package/README.md +4 -2
  2. package/README.zh-CN.md +4 -2
  3. package/dist/acp-host/production-host.js +8843 -9012
  4. package/dist/archivist-record-pointer.js +38 -0
  5. package/dist/compliance-transport.js +7 -5
  6. package/dist/doctor-auditor.js +1 -0
  7. package/dist/engine-detour-usage.js +9 -36
  8. package/dist/gatekeeper-role.js +4 -18
  9. package/dist/headless-host/description.js +0 -209
  10. package/dist/headless-host/production-host.js +7828 -8247
  11. package/dist/host-contracts.js +8 -0
  12. package/dist/judge-role.js +14 -1
  13. package/dist/migrate-book-topology.js +215 -96
  14. package/dist/navigator-attendance.js +10 -9
  15. package/dist/navigator-invocation-identity.js +18 -0
  16. package/dist/packaged-role-registry.js +1 -9
  17. package/dist/pi/role-turn-host.js +46 -19
  18. package/dist/public-cli/auto-resume.js +29 -66
  19. package/dist/public-cli/cli.js +1 -0
  20. package/dist/public-cli/countersign-run.js +4 -18
  21. package/dist/public-cli/instruction-seat-run.js +258 -69
  22. package/dist/public-cli/main.js +7488 -7758
  23. package/dist/public-cli/post-admission.js +451 -349
  24. package/dist/public-cli/process-cancel.js +8 -0
  25. package/dist/public-cli/run-lifecycle.js +24 -94
  26. package/dist/public-cli/settlement.js +624 -1471
  27. package/dist/public-cli/terminal.js +34 -53
  28. package/dist/public-cli/turn-request.js +3 -0
  29. package/dist/public-role-summons.js +3 -8
  30. package/dist/receipt-delivery-policy.js +162 -28
  31. package/dist/run-terminal-artifacts.js +19 -14
  32. package/dist/serialize-thrown-value.js +7 -1
  33. package/dist/submission-errors.js +11 -1
  34. package/dist/submission-gate.js +27 -4
  35. package/dist/submission-ledger.js +21 -89
  36. package/package.json +1 -1
  37. package/scripts/build-package.mjs +0 -2
  38. package/src/acp-host/role-turn-host.ts +168 -75
  39. package/src/archivist-record-pointer.ts +48 -0
  40. package/src/compliance-transport.ts +13 -8
  41. package/src/doctor-auditor.ts +3 -0
  42. package/src/engine-detour-usage.ts +10 -39
  43. package/src/external-host-turn-loop.ts +100 -43
  44. package/src/gatekeeper-role.ts +7 -22
  45. package/src/headless-host/description.ts +4 -214
  46. package/src/headless-host/production-host.ts +2 -1
  47. package/src/headless-host/role-turn-host.ts +153 -186
  48. package/src/host-contracts.ts +20 -10
  49. package/src/judge-role.ts +24 -5
  50. package/src/navigator-attendance.ts +17 -12
  51. package/src/navigator-invocation-identity.ts +31 -0
  52. package/src/navigator-public-session.ts +1 -1
  53. package/src/navigator-session-contracts.ts +1 -1
  54. package/src/packaged-role-registry.ts +2 -10
  55. package/src/pi/adapter.ts +33 -11
  56. package/src/pi/role-turn-host.ts +49 -28
  57. package/src/prepared-role-turn.ts +2 -0
  58. package/src/public-cli/auto-resume.ts +27 -66
  59. package/src/public-cli/cli.ts +1 -0
  60. package/src/public-cli/countersign-run.ts +4 -22
  61. package/src/public-cli/instruction-seat-run.ts +307 -74
  62. package/src/public-cli/post-admission.ts +615 -459
  63. package/src/public-cli/process-cancel.ts +9 -0
  64. package/src/public-cli/run-lifecycle.ts +25 -126
  65. package/src/public-cli/settlement.ts +801 -1859
  66. package/src/public-cli/terminal.ts +42 -70
  67. package/src/public-cli/turn-request.ts +10 -1
  68. package/src/public-role-summons.ts +3 -8
  69. package/src/receipt-delivery-policy.ts +219 -28
  70. package/src/role-envelope.ts +21 -10
  71. package/src/role-runtime-dependencies.ts +1 -0
  72. package/src/role-runtime.ts +79 -102
  73. package/src/run-terminal-artifacts.ts +21 -17
  74. package/src/serialize-thrown-value.ts +7 -1
  75. package/src/submission-correctable-error.ts +5 -2
  76. package/src/submission-errors.ts +12 -1
  77. package/src/submission-gate.ts +34 -4
  78. package/src/submission-ledger.ts +30 -116
  79. package/src/worker-role.ts +19 -7
  80. package/src/worker-submission-gates.ts +29 -7
  81. package/dist/audit-escalation.js +0 -7
  82. package/dist/pi/known-failure.js +0 -51
  83. package/dist/typed-provider-http.js +0 -57
  84. package/dist/upstream-error-testimony.js +0 -19
  85. package/src/pi/known-failure.ts +0 -76
  86. package/src/typed-provider-http.ts +0 -89
  87. package/src/upstream-error-testimony.ts +0 -45
@@ -6,17 +6,18 @@
6
6
  import { dirname, resolve } from "node:path";
7
7
  import { isUnboundRunDirectory } from "../role-run-placement.ts";
8
8
  import { AUDITOR_DOSSIER_PROMPT } from "../compliance-transport.ts";
9
- import { bookDirectOfficerRunPointer } from "../archivist-record-pointer.ts";
9
+ import { bookDirectOfficerRunPointer, readDirectOfficerRunPointer } from "../archivist-record-pointer.ts";
10
10
  import { createPiDoctorAuditor } from "../doctor-auditor.ts";
11
11
 
12
12
  import type { DurablePrincipalAuthority, HostContext, RoleTurnRequest } from "../host-contracts.ts";
13
- import { officerConclusionReask, gateOfficerForSubject } from "../gatekeeper-role.ts";
13
+ import { officerConclusionReask, gateOfficerForSubject, type GateOfficer } from "../gatekeeper-role.ts";
14
14
  import { receivedDiscriminator } from "../submission-errors.ts";
15
15
  import { REVIEW_QUEUE_STATUSES } from "../review-submission.ts";
16
16
  import { runJudgeGates } from "../judge-role.ts";
17
17
  import { WORKER_DONE_STATUSES } from "../worker-submission-contracts.ts";
18
18
  import { SECRETARIAT_GATE_OFFICER_ENTRY_TYPE } from "../secretariat-contracts.ts";
19
19
  import { readableGateItem } from "../readable-gate-item.ts";
20
+ import { deliveryLimitFromConfig } from "../receipt-delivery-policy.ts";
20
21
  import { runIdFromRunDirectory } from "../run-terminal-artifacts.ts";
21
22
  import { createDefaultGateOfficerSummon, requireSubmissionGate } from "../submission-gate.ts";
22
23
  import { readRecordedSubmissionRows } from "../submission-ledger.ts";
@@ -56,6 +57,7 @@ import {
56
57
  } from "./invocation.ts";
57
58
  import {
58
59
  presentControlledFailure,
60
+ packageFaultNoteFor,
59
61
  prepareSummonsResumeMaterials,
60
62
  roleTurnOptions,
61
63
  runPostAdmissionOneShot,
@@ -66,11 +68,13 @@ import {
66
68
  type PostAdmissionAdapters,
67
69
  type PostAdmissionEnv,
68
70
  } from "./post-admission.ts";
71
+ import { presentTerminal } from "./auto-resume.ts";
69
72
  import {
70
73
  buildAutoResumeContinuationPrompt,
71
74
  loadResumablePublicRole,
72
75
  markRunAdmitted,
73
76
  parentRunPathFromGatePointerInstruction,
77
+ readCurrentCourt,
74
78
  type PublicResumeRequest,
75
79
  type RunWriterLease,
76
80
  type SameTicketSummonsMaterials,
@@ -79,9 +83,9 @@ import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
79
83
  import {
80
84
  presentStructuralRejection,
81
85
  readBoundSessionEntries,
82
- seatKnownFailureResolver,
83
86
  attachPostAuditProjection,
84
87
  trySettlePublicSeat,
88
+ type SettlementCourtScope,
85
89
  } from "./settlement.ts";
86
90
  import type { CliIo } from "./cli-io.ts";
87
91
  import { warnMissingMethodSkills } from "./machine-method-skills.ts";
@@ -89,11 +93,9 @@ import {
89
93
  formatTerminalResult,
90
94
  isLawfulTypedTerminalOutcome,
91
95
  type TerminalResult,
92
- type TerminalRoleName,
93
96
  } from "./terminal.ts";
94
97
  import {
95
98
  admittedSeatTurnDetails,
96
- projectPublicTurnAxes,
97
99
  projectRoleTurnRequest,
98
100
  requiredMethodSkills,
99
101
  type RoleTurnRequestProjectionOptions,
@@ -167,17 +169,105 @@ function unreadablePostSubmissionStatus(
167
169
  : route.reask ?? officerConclusionReask(status);
168
170
  }
169
171
 
172
+ /**
173
+ * One more unreadable-status reask. This loop has its own counter and reads
174
+ * the same autoResumeLimit as the other loops. Exhaustion is not a readable
175
+ * status: the caller presents the volumes already in hand and does not advance
176
+ * review. ADR 0007 audit continues are not this shape.
177
+ */
178
+ function withUnreadableReask(env: InstructionSeatRunEnv): InstructionSeatRunEnv | undefined {
179
+ const spent = env.unreadableReasksSpent ?? 0;
180
+ if (spent >= deliveryLimitFromConfig(env.autoResumeLimit)) return undefined;
181
+ return { ...env, unreadableReasksSpent: spent + 1 };
182
+ }
183
+
184
+ /** Budget spent on an unreadable status. Distinct from "status is readable". */
185
+ const UNREADABLE_REASK_EXHAUSTED = { unreadableReaskExhausted: true as const };
186
+
187
+ function isUnreadableReaskExhausted(
188
+ value: SeatRunResult | typeof UNREADABLE_REASK_EXHAUSTED | undefined,
189
+ ): value is typeof UNREADABLE_REASK_EXHAUSTED {
190
+ return value === UNREADABLE_REASK_EXHAUSTED;
191
+ }
192
+
193
+ async function presentOriginalVolume(
194
+ held: readonly string[],
195
+ terminal: TerminalResult | undefined,
196
+ io: CliIo,
197
+ runDirectory?: string,
198
+ ): Promise<void> {
199
+ if (terminal !== undefined && runDirectory !== undefined) {
200
+ // Direction facts were attached after the held volume was formatted.
201
+ await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, runDirectory,
202
+ terminal.roleOutcome.decisiveFacts?.directionUnsettled === true ? undefined : held);
203
+ return;
204
+ }
205
+ // No settled host report exists to preserve at this pre-result boundary.
206
+ for (const value of held) io.stdout(value);
207
+ }
208
+
209
+ /** Runtime fact beside the original volume. Payloads stay as submitted. */
210
+ function withUnsettledDirection(
211
+ parent: TerminalResult,
212
+ officer?: TerminalResult,
213
+ ): TerminalResult {
214
+ const outcome = parent.roleOutcome;
215
+ if (outcome.kind !== "accepted" && outcome.kind !== "audit_escalation") return parent;
216
+ const officerOutcome = officer?.roleOutcome;
217
+ const officerPayloads = officer?.submissions !== undefined && officer.submissions.length > 0
218
+ ? officer.submissions
219
+ : officerOutcome !== undefined && officerOutcome.kind !== "no_receipt"
220
+ ? officerOutcome.payloads ?? []
221
+ : [];
222
+
223
+ return {
224
+ ...parent,
225
+ roleOutcome: {
226
+ ...outcome,
227
+ decisiveFacts: {
228
+ ...(outcome.decisiveFacts ?? {}),
229
+ directionUnsettled: true,
230
+ subsequentAudit: "incomplete",
231
+ ...(officerOutcome === undefined ? {} : { officerRole: officerOutcome.role }),
232
+ ...(officer?.runId === undefined ? {} : { officerRunId: officer.runId }),
233
+ ...(officerPayloads.length === 0 ? {} : { officerPayloads }),
234
+ },
235
+ },
236
+ };
237
+ }
238
+
239
+ function heldUnreadableTerminal(terminal: TerminalResult | undefined): boolean {
240
+ if (terminal?.roleOutcome.kind !== "accepted") return false;
241
+ if (latestPayloadEscalated(terminal.roleOutcome)) return false;
242
+ const status = latestQueueStatus(terminal);
243
+ return typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status);
244
+ }
245
+
246
+ async function presentUnsettledAudit(
247
+ parent: TerminalResult,
248
+ officer: TerminalResult | undefined,
249
+ io: CliIo,
250
+ runDirectory: string,
251
+ ): Promise<TerminalResult> {
252
+ const terminal = withUnsettledDirection(parent, officer);
253
+ await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, runDirectory);
254
+ if (officer !== undefined) await presentTerminal(officer, { ...io, omitFailureStderrDiagnostic: true }, runDirectory);
255
+ return terminal;
256
+ }
257
+
170
258
  async function reaskUnreadablePostSubmissionStatus(
171
259
  admitted: AdmittedRoleInvocation,
172
260
  terminal: TerminalResult | undefined,
173
261
  env: InstructionSeatRunEnv,
174
262
  io: CliIo,
175
- ): Promise<SeatRunResult | undefined> {
176
- const message = unreadablePostSubmissionStatus(admitted, terminal);
263
+ message = unreadablePostSubmissionStatus(admitted, terminal),
264
+ ): Promise<SeatRunResult | typeof UNREADABLE_REASK_EXHAUSTED | undefined> {
177
265
  if (message === undefined) return undefined;
266
+ const next = withUnreadableReask(env);
267
+ if (next === undefined) return UNREADABLE_REASK_EXHAUSTED;
178
268
  return runPublicInstructionSeatResume(
179
- { runId: admitted.runId, message },
180
- { ...env, autoResumeLimit: 0 },
269
+ { runId: admitted.runId, summons: { instruction: message } },
270
+ next,
181
271
  io,
182
272
  );
183
273
  }
@@ -220,11 +310,6 @@ function initialPrompt(
220
310
  );
221
311
  }
222
312
 
223
- function infraFailure(role: PackagedRole) {
224
- const resolveRunnerKnownFailure = seatKnownFailureResolver(role);
225
- return resolveRunnerKnownFailure === undefined ? {} : { resolveRunnerKnownFailure };
226
- }
227
-
228
313
  function isBoardTicketSeat(
229
314
  seat: AdmittedRoleInvocation,
230
315
  ): seat is AdmittedRoleInvocation & { role: "diarist" } {
@@ -244,6 +329,14 @@ async function bindAndRelocateDiarist(
244
329
  return await relocateAdmittedRunToTicket(admitted, authority, lease);
245
330
  }
246
331
 
332
+ function packageFaultScope(
333
+ admitted: Pick<AdmittedRoleInvocation, "runDirectory" | "principal">,
334
+ env: InstructionSeatRunEnv,
335
+ io: CliIo,
336
+ ): SettlementCourtScope {
337
+ return { notePackageFault: packageFaultNoteFor(admitted, env, io) };
338
+ }
339
+
247
340
  function seatAdapters(
248
341
  admitted: AdmittedRoleInvocation,
249
342
  env: InstructionSeatRunEnv,
@@ -257,7 +350,6 @@ function seatAdapters(
257
350
  ...(present === "typed"
258
351
  ? { shouldPresentSettled: (terminal: TerminalResult) => isLawfulTypedTerminalOutcome(terminal.roleOutcome) }
259
352
  : {}),
260
- ...infraFailure(admitted.role),
261
353
  ...(packagedBindsBoardTicket(admitted.role)
262
354
  ? {
263
355
  beforeDispatch: async (seat: AdmittedRoleInvocation, lease?: RunWriterLease) => {
@@ -373,11 +465,18 @@ async function dispatchAdmitted(
373
465
  env,
374
466
  io,
375
467
  );
468
+ if (isUnreadableReaskExhausted(unreadableReask)) {
469
+ const terminal = result.terminal === undefined
470
+ ? undefined
471
+ : withUnsettledDirection(result.terminal);
472
+ await presentOriginalVolume(held, terminal, io, admitted.runDirectory);
473
+ return terminal === undefined ? result : { ...result, terminal };
474
+ }
376
475
  if (unreadableReask !== undefined) return unreadableReask;
377
476
  if (AUDITED_ROLES.has(admitted.role) && result.terminal?.roleOutcome.kind === "accepted") {
378
477
  return auditSubmittedRole(result, env, io);
379
478
  }
380
- for (const value of held) io.stdout(value);
479
+ await presentOriginalVolume(held, result.terminal, io, admitted.runDirectory);
381
480
  return result;
382
481
  }
383
482
 
@@ -454,7 +553,9 @@ async function runOmittedLensBatch(
454
553
  },
455
554
  artifacts: terminals.flatMap((item) => item.artifacts),
456
555
  };
457
- io.stdout(formatTerminalResult(terminal));
556
+ const childRunDirectory = children.completeness.admitted?.runDirectory ?? children.correctness.admitted?.runDirectory;
557
+ if (childRunDirectory !== undefined) await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, childRunDirectory);
558
+ else io.stdout(formatTerminalResult(terminal));
458
559
  return { exitCode: failed ? 1 : 0, terminal };
459
560
  }
460
561
 
@@ -559,23 +660,17 @@ async function runCountersignBody(
559
660
  await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
560
661
  admitted = { ...admitted, sourceRunPath: gateParentRunPath };
561
662
  }
562
- const turnProjection: RoleTurnRequestProjectionOptions = {
563
- ...projectPublicTurnAxes(env),
564
- ...(env.correlationId === undefined || env.correlationId.trim() === ""
565
- ? {}
566
- : { correlationId: env.correlationId }),
567
- continuation: {
568
- kind: "initial",
569
- prompt: (env.reviewReask ?? env.gateReviewInstruction)
570
- ?? buildInstructionTransportPrompt(
571
- admitted,
572
- engineSessionMaterialFromOptions({
573
- ...pickEngineAxis(env),
574
- packageRoot: env.packageRoot,
575
- }),
576
- ),
577
- },
578
- };
663
+ const turnProjection = roleTurnOptions(env, admitted, {
664
+ kind: "initial",
665
+ prompt: (env.reviewReask ?? env.gateReviewInstruction)
666
+ ?? buildInstructionTransportPrompt(
667
+ admitted,
668
+ engineSessionMaterialFromOptions({
669
+ ...pickEngineAxis(env),
670
+ packageRoot: env.packageRoot,
671
+ }),
672
+ ),
673
+ });
579
674
  const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
580
675
  const result = await runPostAdmissionOneShot({
581
676
  admitted,
@@ -624,7 +719,7 @@ export async function runPublicInstructionSeat(
624
719
  stdout: (value) => held.push(value), stderr: io.stderr,
625
720
  });
626
721
  if (result.terminal?.roleOutcome.kind === "accepted") return auditSubmittedRole(result, env, io);
627
- for (const value of held) io.stdout(value);
722
+ await presentOriginalVolume(held, result.terminal, io, result.admitted?.runDirectory);
628
723
  return result;
629
724
  }
630
725
 
@@ -789,7 +884,8 @@ export async function runPublicInstructionSeat(
789
884
  } catch (error) {
790
885
  return await presentControlledFailure(admitted, {
791
886
  timedOut: false, code: null, stderr: "", thrown: error,
792
- }, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
887
+ }, seatAdapters(admitted, env), env.principalAuthority, io, true,
888
+ packageFaultNoteFor(admitted, env, io)) as SeatRunResult;
793
889
  }
794
890
  if (outcome.admitted !== undefined && isUnboundRunDirectory(outcome.admitted.runDirectory)) {
795
891
  await recordChildDiaristRun(admitted, outcome.admitted.runId);
@@ -798,12 +894,13 @@ export async function runPublicInstructionSeat(
798
894
  return await presentControlledFailure(admitted, {
799
895
  timedOut: false, code: null, stderr: "",
800
896
  thrown: new Error(outcome.failedWithoutEscalate.diagnostic),
801
- }, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
897
+ }, seatAdapters(admitted, env), env.principalAuthority, io, true,
898
+ packageFaultNoteFor(admitted, env, io)) as SeatRunResult;
802
899
  }
803
900
  // The diarist's escalation pauses only the diarist's own run; call order
804
901
  // belongs to the caller, so the Secretariat still takes its own turn.
805
902
  if (outcome.identity.kind === "escalate" && outcome.terminal !== undefined) {
806
- io.stdout(formatTerminalResult(outcome.terminal));
903
+ await presentTerminal(outcome.terminal, { ...io, omitFailureStderrDiagnostic: true }, outcome.admitted?.runDirectory ?? admitted.runDirectory);
807
904
  }
808
905
  }
809
906
  return dispatchAdmitted(admitted, env, io);
@@ -879,12 +976,19 @@ export async function runPublicInstructionSeatResume(
879
976
  env,
880
977
  io,
881
978
  );
979
+ if (isUnreadableReaskExhausted(unreadableReask)) {
980
+ const terminal = result.terminal === undefined
981
+ ? undefined
982
+ : withUnsettledDirection(result.terminal);
983
+ await presentOriginalVolume(held, terminal, io, result.admitted?.runDirectory);
984
+ return terminal === undefined ? result : { ...result, terminal };
985
+ }
882
986
  if (unreadableReask !== undefined) return unreadableReask;
883
987
  if (result.admitted !== undefined && AUDITED_ROLES.has(result.admitted.role)
884
988
  && result.terminal?.roleOutcome.kind === "accepted") {
885
989
  return auditSubmittedRole(result, env, io);
886
990
  }
887
- for (const value of held) io.stdout(value);
991
+ await presentOriginalVolume(held, result.terminal, io, result.admitted?.runDirectory);
888
992
  return result;
889
993
  };
890
994
  let binding: Awaited<ReturnType<typeof readAuditorResumeBinding>>;
@@ -910,7 +1014,11 @@ const GATE_CHILD_ROLES = new Set(["notary", "auditor", "inspector", "countersign
910
1014
 
911
1015
  /**
912
1016
  * A gate child's conclusion outside the three states goes back to that child.
913
- * The words are the existing officer re-ask. A host failure stops here.
1017
+ * The words are the existing officer re-ask. Each reask spends this loop's own
1018
+ * copy of the configured ceiling; exhaustion keeps the terminal already in hand.
1019
+ * A host failure stops here.
1020
+ * Countersign already spends that ceiling inside its own seat, or the summoning
1021
+ * gate does. This loop does not open a second one for the same conclusion.
914
1022
  */
915
1023
  async function queueConclusionFromChild(
916
1024
  child: AdmittedRoleInvocation,
@@ -922,22 +1030,41 @@ async function queueConclusionFromChild(
922
1030
  | undefined
923
1031
  > {
924
1032
  if (!GATE_CHILD_ROLES.has(child.role)) return undefined;
925
- let current = child;
1033
+ if (child.role === "countersign") {
1034
+ const terminal = await trySettlePublicSeat(
1035
+ child, env.principalAuthority, { ...await readCurrentCourt(child.runDirectory), ...packageFaultScope(child, env, io) },
1036
+ );
1037
+ const status = latestQueueStatus(terminal);
1038
+ if (terminal !== undefined && typeof status === "string" && REVIEW_QUEUE_STATUSES.has(status)) {
1039
+ return { admitted: child, terminal, status };
1040
+ }
1041
+ return { stop: { exitCode: 0, ...(terminal === undefined ? {} : { terminal }) } };
1042
+ }
1043
+ let current: AdmittedRoleInvocation = child;
1044
+ // Notary, auditor, and inspector have no seat-local status reask. This loop
1045
+ // is their one budget. The child resume keeps the caller's env so delivery,
1046
+ // failure recovery, and other reask loops stay separate.
1047
+ let budgetEnv: InstructionSeatRunEnv = { ...env, unreadableReasksSpent: 0 };
926
1048
  for (;;) {
927
1049
  const terminal = await trySettlePublicSeat(
928
1050
  current,
929
1051
  env.principalAuthority,
930
- undefined,
1052
+ { ...await readCurrentCourt(current.runDirectory), ...packageFaultScope(current, env, io) },
931
1053
  );
932
1054
  const status = latestQueueStatus(terminal);
933
1055
  if (terminal !== undefined && typeof status === "string" && REVIEW_QUEUE_STATUSES.has(status)) {
934
1056
  return { admitted: current, terminal, status };
935
1057
  }
936
- const reasked = await runPublicInstructionSeatResume({
937
- runId: current.runId,
938
- message: officerConclusionReask(status),
939
- }, env, io);
940
- if (reasked.exitCode !== 0 || reasked.admitted === undefined || reasked.terminal === undefined) {
1058
+ const reasked = await reaskUnreadablePostSubmissionStatus(
1059
+ current, terminal, budgetEnv, io, officerConclusionReask(status),
1060
+ );
1061
+ if (reasked === undefined || isUnreadableReaskExhausted(reasked)) {
1062
+ return { stop: { exitCode: 0, ...(terminal === undefined ? {} : { terminal }) } };
1063
+ }
1064
+ budgetEnv = withUnreadableReask(budgetEnv)!;
1065
+ if (reasked.exitCode !== 0 || reasked.admitted === undefined || reasked.terminal === undefined
1066
+ || reasked.terminal.roleOutcome.kind === "no_receipt"
1067
+ || reasked.terminal.roleOutcome.decisiveFacts?.directionUnsettled === true) {
941
1068
  return { stop: reasked };
942
1069
  }
943
1070
  const reaskedStatus = latestQueueStatus(reasked.terminal);
@@ -962,34 +1089,90 @@ export async function continueParentAfterChild(
962
1089
  }
963
1090
  const admitted = loaded.admitted;
964
1091
  const resolved = await queueConclusionFromChild(child, env, io);
965
- if (resolved !== undefined && "stop" in resolved) return resolved.stop;
1092
+ if (resolved !== undefined && "stop" in resolved) {
1093
+ const stopped = resolved.stop;
1094
+ if (
1095
+ stopped.exitCode === 0
1096
+ && heldUnreadableTerminal(stopped.terminal)
1097
+ && stopped.terminal !== undefined
1098
+ ) {
1099
+ const parentTerminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
1100
+ if (
1101
+ parentTerminal?.roleOutcome.kind === "accepted"
1102
+ || parentTerminal?.roleOutcome.kind === "audit_escalation"
1103
+ ) {
1104
+ const terminal = await presentUnsettledAudit(parentTerminal, stopped.terminal, io, admitted.runDirectory);
1105
+ return { exitCode: 0, admitted, terminal };
1106
+ }
1107
+ }
1108
+ return stopped;
1109
+ }
966
1110
  if (resolved !== undefined && resolved.status === "escalate") {
967
1111
  return { exitCode: 0, admitted: resolved.admitted, terminal: resolved.terminal };
968
1112
  }
969
1113
  if (resolved !== undefined && (resolved.status === "continue" || resolved.status === "converged")) {
970
1114
  if (AUDITED_ROLES.has(admitted.role) && resolved.status === "converged") {
971
- const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
1115
+ const terminal = await trySettlePublicSeat(
1116
+ admitted,
1117
+ env.principalAuthority,
1118
+ packageFaultScope(admitted, env, io),
1119
+ );
972
1120
  if (terminal?.roleOutcome.kind !== "accepted") throw new Error("pending submission is not recorded");
973
- return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted.role, latestQueuePayload(resolved.terminal), resolved.admitted.runId);
1121
+ return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
974
1122
  }
975
1123
  return runPublicInstructionSeatResume({
976
1124
  runId: parentRunId,
977
- message: readableGateItem(latestQueuePayload(resolved.terminal)),
978
- }, { ...env, autoResumeLimit: 0 }, io);
1125
+ summons: { instruction: readableGateItem(latestQueuePayload(resolved.terminal)) },
1126
+ }, { ...env, unreadableReasksSpent: 0 }, io);
979
1127
  }
980
1128
  const result = await dispatchAdmitted(admitted, env, { ...io, omitFailureStderrDiagnostic: true });
981
1129
  showResumeErrorPointer(io, result.exitCode, result.terminal);
982
1130
  return result;
983
1131
  }
984
1132
 
1133
+ /**
1134
+ * A gate is already passed only when an officer run reviewed this submission
1135
+ * and its latest queue word is converged. The resumed seat's role is not itself
1136
+ * a pass. Unknown or unreadable evidence does not skip the gate.
1137
+ */
1138
+ async function officerPassedCurrentSubmission(input: {
1139
+ readonly env: InstructionSeatRunEnv;
1140
+ readonly parentSessionFile: string;
1141
+ readonly officer: GateOfficer;
1142
+ readonly toolCallId: string;
1143
+ }): Promise<boolean> {
1144
+ const { env, officer } = input;
1145
+ const pointer = await readDirectOfficerRunPointer(input.parentSessionFile, officer);
1146
+ if (pointer?.runDirectory === undefined) return false;
1147
+ if (pointer.submissionToolCallId !== input.toolCallId) return false;
1148
+ const officerRunId = runIdFromRunDirectory(pointer.runDirectory);
1149
+ if (officerRunId === undefined) return false;
1150
+ let admitted: AdmittedRoleInvocation;
1151
+ try {
1152
+ const loaded = await loadResumablePublicRole(
1153
+ env.home,
1154
+ officerRunId,
1155
+ env.principalAuthority,
1156
+ );
1157
+ admitted = loaded.admitted;
1158
+ } catch {
1159
+ return false;
1160
+ }
1161
+ if (admitted.role !== officer) return false;
1162
+ // A summon binding identifies the requested review, not its outcome. An open
1163
+ // court (including a failed host turn) must not borrow a previous court's seal.
1164
+ const court = await readCurrentCourt(admitted.runDirectory);
1165
+ const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, court);
1166
+ return latestQueueStatus(terminal) === "converged";
1167
+ }
1168
+
985
1169
  /** Finished submissions enter the existing audit gate after their tool call has returned. */
986
1170
  async function auditSubmittedRole(
987
1171
  turn: SeatRunResult,
988
1172
  env: InstructionSeatRunEnv,
989
1173
  io: CliIo,
990
- passedOfficer?: PackagedRole,
1174
+ resumedOfficer?: AdmittedRoleInvocation,
991
1175
  passedReceipt?: unknown,
992
- passedRunId?: string,
993
1176
  ): Promise<SeatRunResult> {
994
1177
  const admitted = turn.admitted;
995
1178
  if (admitted === undefined || !AUDITED_ROLES.has(admitted.role)
@@ -999,13 +1182,28 @@ async function auditSubmittedRole(
999
1182
  const record = isRecord(accepted)
1000
1183
  ? accepted as Record<string, unknown> : undefined;
1001
1184
  const status = admitted.role === "secretariat" ? record?.secretariatStatus : record?.status;
1185
+ // A gate summon already owns this conclusion's reasks. The seat must not
1186
+ // start another budget on the same run (parentRunPath is that summon).
1187
+ const gateOwnsStatusReask = typeof env.parentRunPath === "string" && env.parentRunPath.trim() !== "";
1002
1188
  if (admitted.role === "secretariat" && status !== "converged" && status !== "escalate") {
1003
- return runPublicInstructionSeatResume({ runId: admitted.runId, message: SECRETARIAT_STATUS_REASK },
1004
- { ...env, autoResumeLimit: 0 }, io);
1189
+ if (gateOwnsStatusReask) return turn;
1190
+ const next = withUnreadableReask(env);
1191
+ if (next === undefined) {
1192
+ const terminal = withUnsettledDirection(turn.terminal);
1193
+ await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1194
+ return { ...turn, terminal };
1195
+ }
1196
+ return runPublicInstructionSeatResume({ runId: admitted.runId, summons: { instruction: SECRETARIAT_STATUS_REASK } }, next, io);
1005
1197
  }
1006
1198
  if (admitted.role === "countersign" && (typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status))) {
1007
- return runPublicInstructionSeatResume({ runId: admitted.runId, message: officerConclusionReask(status) },
1008
- { ...env, autoResumeLimit: 0 }, io);
1199
+ if (gateOwnsStatusReask) return turn;
1200
+ const next = withUnreadableReask(env);
1201
+ if (next === undefined) {
1202
+ const terminal = withUnsettledDirection(turn.terminal);
1203
+ await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1204
+ return { ...turn, terminal };
1205
+ }
1206
+ return runPublicInstructionSeatResume({ runId: admitted.runId, summons: { instruction: officerConclusionReask(status) } }, next, io);
1009
1207
  }
1010
1208
  // Self-escalation is a valid open routing state for these seats only.
1011
1209
  // Doctor's declared domain is completed|refused — an open "escalate" must
@@ -1017,7 +1215,7 @@ async function auditSubmittedRole(
1017
1215
  || ((admitted.role === "fixer" || admitted.role === "coder")
1018
1216
  && (typeof status !== "string" || !WORKER_DONE_STATUSES.has(status)));
1019
1217
  if (skipAudit) {
1020
- io.stdout(formatTerminalResult(turn.terminal));
1218
+ await presentTerminal(turn.terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1021
1219
  return turn;
1022
1220
  }
1023
1221
  const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, env.home);
@@ -1025,7 +1223,9 @@ async function auditSubmittedRole(
1025
1223
  const toolCallId = acceptedRow?.toolCallId;
1026
1224
  if (toolCallId === undefined) throw new Error("accepted submission has no tool call identity");
1027
1225
  const sessionFile = env.principalAuthority.decode(admitted.principal).sessionFile;
1028
- const entries = admitted.role === "doctor" ? await readBoundSessionEntries(sessionFile) : [];
1226
+ const entries = admitted.role === "doctor"
1227
+ ? await readBoundSessionEntries(sessionFile)
1228
+ : [];
1029
1229
  const context: HostContext = {
1030
1230
  cwd: admitted.projectRoot,
1031
1231
  mode: "print",
@@ -1041,11 +1241,18 @@ async function auditSubmittedRole(
1041
1241
  },
1042
1242
  abort() {},
1043
1243
  };
1244
+ const passedThisSubmission = (officer: GateOfficer) => officerPassedCurrentSubmission({
1245
+ env,
1246
+ parentSessionFile: sessionFile,
1247
+ officer,
1248
+ toolCallId,
1249
+ });
1044
1250
  if (admitted.role === "doctor") {
1045
- if (passedOfficer !== "auditor") {
1251
+ if (!await passedThisSubmission("auditor")) {
1046
1252
  let lastSummon: PublicSummonResult | undefined;
1047
1253
  const decision = await createPiDoctorAuditor()({
1048
1254
  context, submission: accepted,
1255
+ ...(env.autoResumeLimit === undefined ? {} : { autoResumeLimit: env.autoResumeLimit }),
1049
1256
  ...(env.signal === undefined ? {} : { signal: env.signal }),
1050
1257
  summonAuditor: async (subject, sourceRunDirectory, signal, reask, submission) => {
1051
1258
  const summoned = await summonPublicRole({
@@ -1065,6 +1272,7 @@ async function auditSubmittedRole(
1065
1272
  if (officerSession !== undefined) {
1066
1273
  bookDirectOfficerRunPointer({
1067
1274
  parentSessionFile: sessionFile, officer: "auditor", sessionFile: officerSession,
1275
+ submissionToolCallId: toolCallId,
1068
1276
  ...(summoned.runDirectory === undefined ? {} : { runDirectory: summoned.runDirectory }),
1069
1277
  });
1070
1278
  }
@@ -1074,12 +1282,20 @@ async function auditSubmittedRole(
1074
1282
  });
1075
1283
  if (decision.status === "continue") {
1076
1284
  return runPublicInstructionSeatResume({
1077
- runId: admitted.runId, message: readableGateItem(decision.receipt ?? decision.violations),
1078
- }, { ...env, autoResumeLimit: 0 }, io);
1285
+ runId: admitted.runId, summons: { instruction: readableGateItem(decision.receipt ?? decision.violations) },
1286
+ }, { ...env, unreadableReasksSpent: 0 }, io);
1287
+ }
1288
+ if (decision.status === "received") {
1289
+ if (lastSummon?.terminal === undefined) {
1290
+ throw new Error("doctor audit kept an unreadable reply with no terminal");
1291
+ }
1292
+ if (turn.terminal === undefined) throw new Error("doctor submission has no terminal");
1293
+ const terminal = await presentUnsettledAudit(turn.terminal, lastSummon.terminal, io, admitted.runDirectory);
1294
+ return { exitCode: 0, admitted, terminal };
1079
1295
  }
1080
1296
  if (decision.status !== "converged") {
1081
1297
  if (lastSummon?.terminal !== undefined) {
1082
- io.stdout(formatTerminalResult(lastSummon.terminal));
1298
+ await presentTerminal(lastSummon.terminal, { ...io, omitFailureStderrDiagnostic: true }, lastSummon.admitted?.runDirectory ?? admitted.runDirectory);
1083
1299
  return { exitCode: lastSummon.exitCode,
1084
1300
  ...(lastSummon.admitted === undefined ? {} : { admitted: lastSummon.admitted }),
1085
1301
  terminal: lastSummon.terminal };
@@ -1096,7 +1312,7 @@ async function auditSubmittedRole(
1096
1312
  ...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
1097
1313
  });
1098
1314
  let chain: Awaited<ReturnType<typeof runJudgeGates>>;
1099
- let officerRunId = passedRunId;
1315
+ let officerRunId = resumedOfficer?.runId;
1100
1316
  const runGate = (subject: Parameters<typeof requireSubmissionGate>[0]["subject"]) =>
1101
1317
  requireSubmissionGate({
1102
1318
  context,
@@ -1104,6 +1320,7 @@ async function auditSubmittedRole(
1104
1320
  toolCallId,
1105
1321
  submission: accepted,
1106
1322
  summonOfficer,
1323
+ ...(env.autoResumeLimit === undefined ? {} : { autoResumeLimit: env.autoResumeLimit }),
1107
1324
  ...(env.signal === undefined ? {} : { signal: env.signal }),
1108
1325
  hostActions: {
1109
1326
  failInfrastructure(error): never { throw error; },
@@ -1112,8 +1329,7 @@ async function auditSubmittedRole(
1112
1329
  });
1113
1330
  if (admitted.role === "judge") {
1114
1331
  chain = await runJudgeGates({
1115
- gateAlreadyConverged: async (subject) => passedOfficer === "auditor"
1116
- || (passedOfficer === "notary" && subject.kind === "judge_draft"),
1332
+ gateAlreadyConverged: async (subject) => passedThisSubmission(gateOfficerForSubject(subject)),
1117
1333
  runGate,
1118
1334
  });
1119
1335
  } else {
@@ -1123,7 +1339,7 @@ async function auditSubmittedRole(
1123
1339
  ? { kind: "secretariat_verdict" as const }
1124
1340
  : { kind: "countersign_verdict" as const };
1125
1341
  const officer = gateOfficerForSubject(subject);
1126
- if (passedOfficer === officer) {
1342
+ if (await passedThisSubmission(officer)) {
1127
1343
  chain = { status: "converged", passes: [] };
1128
1344
  } else {
1129
1345
  const pass = await runGate(subject);
@@ -1137,10 +1353,18 @@ async function auditSubmittedRole(
1137
1353
  receipt: pass.receipt,
1138
1354
  ...(pass.runId === undefined ? {} : { runId: pass.runId }),
1139
1355
  ...(pass.runDirectory === undefined ? {} : { runDirectory: pass.runDirectory }),
1356
+ ...(pass.terminal === undefined ? {} : { terminal: pass.terminal }),
1140
1357
  }],
1141
1358
  };
1142
1359
  }
1143
1360
  }
1361
+ if (chain.status === "needs_reask") {
1362
+ const officerTerminal = chain.passes.at(-1)?.terminal;
1363
+ if (officerTerminal === undefined) throw new Error("unreadable audit has no terminal result");
1364
+ if (turn.terminal === undefined) throw new Error("audited submission has no terminal");
1365
+ const terminal = await presentUnsettledAudit(turn.terminal, officerTerminal, io, admitted.runDirectory);
1366
+ return { exitCode: 0, admitted, terminal };
1367
+ }
1144
1368
  if (chain.status === "escalate") {
1145
1369
  const escalation = chain.passes.at(-1);
1146
1370
  const escalatedRunId = escalation?.runId
@@ -1148,16 +1372,20 @@ async function auditSubmittedRole(
1148
1372
  ? undefined : runIdFromRunDirectory(escalation.runDirectory));
1149
1373
  if (escalatedRunId === undefined) throw new Error("escalated audit has no resumable run identity");
1150
1374
  const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority);
1151
- const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority, undefined);
1375
+ const terminal = await trySettlePublicSeat(
1376
+ officer.admitted,
1377
+ env.principalAuthority,
1378
+ { ...await readCurrentCourt(officer.admitted.runDirectory), ...packageFaultScope(officer.admitted, env, io) },
1379
+ );
1152
1380
  if (terminal === undefined) throw new Error("escalated audit has no terminal result");
1153
- io.stdout(formatTerminalResult(terminal));
1381
+ await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, officer.admitted.runDirectory);
1154
1382
  return { exitCode: 0, admitted: officer.admitted, terminal };
1155
1383
  }
1156
1384
  if (chain.status === "continue") {
1157
1385
  return runPublicInstructionSeatResume({
1158
1386
  runId: admitted.runId,
1159
- message: readableGateItem(chain.passes.at(-1)?.receipt),
1160
- }, { ...env, autoResumeLimit: 0 }, io);
1387
+ summons: { instruction: readableGateItem(chain.passes.at(-1)?.receipt) },
1388
+ }, { ...env, unreadableReasksSpent: 0 }, io);
1161
1389
  }
1162
1390
  if (admitted.role === "secretariat") {
1163
1391
  const pass = chain.passes.at(-1);
@@ -1174,8 +1402,13 @@ async function auditSubmittedRole(
1174
1402
  // Audit only adds gate rounds and, for 中书省, the officer fact. A second
1175
1403
  // settle would republish the same attempt.
1176
1404
  if (turn.terminal === undefined) throw new Error(`audited ${admitted.role} submission did not settle`);
1177
- const terminal = await attachPostAuditProjection(admitted, env.principalAuthority, turn.terminal);
1405
+ const terminal = await attachPostAuditProjection(
1406
+ admitted,
1407
+ env.principalAuthority,
1408
+ turn.terminal,
1409
+ packageFaultScope(admitted, env, io),
1410
+ );
1178
1411
  turn = { ...turn, terminal };
1179
- io.stdout(formatTerminalResult(terminal));
1412
+ await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1180
1413
  return turn;
1181
1414
  }