@akagilnc/pi-workflow-roles 0.1.5148 → 0.1.5194

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 (50) hide show
  1. package/dist/acp-host/production-host.js +13086 -13034
  2. package/dist/archivist-record-entry.js +4 -20
  3. package/dist/archivist-record-pointer.js +28 -0
  4. package/dist/audit-escalation.js +3 -119
  5. package/dist/auditor-soul.js +32 -0
  6. package/dist/doctor-auditor.js +26 -0
  7. package/dist/headless-host/production-host.js +11218 -11166
  8. package/dist/judge-role.js +73 -0
  9. package/dist/migrate-book-topology.js +8 -0
  10. package/dist/package-contracts/judge-output.js +1 -18
  11. package/dist/package-contracts/terminating-tools.js +2 -2
  12. package/dist/public-cli/cli.js +2 -2
  13. package/dist/public-cli/countersign-run.js +11 -0
  14. package/dist/public-cli/instruction-seat-run.js +428 -48
  15. package/dist/public-cli/invocation.js +10 -5
  16. package/dist/public-cli/main.js +10049 -8911
  17. package/dist/public-cli/settlement.js +28 -8
  18. package/dist/public-role-summons.js +6 -4
  19. package/dist/submission-errors.js +1 -1
  20. package/dist/submission-gate.js +165 -0
  21. package/dist/submission-ledger.js +0 -27
  22. package/dist/worker-submission-contracts.js +8 -0
  23. package/package.json +1 -1
  24. package/src/archivist-record-entry.ts +5 -49
  25. package/src/archivist-record-pointer.ts +49 -0
  26. package/src/audit-escalation.ts +4 -217
  27. package/src/auditor-soul.ts +33 -0
  28. package/src/doctor-role.ts +11 -5
  29. package/src/gatekeeper-role.ts +2 -2
  30. package/src/host-contracts.ts +0 -25
  31. package/src/judge-role.ts +63 -90
  32. package/src/package-contracts/judge-output.ts +0 -17
  33. package/src/package-contracts/terminating-tools.ts +0 -2
  34. package/src/pi/adapter.ts +1 -33
  35. package/src/public-cli/cli.ts +2 -2
  36. package/src/public-cli/countersign-run.ts +11 -0
  37. package/src/public-cli/instruction-seat-run.ts +433 -16
  38. package/src/public-cli/invocation.ts +9 -3
  39. package/src/public-cli/post-admission.ts +0 -1
  40. package/src/public-cli/settlement.ts +32 -8
  41. package/src/public-role-summons.ts +9 -6
  42. package/src/role-envelope.ts +0 -32
  43. package/src/role-runtime-dependencies.ts +0 -4
  44. package/src/role-runtime.ts +15 -218
  45. package/src/submission-errors.ts +1 -1
  46. package/src/submission-gate.ts +46 -10
  47. package/src/submission-ledger.ts +0 -27
  48. package/src/worker-role.ts +23 -102
  49. package/src/worker-submission-contracts.ts +8 -0
  50. package/src/worker-submission-gates.ts +2 -2
@@ -4,22 +4,89 @@
4
4
  * starts two ordinary single-axis runs here. Countersign keeps deferred
5
5
  * identity on this entry; the court diarist station stays in countersign-run.
6
6
  */
7
- import { resolve, sep } from "node:path";
7
+ import { dirname, resolve, sep } from "node:path";
8
+ import { AUDITOR_DOSSIER_PROMPT } from "../compliance-transport.js";
9
+ import { bookDirectOfficerRunPointer } from "../archivist-record-pointer.js";
10
+ import { createPiDoctorAuditor } from "../doctor-auditor.js";
11
+ import { officerConclusionReask, gateOfficerForSubject } from "../gatekeeper-role.js";
12
+ import { runJudgeGates } from "../judge-role.js";
13
+ import { WORKER_DONE_STATUSES } from "../worker-submission-contracts.js";
14
+ import { SECRETARIAT_GATE_OFFICER_ENTRY_TYPE } from "../secretariat-contracts.js";
15
+ import { readableGateItem } from "../readable-gate-item.js";
16
+ import { runIdFromRunDirectory } from "../run-terminal-artifacts.js";
17
+ import { createDefaultGateOfficerSummon, requireSubmissionGate } from "../submission-gate.js";
18
+ import { readRecordedSubmissionRows } from "../submission-ledger.js";
19
+ import { summonPublicRole } from "../public-role-summons.js";
20
+ import { sessionFileFromPublicSummon } from "../session-assistant-usage.js";
21
+ import { latestQueuePayload, latestQueueStatus, } from "../submission-gate.js";
8
22
  import { isSafePositiveTicketNumber, readBoardTicketNumber } from "../run-ticket-number.js";
9
23
  import { NotarySourceRunError, resolveNotarySourceRunLocator } from "../notary-source-run.js";
10
24
  import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.js";
11
25
  import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedRebindSourceOnResume, packagedRoleMetadata, } from "../packaged-role-registry.js";
12
- import { isAuditorSoulRole } from "../auditor-soul.js";
26
+ import { isAuditorSoulRole, readAuditorResumeBinding } from "../auditor-soul.js";
13
27
  import { CliUsageError } from "./cli-errors.js";
14
28
  import { admitPublicRole, bindAdmittedTicketNumber, bindCourtTicketNumbersOnAdmitted, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
15
29
  import { presentControlledFailure, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
16
30
  import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, } from "./run-lifecycle.js";
17
31
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
18
- import { presentStructuralRejection, seatKnownFailureResolver, trySettlePublicSeat, } from "./settlement.js";
32
+ import { presentStructuralRejection, readBoundSessionEntries, seatKnownFailureResolver, attachPostAuditProjection, trySettlePublicSeat, } from "./settlement.js";
19
33
  import { warnMissingMethodSkills } from "./machine-method-skills.js";
20
34
  import { formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
21
35
  import { admittedSeatTurnDetails, projectRoleTurnRequest, requiredMethodSkills, } from "./turn-request.js";
22
- import { invokeCourtDiarist, runCountersignCourtDiaristStation, } from "./countersign-run.js";
36
+ import { invokeCourtDiarist, latestPayloadEscalated, runCountersignCourtDiaristStation, } from "./countersign-run.js";
37
+ const AUDITED_ROLES = new Set(["judge", "fixer", "coder", "secretariat", "countersign", "doctor"]);
38
+ const SECRETARIAT_STATUS_REASK = "secretariatStatus 不是 converged、escalate 之一。请重新交卷,secretariatStatus 写明其一。";
39
+ const COUNTERSIGN_STATUS_REASK = "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。";
40
+ const WORKER_ROUTING_STATUSES = new Set([
41
+ "planned", "completed", "refused", "partially_completed", "unfinished",
42
+ ]);
43
+ const WORKER_STATUS_REASK = "status 不是 planned、completed、refused、partially_completed、unfinished 之一。请重新交卷,status 写明其一。";
44
+ const DOCTOR_ROUTING_STATUSES = new Set(["completed", "refused"]);
45
+ const DOCTOR_STATUS_REASK = "status 不是 completed、refused 之一。请重新交卷,status 写明其一。";
46
+ const POST_SUBMISSION_ROUTING = {
47
+ judge: {
48
+ statuses: new Set(["converged", "continue", "escalate"]),
49
+ reask: "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。",
50
+ },
51
+ coder: {
52
+ statuses: WORKER_ROUTING_STATUSES,
53
+ reask: WORKER_STATUS_REASK,
54
+ },
55
+ fixer: {
56
+ statuses: WORKER_ROUTING_STATUSES,
57
+ reask: WORKER_STATUS_REASK,
58
+ },
59
+ diarist: {
60
+ statuses: new Set(["completed", "escalate"]),
61
+ reask: "status 不是 completed、escalate 之一。请重新交卷,status 写明其一。",
62
+ },
63
+ // Doctor contract domain is completed|refused; open-domain escalate is accepted
64
+ // by the tool then routed back here before mandatory auditor (#1057).
65
+ doctor: {
66
+ statuses: DOCTOR_ROUTING_STATUSES,
67
+ reask: DOCTOR_STATUS_REASK,
68
+ },
69
+ };
70
+ function unreadablePostSubmissionStatus(admitted, terminal) {
71
+ if (terminal?.roleOutcome.kind !== "accepted")
72
+ return undefined;
73
+ const route = POST_SUBMISSION_ROUTING[admitted.role];
74
+ if (route === undefined)
75
+ return undefined;
76
+ const payload = terminal.roleOutcome.payloads?.at(-1);
77
+ const status = payload !== null && typeof payload === "object" && !Array.isArray(payload)
78
+ ? payload.status
79
+ : undefined;
80
+ return typeof status === "string" && route.statuses.has(status)
81
+ ? undefined
82
+ : route.reask;
83
+ }
84
+ async function reaskUnreadablePostSubmissionStatus(admitted, terminal, env, io) {
85
+ const message = unreadablePostSubmissionStatus(admitted, terminal);
86
+ if (message === undefined)
87
+ return undefined;
88
+ return runPublicInstructionSeatResume({ runId: admitted.runId, message }, { ...env, autoResumeLimit: 0 }, io);
89
+ }
23
90
  function roleRecord(role) {
24
91
  const record = packagedRoleMetadata(role);
25
92
  if (record === undefined) {
@@ -128,13 +195,21 @@ async function dispatchAdmitted(admitted, env, io) {
128
195
  const record = roleRecord(admitted.role);
129
196
  await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
130
197
  const adapters = seatAdapters(admitted, env);
198
+ const held = [];
199
+ const turnIo = AUDITED_ROLES.has(admitted.role)
200
+ || POST_SUBMISSION_ROUTING[admitted.role] !== undefined
201
+ ? { stdout: (value) => held.push(value), stderr: io.stderr }
202
+ : io;
131
203
  const execute = async (activeEnv) => {
204
+ if (activeEnv.correlationId !== undefined && activeEnv.correlationId.trim() !== "") {
205
+ await recordAdmittedCorrelation(admitted, activeEnv.correlationId);
206
+ }
132
207
  const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
133
208
  if (auto) {
134
209
  return await runPostAdmissionResumable({
135
210
  admitted,
136
211
  env: activeEnv,
137
- io,
212
+ io: turnIo,
138
213
  buildInitialRequest: () => buildInstructionSeatTurnRequest(admitted, roleTurnOptions(activeEnv, admitted, { kind: "initial", prompt: initialPrompt(admitted, activeEnv) })),
139
214
  buildResumeRequest: () => buildInstructionSeatTurnRequest(admitted, roleTurnOptions(activeEnv, admitted, {
140
215
  kind: "resume",
@@ -150,13 +225,22 @@ async function dispatchAdmitted(admitted, env, io) {
150
225
  return await runPostAdmissionOneShot({
151
226
  admitted,
152
227
  env: activeEnv,
153
- io,
228
+ io: turnIo,
154
229
  request: buildInstructionSeatTurnRequest(admitted, roleTurnOptions(activeEnv, admitted, { kind: "initial", prompt: initialPrompt(admitted, activeEnv) })),
155
230
  adapters,
156
231
  ...(activeEnv.engine === undefined ? {} : { effectiveEngine: activeEnv.engine }),
157
232
  });
158
233
  };
159
- return execute(env);
234
+ const result = await execute(env);
235
+ const unreadableReask = await reaskUnreadablePostSubmissionStatus(admitted, result.terminal, env, io);
236
+ if (unreadableReask !== undefined)
237
+ return unreadableReask;
238
+ if (AUDITED_ROLES.has(admitted.role) && result.terminal?.roleOutcome.kind === "accepted") {
239
+ return auditSubmittedRole(result, env, io);
240
+ }
241
+ for (const value of held)
242
+ io.stdout(value);
243
+ return result;
160
244
  }
161
245
  /**
162
246
  * Omitted lens on a parallel-lens seat: two ordinary single-axis runs, no parent run.
@@ -179,7 +263,7 @@ async function runOmittedLensBatch(argv, parsed, env, io) {
179
263
  packageRoot: env.packageRoot,
180
264
  ...(env.signal === undefined ? {} : { signal: env.signal }),
181
265
  ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
182
- roleTurnHost: env.roleTurnHost,
266
+ ...(env.hostAdapters === undefined ? { roleTurnHost: env.roleTurnHost } : {}),
183
267
  ...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
184
268
  principalAuthority: env.principalAuthority,
185
269
  ...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
@@ -421,7 +505,15 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
421
505
  throw error;
422
506
  }
423
507
  if (packagedAdmitsCountersign(role)) {
424
- return runCountersignBody(parsed, env, io);
508
+ const held = [];
509
+ const result = await runCountersignBody(parsed, env, {
510
+ stdout: (value) => held.push(value), stderr: io.stderr,
511
+ });
512
+ if (result.terminal?.roleOutcome.kind === "accepted")
513
+ return auditSubmittedRole(result, env, io);
514
+ for (const value of held)
515
+ io.stdout(value);
516
+ return result;
425
517
  }
426
518
  if ("parallelLenses" in record && record.parallelLenses === true) {
427
519
  if (parsed.baseRevision === undefined) {
@@ -559,7 +651,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
559
651
  }
560
652
  }
561
653
  if (record.sameParent === "subject-source" && auditorSource !== undefined) {
562
- await persistAdmittedSourceRunPath(admitted, auditorSource);
654
+ await persistAdmittedSourceRunPath(admitted, auditorSource, auditorSubject);
563
655
  }
564
656
  const runAdmitted = async () => {
565
657
  await markRunAdmitted(admitted, env.principalAuthority);
@@ -605,57 +697,345 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
605
697
  return runAdmitted();
606
698
  }
607
699
  export async function runPublicInstructionSeatResume(request, env, io) {
608
- return runPostAdmissionSeatResume({
609
- request,
610
- env,
611
- io,
612
- load: async (effective) => {
613
- const loaded = await loadResumablePublicRole(env.home, effective.runId, env.principalAuthority);
614
- if (packagedRebindSourceOnResume(loaded.admitted.role)
615
- && effective.summons?.sourceRunPath !== undefined
616
- && effective.summons.sourceRun !== undefined) {
617
- return {
618
- ...loaded,
619
- admitted: {
620
- ...loaded.admitted,
621
- sourceRunPath: effective.summons.sourceRunPath,
622
- sourceRun: effective.summons.sourceRun,
623
- },
624
- };
625
- }
626
- return loaded;
627
- },
628
- buildTurnRequest: async (admitted, effective) => {
629
- const summonsPrepared = await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
630
- return buildInstructionSeatTurnRequest(admitted, {
631
- ...resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared),
632
- });
633
- },
634
- adapters: {
635
- trySettle: async () => undefined,
636
- },
637
- afterAdmittedLoad: async (admitted) => {
638
- await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
639
- return { kind: "continue", adapters: seatAdapters(admitted, env) };
640
- },
641
- ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
642
- });
700
+ const resume = async () => {
701
+ const held = [];
702
+ const turnIo = {
703
+ stdout: (value) => held.push(value),
704
+ stderr: io.stderr,
705
+ };
706
+ const result = await runPostAdmissionSeatResume({
707
+ request,
708
+ env,
709
+ io: turnIo,
710
+ load: async (effective) => {
711
+ const loaded = await loadResumablePublicRole(env.home, effective.runId, env.principalAuthority);
712
+ if (packagedRebindSourceOnResume(loaded.admitted.role)
713
+ && effective.summons?.sourceRunPath !== undefined
714
+ && effective.summons.sourceRun !== undefined) {
715
+ return {
716
+ ...loaded,
717
+ admitted: {
718
+ ...loaded.admitted,
719
+ sourceRunPath: effective.summons.sourceRunPath,
720
+ sourceRun: effective.summons.sourceRun,
721
+ },
722
+ };
723
+ }
724
+ return loaded;
725
+ },
726
+ buildTurnRequest: async (admitted, effective) => {
727
+ const summonsPrepared = await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
728
+ return buildInstructionSeatTurnRequest(admitted, {
729
+ ...resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared),
730
+ });
731
+ },
732
+ adapters: {
733
+ trySettle: async () => undefined,
734
+ },
735
+ afterAdmittedLoad: async (admitted) => {
736
+ await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
737
+ return { kind: "continue", adapters: seatAdapters(admitted, env) };
738
+ },
739
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
740
+ });
741
+ const unreadableReask = result.admitted === undefined
742
+ ? undefined
743
+ : await reaskUnreadablePostSubmissionStatus(result.admitted, result.terminal, env, io);
744
+ if (unreadableReask !== undefined)
745
+ return unreadableReask;
746
+ if (result.admitted !== undefined && AUDITED_ROLES.has(result.admitted.role)
747
+ && result.terminal?.roleOutcome.kind === "accepted") {
748
+ return auditSubmittedRole(result, env, io);
749
+ }
750
+ for (const value of held)
751
+ io.stdout(value);
752
+ return result;
753
+ };
754
+ let binding;
755
+ try {
756
+ const loaded = await loadResumablePublicRole(env.home, request.runId, env.principalAuthority, true);
757
+ binding = loaded.admitted.role === "auditor"
758
+ ? await readAuditorResumeBinding(loaded.admitted.runDirectory)
759
+ : undefined;
760
+ }
761
+ catch (error) {
762
+ if (!(error instanceof CliUsageError))
763
+ throw error;
764
+ }
765
+ if (binding !== undefined) {
766
+ return withAuditorSoulEnv({
767
+ subject: binding.subject,
768
+ sourceRunDirectory: binding.sourceRunDirectory,
769
+ run: resume,
770
+ });
771
+ }
772
+ return resume();
643
773
  }
644
- /** Continue the parent once any escalated child has submitted. */
774
+ const QUEUE_CONCLUSIONS = new Set(["converged", "continue", "escalate"]);
775
+ const GATE_CHILD_ROLES = new Set(["notary", "auditor", "inspector", "countersign"]);
776
+ /**
777
+ * A gate child's conclusion outside the three states goes back to that child.
778
+ * The words are the existing officer re-ask. A host failure stops here.
779
+ */
780
+ async function queueConclusionFromChild(child, env, io) {
781
+ if (!GATE_CHILD_ROLES.has(child.role))
782
+ return undefined;
783
+ let current = child;
784
+ for (;;) {
785
+ const terminal = await trySettlePublicSeat(current, env.principalAuthority, undefined);
786
+ const status = latestQueueStatus(terminal);
787
+ if (terminal !== undefined && status !== undefined && QUEUE_CONCLUSIONS.has(status)) {
788
+ return { admitted: current, terminal, status };
789
+ }
790
+ const reasked = await runPublicInstructionSeatResume({
791
+ runId: current.runId,
792
+ message: officerConclusionReask(status),
793
+ }, env, io);
794
+ if (reasked.exitCode !== 0 || reasked.admitted === undefined || reasked.terminal === undefined) {
795
+ return { stop: reasked };
796
+ }
797
+ const reaskedStatus = latestQueueStatus(reasked.terminal);
798
+ if (reaskedStatus !== undefined && QUEUE_CONCLUSIONS.has(reaskedStatus)) {
799
+ return { admitted: reasked.admitted, terminal: reasked.terminal, status: reaskedStatus };
800
+ }
801
+ if (latestPayloadEscalated(reasked.terminal.roleOutcome))
802
+ return { stop: reasked };
803
+ current = reasked.admitted;
804
+ }
805
+ }
806
+ /** Continue the parent once a child has submitted. The child words ride the existing resume. */
645
807
  export async function continueParentAfterChild(parentRunId, child, env, io) {
646
808
  const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority, true);
647
- if (loaded.run.state !== "admitted") {
809
+ if (loaded.run.state !== "admitted" && !AUDITED_ROLES.has(loaded.admitted.role)) {
648
810
  return runPublicInstructionSeatResume({ runId: parentRunId }, env, io);
649
811
  }
650
812
  const admitted = loaded.admitted;
651
- if (admitted.role === "countersign" && admitted.ticketNumber !== undefined) {
813
+ // Court diarist refresh advances only after a diarist child; gate officers
814
+ // (notary/auditor/…) resume straight into queueConclusionFromChild (#1057).
815
+ if (admitted.role === "countersign" && admitted.ticketNumber !== undefined
816
+ && child.role === "diarist") {
652
817
  const refresh = await runCountersignCourtDiaristStation(admitted, env, io, child.ticketNumber);
653
818
  if (refresh !== undefined) {
654
819
  io.stdout(formatTerminalResult(refresh.terminal));
655
820
  return { exitCode: 0, admitted, terminal: refresh.terminal };
656
821
  }
657
822
  }
823
+ const resolved = await queueConclusionFromChild(child, env, io);
824
+ if (resolved !== undefined && "stop" in resolved)
825
+ return resolved.stop;
826
+ if (resolved !== undefined && resolved.status === "escalate") {
827
+ return { exitCode: 0, admitted: resolved.admitted, terminal: resolved.terminal };
828
+ }
829
+ if (resolved !== undefined && (resolved.status === "continue" || resolved.status === "converged")) {
830
+ if (AUDITED_ROLES.has(admitted.role) && resolved.status === "converged") {
831
+ const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
832
+ if (terminal?.roleOutcome.kind !== "accepted")
833
+ throw new Error("pending submission is not recorded");
834
+ return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted.role, latestQueuePayload(resolved.terminal), resolved.admitted.runId);
835
+ }
836
+ return runPublicInstructionSeatResume({
837
+ runId: parentRunId,
838
+ message: readableGateItem(latestQueuePayload(resolved.terminal)),
839
+ }, { ...env, autoResumeLimit: 0 }, io);
840
+ }
658
841
  const result = await dispatchAdmitted(admitted, env, { ...io, omitFailureStderrDiagnostic: true });
659
842
  showResumeErrorPointer(io, result.exitCode, result.terminal);
660
843
  return result;
661
844
  }
845
+ /** Finished submissions enter the existing audit gate after their tool call has returned. */
846
+ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, passedRunId) {
847
+ const admitted = turn.admitted;
848
+ if (admitted === undefined || !AUDITED_ROLES.has(admitted.role)
849
+ || turn.terminal?.roleOutcome.kind !== "accepted")
850
+ return turn;
851
+ const accepted = turn.terminal.roleOutcome.payloads?.at(-1);
852
+ if (accepted === undefined)
853
+ throw new Error("accepted submission has no payload");
854
+ const record = accepted !== null && typeof accepted === "object" && !Array.isArray(accepted)
855
+ ? accepted : undefined;
856
+ const status = admitted.role === "secretariat" ? record?.secretariatStatus : record?.status;
857
+ if (admitted.role === "secretariat" && status !== "converged" && status !== "escalate") {
858
+ return runPublicInstructionSeatResume({ runId: admitted.runId, message: SECRETARIAT_STATUS_REASK }, { ...env, autoResumeLimit: 0 }, io);
859
+ }
860
+ if (admitted.role === "countersign" && (typeof status !== "string" || !QUEUE_CONCLUSIONS.has(status))) {
861
+ return runPublicInstructionSeatResume({ runId: admitted.runId, message: COUNTERSIGN_STATUS_REASK }, { ...env, autoResumeLimit: 0 }, io);
862
+ }
863
+ // Self-escalation is a valid open routing state for these seats only.
864
+ // Doctor's declared domain is completed|refused — an open "escalate" must
865
+ // not skip its mandatory auditor (#1057 / PR #1075).
866
+ const selfEscalationSkipsAudit = status === "escalate"
867
+ && (admitted.role === "judge" || admitted.role === "secretariat"
868
+ || admitted.role === "countersign");
869
+ const skipAudit = selfEscalationSkipsAudit
870
+ || ((admitted.role === "fixer" || admitted.role === "coder")
871
+ && (typeof status !== "string" || !WORKER_DONE_STATUSES.has(status)));
872
+ if (skipAudit) {
873
+ io.stdout(formatTerminalResult(turn.terminal));
874
+ return turn;
875
+ }
876
+ const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, env.home);
877
+ const acceptedRow = [...rows].reverse().find((row) => row.role === admitted.role && row.kind === "accepted");
878
+ const toolCallId = acceptedRow?.toolCallId;
879
+ if (toolCallId === undefined)
880
+ throw new Error("accepted submission has no tool call identity");
881
+ const sessionFile = env.principalAuthority.decode(admitted.principal).sessionFile;
882
+ const entries = admitted.role === "doctor" ? await readBoundSessionEntries(sessionFile) : [];
883
+ const context = {
884
+ cwd: admitted.projectRoot,
885
+ mode: "print",
886
+ model: undefined,
887
+ runDirectory: admitted.runDirectory,
888
+ sessionManager: {
889
+ getSessionFile: () => sessionFile,
890
+ getSessionDir: () => dirname(sessionFile),
891
+ getEntries: () => entries,
892
+ getLeafEntry: () => undefined,
893
+ getLeafId: () => admitted.runId,
894
+ getHeader: () => ({ type: "session", id: admitted.runId }),
895
+ },
896
+ abort() { },
897
+ };
898
+ if (admitted.role === "doctor") {
899
+ if (passedOfficer !== "auditor") {
900
+ let lastSummon;
901
+ const decision = await createPiDoctorAuditor()({
902
+ context, submission: accepted,
903
+ ...(env.signal === undefined ? {} : { signal: env.signal }),
904
+ summonAuditor: async (subject, sourceRunDirectory, signal, reask, submission) => {
905
+ const summoned = await summonPublicRole({
906
+ role: "auditor",
907
+ argv: ["--subject", subject, "--source-run", sourceRunDirectory,
908
+ `${AUDITOR_DOSSIER_PROMPT}${sourceRunDirectory}`],
909
+ cwd: admitted.projectRoot, home: env.home, packageRoot: env.packageRoot,
910
+ principalAuthority: env.principalAuthority,
911
+ correlationId: admitted.runId,
912
+ ...(env.credentials === undefined ? {} : { credentials: env.credentials }),
913
+ ...(env.hostAdapters === undefined ? { roleTurnHost: env.roleTurnHost } : { hostAdapters: env.hostAdapters }),
914
+ ...(signal === undefined ? {} : { signal }),
915
+ ...(reask === undefined ? {} : { reviewReask: reask }),
916
+ ...(submission === undefined ? {} : { gateReviewInstruction: submission }),
917
+ });
918
+ const officerSession = sessionFileFromPublicSummon(summoned);
919
+ if (officerSession !== undefined) {
920
+ bookDirectOfficerRunPointer({
921
+ parentSessionFile: sessionFile, officer: "auditor", sessionFile: officerSession,
922
+ ...(summoned.runDirectory === undefined ? {} : { runDirectory: summoned.runDirectory }),
923
+ });
924
+ }
925
+ lastSummon = summoned;
926
+ return summoned;
927
+ },
928
+ });
929
+ if (decision.status === "continue") {
930
+ return runPublicInstructionSeatResume({
931
+ runId: admitted.runId, message: readableGateItem(decision.receipt ?? decision.violations),
932
+ }, { ...env, autoResumeLimit: 0 }, io);
933
+ }
934
+ if (decision.status !== "converged") {
935
+ if (lastSummon?.terminal !== undefined) {
936
+ io.stdout(formatTerminalResult(lastSummon.terminal));
937
+ return { exitCode: lastSummon.exitCode,
938
+ ...(lastSummon.admitted === undefined ? {} : { admitted: lastSummon.admitted }),
939
+ terminal: lastSummon.terminal };
940
+ }
941
+ throw new Error(`doctor audit did not converge: ${decision.status}`);
942
+ }
943
+ }
944
+ }
945
+ else {
946
+ const summonOfficer = createDefaultGateOfficerSummon({
947
+ cwd: admitted.projectRoot,
948
+ home: env.home,
949
+ packageRoot: env.packageRoot,
950
+ roleTurnHost: env.roleTurnHost,
951
+ ...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
952
+ });
953
+ let chain;
954
+ let officerRunId = passedRunId;
955
+ const runGate = (subject) => requireSubmissionGate({
956
+ context,
957
+ subject,
958
+ toolCallId,
959
+ submission: accepted,
960
+ summonOfficer,
961
+ ...(env.signal === undefined ? {} : { signal: env.signal }),
962
+ hostActions: {
963
+ failInfrastructure(error) { throw error; },
964
+ bindSubmissionNonPass() { },
965
+ },
966
+ });
967
+ if (admitted.role === "judge") {
968
+ chain = await runJudgeGates({
969
+ gateAlreadyConverged: async (subject) => passedOfficer === "auditor"
970
+ || (passedOfficer === "notary" && subject.kind === "judge_draft"),
971
+ runGate,
972
+ });
973
+ }
974
+ else {
975
+ const subject = admitted.role === "fixer" || admitted.role === "coder"
976
+ ? { kind: "worker_completion" }
977
+ : admitted.role === "secretariat"
978
+ ? { kind: "secretariat_verdict" }
979
+ : { kind: "countersign_verdict" };
980
+ const officer = gateOfficerForSubject(subject);
981
+ if (passedOfficer === officer) {
982
+ chain = { status: "converged", passes: [] };
983
+ }
984
+ else {
985
+ const pass = await runGate(subject);
986
+ if (pass === undefined)
987
+ throw new Error("audit gate returned no conclusion");
988
+ officerRunId = pass.runId;
989
+ chain = {
990
+ status: pass.status,
991
+ passes: [{
992
+ subject,
993
+ status: pass.status,
994
+ receipt: pass.receipt,
995
+ ...(pass.runId === undefined ? {} : { runId: pass.runId }),
996
+ ...(pass.runDirectory === undefined ? {} : { runDirectory: pass.runDirectory }),
997
+ }],
998
+ };
999
+ }
1000
+ }
1001
+ if (chain.status === "escalate") {
1002
+ const escalation = chain.passes.at(-1);
1003
+ const escalatedRunId = escalation?.runId
1004
+ ?? (escalation?.runDirectory === undefined
1005
+ ? undefined : runIdFromRunDirectory(escalation.runDirectory));
1006
+ if (escalatedRunId === undefined)
1007
+ throw new Error("escalated audit has no resumable run identity");
1008
+ const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority, true);
1009
+ const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority, undefined);
1010
+ if (terminal === undefined)
1011
+ throw new Error("escalated audit has no terminal result");
1012
+ io.stdout(formatTerminalResult(terminal));
1013
+ return { exitCode: 0, admitted: officer.admitted, terminal };
1014
+ }
1015
+ if (chain.status === "continue") {
1016
+ return runPublicInstructionSeatResume({
1017
+ runId: admitted.runId,
1018
+ message: readableGateItem(chain.passes.at(-1)?.receipt),
1019
+ }, { ...env, autoResumeLimit: 0 }, io);
1020
+ }
1021
+ if (admitted.role === "secretariat") {
1022
+ const pass = chain.passes.at(-1);
1023
+ const receipt = pass?.receipt ?? passedReceipt;
1024
+ if (receipt !== undefined) {
1025
+ await env.sessionAppender(env.principalAuthority, admitted.principal, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, {
1026
+ officer: "countersign", receipt,
1027
+ ...(officerRunId === undefined ? {} : { runId: officerRunId }),
1028
+ });
1029
+ }
1030
+ }
1031
+ }
1032
+ // The turn already settled this court (payloads, usage, autoResumeCount, history).
1033
+ // Audit only adds gate rounds and, for 中书省, the officer fact. A second
1034
+ // settle would republish the same attempt.
1035
+ if (turn.terminal === undefined)
1036
+ throw new Error(`audited ${admitted.role} submission did not settle`);
1037
+ const terminal = await attachPostAuditProjection(admitted, env.principalAuthority, turn.terminal);
1038
+ turn = { ...turn, terminal };
1039
+ io.stdout(formatTerminalResult(terminal));
1040
+ return turn;
1041
+ }
@@ -148,18 +148,23 @@ async function mergeInvocationIdentityPage(runDirectory, fields) {
148
148
  * Persist parent --source-run path onto admitted-request for officer resume lookup (#747).
149
149
  * Reuses the existing notary sourceRunPath key; does not invent a new field name.
150
150
  */
151
- export async function persistAdmittedSourceRunPath(admitted, sourceRunPath) {
151
+ export async function persistAdmittedSourceRunPath(admitted, sourceRunPath, auditorSubject) {
152
152
  if (sourceRunPath.trim() === "") {
153
153
  throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
154
154
  }
155
155
  const admittedPath = admitted.admittedRequestPath;
156
156
  const current = JSON.parse(await readFile(admittedPath, "utf8"));
157
- if (typeof current.sourceRunPath === "string") {
158
- if (current.sourceRunPath === sourceRunPath)
159
- return;
157
+ if (typeof current.sourceRunPath === "string" && current.sourceRunPath !== sourceRunPath) {
160
158
  throw new Error(`persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`);
161
159
  }
162
- await writeFile(admittedPath, `${JSON.stringify({ ...current, sourceRunPath }, null, 2)}\n`, "utf8");
160
+ const sameSubject = auditorSubject === undefined || current.auditorSubject === auditorSubject;
161
+ if (current.sourceRunPath === sourceRunPath && sameSubject)
162
+ return;
163
+ await writeFile(admittedPath, `${JSON.stringify({
164
+ ...current,
165
+ sourceRunPath,
166
+ ...(auditorSubject === undefined ? {} : { auditorSubject }),
167
+ }, null, 2)}\n`, "utf8");
163
168
  }
164
169
  /**
165
170
  * Bind a post-admission resolved ticketNumber onto the in-memory admitted