@akagilnc/pi-workflow-roles 0.1.5153 → 0.1.5196
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.
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/acp-host/production-host.js +13086 -13034
- package/dist/archivist-record-entry.js +4 -20
- package/dist/archivist-record-pointer.js +28 -0
- package/dist/audit-escalation.js +3 -119
- package/dist/auditor-soul.js +32 -0
- package/dist/doctor-auditor.js +26 -0
- package/dist/headless-host/production-host.js +11218 -11166
- package/dist/judge-role.js +73 -0
- package/dist/migrate-book-topology.js +8 -0
- package/dist/package-contracts/judge-output.js +1 -18
- package/dist/package-contracts/terminating-tools.js +2 -2
- package/dist/public-cli/cli.js +2 -2
- package/dist/public-cli/countersign-run.js +11 -0
- package/dist/public-cli/instruction-seat-run.js +428 -48
- package/dist/public-cli/invocation.js +10 -5
- package/dist/public-cli/main.js +10049 -8911
- package/dist/public-cli/settlement.js +28 -8
- package/dist/public-role-summons.js +6 -4
- package/dist/submission-errors.js +1 -1
- package/dist/submission-gate.js +165 -0
- package/dist/submission-ledger.js +0 -27
- package/dist/worker-submission-contracts.js +8 -0
- package/package.json +1 -1
- package/src/archivist-record-entry.ts +5 -49
- package/src/archivist-record-pointer.ts +49 -0
- package/src/audit-escalation.ts +4 -217
- package/src/auditor-soul.ts +33 -0
- package/src/doctor-role.ts +11 -5
- package/src/gatekeeper-role.ts +2 -2
- package/src/host-contracts.ts +0 -25
- package/src/judge-role.ts +63 -90
- package/src/package-contracts/judge-output.ts +0 -17
- package/src/package-contracts/terminating-tools.ts +0 -2
- package/src/pi/adapter.ts +1 -33
- package/src/public-cli/cli.ts +2 -2
- package/src/public-cli/countersign-run.ts +11 -0
- package/src/public-cli/instruction-seat-run.ts +433 -16
- package/src/public-cli/invocation.ts +9 -3
- package/src/public-cli/post-admission.ts +0 -1
- package/src/public-cli/settlement.ts +32 -8
- package/src/public-role-summons.ts +9 -6
- package/src/role-envelope.ts +0 -32
- package/src/role-runtime-dependencies.ts +0 -4
- package/src/role-runtime.ts +15 -218
- package/src/submission-errors.ts +1 -1
- package/src/submission-gate.ts +46 -10
- package/src/submission-ledger.ts +0 -27
- package/src/worker-role.ts +23 -102
- package/src/worker-submission-contracts.ts +8 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|