@akagilnc/pi-workflow-roles 0.1.5714 → 0.1.5753

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 (63) hide show
  1. package/dist/acp-host/production-host.js +517 -516
  2. package/dist/analyst-gate-cycles-read.js +7 -6
  3. package/dist/archivist-record-pointer.js +9 -45
  4. package/dist/auditor-soul.js +7 -5
  5. package/dist/external-host-turn-loop.js +2 -16
  6. package/dist/gatekeeper-role.js +14 -1
  7. package/dist/headless-host/production-host.js +517 -516
  8. package/dist/judge-role.js +1 -1
  9. package/dist/migrate-book-topology.js +61 -51
  10. package/dist/navigator-public-session.js +17 -8
  11. package/dist/notary-contracts.js +2 -2
  12. package/dist/notary-source-run.js +2 -2
  13. package/dist/packaged-role-registry.js +0 -10
  14. package/dist/public-cli/cli.js +13 -6
  15. package/dist/public-cli/instruction-seat-run.js +157 -138
  16. package/dist/public-cli/invocation.js +35 -0
  17. package/dist/public-cli/main.js +497 -505
  18. package/dist/public-cli/post-admission.js +11 -12
  19. package/dist/public-cli/run-lifecycle.js +56 -34
  20. package/dist/public-cli/seat-ticket-binding.js +8 -7
  21. package/dist/public-cli/settlement.js +4 -126
  22. package/dist/public-cli/terminal.js +3 -3
  23. package/dist/public-role-summons.js +14 -6
  24. package/dist/review-submission.js +47 -14
  25. package/dist/role-run-relocation.js +4 -3
  26. package/dist/role-submission-declarations.js +5 -5
  27. package/dist/run-dossier.js +6 -21
  28. package/dist/run-terminal-artifacts.js +11 -0
  29. package/dist/secretariat-contracts.js +0 -4
  30. package/dist/submission-gate.js +58 -42
  31. package/dist/submission-ledger.js +3 -2
  32. package/package.json +1 -1
  33. package/souls/notary.md +6 -5
  34. package/src/analyst-gate-cycles-read.ts +7 -6
  35. package/src/analyst-ledger.ts +2 -2
  36. package/src/archivist-record-pointer.ts +10 -64
  37. package/src/auditor-soul.ts +7 -5
  38. package/src/countersign-role.ts +5 -5
  39. package/src/external-host-turn-loop.ts +2 -23
  40. package/src/gatekeeper-role.ts +24 -3
  41. package/src/judge-role.ts +1 -1
  42. package/src/navigator-public-session.ts +22 -10
  43. package/src/notary-contracts.ts +3 -2
  44. package/src/notary-source-run.ts +2 -7
  45. package/src/packaged-role-registry.ts +0 -13
  46. package/src/public-cli/cli.ts +18 -4
  47. package/src/public-cli/instruction-seat-run.ts +213 -155
  48. package/src/public-cli/invocation.ts +39 -0
  49. package/src/public-cli/post-admission.ts +11 -9
  50. package/src/public-cli/run-lifecycle.ts +80 -33
  51. package/src/public-cli/seat-ticket-binding.ts +9 -7
  52. package/src/public-cli/settlement.ts +3 -167
  53. package/src/public-cli/terminal.ts +3 -3
  54. package/src/public-role-summons.ts +40 -12
  55. package/src/review-submission.ts +72 -21
  56. package/src/role-run-relocation.ts +4 -3
  57. package/src/role-runtime.ts +3 -3
  58. package/src/role-submission-declarations.ts +5 -5
  59. package/src/run-dossier.ts +7 -19
  60. package/src/run-terminal-artifacts.ts +13 -0
  61. package/src/secretariat-contracts.ts +0 -19
  62. package/src/submission-gate.ts +75 -62
  63. package/src/submission-ledger.ts +3 -6
@@ -8,33 +8,30 @@ import { MISSING_TICKET_REASK_MATERIAL } from "../report-ticket-tool.js";
8
8
  import { isUnboundRunDirectory, sessionFileOf } from "../role-run-placement.js";
9
9
  import { rewriteRunDirectoryPathValue } from "../role-run-relocation.js";
10
10
  import { readPackageMaterial } from "../session-opening-materials.js";
11
- import { bookDirectOfficerRunPointer, readDirectOfficerRunPointer } from "../archivist-record-pointer.js";
12
11
  import { createPiDoctorAuditor } from "../doctor-auditor.js";
13
12
  import { officerConclusionReask, gateOfficerForSubject } from "../gatekeeper-role.js";
14
13
  import { receivedDiscriminator } from "../submission-errors.js";
15
14
  import { REVIEW_QUEUE_STATUSES } from "../review-submission.js";
16
- import { runJudgeGates } from "../judge-role.js";
15
+ import { JUDGE_GATES, runJudgeGates } from "../judge-role.js";
17
16
  import { WORKER_DONE_STATUSES } from "../worker-submission-contracts.js";
18
- import { SECRETARIAT_GATE_OFFICER_ENTRY_TYPE } from "../secretariat-contracts.js";
19
17
  import { readableGateItem } from "../readable-gate-item.js";
20
18
  import { deliveryLimitFromConfig } from "../receipt-delivery-policy.js";
21
19
  import { runIdFromRunDirectory } from "../run-terminal-artifacts.js";
22
20
  import { createDefaultGateOfficerSummon, requireSubmissionGate } from "../submission-gate.js";
23
21
  import { readRecordedSubmissionRows } from "../submission-ledger.js";
24
22
  import { summonPublicRole } from "../public-role-summons.js";
25
- import { sessionFileFromPublicSummon } from "../session-assistant-usage.js";
26
23
  import { latestQueuePayload, latestQueueStatus, } from "../submission-gate.js";
27
24
  import { isSafePositiveTicketNumber, readBoardTicketNumber, } from "../run-ticket-number.js";
28
25
  import { NotarySourceRunError, resolveNotarySourceRunLocator } from "../notary-source-run.js";
29
26
  import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedRebindSourceOnResume, packagedRoleMetadata, } from "../packaged-role-registry.js";
30
27
  import { readAuditorResumeBinding } from "../auditor-soul.js";
31
28
  import { CliUsageError } from "./cli-errors.js";
32
- import { admitPublicRole, bindAdmittedTicketNumber, appendCallerFileFlagPaths, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, } from "./invocation.js";
29
+ import { admitPublicRole, admittedSourceRunPath, bindAdmittedTicketNumber, appendCallerFileFlagPaths, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedAuditedSubmissionToolCallId, persistAdmittedSourceRunPath, readAdmittedAuditedSubmissionToolCallId, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, } from "./invocation.js";
33
30
  import { bindDiaristAssertedTicketNumber, presentControlledFailure, packageFaultNoteFor, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
34
31
  import { presentTerminal } from "./auto-resume.js";
35
32
  import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, readCurrentCourt, } from "./run-lifecycle.js";
36
33
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
37
- import { presentStructuralRejection, readBoundSessionEntries, attachPostAuditCountersignFact, trySettlePublicSeat, } from "./settlement.js";
34
+ import { presentStructuralRejection, readBoundSessionEntries, trySettlePublicSeat, } from "./settlement.js";
38
35
  import { warnMissingMethodSkills } from "./machine-method-skills.js";
39
36
  import { formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
40
37
  import { admittedSeatTurnDetails, projectRoleTurnRequest, requiredMethodSkills, } from "./turn-request.js";
@@ -120,17 +117,14 @@ async function presentOriginalVolume(held, terminal, io, runDirectory) {
120
117
  for (const value of held)
121
118
  io.stdout(value);
122
119
  }
123
- /** Runtime fact beside the original volume. Payloads stay as submitted. */
124
- function withUnsettledDirection(parent, officer) {
120
+ /**
121
+ * Mark this leg's terminal that subsequent audit did not settle a queue word.
122
+ * Other-seat receipts / run ids stay on those runs — not copied here (#1195).
123
+ */
124
+ function withUnsettledDirection(parent) {
125
125
  const outcome = parent.roleOutcome;
126
126
  if (outcome.kind !== "accepted" && outcome.kind !== "audit_escalation")
127
127
  return parent;
128
- const officerOutcome = officer?.roleOutcome;
129
- const officerPayloads = officer?.submissions !== undefined && officer.submissions.length > 0
130
- ? officer.submissions
131
- : officerOutcome !== undefined && officerOutcome.kind !== "no_receipt"
132
- ? officerOutcome.payloads ?? []
133
- : [];
134
128
  return {
135
129
  ...parent,
136
130
  roleOutcome: {
@@ -139,9 +133,6 @@ function withUnsettledDirection(parent, officer) {
139
133
  ...(outcome.decisiveFacts ?? {}),
140
134
  directionUnsettled: true,
141
135
  subsequentAudit: "incomplete",
142
- ...(officerOutcome === undefined ? {} : { officerRole: officerOutcome.role }),
143
- ...(officer?.runId === undefined ? {} : { officerRunId: officer.runId }),
144
- ...(officerPayloads.length === 0 ? {} : { officerPayloads }),
145
136
  },
146
137
  },
147
138
  };
@@ -154,11 +145,13 @@ function heldUnreadableTerminal(terminal) {
154
145
  const status = latestQueueStatus(terminal);
155
146
  return typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status);
156
147
  }
157
- async function presentUnsettledAudit(parent, officer, io, runDirectory) {
158
- const terminal = withUnsettledDirection(parent, officer);
148
+ /**
149
+ * Present the actual audit seat's volume for an unreadable conclusion (ADR 0055).
150
+ * Parent is not given a copy of the officer receipt (#1195).
151
+ */
152
+ async function presentUnsettledOfficer(officer, io, runDirectory) {
153
+ const terminal = withUnsettledDirection(officer);
159
154
  await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, runDirectory);
160
- if (officer !== undefined)
161
- await presentTerminal(officer, { ...io, omitFailureStderrDiagnostic: true }, runDirectory);
162
155
  return terminal;
163
156
  }
164
157
  async function reaskUnreadablePostSubmissionStatus(admitted, terminal, env, io, message = unreadablePostSubmissionStatus(admitted, terminal)) {
@@ -167,7 +160,11 @@ async function reaskUnreadablePostSubmissionStatus(admitted, terminal, env, io,
167
160
  const next = withUnreadableReask(env);
168
161
  if (next === undefined)
169
162
  return UNREADABLE_REASK_EXHAUSTED;
170
- return runPublicInstructionSeatResume({ runId: admitted.runId, summons: { instruction: message } }, next, io);
163
+ return runPublicInstructionSeatResume({
164
+ runId: admitted.runId,
165
+ runDirectory: admitted.runDirectory,
166
+ summons: { instruction: message },
167
+ }, next, io);
171
168
  }
172
169
  /**
173
170
  * #1171: sealed submission still leaves the leg unbound → soft reask once.
@@ -204,7 +201,11 @@ async function reaskMissingTicketOnce(admitted, terminal, env, io) {
204
201
  softTicketReaskTurn: true,
205
202
  softTicketReaskOwnership,
206
203
  };
207
- const result = await runPublicInstructionSeatResume({ runId: admitted.runId, summons: { instruction } }, softReaskEnv, io);
204
+ const result = await runPublicInstructionSeatResume({
205
+ runId: admitted.runId,
206
+ runDirectory: admitted.runDirectory,
207
+ summons: { instruction },
208
+ }, softReaskEnv, io);
208
209
  if (softTicketReaskOwnership.sealedSubstitute
209
210
  || terminalCarriesSealedSubmission(result.terminal)) {
210
211
  return { kind: "nested_owns", result };
@@ -347,6 +348,7 @@ async function loadLedgerPeerBody(sourceRunDirectory, projectRoot, home) {
347
348
  * One delivery rule for review-seat dialogue on new turns and same-parent resume
348
349
  * (#1166 / ADR 0085 / ADR 0087): explicit reask → non-empty caller dispatch →
349
350
  * source-only ledger peer body. Identity stays in startup materials.
351
+ * #1195: ticket-court notary skips peer-body preload (countersign source only).
350
352
  */
351
353
  async function resolveReviewSeatDialogueBody(input) {
352
354
  const reask = input.reask;
@@ -355,26 +357,26 @@ async function resolveReviewSeatDialogueBody(input) {
355
357
  const caller = input.callerInstruction ?? "";
356
358
  if (caller.length > 0)
357
359
  return caller;
358
- const sourceRunPath = input.sourceRunPath?.trim() ?? "";
359
- if (sourceRunPath === "")
360
+ const sourceRunPath = admittedSourceRunPath(input)?.trim();
361
+ if (sourceRunPath === undefined)
360
362
  return undefined;
363
+ if (input.role === "notary") {
364
+ const { isTicketCourtCountersignSource } = await import("../run-terminal-artifacts.js");
365
+ if (isTicketCourtCountersignSource(sourceRunPath))
366
+ return undefined;
367
+ }
361
368
  return await loadLedgerPeerBody(sourceRunPath, input.projectRoot, input.home);
362
369
  }
363
- function admittedSourceRunPath(admitted) {
364
- return "sourceRunPath" in admitted
365
- && typeof admitted.sourceRunPath === "string"
366
- ? admitted.sourceRunPath.trim()
367
- : "";
368
- }
369
370
  async function resolveInitialPrompt(admitted, env) {
370
371
  const record = roleRecord(admitted.role);
371
372
  if ("reaskPrompt" in record && record.reaskPrompt === true) {
372
373
  const sourceRunPath = admittedSourceRunPath(admitted);
373
374
  const reask = env.reviewReask ?? env.gateReviewInstruction;
374
375
  const body = await resolveReviewSeatDialogueBody({
376
+ role: admitted.role,
375
377
  ...(reask === undefined ? {} : { reask }),
376
378
  callerInstruction: admitted.instruction,
377
- ...(sourceRunPath === "" ? {} : { sourceRunPath }),
379
+ ...(sourceRunPath === undefined ? {} : { sourceRunPath }),
378
380
  projectRoot: admitted.projectRoot,
379
381
  home: env.home,
380
382
  });
@@ -602,16 +604,21 @@ function resumeSameParentInstructionSeat(input) {
602
604
  ...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
603
605
  freshSummons: input.env.freshSummons,
604
606
  summons: input.summons,
605
- resume: (runId, materials) => runPublicInstructionSeatResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, input.env, input.io),
607
+ resume: (runId, materials, runDirectory) => runPublicInstructionSeatResume({
608
+ runId,
609
+ runDirectory,
610
+ ...(materials === undefined ? {} : { summons: materials }),
611
+ }, input.env, input.io),
606
612
  });
607
613
  }
608
614
  /**
609
615
  * Same delivery rule as resolveInitialPrompt for same-parent resume summons.
610
616
  * Reask / explicit caller text / source-only ledger peer — one authority.
611
617
  */
612
- async function sameParentDialogue(env, fallback, sourceRunPath, projectRoot) {
618
+ async function sameParentDialogue(env, fallback, sourceRunPath, projectRoot, role) {
613
619
  const reask = env.reviewReask ?? env.gateReviewInstruction;
614
620
  const body = await resolveReviewSeatDialogueBody({
621
+ role,
615
622
  ...(reask === undefined ? {} : { reask }),
616
623
  callerInstruction: fallback.instruction,
617
624
  sourceRunPath,
@@ -753,6 +760,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
753
760
  let auditorSource;
754
761
  let auditorTicket;
755
762
  let resolvedNotarySource;
763
+ let instructionSourceRunPath;
756
764
  if (record.sameParent === "subject-source") {
757
765
  if (parsed.subject === undefined) {
758
766
  presentStructuralRejection(new CliUsageError("auditor --subject requires judge|doctor"), io);
@@ -780,7 +788,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
780
788
  ...(await sameParentDialogue(env, {
781
789
  instruction: parsed.instruction ?? "",
782
790
  instructionEmpty: (parsed.instruction ?? "").length === 0,
783
- }, sourceDirectory, projectRoot)),
791
+ }, sourceDirectory, projectRoot, role)),
784
792
  attachmentPaths: parsed.attachmentPaths ?? [],
785
793
  };
786
794
  const resumed = await withAuditorSoulEnv({
@@ -801,9 +809,8 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
801
809
  if (record.sameParent === "source-run") {
802
810
  // #1166: structured --source-run only. Instruction bytes stay opaque caller/peer text.
803
811
  if (parsed.sourceRun !== undefined) {
804
- let parentRunPath;
805
812
  try {
806
- parentRunPath = (await resolveNotarySourceRunLocator({
813
+ instructionSourceRunPath = (await resolveNotarySourceRunLocator({
807
814
  projectRoot,
808
815
  sourceRun: parsed.sourceRun,
809
816
  home: env.home,
@@ -816,12 +823,13 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
816
823
  }
817
824
  throw error;
818
825
  }
826
+ const parentRunPath = instructionSourceRunPath;
819
827
  const summons = {
820
828
  sourceRunPath: parentRunPath,
821
829
  ...(await sameParentDialogue(env, {
822
830
  instruction: parsed.instruction ?? "",
823
831
  instructionEmpty: (parsed.instruction ?? "").length === 0,
824
- }, parentRunPath, projectRoot)),
832
+ }, parentRunPath, projectRoot, role)),
825
833
  attachmentPaths: parsed.attachmentPaths ?? [],
826
834
  };
827
835
  const resumed = await resumeSameParentInstructionSeat({
@@ -855,7 +863,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
855
863
  const summons = {
856
864
  sourceRunPath: source.runDirectory,
857
865
  sourceRun: source,
858
- ...(await sameParentDialogue(env, { instruction: "", instructionEmpty: true }, source.runDirectory, projectRoot)),
866
+ ...(await sameParentDialogue(env, { instruction: "", instructionEmpty: true }, source.runDirectory, projectRoot, role)),
859
867
  };
860
868
  resolvedNotarySource = source;
861
869
  const resumed = await resumeSameParentInstructionSeat({
@@ -883,14 +891,9 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
883
891
  return rejected;
884
892
  throw error;
885
893
  }
886
- if (record.sameParent === "source-run" && parsed.sourceRun !== undefined) {
887
- const parentRunPath = (await resolveNotarySourceRunLocator({
888
- projectRoot,
889
- sourceRun: parsed.sourceRun,
890
- home: env.home,
891
- })).runDirectory;
892
- await persistAdmittedSourceRunPath(admitted, parentRunPath);
893
- admitted = { ...admitted, sourceRunPath: parentRunPath };
894
+ if (instructionSourceRunPath !== undefined) {
895
+ await persistAdmittedSourceRunPath(admitted, instructionSourceRunPath);
896
+ admitted = { ...admitted, sourceRunPath: instructionSourceRunPath };
894
897
  }
895
898
  if (record.sameParent === "subject-source" && auditorSource !== undefined) {
896
899
  await persistAdmittedSourceRunPath(admitted, auditorSource, auditorSubject);
@@ -941,6 +944,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
941
944
  return runAdmitted();
942
945
  }
943
946
  export async function runPublicInstructionSeatResume(request, env, io) {
947
+ let selectedRequest = request;
944
948
  const resume = async () => {
945
949
  const held = [];
946
950
  const turnIo = {
@@ -948,11 +952,11 @@ export async function runPublicInstructionSeatResume(request, env, io) {
948
952
  stderr: io.stderr,
949
953
  };
950
954
  const result = await runPostAdmissionSeatResume({
951
- request,
955
+ request: selectedRequest,
952
956
  env,
953
957
  io: turnIo,
954
958
  load: async (effective) => {
955
- const loaded = await loadResumablePublicRole(env.home, effective.runId, env.principalAuthority);
959
+ const loaded = await loadResumablePublicRole(env.home, effective.runId, env.principalAuthority, effective.runDirectory === undefined ? undefined : { runDirectory: effective.runDirectory });
956
960
  if (packagedRebindSourceOnResume(loaded.admitted.role)
957
961
  && effective.summons?.sourceRunPath !== undefined
958
962
  && effective.summons.sourceRun !== undefined) {
@@ -990,9 +994,10 @@ export async function runPublicInstructionSeatResume(request, env, io) {
990
994
  };
991
995
  let binding;
992
996
  try {
993
- const loaded = await loadResumablePublicRole(env.home, request.runId, env.principalAuthority);
997
+ const loaded = await loadResumablePublicRole(env.home, request.runId, env.principalAuthority, request.runDirectory === undefined ? undefined : { runDirectory: request.runDirectory });
998
+ selectedRequest = { ...request, runDirectory: loaded.admitted.runDirectory };
994
999
  binding = loaded.admitted.role === "auditor"
995
- ? await readAuditorResumeBinding(loaded.admitted.runDirectory)
1000
+ ? await readAuditorResumeBinding(loaded.admitted)
996
1001
  : undefined;
997
1002
  }
998
1003
  catch (error) {
@@ -1060,23 +1065,25 @@ async function queueConclusionFromChild(child, env, io) {
1060
1065
  }
1061
1066
  /** Continue the parent once a child has submitted. The child words ride the existing resume. */
1062
1067
  export async function continueParentAfterChild(parentRunId, child, env, io) {
1063
- const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority);
1068
+ const knownParentDirectory = admittedSourceRunPath(child);
1069
+ const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority, knownParentDirectory === undefined ? undefined : { runDirectory: knownParentDirectory });
1064
1070
  if (loaded.run.state !== "admitted" && !AUDITED_ROLES.has(loaded.admitted.role)) {
1065
- return runPublicInstructionSeatResume({ runId: parentRunId }, env, io);
1071
+ return runPublicInstructionSeatResume({
1072
+ runId: parentRunId,
1073
+ runDirectory: loaded.admitted.runDirectory,
1074
+ }, env, io);
1066
1075
  }
1067
1076
  const admitted = loaded.admitted;
1068
1077
  const resolved = await queueConclusionFromChild(child, env, io);
1069
1078
  if (resolved !== undefined && "stop" in resolved) {
1070
1079
  const stopped = resolved.stop;
1080
+ // Unreadable child conclusion stays on that seat — resume 审核席本人 (ADR 0055 / #1195).
1071
1081
  if (stopped.exitCode === 0
1072
1082
  && heldUnreadableTerminal(stopped.terminal)
1073
- && stopped.terminal !== undefined) {
1074
- const parentTerminal = await trySettlePublicSeat(admitted, env.principalAuthority, { ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) });
1075
- if (parentTerminal?.roleOutcome.kind === "accepted"
1076
- || parentTerminal?.roleOutcome.kind === "audit_escalation") {
1077
- const terminal = await presentUnsettledAudit(parentTerminal, stopped.terminal, io, admitted.runDirectory);
1078
- return { exitCode: 0, admitted, terminal };
1079
- }
1083
+ && stopped.terminal !== undefined
1084
+ && stopped.admitted !== undefined) {
1085
+ const terminal = await presentUnsettledOfficer(stopped.terminal, io, stopped.admitted.runDirectory);
1086
+ return { exitCode: 0, admitted: stopped.admitted, terminal };
1080
1087
  }
1081
1088
  return stopped;
1082
1089
  }
@@ -1088,10 +1095,14 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
1088
1095
  const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, { ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) });
1089
1096
  if (terminal?.roleOutcome.kind !== "accepted")
1090
1097
  throw new Error("pending submission is not recorded");
1091
- return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
1098
+ // Live child only when admitted-request.auditedSubmissionToolCallId matches
1099
+ // the parent seal under audit (#1195 option 1 / owner 00644146).
1100
+ const live = liveChildForCurrentParentSeal(terminal, resolved.admitted, latestQueuePayload(resolved.terminal));
1101
+ return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, live?.officer, live?.receipt);
1092
1102
  }
1093
1103
  return runPublicInstructionSeatResume({
1094
1104
  runId: parentRunId,
1105
+ runDirectory: admitted.runDirectory,
1095
1106
  summons: { instruction: readableGateItem(latestQueuePayload(resolved.terminal)) },
1096
1107
  }, envForCourtContinue(env), io);
1097
1108
  }
@@ -1100,35 +1111,39 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
1100
1111
  return result;
1101
1112
  }
1102
1113
  /**
1103
- * A gate is already passed only when an officer run reviewed this submission
1104
- * and its latest queue word is converged. The resumed seat's role is not itself
1105
- * a pass. Unknown or unreadable evidence does not skip the gate.
1114
+ * Hand continueParentAfterChild's live child to audit only when the child's own
1115
+ * admitted-request records this parent seal's toolCallId (#1195 option 1).
1116
+ * Read failures propagate — never wash into "no match".
1106
1117
  */
1107
- async function officerPassedCurrentSubmission(input) {
1108
- const { env, officer } = input;
1109
- const pointer = await readDirectOfficerRunPointer(input.parentSessionFile, officer);
1110
- if (pointer?.runDirectory === undefined)
1111
- return false;
1112
- if (pointer.submissionToolCallId !== input.toolCallId)
1113
- return false;
1114
- const officerRunId = runIdFromRunDirectory(pointer.runDirectory);
1115
- if (officerRunId === undefined)
1116
- return false;
1117
- let admitted;
1118
- try {
1119
- const loaded = await loadResumablePublicRole(env.home, officerRunId, env.principalAuthority);
1120
- admitted = loaded.admitted;
1121
- }
1122
- catch {
1118
+ function liveChildForCurrentParentSeal(parentTerminal, child, receipt) {
1119
+ if (receipt === undefined)
1120
+ return undefined;
1121
+ const parentSealId = parentTerminal.submissionToolCallId?.trim() ?? "";
1122
+ if (parentSealId === "")
1123
+ return undefined;
1124
+ const childSealId = readAdmittedAuditedSubmissionToolCallId(child.runDirectory);
1125
+ if (childSealId !== parentSealId)
1126
+ return undefined;
1127
+ return { officer: child, receipt };
1128
+ }
1129
+ /**
1130
+ * Gate already satisfied for this routing turn when continueParentAfterChild
1131
+ * handed a live converged child that belongs to this parent seal, or — for a
1132
+ * multi-gate chain on that same seal — an earlier gate before that child
1133
+ * (ADR 0003 remaining gates). Parent-side officer pointers are not consulted (#1195).
1134
+ */
1135
+ function officerSatisfiedByLiveChild(officer, resumedOfficer, passedReceipt,
1136
+ /** Ordered officers of this parent's gate chain; earlier than live child = done. */
1137
+ chainOrder) {
1138
+ if (resumedOfficer === undefined || passedReceipt === undefined)
1123
1139
  return false;
1124
- }
1125
- if (admitted.role !== officer)
1140
+ if (resumedOfficer.role === officer)
1141
+ return true;
1142
+ if (chainOrder === undefined)
1126
1143
  return false;
1127
- // A summon binding identifies the requested review, not its outcome. An open
1128
- // court (including a failed host turn) must not borrow a previous court's seal.
1129
- const court = await readCurrentCourt(admitted.runDirectory);
1130
- const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, court);
1131
- return latestQueueStatus(terminal) === "converged";
1144
+ const childIndex = chainOrder.indexOf(resumedOfficer.role);
1145
+ const officerIndex = chainOrder.indexOf(officer);
1146
+ return childIndex >= 0 && officerIndex >= 0 && officerIndex < childIndex;
1132
1147
  }
1133
1148
  /** Finished submissions enter the existing audit gate after their tool call has returned. */
1134
1149
  async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt) {
@@ -1156,7 +1171,11 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1156
1171
  await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1157
1172
  return { ...turn, terminal };
1158
1173
  }
1159
- return runPublicInstructionSeatResume({ runId: admitted.runId, summons: { instruction: SECRETARIAT_STATUS_REASK } }, next, io);
1174
+ return runPublicInstructionSeatResume({
1175
+ runId: admitted.runId,
1176
+ runDirectory: admitted.runDirectory,
1177
+ summons: { instruction: SECRETARIAT_STATUS_REASK },
1178
+ }, next, io);
1160
1179
  }
1161
1180
  if (admitted.role === "countersign" && (typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status))) {
1162
1181
  if (gateOwnsStatusReask)
@@ -1167,7 +1186,11 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1167
1186
  await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1168
1187
  return { ...turn, terminal };
1169
1188
  }
1170
- return runPublicInstructionSeatResume({ runId: admitted.runId, summons: { instruction: officerConclusionReask(status) } }, next, io);
1189
+ return runPublicInstructionSeatResume({
1190
+ runId: admitted.runId,
1191
+ runDirectory: admitted.runDirectory,
1192
+ summons: { instruction: officerConclusionReask(status) },
1193
+ }, next, io);
1171
1194
  }
1172
1195
  // Self-escalation is a valid open routing state for these seats only.
1173
1196
  // Doctor's declared domain is completed|refused — an open "escalate" must
@@ -1208,14 +1231,9 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1208
1231
  },
1209
1232
  abort() { },
1210
1233
  };
1211
- const passedThisSubmission = (officer) => officerPassedCurrentSubmission({
1212
- env,
1213
- parentSessionFile: sessionFile,
1214
- officer,
1215
- toolCallId,
1216
- });
1234
+ const gateSatisfied = (officer, chainOrder) => officerSatisfiedByLiveChild(officer, resumedOfficer, passedReceipt, chainOrder);
1217
1235
  if (admitted.role === "doctor") {
1218
- if (!await passedThisSubmission("auditor")) {
1236
+ if (!gateSatisfied("auditor")) {
1219
1237
  let lastSummon;
1220
1238
  const decision = await createPiDoctorAuditor()({
1221
1239
  context, submission: accepted,
@@ -1234,31 +1252,33 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1234
1252
  ...(reask === undefined ? {} : { reviewReask: reask }),
1235
1253
  ...(submission === undefined ? {} : { gateReviewInstruction: submission }),
1236
1254
  });
1237
- const officerSession = sessionFileFromPublicSummon(summoned);
1238
- if (officerSession !== undefined) {
1239
- bookDirectOfficerRunPointer({
1240
- parentSessionFile: sessionFile, officer: "auditor", sessionFile: officerSession,
1241
- submissionToolCallId: toolCallId,
1242
- ...(summoned.runDirectory === undefined ? {} : { runDirectory: summoned.runDirectory }),
1243
- });
1244
- }
1245
1255
  lastSummon = summoned;
1256
+ const officerDir = summoned.runDirectory ?? summoned.admitted?.runDirectory;
1257
+ if (officerDir !== undefined && toolCallId.length > 0) {
1258
+ persistAdmittedAuditedSubmissionToolCallId(officerDir, toolCallId);
1259
+ }
1246
1260
  return summoned;
1247
1261
  },
1248
1262
  });
1249
1263
  if (decision.status === "continue") {
1250
1264
  return runPublicInstructionSeatResume({
1251
- runId: admitted.runId, summons: { instruction: readableGateItem(decision.receipt ?? decision.violations) },
1265
+ runId: admitted.runId,
1266
+ runDirectory: admitted.runDirectory,
1267
+ summons: { instruction: readableGateItem(decision.receipt ?? decision.violations) },
1252
1268
  }, envForCourtContinue(env), io);
1253
1269
  }
1254
1270
  if (decision.status === "received") {
1255
1271
  if (lastSummon?.terminal === undefined) {
1256
1272
  throw new Error("doctor audit kept an unreadable reply with no terminal");
1257
1273
  }
1258
- if (turn.terminal === undefined)
1259
- throw new Error("doctor submission has no terminal");
1260
- const terminal = await presentUnsettledAudit(turn.terminal, lastSummon.terminal, io, admitted.runDirectory);
1261
- return { exitCode: 0, admitted, terminal };
1274
+ const officerAdmitted = lastSummon.admitted;
1275
+ const officerDir = officerAdmitted?.runDirectory ?? lastSummon.runDirectory ?? admitted.runDirectory;
1276
+ const terminal = await presentUnsettledOfficer(lastSummon.terminal, io, officerDir);
1277
+ return {
1278
+ exitCode: 0,
1279
+ ...(officerAdmitted === undefined ? { admitted } : { admitted: officerAdmitted }),
1280
+ terminal,
1281
+ };
1262
1282
  }
1263
1283
  if (decision.status !== "converged") {
1264
1284
  if (lastSummon?.terminal !== undefined) {
@@ -1280,7 +1300,6 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1280
1300
  ...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
1281
1301
  });
1282
1302
  let chain;
1283
- let officerRunId = resumedOfficer?.runId;
1284
1303
  const runGate = (subject) => requireSubmissionGate({
1285
1304
  context,
1286
1305
  subject,
@@ -1295,8 +1314,10 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1295
1314
  },
1296
1315
  });
1297
1316
  if (admitted.role === "judge") {
1317
+ // Same order as runJudgeGates — sole source JUDGE_GATES + gateOfficerForSubject.
1318
+ const judgeGateOrder = JUDGE_GATES.map(gateOfficerForSubject);
1298
1319
  chain = await runJudgeGates({
1299
- gateAlreadyConverged: async (subject) => passedThisSubmission(gateOfficerForSubject(subject)),
1320
+ gateAlreadyConverged: async (subject) => gateSatisfied(gateOfficerForSubject(subject), judgeGateOrder),
1300
1321
  runGate,
1301
1322
  });
1302
1323
  }
@@ -1307,14 +1328,13 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1307
1328
  ? { kind: "secretariat_verdict" }
1308
1329
  : { kind: "countersign_verdict" };
1309
1330
  const officer = gateOfficerForSubject(subject);
1310
- if (await passedThisSubmission(officer)) {
1331
+ if (gateSatisfied(officer)) {
1311
1332
  chain = { status: "converged", passes: [] };
1312
1333
  }
1313
1334
  else {
1314
1335
  const pass = await runGate(subject);
1315
1336
  if (pass === undefined)
1316
1337
  throw new Error("audit gate returned no conclusion");
1317
- officerRunId = pass.runId;
1318
1338
  chain = {
1319
1339
  status: pass.status,
1320
1340
  passes: [{
@@ -1329,23 +1349,34 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1329
1349
  }
1330
1350
  }
1331
1351
  if (chain.status === "needs_reask") {
1332
- const officerTerminal = chain.passes.at(-1)?.terminal;
1352
+ const pass = chain.passes.at(-1);
1353
+ const officerTerminal = pass?.terminal;
1333
1354
  if (officerTerminal === undefined)
1334
1355
  throw new Error("unreadable audit has no terminal result");
1335
- if (turn.terminal === undefined)
1336
- throw new Error("audited submission has no terminal");
1337
- const terminal = await presentUnsettledAudit(turn.terminal, officerTerminal, io, admitted.runDirectory);
1338
- return { exitCode: 0, admitted, terminal };
1356
+ const officerRunDirectory = pass?.runDirectory;
1357
+ const officerRunId = pass?.runId
1358
+ ?? (officerRunDirectory === undefined ? undefined : runIdFromRunDirectory(officerRunDirectory));
1359
+ if (officerRunId === undefined)
1360
+ throw new Error("unreadable audit has no resumable run identity");
1361
+ const officer = await loadResumablePublicRole(env.home, officerRunId, env.principalAuthority, officerRunDirectory === undefined ? undefined : { runDirectory: officerRunDirectory });
1362
+ const terminal = await presentUnsettledOfficer(officerTerminal, io, officer.admitted.runDirectory);
1363
+ return { exitCode: 0, admitted: officer.admitted, terminal };
1339
1364
  }
1340
1365
  if (chain.status === "escalate") {
1341
1366
  const escalation = chain.passes.at(-1);
1367
+ const escalatedRunDirectory = escalation?.runDirectory;
1342
1368
  const escalatedRunId = escalation?.runId
1343
- ?? (escalation?.runDirectory === undefined
1344
- ? undefined : runIdFromRunDirectory(escalation.runDirectory));
1369
+ ?? (escalatedRunDirectory === undefined
1370
+ ? undefined : runIdFromRunDirectory(escalatedRunDirectory));
1345
1371
  if (escalatedRunId === undefined)
1346
1372
  throw new Error("escalated audit has no resumable run identity");
1347
- const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority);
1348
- const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority, { ...await readCurrentCourt(officer.admitted.runDirectory), ...packageFaultScope(officer.admitted, env, io) });
1373
+ const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority, escalatedRunDirectory === undefined ? undefined : { runDirectory: escalatedRunDirectory });
1374
+ // Prefer the officer terminal already returned. Re-settle only when absent.
1375
+ // Other-seat tables stay on the officer run — parent does not attach copies (#1195).
1376
+ let terminal = escalation?.terminal;
1377
+ if (terminal === undefined) {
1378
+ terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority, { ...await readCurrentCourt(officer.admitted.runDirectory), ...packageFaultScope(officer.admitted, env, io) });
1379
+ }
1349
1380
  if (terminal === undefined)
1350
1381
  throw new Error("escalated audit has no terminal result");
1351
1382
  await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, officer.admitted.runDirectory);
@@ -1354,27 +1385,15 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1354
1385
  if (chain.status === "continue") {
1355
1386
  return runPublicInstructionSeatResume({
1356
1387
  runId: admitted.runId,
1388
+ runDirectory: admitted.runDirectory,
1357
1389
  summons: { instruction: readableGateItem(chain.passes.at(-1)?.receipt) },
1358
1390
  }, envForCourtContinue(env), io);
1359
1391
  }
1360
- if (admitted.role === "secretariat") {
1361
- const pass = chain.passes.at(-1);
1362
- const receipt = pass?.receipt ?? passedReceipt;
1363
- if (receipt !== undefined) {
1364
- await env.sessionAppender(env.principalAuthority, admitted.principal, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, {
1365
- officer: "countersign", receipt,
1366
- ...(officerRunId === undefined ? {} : { runId: officerRunId }),
1367
- });
1368
- }
1369
- }
1370
1392
  }
1371
- // The turn already settled this court (payloads, usage, autoResumeCount, history).
1372
- // Audit only adds, for 中书省, the officer fact. A second settle would
1373
- // republish the same attempt.
1393
+ // The turn already settled this court. Gate does not project other-seat finals
1394
+ // onto the parent public terminal (#1195) — present this leg's own volume.
1374
1395
  if (turn.terminal === undefined)
1375
1396
  throw new Error(`audited ${admitted.role} submission did not settle`);
1376
- const terminal = await attachPostAuditCountersignFact(admitted, env.principalAuthority, turn.terminal, packageFaultScope(admitted, env, io));
1377
- turn = { ...turn, terminal };
1378
- await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1397
+ await presentTerminal(turn.terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1379
1398
  return turn;
1380
1399
  }
@@ -26,6 +26,13 @@ import { NotarySourceRunError, resolveNotarySourceRunLocator, } from "../notary-
26
26
  import { CliUsageError } from "./cli-errors.js";
27
27
  import { REJECTED_PUBLIC_SPELLINGS, createTypedOptionConsumer, evaluateAnalystModeOptionContract, optionsForOwner, resolveAnalystMode, } from "./option-definitions.js";
28
28
  import { errorText, isRecord } from "../unknown-value.js";
29
+ /** Nonblank admitted source path, preserving its original bytes. */
30
+ export function admittedSourceRunPath(admitted) {
31
+ if (!isRecord(admitted))
32
+ return undefined;
33
+ const path = admitted.sourceRunPath;
34
+ return typeof path === "string" && path.trim() !== "" ? path : undefined;
35
+ }
29
36
  /** Normalize admitted attachment records (caller path only; #1165). */
30
37
  export function normalizeAdmittedAttachment(raw) {
31
38
  if (typeof raw !== "object" || raw === null)
@@ -174,6 +181,34 @@ export async function persistAdmittedSourceRunPath(admitted, sourceRunPath, audi
174
181
  };
175
182
  });
176
183
  }
184
+ /**
185
+ * #1195 option 1: record the parent sealed toolCallId this officer is reviewing
186
+ * on the officer's own admitted-request (one field). Re-summons for a newer seal
187
+ * replace the value; continueParentAfterChild only trusts a child whose field
188
+ * matches the parent seal currently under audit.
189
+ */
190
+ export function persistAdmittedAuditedSubmissionToolCallId(runDirectory, auditedSubmissionToolCallId) {
191
+ const id = auditedSubmissionToolCallId.trim();
192
+ if (id === "") {
193
+ throw new Error("persistAdmittedAuditedSubmissionToolCallId requires a non-empty id");
194
+ }
195
+ // Offline gate mocks may return a runDirectory without admission — skip, do not invent.
196
+ if (readPageSync(runDirectory, "admitted") === undefined)
197
+ return;
198
+ updateSectionSync(runDirectory, "admitted", (current) => {
199
+ if (current.auditedSubmissionToolCallId === id)
200
+ return undefined;
201
+ return { ...current, auditedSubmissionToolCallId: id };
202
+ });
203
+ }
204
+ /** Read the sealed id this officer admitted-request last recorded (#1195). */
205
+ export function readAdmittedAuditedSubmissionToolCallId(runDirectory) {
206
+ const page = readPageSync(runDirectory, "admitted");
207
+ if (page === undefined)
208
+ return undefined;
209
+ const id = page.auditedSubmissionToolCallId;
210
+ return typeof id === "string" && id.trim() !== "" ? id : undefined;
211
+ }
177
212
  /**
178
213
  * Bind a post-admission resolved ticketNumber onto the in-memory admitted
179
214
  * object and both durable sections (invocation + admitted).