@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
|
@@ -199,6 +199,17 @@ export function courtTicketNumbersFromOutcome(
|
|
|
199
199
|
return latest;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/** Latest payload only. An earlier escalate must not block a later non-escalate submission. */
|
|
203
|
+
export function latestPayloadEscalated(
|
|
204
|
+
roleOutcome: TerminalRoleOutcome | undefined,
|
|
205
|
+
): boolean {
|
|
206
|
+
if (roleOutcome === undefined) return false;
|
|
207
|
+
if (roleOutcome.kind === "audit_escalation") return true;
|
|
208
|
+
if (roleOutcome.kind !== "accepted") return false;
|
|
209
|
+
const payloads = roleOutcome.payloads ?? [];
|
|
210
|
+
return isEscalatePayload(payloads[payloads.length - 1]);
|
|
211
|
+
}
|
|
212
|
+
|
|
202
213
|
/** Routing boolean over the child's own typed sequence — does not pick or rewrite a sole row. */
|
|
203
214
|
export function courtDiaristEscalated(
|
|
204
215
|
roleOutcome: TerminalRoleOutcome | undefined,
|
|
@@ -4,9 +4,26 @@
|
|
|
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.ts";
|
|
9
|
+
import { bookDirectOfficerRunPointer } from "../archivist-record-pointer.ts";
|
|
10
|
+
import { createPiDoctorAuditor } from "../doctor-auditor.ts";
|
|
8
11
|
|
|
9
|
-
import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
|
|
12
|
+
import type { DurablePrincipalAuthority, HostContext, RoleTurnRequest } from "../host-contracts.ts";
|
|
13
|
+
import { officerConclusionReask, gateOfficerForSubject } from "../gatekeeper-role.ts";
|
|
14
|
+
import { runJudgeGates } from "../judge-role.ts";
|
|
15
|
+
import { WORKER_DONE_STATUSES } from "../worker-submission-contracts.ts";
|
|
16
|
+
import { SECRETARIAT_GATE_OFFICER_ENTRY_TYPE } from "../secretariat-contracts.ts";
|
|
17
|
+
import { readableGateItem } from "../readable-gate-item.ts";
|
|
18
|
+
import { runIdFromRunDirectory } from "../run-terminal-artifacts.ts";
|
|
19
|
+
import { createDefaultGateOfficerSummon, requireSubmissionGate } from "../submission-gate.ts";
|
|
20
|
+
import { readRecordedSubmissionRows } from "../submission-ledger.ts";
|
|
21
|
+
import { summonPublicRole, type PublicSummonResult } from "../public-role-summons.ts";
|
|
22
|
+
import { sessionFileFromPublicSummon } from "../session-assistant-usage.ts";
|
|
23
|
+
import {
|
|
24
|
+
latestQueuePayload,
|
|
25
|
+
latestQueueStatus,
|
|
26
|
+
} from "../submission-gate.ts";
|
|
10
27
|
import { isSafePositiveTicketNumber, readBoardTicketNumber } from "../run-ticket-number.ts";
|
|
11
28
|
import { NotarySourceRunError, resolveNotarySourceRunLocator } from "../notary-source-run.ts";
|
|
12
29
|
import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.ts";
|
|
@@ -17,7 +34,7 @@ import {
|
|
|
17
34
|
packagedRebindSourceOnResume,
|
|
18
35
|
packagedRoleMetadata,
|
|
19
36
|
} from "../packaged-role-registry.ts";
|
|
20
|
-
import { isAuditorSoulRole } from "../auditor-soul.ts";
|
|
37
|
+
import { isAuditorSoulRole, readAuditorResumeBinding } from "../auditor-soul.ts";
|
|
21
38
|
import { CliUsageError } from "./cli-errors.ts";
|
|
22
39
|
import {
|
|
23
40
|
admitPublicRole,
|
|
@@ -59,7 +76,9 @@ import {
|
|
|
59
76
|
import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
|
|
60
77
|
import {
|
|
61
78
|
presentStructuralRejection,
|
|
79
|
+
readBoundSessionEntries,
|
|
62
80
|
seatKnownFailureResolver,
|
|
81
|
+
attachPostAuditProjection,
|
|
63
82
|
trySettlePublicSeat,
|
|
64
83
|
} from "./settlement.ts";
|
|
65
84
|
import type { CliIo } from "./cli-io.ts";
|
|
@@ -68,6 +87,7 @@ import {
|
|
|
68
87
|
formatTerminalResult,
|
|
69
88
|
isLawfulTypedTerminalOutcome,
|
|
70
89
|
type TerminalResult,
|
|
90
|
+
type TerminalRoleName,
|
|
71
91
|
} from "./terminal.ts";
|
|
72
92
|
import {
|
|
73
93
|
admittedSeatTurnDetails,
|
|
@@ -77,10 +97,10 @@ import {
|
|
|
77
97
|
} from "./turn-request.ts";
|
|
78
98
|
import {
|
|
79
99
|
invokeCourtDiarist,
|
|
100
|
+
latestPayloadEscalated,
|
|
80
101
|
runCountersignCourtDiaristStation,
|
|
81
102
|
type CountersignRunEnv,
|
|
82
103
|
} from "./countersign-run.ts";
|
|
83
|
-
|
|
84
104
|
export type InstructionSeatRunEnv = PostAdmissionEnv & Pick<
|
|
85
105
|
CountersignRunEnv,
|
|
86
106
|
"reviewReask" | "gateReviewInstruction" | "parentRunPath" | "runCourtDiaristStation"
|
|
@@ -92,6 +112,76 @@ type SeatRunResult = {
|
|
|
92
112
|
terminal?: TerminalResult;
|
|
93
113
|
};
|
|
94
114
|
|
|
115
|
+
const AUDITED_ROLES = new Set<PackagedRole>(["judge", "fixer", "coder", "secretariat", "countersign", "doctor"]);
|
|
116
|
+
const SECRETARIAT_STATUS_REASK = "secretariatStatus 不是 converged、escalate 之一。请重新交卷,secretariatStatus 写明其一。";
|
|
117
|
+
const COUNTERSIGN_STATUS_REASK = "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。";
|
|
118
|
+
const WORKER_ROUTING_STATUSES: ReadonlySet<string> = new Set([
|
|
119
|
+
"planned", "completed", "refused", "partially_completed", "unfinished",
|
|
120
|
+
]);
|
|
121
|
+
const WORKER_STATUS_REASK =
|
|
122
|
+
"status 不是 planned、completed、refused、partially_completed、unfinished 之一。请重新交卷,status 写明其一。";
|
|
123
|
+
const DOCTOR_ROUTING_STATUSES: ReadonlySet<string> = new Set(["completed", "refused"]);
|
|
124
|
+
const DOCTOR_STATUS_REASK =
|
|
125
|
+
"status 不是 completed、refused 之一。请重新交卷,status 写明其一。";
|
|
126
|
+
const POST_SUBMISSION_ROUTING: Partial<Record<PackagedRole, {
|
|
127
|
+
readonly statuses: ReadonlySet<string>;
|
|
128
|
+
readonly reask: string;
|
|
129
|
+
}>> = {
|
|
130
|
+
judge: {
|
|
131
|
+
statuses: new Set(["converged", "continue", "escalate"]),
|
|
132
|
+
reask: "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。",
|
|
133
|
+
},
|
|
134
|
+
coder: {
|
|
135
|
+
statuses: WORKER_ROUTING_STATUSES,
|
|
136
|
+
reask: WORKER_STATUS_REASK,
|
|
137
|
+
},
|
|
138
|
+
fixer: {
|
|
139
|
+
statuses: WORKER_ROUTING_STATUSES,
|
|
140
|
+
reask: WORKER_STATUS_REASK,
|
|
141
|
+
},
|
|
142
|
+
diarist: {
|
|
143
|
+
statuses: new Set(["completed", "escalate"]),
|
|
144
|
+
reask: "status 不是 completed、escalate 之一。请重新交卷,status 写明其一。",
|
|
145
|
+
},
|
|
146
|
+
// Doctor contract domain is completed|refused; open-domain escalate is accepted
|
|
147
|
+
// by the tool then routed back here before mandatory auditor (#1057).
|
|
148
|
+
doctor: {
|
|
149
|
+
statuses: DOCTOR_ROUTING_STATUSES,
|
|
150
|
+
reask: DOCTOR_STATUS_REASK,
|
|
151
|
+
},
|
|
152
|
+
} as const;
|
|
153
|
+
|
|
154
|
+
function unreadablePostSubmissionStatus(
|
|
155
|
+
admitted: AdmittedRoleInvocation,
|
|
156
|
+
terminal: TerminalResult | undefined,
|
|
157
|
+
): string | undefined {
|
|
158
|
+
if (terminal?.roleOutcome.kind !== "accepted") return undefined;
|
|
159
|
+
const route = POST_SUBMISSION_ROUTING[admitted.role];
|
|
160
|
+
if (route === undefined) return undefined;
|
|
161
|
+
const payload = terminal.roleOutcome.payloads?.at(-1);
|
|
162
|
+
const status = payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
163
|
+
? (payload as Record<string, unknown>).status
|
|
164
|
+
: undefined;
|
|
165
|
+
return typeof status === "string" && route.statuses.has(status)
|
|
166
|
+
? undefined
|
|
167
|
+
: route.reask;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function reaskUnreadablePostSubmissionStatus(
|
|
171
|
+
admitted: AdmittedRoleInvocation,
|
|
172
|
+
terminal: TerminalResult | undefined,
|
|
173
|
+
env: InstructionSeatRunEnv,
|
|
174
|
+
io: CliIo,
|
|
175
|
+
): Promise<SeatRunResult | undefined> {
|
|
176
|
+
const message = unreadablePostSubmissionStatus(admitted, terminal);
|
|
177
|
+
if (message === undefined) return undefined;
|
|
178
|
+
return runPublicInstructionSeatResume(
|
|
179
|
+
{ runId: admitted.runId, message },
|
|
180
|
+
{ ...env, autoResumeLimit: 0 },
|
|
181
|
+
io,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
95
185
|
function roleRecord(role: PackagedRole) {
|
|
96
186
|
const record = packagedRoleMetadata(role);
|
|
97
187
|
if (record === undefined) {
|
|
@@ -231,13 +321,21 @@ async function dispatchAdmitted(
|
|
|
231
321
|
const record = roleRecord(admitted.role);
|
|
232
322
|
await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
|
|
233
323
|
const adapters = seatAdapters(admitted, env);
|
|
324
|
+
const held: string[] = [];
|
|
325
|
+
const turnIo: CliIo = AUDITED_ROLES.has(admitted.role)
|
|
326
|
+
|| POST_SUBMISSION_ROUTING[admitted.role] !== undefined
|
|
327
|
+
? { stdout: (value) => held.push(value), stderr: io.stderr }
|
|
328
|
+
: io;
|
|
234
329
|
const execute = async (activeEnv: InstructionSeatRunEnv): Promise<SeatRunResult> => {
|
|
330
|
+
if (activeEnv.correlationId !== undefined && activeEnv.correlationId.trim() !== "") {
|
|
331
|
+
await recordAdmittedCorrelation(admitted, activeEnv.correlationId);
|
|
332
|
+
}
|
|
235
333
|
const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
|
|
236
334
|
if (auto) {
|
|
237
335
|
return await runPostAdmissionResumable({
|
|
238
336
|
admitted,
|
|
239
337
|
env: activeEnv,
|
|
240
|
-
io,
|
|
338
|
+
io: turnIo,
|
|
241
339
|
buildInitialRequest: () => buildInstructionSeatTurnRequest(
|
|
242
340
|
admitted,
|
|
243
341
|
roleTurnOptions(activeEnv, admitted, { kind: "initial", prompt: initialPrompt(admitted, activeEnv) }),
|
|
@@ -259,7 +357,7 @@ async function dispatchAdmitted(
|
|
|
259
357
|
return await runPostAdmissionOneShot({
|
|
260
358
|
admitted,
|
|
261
359
|
env: activeEnv,
|
|
262
|
-
io,
|
|
360
|
+
io: turnIo,
|
|
263
361
|
request: buildInstructionSeatTurnRequest(
|
|
264
362
|
admitted,
|
|
265
363
|
roleTurnOptions(activeEnv, admitted, { kind: "initial", prompt: initialPrompt(admitted, activeEnv) }),
|
|
@@ -268,7 +366,19 @@ async function dispatchAdmitted(
|
|
|
268
366
|
...(activeEnv.engine === undefined ? {} : { effectiveEngine: activeEnv.engine }),
|
|
269
367
|
});
|
|
270
368
|
};
|
|
271
|
-
|
|
369
|
+
const result = await execute(env);
|
|
370
|
+
const unreadableReask = await reaskUnreadablePostSubmissionStatus(
|
|
371
|
+
admitted,
|
|
372
|
+
result.terminal,
|
|
373
|
+
env,
|
|
374
|
+
io,
|
|
375
|
+
);
|
|
376
|
+
if (unreadableReask !== undefined) return unreadableReask;
|
|
377
|
+
if (AUDITED_ROLES.has(admitted.role) && result.terminal?.roleOutcome.kind === "accepted") {
|
|
378
|
+
return auditSubmittedRole(result, env, io);
|
|
379
|
+
}
|
|
380
|
+
for (const value of held) io.stdout(value);
|
|
381
|
+
return result;
|
|
272
382
|
}
|
|
273
383
|
|
|
274
384
|
/**
|
|
@@ -297,7 +407,7 @@ async function runOmittedLensBatch(
|
|
|
297
407
|
packageRoot: env.packageRoot,
|
|
298
408
|
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
299
409
|
...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
|
|
300
|
-
roleTurnHost: env.roleTurnHost,
|
|
410
|
+
...(env.hostAdapters === undefined ? { roleTurnHost: env.roleTurnHost } : {}),
|
|
301
411
|
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
302
412
|
principalAuthority: env.principalAuthority,
|
|
303
413
|
...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
|
|
@@ -560,7 +670,13 @@ export async function runPublicInstructionSeat(
|
|
|
560
670
|
throw error;
|
|
561
671
|
}
|
|
562
672
|
if (packagedAdmitsCountersign(role)) {
|
|
563
|
-
|
|
673
|
+
const held: string[] = [];
|
|
674
|
+
const result = await runCountersignBody(parsed, env, {
|
|
675
|
+
stdout: (value) => held.push(value), stderr: io.stderr,
|
|
676
|
+
});
|
|
677
|
+
if (result.terminal?.roleOutcome.kind === "accepted") return auditSubmittedRole(result, env, io);
|
|
678
|
+
for (const value of held) io.stdout(value);
|
|
679
|
+
return result;
|
|
564
680
|
}
|
|
565
681
|
|
|
566
682
|
if ("parallelLenses" in record && record.parallelLenses === true) {
|
|
@@ -713,7 +829,7 @@ export async function runPublicInstructionSeat(
|
|
|
713
829
|
}
|
|
714
830
|
}
|
|
715
831
|
if (record.sameParent === "subject-source" && auditorSource !== undefined) {
|
|
716
|
-
await persistAdmittedSourceRunPath(admitted, auditorSource);
|
|
832
|
+
await persistAdmittedSourceRunPath(admitted, auditorSource, auditorSubject);
|
|
717
833
|
}
|
|
718
834
|
|
|
719
835
|
const runAdmitted = async (): Promise<SeatRunResult> => {
|
|
@@ -765,10 +881,17 @@ export async function runPublicInstructionSeatResume(
|
|
|
765
881
|
env: InstructionSeatRunEnv,
|
|
766
882
|
io: CliIo,
|
|
767
883
|
): Promise<SeatRunResult> {
|
|
768
|
-
|
|
884
|
+
const resume = async () => {
|
|
885
|
+
const held: string[] = [];
|
|
886
|
+
const turnIo: CliIo = {
|
|
887
|
+
stdout: (value) => held.push(value),
|
|
888
|
+
stderr: io.stderr,
|
|
889
|
+
};
|
|
890
|
+
const result = await runPostAdmissionSeatResume<AdmittedRoleInvocation>({
|
|
891
|
+
|
|
769
892
|
request,
|
|
770
893
|
env,
|
|
771
|
-
io,
|
|
894
|
+
io: turnIo,
|
|
772
895
|
load: async (effective) => {
|
|
773
896
|
const loaded = await loadResumablePublicRole(env.home, effective.runId, env.principalAuthority);
|
|
774
897
|
if (
|
|
@@ -804,10 +927,87 @@ export async function runPublicInstructionSeatResume(
|
|
|
804
927
|
return { kind: "continue" as const, adapters: seatAdapters(admitted, env) };
|
|
805
928
|
},
|
|
806
929
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
807
|
-
|
|
930
|
+
});
|
|
931
|
+
const unreadableReask = result.admitted === undefined
|
|
932
|
+
? undefined
|
|
933
|
+
: await reaskUnreadablePostSubmissionStatus(
|
|
934
|
+
result.admitted,
|
|
935
|
+
result.terminal,
|
|
936
|
+
env,
|
|
937
|
+
io,
|
|
938
|
+
);
|
|
939
|
+
if (unreadableReask !== undefined) return unreadableReask;
|
|
940
|
+
if (result.admitted !== undefined && AUDITED_ROLES.has(result.admitted.role)
|
|
941
|
+
&& result.terminal?.roleOutcome.kind === "accepted") {
|
|
942
|
+
return auditSubmittedRole(result, env, io);
|
|
943
|
+
}
|
|
944
|
+
for (const value of held) io.stdout(value);
|
|
945
|
+
return result;
|
|
946
|
+
};
|
|
947
|
+
let binding: Awaited<ReturnType<typeof readAuditorResumeBinding>>;
|
|
948
|
+
try {
|
|
949
|
+
const loaded = await loadResumablePublicRole(env.home, request.runId, env.principalAuthority, true);
|
|
950
|
+
binding = loaded.admitted.role === "auditor"
|
|
951
|
+
? await readAuditorResumeBinding(loaded.admitted.runDirectory)
|
|
952
|
+
: undefined;
|
|
953
|
+
} catch (error) {
|
|
954
|
+
if (!(error instanceof CliUsageError)) throw error;
|
|
955
|
+
}
|
|
956
|
+
if (binding !== undefined) {
|
|
957
|
+
return withAuditorSoulEnv({
|
|
958
|
+
subject: binding.subject,
|
|
959
|
+
sourceRunDirectory: binding.sourceRunDirectory,
|
|
960
|
+
run: resume,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
return resume();
|
|
808
964
|
}
|
|
809
965
|
|
|
810
|
-
|
|
966
|
+
const QUEUE_CONCLUSIONS = new Set(["converged", "continue", "escalate"]);
|
|
967
|
+
const GATE_CHILD_ROLES = new Set(["notary", "auditor", "inspector", "countersign"]);
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* A gate child's conclusion outside the three states goes back to that child.
|
|
971
|
+
* The words are the existing officer re-ask. A host failure stops here.
|
|
972
|
+
*/
|
|
973
|
+
async function queueConclusionFromChild(
|
|
974
|
+
child: AdmittedRoleInvocation,
|
|
975
|
+
env: InstructionSeatRunEnv,
|
|
976
|
+
io: CliIo,
|
|
977
|
+
): Promise<
|
|
978
|
+
| { readonly admitted: AdmittedRoleInvocation; readonly terminal: TerminalResult; readonly status: string }
|
|
979
|
+
| { readonly stop: SeatRunResult }
|
|
980
|
+
| undefined
|
|
981
|
+
> {
|
|
982
|
+
if (!GATE_CHILD_ROLES.has(child.role)) return undefined;
|
|
983
|
+
let current = child;
|
|
984
|
+
for (;;) {
|
|
985
|
+
const terminal = await trySettlePublicSeat(
|
|
986
|
+
current,
|
|
987
|
+
env.principalAuthority,
|
|
988
|
+
undefined,
|
|
989
|
+
);
|
|
990
|
+
const status = latestQueueStatus(terminal);
|
|
991
|
+
if (terminal !== undefined && status !== undefined && QUEUE_CONCLUSIONS.has(status)) {
|
|
992
|
+
return { admitted: current, terminal, status };
|
|
993
|
+
}
|
|
994
|
+
const reasked = await runPublicInstructionSeatResume({
|
|
995
|
+
runId: current.runId,
|
|
996
|
+
message: officerConclusionReask(status),
|
|
997
|
+
}, env, io);
|
|
998
|
+
if (reasked.exitCode !== 0 || reasked.admitted === undefined || reasked.terminal === undefined) {
|
|
999
|
+
return { stop: reasked };
|
|
1000
|
+
}
|
|
1001
|
+
const reaskedStatus = latestQueueStatus(reasked.terminal);
|
|
1002
|
+
if (reaskedStatus !== undefined && QUEUE_CONCLUSIONS.has(reaskedStatus)) {
|
|
1003
|
+
return { admitted: reasked.admitted, terminal: reasked.terminal, status: reaskedStatus };
|
|
1004
|
+
}
|
|
1005
|
+
if (latestPayloadEscalated(reasked.terminal.roleOutcome)) return { stop: reasked };
|
|
1006
|
+
current = reasked.admitted;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/** Continue the parent once a child has submitted. The child words ride the existing resume. */
|
|
811
1011
|
export async function continueParentAfterChild(
|
|
812
1012
|
parentRunId: string,
|
|
813
1013
|
child: AdmittedRoleInvocation,
|
|
@@ -815,18 +1015,235 @@ export async function continueParentAfterChild(
|
|
|
815
1015
|
io: CliIo,
|
|
816
1016
|
): Promise<SeatRunResult> {
|
|
817
1017
|
const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority, true);
|
|
818
|
-
if (loaded.run.state !== "admitted") {
|
|
1018
|
+
if (loaded.run.state !== "admitted" && !AUDITED_ROLES.has(loaded.admitted.role)) {
|
|
819
1019
|
return runPublicInstructionSeatResume({ runId: parentRunId }, env, io);
|
|
820
1020
|
}
|
|
821
1021
|
const admitted = loaded.admitted;
|
|
822
|
-
|
|
1022
|
+
// Court diarist refresh advances only after a diarist child; gate officers
|
|
1023
|
+
// (notary/auditor/…) resume straight into queueConclusionFromChild (#1057).
|
|
1024
|
+
if (admitted.role === "countersign" && admitted.ticketNumber !== undefined
|
|
1025
|
+
&& child.role === "diarist") {
|
|
823
1026
|
const refresh = await runCountersignCourtDiaristStation(admitted, env, io, child.ticketNumber);
|
|
824
1027
|
if (refresh !== undefined) {
|
|
825
1028
|
io.stdout(formatTerminalResult(refresh.terminal));
|
|
826
1029
|
return { exitCode: 0, admitted, terminal: refresh.terminal };
|
|
827
1030
|
}
|
|
828
1031
|
}
|
|
1032
|
+
const resolved = await queueConclusionFromChild(child, env, io);
|
|
1033
|
+
if (resolved !== undefined && "stop" in resolved) return resolved.stop;
|
|
1034
|
+
if (resolved !== undefined && resolved.status === "escalate") {
|
|
1035
|
+
return { exitCode: 0, admitted: resolved.admitted, terminal: resolved.terminal };
|
|
1036
|
+
}
|
|
1037
|
+
if (resolved !== undefined && (resolved.status === "continue" || resolved.status === "converged")) {
|
|
1038
|
+
if (AUDITED_ROLES.has(admitted.role) && resolved.status === "converged") {
|
|
1039
|
+
const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
|
|
1040
|
+
if (terminal?.roleOutcome.kind !== "accepted") throw new Error("pending submission is not recorded");
|
|
1041
|
+
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted.role, latestQueuePayload(resolved.terminal), resolved.admitted.runId);
|
|
1042
|
+
}
|
|
1043
|
+
return runPublicInstructionSeatResume({
|
|
1044
|
+
runId: parentRunId,
|
|
1045
|
+
message: readableGateItem(latestQueuePayload(resolved.terminal)),
|
|
1046
|
+
}, { ...env, autoResumeLimit: 0 }, io);
|
|
1047
|
+
}
|
|
829
1048
|
const result = await dispatchAdmitted(admitted, env, { ...io, omitFailureStderrDiagnostic: true });
|
|
830
1049
|
showResumeErrorPointer(io, result.exitCode, result.terminal);
|
|
831
1050
|
return result;
|
|
832
1051
|
}
|
|
1052
|
+
|
|
1053
|
+
/** Finished submissions enter the existing audit gate after their tool call has returned. */
|
|
1054
|
+
async function auditSubmittedRole(
|
|
1055
|
+
turn: SeatRunResult,
|
|
1056
|
+
env: InstructionSeatRunEnv,
|
|
1057
|
+
io: CliIo,
|
|
1058
|
+
passedOfficer?: PackagedRole,
|
|
1059
|
+
passedReceipt?: unknown,
|
|
1060
|
+
passedRunId?: string,
|
|
1061
|
+
): Promise<SeatRunResult> {
|
|
1062
|
+
const admitted = turn.admitted;
|
|
1063
|
+
if (admitted === undefined || !AUDITED_ROLES.has(admitted.role)
|
|
1064
|
+
|| turn.terminal?.roleOutcome.kind !== "accepted") return turn;
|
|
1065
|
+
const accepted = turn.terminal.roleOutcome.payloads?.at(-1);
|
|
1066
|
+
if (accepted === undefined) throw new Error("accepted submission has no payload");
|
|
1067
|
+
const record = accepted !== null && typeof accepted === "object" && !Array.isArray(accepted)
|
|
1068
|
+
? accepted as Record<string, unknown> : undefined;
|
|
1069
|
+
const status = admitted.role === "secretariat" ? record?.secretariatStatus : record?.status;
|
|
1070
|
+
if (admitted.role === "secretariat" && status !== "converged" && status !== "escalate") {
|
|
1071
|
+
return runPublicInstructionSeatResume({ runId: admitted.runId, message: SECRETARIAT_STATUS_REASK },
|
|
1072
|
+
{ ...env, autoResumeLimit: 0 }, io);
|
|
1073
|
+
}
|
|
1074
|
+
if (admitted.role === "countersign" && (typeof status !== "string" || !QUEUE_CONCLUSIONS.has(status))) {
|
|
1075
|
+
return runPublicInstructionSeatResume({ runId: admitted.runId, message: COUNTERSIGN_STATUS_REASK },
|
|
1076
|
+
{ ...env, autoResumeLimit: 0 }, io);
|
|
1077
|
+
}
|
|
1078
|
+
// Self-escalation is a valid open routing state for these seats only.
|
|
1079
|
+
// Doctor's declared domain is completed|refused — an open "escalate" must
|
|
1080
|
+
// not skip its mandatory auditor (#1057 / PR #1075).
|
|
1081
|
+
const selfEscalationSkipsAudit = status === "escalate"
|
|
1082
|
+
&& (admitted.role === "judge" || admitted.role === "secretariat"
|
|
1083
|
+
|| admitted.role === "countersign");
|
|
1084
|
+
const skipAudit = selfEscalationSkipsAudit
|
|
1085
|
+
|| ((admitted.role === "fixer" || admitted.role === "coder")
|
|
1086
|
+
&& (typeof status !== "string" || !WORKER_DONE_STATUSES.has(status)));
|
|
1087
|
+
if (skipAudit) {
|
|
1088
|
+
io.stdout(formatTerminalResult(turn.terminal));
|
|
1089
|
+
return turn;
|
|
1090
|
+
}
|
|
1091
|
+
const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, env.home);
|
|
1092
|
+
const acceptedRow = [...rows].reverse().find((row) => row.role === admitted.role && row.kind === "accepted");
|
|
1093
|
+
const toolCallId = acceptedRow?.toolCallId;
|
|
1094
|
+
if (toolCallId === undefined) throw new Error("accepted submission has no tool call identity");
|
|
1095
|
+
const sessionFile = env.principalAuthority.decode(admitted.principal).sessionFile;
|
|
1096
|
+
const entries = admitted.role === "doctor" ? await readBoundSessionEntries(sessionFile) : [];
|
|
1097
|
+
const context: HostContext = {
|
|
1098
|
+
cwd: admitted.projectRoot,
|
|
1099
|
+
mode: "print",
|
|
1100
|
+
model: undefined,
|
|
1101
|
+
runDirectory: admitted.runDirectory,
|
|
1102
|
+
sessionManager: {
|
|
1103
|
+
getSessionFile: () => sessionFile,
|
|
1104
|
+
getSessionDir: () => dirname(sessionFile),
|
|
1105
|
+
getEntries: () => entries as unknown as Iterable<{ type: string }>,
|
|
1106
|
+
getLeafEntry: () => undefined,
|
|
1107
|
+
getLeafId: () => admitted.runId,
|
|
1108
|
+
getHeader: () => ({ type: "session", id: admitted.runId }),
|
|
1109
|
+
},
|
|
1110
|
+
abort() {},
|
|
1111
|
+
};
|
|
1112
|
+
if (admitted.role === "doctor") {
|
|
1113
|
+
if (passedOfficer !== "auditor") {
|
|
1114
|
+
let lastSummon: PublicSummonResult | undefined;
|
|
1115
|
+
const decision = await createPiDoctorAuditor()({
|
|
1116
|
+
context, submission: accepted,
|
|
1117
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
1118
|
+
summonAuditor: async (subject, sourceRunDirectory, signal, reask, submission) => {
|
|
1119
|
+
const summoned = await summonPublicRole({
|
|
1120
|
+
role: "auditor",
|
|
1121
|
+
argv: ["--subject", subject, "--source-run", sourceRunDirectory,
|
|
1122
|
+
`${AUDITOR_DOSSIER_PROMPT}${sourceRunDirectory}`],
|
|
1123
|
+
cwd: admitted.projectRoot, home: env.home, packageRoot: env.packageRoot,
|
|
1124
|
+
principalAuthority: env.principalAuthority,
|
|
1125
|
+
correlationId: admitted.runId,
|
|
1126
|
+
...(env.credentials === undefined ? {} : { credentials: env.credentials }),
|
|
1127
|
+
...(env.hostAdapters === undefined ? { roleTurnHost: env.roleTurnHost } : { hostAdapters: env.hostAdapters }),
|
|
1128
|
+
...(signal === undefined ? {} : { signal }),
|
|
1129
|
+
...(reask === undefined ? {} : { reviewReask: reask }),
|
|
1130
|
+
...(submission === undefined ? {} : { gateReviewInstruction: submission }),
|
|
1131
|
+
});
|
|
1132
|
+
const officerSession = sessionFileFromPublicSummon(summoned);
|
|
1133
|
+
if (officerSession !== undefined) {
|
|
1134
|
+
bookDirectOfficerRunPointer({
|
|
1135
|
+
parentSessionFile: sessionFile, officer: "auditor", sessionFile: officerSession,
|
|
1136
|
+
...(summoned.runDirectory === undefined ? {} : { runDirectory: summoned.runDirectory }),
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
lastSummon = summoned;
|
|
1140
|
+
return summoned;
|
|
1141
|
+
},
|
|
1142
|
+
});
|
|
1143
|
+
if (decision.status === "continue") {
|
|
1144
|
+
return runPublicInstructionSeatResume({
|
|
1145
|
+
runId: admitted.runId, message: readableGateItem(decision.receipt ?? decision.violations),
|
|
1146
|
+
}, { ...env, autoResumeLimit: 0 }, io);
|
|
1147
|
+
}
|
|
1148
|
+
if (decision.status !== "converged") {
|
|
1149
|
+
if (lastSummon?.terminal !== undefined) {
|
|
1150
|
+
io.stdout(formatTerminalResult(lastSummon.terminal));
|
|
1151
|
+
return { exitCode: lastSummon.exitCode,
|
|
1152
|
+
...(lastSummon.admitted === undefined ? {} : { admitted: lastSummon.admitted }),
|
|
1153
|
+
terminal: lastSummon.terminal };
|
|
1154
|
+
}
|
|
1155
|
+
throw new Error(`doctor audit did not converge: ${decision.status}`);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
const summonOfficer = createDefaultGateOfficerSummon({
|
|
1160
|
+
cwd: admitted.projectRoot,
|
|
1161
|
+
home: env.home,
|
|
1162
|
+
packageRoot: env.packageRoot,
|
|
1163
|
+
roleTurnHost: env.roleTurnHost,
|
|
1164
|
+
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
1165
|
+
});
|
|
1166
|
+
let chain: Awaited<ReturnType<typeof runJudgeGates>>;
|
|
1167
|
+
let officerRunId = passedRunId;
|
|
1168
|
+
const runGate = (subject: Parameters<typeof requireSubmissionGate>[0]["subject"]) =>
|
|
1169
|
+
requireSubmissionGate({
|
|
1170
|
+
context,
|
|
1171
|
+
subject,
|
|
1172
|
+
toolCallId,
|
|
1173
|
+
submission: accepted,
|
|
1174
|
+
summonOfficer,
|
|
1175
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
1176
|
+
hostActions: {
|
|
1177
|
+
failInfrastructure(error): never { throw error; },
|
|
1178
|
+
bindSubmissionNonPass() {},
|
|
1179
|
+
},
|
|
1180
|
+
});
|
|
1181
|
+
if (admitted.role === "judge") {
|
|
1182
|
+
chain = await runJudgeGates({
|
|
1183
|
+
gateAlreadyConverged: async (subject) => passedOfficer === "auditor"
|
|
1184
|
+
|| (passedOfficer === "notary" && subject.kind === "judge_draft"),
|
|
1185
|
+
runGate,
|
|
1186
|
+
});
|
|
1187
|
+
} else {
|
|
1188
|
+
const subject = admitted.role === "fixer" || admitted.role === "coder"
|
|
1189
|
+
? { kind: "worker_completion" as const }
|
|
1190
|
+
: admitted.role === "secretariat"
|
|
1191
|
+
? { kind: "secretariat_verdict" as const }
|
|
1192
|
+
: { kind: "countersign_verdict" as const };
|
|
1193
|
+
const officer = gateOfficerForSubject(subject);
|
|
1194
|
+
if (passedOfficer === officer) {
|
|
1195
|
+
chain = { status: "converged", passes: [] };
|
|
1196
|
+
} else {
|
|
1197
|
+
const pass = await runGate(subject);
|
|
1198
|
+
if (pass === undefined) throw new Error("audit gate returned no conclusion");
|
|
1199
|
+
officerRunId = pass.runId;
|
|
1200
|
+
chain = {
|
|
1201
|
+
status: pass.status,
|
|
1202
|
+
passes: [{
|
|
1203
|
+
subject,
|
|
1204
|
+
status: pass.status,
|
|
1205
|
+
receipt: pass.receipt,
|
|
1206
|
+
...(pass.runId === undefined ? {} : { runId: pass.runId }),
|
|
1207
|
+
...(pass.runDirectory === undefined ? {} : { runDirectory: pass.runDirectory }),
|
|
1208
|
+
}],
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
if (chain.status === "escalate") {
|
|
1213
|
+
const escalation = chain.passes.at(-1);
|
|
1214
|
+
const escalatedRunId = escalation?.runId
|
|
1215
|
+
?? (escalation?.runDirectory === undefined
|
|
1216
|
+
? undefined : runIdFromRunDirectory(escalation.runDirectory));
|
|
1217
|
+
if (escalatedRunId === undefined) throw new Error("escalated audit has no resumable run identity");
|
|
1218
|
+
const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority, true);
|
|
1219
|
+
const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority, undefined);
|
|
1220
|
+
if (terminal === undefined) throw new Error("escalated audit has no terminal result");
|
|
1221
|
+
io.stdout(formatTerminalResult(terminal));
|
|
1222
|
+
return { exitCode: 0, admitted: officer.admitted, terminal };
|
|
1223
|
+
}
|
|
1224
|
+
if (chain.status === "continue") {
|
|
1225
|
+
return runPublicInstructionSeatResume({
|
|
1226
|
+
runId: admitted.runId,
|
|
1227
|
+
message: readableGateItem(chain.passes.at(-1)?.receipt),
|
|
1228
|
+
}, { ...env, autoResumeLimit: 0 }, io);
|
|
1229
|
+
}
|
|
1230
|
+
if (admitted.role === "secretariat") {
|
|
1231
|
+
const pass = chain.passes.at(-1);
|
|
1232
|
+
const receipt = pass?.receipt ?? passedReceipt;
|
|
1233
|
+
if (receipt !== undefined) {
|
|
1234
|
+
await env.sessionAppender(env.principalAuthority, admitted.principal, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, {
|
|
1235
|
+
officer: "countersign", receipt,
|
|
1236
|
+
...(officerRunId === undefined ? {} : { runId: officerRunId }),
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
// The turn already settled this court (payloads, usage, autoResumeCount, history).
|
|
1242
|
+
// Audit only adds gate rounds and, for 中书省, the officer fact. A second
|
|
1243
|
+
// settle would republish the same attempt.
|
|
1244
|
+
if (turn.terminal === undefined) throw new Error(`audited ${admitted.role} submission did not settle`);
|
|
1245
|
+
const terminal = await attachPostAuditProjection(admitted, env.principalAuthority, turn.terminal);
|
|
1246
|
+
turn = { ...turn, terminal };
|
|
1247
|
+
io.stdout(formatTerminalResult(terminal));
|
|
1248
|
+
return turn;
|
|
1249
|
+
}
|
|
@@ -519,6 +519,7 @@ async function mergeInvocationIdentityPage(
|
|
|
519
519
|
export async function persistAdmittedSourceRunPath(
|
|
520
520
|
admitted: AdmittedRoleInvocation,
|
|
521
521
|
sourceRunPath: string,
|
|
522
|
+
auditorSubject?: "judge" | "doctor",
|
|
522
523
|
): Promise<void> {
|
|
523
524
|
if (sourceRunPath.trim() === "") {
|
|
524
525
|
throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
|
|
@@ -528,15 +529,20 @@ export async function persistAdmittedSourceRunPath(
|
|
|
528
529
|
string,
|
|
529
530
|
unknown
|
|
530
531
|
>;
|
|
531
|
-
if (typeof current.sourceRunPath === "string") {
|
|
532
|
-
if (current.sourceRunPath === sourceRunPath) return;
|
|
532
|
+
if (typeof current.sourceRunPath === "string" && current.sourceRunPath !== sourceRunPath) {
|
|
533
533
|
throw new Error(
|
|
534
534
|
`persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`,
|
|
535
535
|
);
|
|
536
536
|
}
|
|
537
|
+
const sameSubject = auditorSubject === undefined || current.auditorSubject === auditorSubject;
|
|
538
|
+
if (current.sourceRunPath === sourceRunPath && sameSubject) return;
|
|
537
539
|
await writeFile(
|
|
538
540
|
admittedPath,
|
|
539
|
-
`${JSON.stringify({
|
|
541
|
+
`${JSON.stringify({
|
|
542
|
+
...current,
|
|
543
|
+
sourceRunPath,
|
|
544
|
+
...(auditorSubject === undefined ? {} : { auditorSubject }),
|
|
545
|
+
}, null, 2)}\n`,
|
|
540
546
|
"utf8",
|
|
541
547
|
);
|
|
542
548
|
}
|
|
@@ -1084,7 +1084,6 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1084
1084
|
directHostFailureSignal
|
|
1085
1085
|
|| (result.code !== null && result.code !== 0)
|
|
1086
1086
|
|| resolution.knownFailure !== undefined;
|
|
1087
|
-
|
|
1088
1087
|
settled = await adapters.trySettle(admitted, env.principalAuthority, courtScope);
|
|
1089
1088
|
if (settled !== undefined) {
|
|
1090
1089
|
settled = await attachRecordedSubmissions(admitted, settled, courtScope) as T;
|