@akagilnc/pi-workflow-roles 0.1.5375 → 0.1.5406
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 -0
- package/README.zh-CN.md +2 -0
- package/dist/acp-host/production-host.js +6381 -5684
- package/dist/archivist-record-pointer.js +38 -0
- package/dist/collector-tool-schemas.js +18 -0
- package/dist/compliance-transport.js +5 -0
- package/dist/diarist-contracts.js +26 -0
- package/dist/doctor-auditor.js +1 -0
- package/dist/engine-detour-usage.js +6 -6
- package/dist/filed-submission.js +28 -0
- package/dist/gatekeeper-role.js +6 -10
- package/dist/gleaner-left-contracts.js +16 -0
- package/dist/headless-host/production-host.js +1644 -947
- package/dist/judge-role.js +21 -16
- package/dist/migrate-book-topology.js +452 -81
- package/dist/navigator-attendance.js +10 -9
- package/dist/navigator-invocation-identity.js +18 -0
- package/dist/package-contracts/reviewer-output.js +16 -0
- package/dist/package-contracts/worker-output.js +18 -2
- package/dist/pi/role-turn-host.js +9 -0
- package/dist/public-cli/cli.js +1 -0
- package/dist/public-cli/instruction-seat-run.js +258 -55
- package/dist/public-cli/main.js +1209 -346
- package/dist/public-cli/post-admission.js +427 -244
- package/dist/public-cli/settlement.js +111 -48
- package/dist/public-cli/terminal.js +12 -1
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/receipt-delivery-policy.js +152 -17
- package/dist/role-submission-declarations.js +131 -0
- package/dist/secretariat-contracts.js +23 -1
- package/dist/submission-errors.js +11 -1
- package/dist/submission-gate.js +32 -12
- package/package.json +1 -1
- package/src/archivist-record-pointer.ts +48 -0
- package/src/auditor-role.ts +2 -8
- package/src/collector-role.ts +5 -28
- package/src/compliance-transport.ts +10 -2
- package/src/countersign-role.ts +2 -7
- package/src/diarist-contracts.ts +37 -0
- package/src/diarist-role.ts +4 -44
- package/src/doctor-auditor.ts +3 -0
- package/src/doctor-role.ts +16 -11
- package/src/engine-detour-usage.ts +7 -6
- package/src/external-host-turn-loop.ts +24 -12
- package/src/filed-submission.ts +44 -0
- package/src/gatekeeper-role.ts +8 -16
- package/src/gleaner-left-contracts.ts +26 -1
- package/src/gleaner-left-role.ts +4 -32
- package/src/host-contracts.ts +7 -0
- package/src/inspector-role.ts +2 -7
- package/src/judge-role.ts +31 -24
- package/src/merger-role.ts +5 -8
- package/src/navigator-attendance.ts +17 -12
- package/src/navigator-invocation-identity.ts +31 -0
- package/src/navigator-role.ts +2 -8
- package/src/notary-role.ts +5 -20
- package/src/package-contracts/reviewer-output.ts +25 -0
- package/src/package-contracts/worker-output.ts +43 -5
- package/src/pi/adapter.ts +33 -8
- package/src/pi/role-turn-host.ts +7 -0
- package/src/public-cli/cli.ts +1 -0
- package/src/public-cli/instruction-seat-run.ts +292 -61
- package/src/public-cli/post-admission.ts +556 -324
- package/src/public-cli/settlement.ts +112 -50
- package/src/public-cli/terminal.ts +16 -4
- package/src/public-cli/turn-request.ts +10 -1
- package/src/receipt-delivery-policy.ts +209 -17
- package/src/reviewer-role.ts +7 -35
- package/src/role-envelope.ts +5 -1
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +72 -56
- package/src/role-submission-declarations.ts +143 -0
- package/src/secretariat-contracts.ts +35 -0
- package/src/secretariat-role.ts +4 -40
- package/src/submission-correctable-error.ts +5 -2
- package/src/submission-errors.ts +12 -1
- package/src/submission-gate.ts +41 -12
- package/src/worker-role.ts +81 -86
- package/src/worker-submission-gates.ts +32 -7
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
import { dirname, resolve } from "node:path";
|
|
7
7
|
import { isUnboundRunDirectory } from "../role-run-placement.js";
|
|
8
8
|
import { AUDITOR_DOSSIER_PROMPT } from "../compliance-transport.js";
|
|
9
|
-
import { bookDirectOfficerRunPointer } from "../archivist-record-pointer.js";
|
|
9
|
+
import { bookDirectOfficerRunPointer, readDirectOfficerRunPointer } from "../archivist-record-pointer.js";
|
|
10
10
|
import { createPiDoctorAuditor } from "../doctor-auditor.js";
|
|
11
11
|
import { officerConclusionReask, gateOfficerForSubject } from "../gatekeeper-role.js";
|
|
12
|
+
import { receivedDiscriminator } from "../submission-errors.js";
|
|
13
|
+
import { REVIEW_QUEUE_STATUSES } from "../review-submission.js";
|
|
12
14
|
import { runJudgeGates } from "../judge-role.js";
|
|
13
15
|
import { WORKER_DONE_STATUSES } from "../worker-submission-contracts.js";
|
|
14
16
|
import { SECRETARIAT_GATE_OFFICER_ENTRY_TYPE } from "../secretariat-contracts.js";
|
|
15
17
|
import { readableGateItem } from "../readable-gate-item.js";
|
|
18
|
+
import { deliveryLimitFromConfig } from "../receipt-delivery-policy.js";
|
|
16
19
|
import { runIdFromRunDirectory } from "../run-terminal-artifacts.js";
|
|
17
20
|
import { createDefaultGateOfficerSummon, requireSubmissionGate } from "../submission-gate.js";
|
|
18
21
|
import { readRecordedSubmissionRows } from "../submission-ledger.js";
|
|
@@ -27,17 +30,16 @@ import { readAuditorResumeBinding } from "../auditor-soul.js";
|
|
|
27
30
|
import { CliUsageError } from "./cli-errors.js";
|
|
28
31
|
import { admitPublicRole, bindAdmittedTicketNumber, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
|
|
29
32
|
import { presentControlledFailure, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
|
|
30
|
-
import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, } from "./run-lifecycle.js";
|
|
33
|
+
import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, readCurrentCourt, } from "./run-lifecycle.js";
|
|
31
34
|
import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
|
|
32
35
|
import { presentStructuralRejection, readBoundSessionEntries, seatKnownFailureResolver, attachPostAuditProjection, trySettlePublicSeat, } from "./settlement.js";
|
|
33
36
|
import { warnMissingMethodSkills } from "./machine-method-skills.js";
|
|
34
|
-
import { formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
|
|
35
|
-
import { admittedSeatTurnDetails,
|
|
37
|
+
import { coalesceSubmissionRows, formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
|
|
38
|
+
import { admittedSeatTurnDetails, projectRoleTurnRequest, requiredMethodSkills, } from "./turn-request.js";
|
|
36
39
|
import { invokeCourtDiarist, latestPayloadEscalated, } from "./countersign-run.js";
|
|
37
40
|
import { isRecord, errorText } from "../unknown-value.js";
|
|
38
41
|
const AUDITED_ROLES = new Set(["judge", "fixer", "coder", "secretariat", "countersign", "doctor"]);
|
|
39
42
|
const SECRETARIAT_STATUS_REASK = "secretariatStatus 不是 converged、escalate 之一。请重新交卷,secretariatStatus 写明其一。";
|
|
40
|
-
const COUNTERSIGN_STATUS_REASK = "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。";
|
|
41
43
|
const WORKER_ROUTING_STATUSES = new Set([
|
|
42
44
|
"planned", "completed", "refused", "partially_completed", "unfinished",
|
|
43
45
|
]);
|
|
@@ -46,8 +48,7 @@ const DOCTOR_ROUTING_STATUSES = new Set(["completed", "refused"]);
|
|
|
46
48
|
const DOCTOR_STATUS_REASK = "status 不是 completed、refused 之一。请重新交卷,status 写明其一。";
|
|
47
49
|
const POST_SUBMISSION_ROUTING = {
|
|
48
50
|
judge: {
|
|
49
|
-
statuses:
|
|
50
|
-
reask: "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。",
|
|
51
|
+
statuses: REVIEW_QUEUE_STATUSES,
|
|
51
52
|
},
|
|
52
53
|
coder: {
|
|
53
54
|
statuses: WORKER_ROUTING_STATUSES,
|
|
@@ -75,18 +76,88 @@ function unreadablePostSubmissionStatus(admitted, terminal) {
|
|
|
75
76
|
if (route === undefined)
|
|
76
77
|
return undefined;
|
|
77
78
|
const payload = terminal.roleOutcome.payloads?.at(-1);
|
|
78
|
-
const status =
|
|
79
|
-
? payload.status
|
|
80
|
-
: undefined;
|
|
79
|
+
const status = receivedDiscriminator(payload, "status");
|
|
81
80
|
return typeof status === "string" && route.statuses.has(status)
|
|
82
81
|
? undefined
|
|
83
|
-
: route.reask;
|
|
82
|
+
: route.reask ?? officerConclusionReask(status);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* One more unreadable-status reask. This loop has its own counter and reads
|
|
86
|
+
* the same autoResumeLimit as the other loops. Exhaustion is not a readable
|
|
87
|
+
* status: the caller presents the volumes already in hand and does not advance
|
|
88
|
+
* review. ADR 0007 audit continues are not this shape.
|
|
89
|
+
*/
|
|
90
|
+
function withUnreadableReask(env) {
|
|
91
|
+
const spent = env.unreadableReasksSpent ?? 0;
|
|
92
|
+
if (spent >= deliveryLimitFromConfig(env.autoResumeLimit))
|
|
93
|
+
return undefined;
|
|
94
|
+
return { ...env, unreadableReasksSpent: spent + 1 };
|
|
95
|
+
}
|
|
96
|
+
/** Budget spent on an unreadable status. Distinct from "status is readable". */
|
|
97
|
+
const UNREADABLE_REASK_EXHAUSTED = { unreadableReaskExhausted: true };
|
|
98
|
+
function isUnreadableReaskExhausted(value) {
|
|
99
|
+
return value === UNREADABLE_REASK_EXHAUSTED;
|
|
100
|
+
}
|
|
101
|
+
function presentOriginalVolume(held, terminal, io) {
|
|
102
|
+
// The held buffer was formatted before direction facts were attached.
|
|
103
|
+
if (terminal?.roleOutcome.decisiveFacts?.directionUnsettled === true) {
|
|
104
|
+
io.stdout(formatTerminalResult(terminal));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (held.length > 0) {
|
|
108
|
+
for (const value of held)
|
|
109
|
+
io.stdout(value);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (terminal !== undefined)
|
|
113
|
+
io.stdout(formatTerminalResult(terminal));
|
|
114
|
+
}
|
|
115
|
+
/** Runtime fact beside the original volume. Payloads stay as submitted. */
|
|
116
|
+
function withUnsettledDirection(parent, officer) {
|
|
117
|
+
const outcome = parent.roleOutcome;
|
|
118
|
+
if (outcome.kind !== "accepted" && outcome.kind !== "audit_escalation")
|
|
119
|
+
return parent;
|
|
120
|
+
const officerOutcome = officer?.roleOutcome;
|
|
121
|
+
const officerPayloads = coalesceSubmissionRows(officer?.submissions, officerOutcome !== undefined && officerOutcome.kind !== "no_receipt"
|
|
122
|
+
? officerOutcome.payloads
|
|
123
|
+
: undefined);
|
|
124
|
+
return {
|
|
125
|
+
...parent,
|
|
126
|
+
roleOutcome: {
|
|
127
|
+
...outcome,
|
|
128
|
+
decisiveFacts: {
|
|
129
|
+
...(outcome.decisiveFacts ?? {}),
|
|
130
|
+
directionUnsettled: true,
|
|
131
|
+
subsequentAudit: "incomplete",
|
|
132
|
+
...(officerOutcome === undefined ? {} : { officerRole: officerOutcome.role }),
|
|
133
|
+
...(officer?.runId === undefined ? {} : { officerRunId: officer.runId }),
|
|
134
|
+
...(officerPayloads.length === 0 ? {} : { officerPayloads }),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function heldUnreadableTerminal(terminal) {
|
|
140
|
+
if (terminal?.roleOutcome.kind !== "accepted")
|
|
141
|
+
return false;
|
|
142
|
+
if (latestPayloadEscalated(terminal.roleOutcome))
|
|
143
|
+
return false;
|
|
144
|
+
const status = latestQueueStatus(terminal);
|
|
145
|
+
return typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status);
|
|
146
|
+
}
|
|
147
|
+
function presentUnsettledAudit(parent, officer, io) {
|
|
148
|
+
const terminal = withUnsettledDirection(parent, officer);
|
|
149
|
+
io.stdout(formatTerminalResult(terminal));
|
|
150
|
+
if (officer !== undefined)
|
|
151
|
+
io.stdout(formatTerminalResult(officer));
|
|
152
|
+
return terminal;
|
|
84
153
|
}
|
|
85
|
-
async function reaskUnreadablePostSubmissionStatus(admitted, terminal, env, io) {
|
|
86
|
-
const message = unreadablePostSubmissionStatus(admitted, terminal);
|
|
154
|
+
async function reaskUnreadablePostSubmissionStatus(admitted, terminal, env, io, message = unreadablePostSubmissionStatus(admitted, terminal)) {
|
|
87
155
|
if (message === undefined)
|
|
88
156
|
return undefined;
|
|
89
|
-
|
|
157
|
+
const next = withUnreadableReask(env);
|
|
158
|
+
if (next === undefined)
|
|
159
|
+
return UNREADABLE_REASK_EXHAUSTED;
|
|
160
|
+
return runPublicInstructionSeatResume({ runId: admitted.runId, message }, next, io);
|
|
90
161
|
}
|
|
91
162
|
function roleRecord(role) {
|
|
92
163
|
const record = packagedRoleMetadata(role);
|
|
@@ -234,6 +305,13 @@ async function dispatchAdmitted(admitted, env, io) {
|
|
|
234
305
|
};
|
|
235
306
|
const result = await execute(env);
|
|
236
307
|
const unreadableReask = await reaskUnreadablePostSubmissionStatus(admitted, result.terminal, env, io);
|
|
308
|
+
if (isUnreadableReaskExhausted(unreadableReask)) {
|
|
309
|
+
const terminal = result.terminal === undefined
|
|
310
|
+
? undefined
|
|
311
|
+
: withUnsettledDirection(result.terminal);
|
|
312
|
+
presentOriginalVolume(held, terminal, io);
|
|
313
|
+
return terminal === undefined ? result : { ...result, terminal };
|
|
314
|
+
}
|
|
237
315
|
if (unreadableReask !== undefined)
|
|
238
316
|
return unreadableReask;
|
|
239
317
|
if (AUDITED_ROLES.has(admitted.role) && result.terminal?.roleOutcome.kind === "accepted") {
|
|
@@ -389,20 +467,14 @@ async function runCountersignBody(parsed, env, io) {
|
|
|
389
467
|
await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
|
|
390
468
|
admitted = { ...admitted, sourceRunPath: gateParentRunPath };
|
|
391
469
|
}
|
|
392
|
-
const turnProjection = {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
?? buildInstructionTransportPrompt(admitted, engineSessionMaterialFromOptions({
|
|
401
|
-
...pickEngineAxis(env),
|
|
402
|
-
packageRoot: env.packageRoot,
|
|
403
|
-
})),
|
|
404
|
-
},
|
|
405
|
-
};
|
|
470
|
+
const turnProjection = roleTurnOptions(env, admitted, {
|
|
471
|
+
kind: "initial",
|
|
472
|
+
prompt: (env.reviewReask ?? env.gateReviewInstruction)
|
|
473
|
+
?? buildInstructionTransportPrompt(admitted, engineSessionMaterialFromOptions({
|
|
474
|
+
...pickEngineAxis(env),
|
|
475
|
+
packageRoot: env.packageRoot,
|
|
476
|
+
})),
|
|
477
|
+
});
|
|
406
478
|
const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
|
|
407
479
|
const result = await runPostAdmissionOneShot({
|
|
408
480
|
admitted,
|
|
@@ -679,13 +751,37 @@ export async function runPublicInstructionSeatResume(request, env, io) {
|
|
|
679
751
|
},
|
|
680
752
|
afterAdmittedLoad: async (admitted) => {
|
|
681
753
|
await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
|
|
682
|
-
|
|
754
|
+
const adapters = seatAdapters(admitted, env);
|
|
755
|
+
if ((env.unreadableReasksSpent ?? 0) === 0 && env.reviewReask === undefined) {
|
|
756
|
+
return { kind: "continue", adapters };
|
|
757
|
+
}
|
|
758
|
+
const original = await adapters.trySettle(admitted, env.principalAuthority, await readCurrentCourt(admitted.runDirectory));
|
|
759
|
+
const unsettled = POST_SUBMISSION_ROUTING[admitted.role] === undefined
|
|
760
|
+
? heldUnreadableTerminal(original)
|
|
761
|
+
: unreadablePostSubmissionStatus(admitted, original) !== undefined;
|
|
762
|
+
// Select this reask's existing subject before dispatch. The common
|
|
763
|
+
// settlement then keeps it only when no fresh receipt supersedes it;
|
|
764
|
+
// actual failures still take their normal failure path.
|
|
765
|
+
return {
|
|
766
|
+
kind: "continue",
|
|
767
|
+
adapters: original === undefined || !unsettled ? adapters : {
|
|
768
|
+
...adapters,
|
|
769
|
+
trySettle: async (seat, authority, scope) => await adapters.trySettle(seat, authority, scope) ?? original,
|
|
770
|
+
},
|
|
771
|
+
};
|
|
683
772
|
},
|
|
684
773
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
685
774
|
});
|
|
686
775
|
const unreadableReask = result.admitted === undefined
|
|
687
776
|
? undefined
|
|
688
777
|
: await reaskUnreadablePostSubmissionStatus(result.admitted, result.terminal, env, io);
|
|
778
|
+
if (isUnreadableReaskExhausted(unreadableReask)) {
|
|
779
|
+
const terminal = result.terminal === undefined
|
|
780
|
+
? undefined
|
|
781
|
+
: withUnsettledDirection(result.terminal);
|
|
782
|
+
presentOriginalVolume(held, terminal, io);
|
|
783
|
+
return terminal === undefined ? result : { ...result, terminal };
|
|
784
|
+
}
|
|
689
785
|
if (unreadableReask !== undefined)
|
|
690
786
|
return unreadableReask;
|
|
691
787
|
if (result.admitted !== undefined && AUDITED_ROLES.has(result.admitted.role)
|
|
@@ -716,31 +812,49 @@ export async function runPublicInstructionSeatResume(request, env, io) {
|
|
|
716
812
|
}
|
|
717
813
|
return resume();
|
|
718
814
|
}
|
|
719
|
-
const QUEUE_CONCLUSIONS = new Set(["converged", "continue", "escalate"]);
|
|
720
815
|
const GATE_CHILD_ROLES = new Set(["notary", "auditor", "inspector", "countersign"]);
|
|
721
816
|
/**
|
|
722
817
|
* A gate child's conclusion outside the three states goes back to that child.
|
|
723
|
-
* The words are the existing officer re-ask.
|
|
818
|
+
* The words are the existing officer re-ask. Each reask spends this loop's own
|
|
819
|
+
* copy of the configured ceiling; exhaustion keeps the terminal already in hand.
|
|
820
|
+
* A host failure stops here.
|
|
821
|
+
* Countersign already spends that ceiling inside its own seat, or the summoning
|
|
822
|
+
* gate does. This loop does not open a second one for the same conclusion.
|
|
724
823
|
*/
|
|
725
824
|
async function queueConclusionFromChild(child, env, io) {
|
|
726
825
|
if (!GATE_CHILD_ROLES.has(child.role))
|
|
727
826
|
return undefined;
|
|
827
|
+
if (child.role === "countersign") {
|
|
828
|
+
const terminal = await trySettlePublicSeat(child, env.principalAuthority, await readCurrentCourt(child.runDirectory));
|
|
829
|
+
const status = latestQueueStatus(terminal);
|
|
830
|
+
if (terminal !== undefined && typeof status === "string" && REVIEW_QUEUE_STATUSES.has(status)) {
|
|
831
|
+
return { admitted: child, terminal, status };
|
|
832
|
+
}
|
|
833
|
+
return { stop: { exitCode: 0, ...(terminal === undefined ? {} : { terminal }) } };
|
|
834
|
+
}
|
|
728
835
|
let current = child;
|
|
836
|
+
// Notary, auditor, and inspector have no seat-local status reask. This loop
|
|
837
|
+
// is their one budget. The child resume keeps the caller's env so delivery,
|
|
838
|
+
// failure recovery, and other reask loops stay separate.
|
|
839
|
+
let budgetEnv = { ...env, unreadableReasksSpent: 0 };
|
|
729
840
|
for (;;) {
|
|
730
|
-
const terminal = await trySettlePublicSeat(current, env.principalAuthority,
|
|
841
|
+
const terminal = await trySettlePublicSeat(current, env.principalAuthority, await readCurrentCourt(current.runDirectory));
|
|
731
842
|
const status = latestQueueStatus(terminal);
|
|
732
|
-
if (terminal !== undefined && status
|
|
843
|
+
if (terminal !== undefined && typeof status === "string" && REVIEW_QUEUE_STATUSES.has(status)) {
|
|
733
844
|
return { admitted: current, terminal, status };
|
|
734
845
|
}
|
|
735
|
-
const reasked = await
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
|
|
846
|
+
const reasked = await reaskUnreadablePostSubmissionStatus(current, terminal, budgetEnv, io, officerConclusionReask(status));
|
|
847
|
+
if (reasked === undefined || isUnreadableReaskExhausted(reasked)) {
|
|
848
|
+
return { stop: { exitCode: 0, ...(terminal === undefined ? {} : { terminal }) } };
|
|
849
|
+
}
|
|
850
|
+
budgetEnv = withUnreadableReask(budgetEnv);
|
|
851
|
+
if (reasked.exitCode !== 0 || reasked.admitted === undefined || reasked.terminal === undefined
|
|
852
|
+
|| reasked.terminal.roleOutcome.kind === "no_receipt"
|
|
853
|
+
|| reasked.terminal.roleOutcome.decisiveFacts?.directionUnsettled === true) {
|
|
740
854
|
return { stop: reasked };
|
|
741
855
|
}
|
|
742
856
|
const reaskedStatus = latestQueueStatus(reasked.terminal);
|
|
743
|
-
if (reaskedStatus
|
|
857
|
+
if (typeof reaskedStatus === "string" && REVIEW_QUEUE_STATUSES.has(reaskedStatus)) {
|
|
744
858
|
return { admitted: reasked.admitted, terminal: reasked.terminal, status: reaskedStatus };
|
|
745
859
|
}
|
|
746
860
|
if (latestPayloadEscalated(reasked.terminal.roleOutcome))
|
|
@@ -756,8 +870,20 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
|
|
|
756
870
|
}
|
|
757
871
|
const admitted = loaded.admitted;
|
|
758
872
|
const resolved = await queueConclusionFromChild(child, env, io);
|
|
759
|
-
if (resolved !== undefined && "stop" in resolved)
|
|
760
|
-
|
|
873
|
+
if (resolved !== undefined && "stop" in resolved) {
|
|
874
|
+
const stopped = resolved.stop;
|
|
875
|
+
if (stopped.exitCode === 0
|
|
876
|
+
&& heldUnreadableTerminal(stopped.terminal)
|
|
877
|
+
&& stopped.terminal !== undefined) {
|
|
878
|
+
const parentTerminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
|
|
879
|
+
if (parentTerminal?.roleOutcome.kind === "accepted"
|
|
880
|
+
|| parentTerminal?.roleOutcome.kind === "audit_escalation") {
|
|
881
|
+
const terminal = presentUnsettledAudit(parentTerminal, stopped.terminal, io);
|
|
882
|
+
return { exitCode: 0, admitted, terminal };
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return stopped;
|
|
886
|
+
}
|
|
761
887
|
if (resolved !== undefined && resolved.status === "escalate") {
|
|
762
888
|
return { exitCode: 0, admitted: resolved.admitted, terminal: resolved.terminal };
|
|
763
889
|
}
|
|
@@ -766,19 +892,50 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
|
|
|
766
892
|
const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
|
|
767
893
|
if (terminal?.roleOutcome.kind !== "accepted")
|
|
768
894
|
throw new Error("pending submission is not recorded");
|
|
769
|
-
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted
|
|
895
|
+
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
|
|
770
896
|
}
|
|
771
897
|
return runPublicInstructionSeatResume({
|
|
772
898
|
runId: parentRunId,
|
|
773
899
|
message: readableGateItem(latestQueuePayload(resolved.terminal)),
|
|
774
|
-
}, { ...env,
|
|
900
|
+
}, { ...env, unreadableReasksSpent: 0 }, io);
|
|
775
901
|
}
|
|
776
902
|
const result = await dispatchAdmitted(admitted, env, { ...io, omitFailureStderrDiagnostic: true });
|
|
777
903
|
showResumeErrorPointer(io, result.exitCode, result.terminal);
|
|
778
904
|
return result;
|
|
779
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* A gate is already passed only when an officer run reviewed this submission
|
|
908
|
+
* and its latest queue word is converged. The resumed seat's role is not itself
|
|
909
|
+
* a pass. Unknown or unreadable evidence does not skip the gate.
|
|
910
|
+
*/
|
|
911
|
+
async function officerPassedCurrentSubmission(input) {
|
|
912
|
+
const { env, officer } = input;
|
|
913
|
+
const pointer = await readDirectOfficerRunPointer(input.parentSessionFile, officer);
|
|
914
|
+
if (pointer?.runDirectory === undefined)
|
|
915
|
+
return false;
|
|
916
|
+
if (pointer.submissionToolCallId !== input.toolCallId)
|
|
917
|
+
return false;
|
|
918
|
+
const officerRunId = runIdFromRunDirectory(pointer.runDirectory);
|
|
919
|
+
if (officerRunId === undefined)
|
|
920
|
+
return false;
|
|
921
|
+
let admitted;
|
|
922
|
+
try {
|
|
923
|
+
const loaded = await loadResumablePublicRole(env.home, officerRunId, env.principalAuthority);
|
|
924
|
+
admitted = loaded.admitted;
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
if (admitted.role !== officer)
|
|
930
|
+
return false;
|
|
931
|
+
// A summon binding identifies the requested review, not its outcome. An open
|
|
932
|
+
// court (including a failed host turn) must not borrow a previous court's seal.
|
|
933
|
+
const court = await readCurrentCourt(admitted.runDirectory);
|
|
934
|
+
const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, court);
|
|
935
|
+
return latestQueueStatus(terminal) === "converged";
|
|
936
|
+
}
|
|
780
937
|
/** Finished submissions enter the existing audit gate after their tool call has returned. */
|
|
781
|
-
async function auditSubmittedRole(turn, env, io,
|
|
938
|
+
async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt) {
|
|
782
939
|
const admitted = turn.admitted;
|
|
783
940
|
if (admitted === undefined || !AUDITED_ROLES.has(admitted.role)
|
|
784
941
|
|| turn.terminal?.roleOutcome.kind !== "accepted")
|
|
@@ -789,11 +946,30 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
789
946
|
const record = isRecord(accepted)
|
|
790
947
|
? accepted : undefined;
|
|
791
948
|
const status = admitted.role === "secretariat" ? record?.secretariatStatus : record?.status;
|
|
949
|
+
// A gate summon already owns this conclusion's reasks. The seat must not
|
|
950
|
+
// start another budget on the same run (parentRunPath is that summon).
|
|
951
|
+
const gateOwnsStatusReask = typeof env.parentRunPath === "string" && env.parentRunPath.trim() !== "";
|
|
792
952
|
if (admitted.role === "secretariat" && status !== "converged" && status !== "escalate") {
|
|
793
|
-
|
|
953
|
+
if (gateOwnsStatusReask)
|
|
954
|
+
return turn;
|
|
955
|
+
const next = withUnreadableReask(env);
|
|
956
|
+
if (next === undefined) {
|
|
957
|
+
const terminal = withUnsettledDirection(turn.terminal);
|
|
958
|
+
io.stdout(formatTerminalResult(terminal));
|
|
959
|
+
return { ...turn, terminal };
|
|
960
|
+
}
|
|
961
|
+
return runPublicInstructionSeatResume({ runId: admitted.runId, message: SECRETARIAT_STATUS_REASK }, next, io);
|
|
794
962
|
}
|
|
795
|
-
if (admitted.role === "countersign" && (typeof status !== "string" || !
|
|
796
|
-
|
|
963
|
+
if (admitted.role === "countersign" && (typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status))) {
|
|
964
|
+
if (gateOwnsStatusReask)
|
|
965
|
+
return turn;
|
|
966
|
+
const next = withUnreadableReask(env);
|
|
967
|
+
if (next === undefined) {
|
|
968
|
+
const terminal = withUnsettledDirection(turn.terminal);
|
|
969
|
+
io.stdout(formatTerminalResult(terminal));
|
|
970
|
+
return { ...turn, terminal };
|
|
971
|
+
}
|
|
972
|
+
return runPublicInstructionSeatResume({ runId: admitted.runId, message: officerConclusionReask(status) }, next, io);
|
|
797
973
|
}
|
|
798
974
|
// Self-escalation is a valid open routing state for these seats only.
|
|
799
975
|
// Doctor's declared domain is completed|refused — an open "escalate" must
|
|
@@ -830,11 +1006,18 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
830
1006
|
},
|
|
831
1007
|
abort() { },
|
|
832
1008
|
};
|
|
1009
|
+
const passedThisSubmission = (officer) => officerPassedCurrentSubmission({
|
|
1010
|
+
env,
|
|
1011
|
+
parentSessionFile: sessionFile,
|
|
1012
|
+
officer,
|
|
1013
|
+
toolCallId,
|
|
1014
|
+
});
|
|
833
1015
|
if (admitted.role === "doctor") {
|
|
834
|
-
if (
|
|
1016
|
+
if (!await passedThisSubmission("auditor")) {
|
|
835
1017
|
let lastSummon;
|
|
836
1018
|
const decision = await createPiDoctorAuditor()({
|
|
837
1019
|
context, submission: accepted,
|
|
1020
|
+
...(env.autoResumeLimit === undefined ? {} : { autoResumeLimit: env.autoResumeLimit }),
|
|
838
1021
|
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
839
1022
|
summonAuditor: async (subject, sourceRunDirectory, signal, reask, submission) => {
|
|
840
1023
|
const summoned = await summonPublicRole({
|
|
@@ -854,6 +1037,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
854
1037
|
if (officerSession !== undefined) {
|
|
855
1038
|
bookDirectOfficerRunPointer({
|
|
856
1039
|
parentSessionFile: sessionFile, officer: "auditor", sessionFile: officerSession,
|
|
1040
|
+
submissionToolCallId: toolCallId,
|
|
857
1041
|
...(summoned.runDirectory === undefined ? {} : { runDirectory: summoned.runDirectory }),
|
|
858
1042
|
});
|
|
859
1043
|
}
|
|
@@ -864,7 +1048,16 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
864
1048
|
if (decision.status === "continue") {
|
|
865
1049
|
return runPublicInstructionSeatResume({
|
|
866
1050
|
runId: admitted.runId, message: readableGateItem(decision.receipt ?? decision.violations),
|
|
867
|
-
}, { ...env,
|
|
1051
|
+
}, { ...env, unreadableReasksSpent: 0 }, io);
|
|
1052
|
+
}
|
|
1053
|
+
if (decision.status === "received") {
|
|
1054
|
+
if (lastSummon?.terminal === undefined) {
|
|
1055
|
+
throw new Error("doctor audit kept an unreadable reply with no terminal");
|
|
1056
|
+
}
|
|
1057
|
+
if (turn.terminal === undefined)
|
|
1058
|
+
throw new Error("doctor submission has no terminal");
|
|
1059
|
+
const terminal = presentUnsettledAudit(turn.terminal, lastSummon.terminal, io);
|
|
1060
|
+
return { exitCode: 0, admitted, terminal };
|
|
868
1061
|
}
|
|
869
1062
|
if (decision.status !== "converged") {
|
|
870
1063
|
if (lastSummon?.terminal !== undefined) {
|
|
@@ -886,13 +1079,14 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
886
1079
|
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
887
1080
|
});
|
|
888
1081
|
let chain;
|
|
889
|
-
let officerRunId =
|
|
1082
|
+
let officerRunId = resumedOfficer?.runId;
|
|
890
1083
|
const runGate = (subject) => requireSubmissionGate({
|
|
891
1084
|
context,
|
|
892
1085
|
subject,
|
|
893
1086
|
toolCallId,
|
|
894
1087
|
submission: accepted,
|
|
895
1088
|
summonOfficer,
|
|
1089
|
+
...(env.autoResumeLimit === undefined ? {} : { autoResumeLimit: env.autoResumeLimit }),
|
|
896
1090
|
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
897
1091
|
hostActions: {
|
|
898
1092
|
failInfrastructure(error) { throw error; },
|
|
@@ -901,8 +1095,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
901
1095
|
});
|
|
902
1096
|
if (admitted.role === "judge") {
|
|
903
1097
|
chain = await runJudgeGates({
|
|
904
|
-
gateAlreadyConverged: async (subject) =>
|
|
905
|
-
|| (passedOfficer === "notary" && subject.kind === "judge_draft"),
|
|
1098
|
+
gateAlreadyConverged: async (subject) => passedThisSubmission(gateOfficerForSubject(subject)),
|
|
906
1099
|
runGate,
|
|
907
1100
|
});
|
|
908
1101
|
}
|
|
@@ -913,7 +1106,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
913
1106
|
? { kind: "secretariat_verdict" }
|
|
914
1107
|
: { kind: "countersign_verdict" };
|
|
915
1108
|
const officer = gateOfficerForSubject(subject);
|
|
916
|
-
if (
|
|
1109
|
+
if (await passedThisSubmission(officer)) {
|
|
917
1110
|
chain = { status: "converged", passes: [] };
|
|
918
1111
|
}
|
|
919
1112
|
else {
|
|
@@ -929,10 +1122,20 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
929
1122
|
receipt: pass.receipt,
|
|
930
1123
|
...(pass.runId === undefined ? {} : { runId: pass.runId }),
|
|
931
1124
|
...(pass.runDirectory === undefined ? {} : { runDirectory: pass.runDirectory }),
|
|
1125
|
+
...(pass.terminal === undefined ? {} : { terminal: pass.terminal }),
|
|
932
1126
|
}],
|
|
933
1127
|
};
|
|
934
1128
|
}
|
|
935
1129
|
}
|
|
1130
|
+
if (chain.status === "needs_reask") {
|
|
1131
|
+
const officerTerminal = chain.passes.at(-1)?.terminal;
|
|
1132
|
+
if (officerTerminal === undefined)
|
|
1133
|
+
throw new Error("unreadable audit has no terminal result");
|
|
1134
|
+
if (turn.terminal === undefined)
|
|
1135
|
+
throw new Error("audited submission has no terminal");
|
|
1136
|
+
const terminal = presentUnsettledAudit(turn.terminal, officerTerminal, io);
|
|
1137
|
+
return { exitCode: 0, admitted, terminal };
|
|
1138
|
+
}
|
|
936
1139
|
if (chain.status === "escalate") {
|
|
937
1140
|
const escalation = chain.passes.at(-1);
|
|
938
1141
|
const escalatedRunId = escalation?.runId
|
|
@@ -941,7 +1144,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
941
1144
|
if (escalatedRunId === undefined)
|
|
942
1145
|
throw new Error("escalated audit has no resumable run identity");
|
|
943
1146
|
const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority);
|
|
944
|
-
const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority,
|
|
1147
|
+
const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority, await readCurrentCourt(officer.admitted.runDirectory));
|
|
945
1148
|
if (terminal === undefined)
|
|
946
1149
|
throw new Error("escalated audit has no terminal result");
|
|
947
1150
|
io.stdout(formatTerminalResult(terminal));
|
|
@@ -951,7 +1154,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
951
1154
|
return runPublicInstructionSeatResume({
|
|
952
1155
|
runId: admitted.runId,
|
|
953
1156
|
message: readableGateItem(chain.passes.at(-1)?.receipt),
|
|
954
|
-
}, { ...env,
|
|
1157
|
+
}, { ...env, unreadableReasksSpent: 0 }, io);
|
|
955
1158
|
}
|
|
956
1159
|
if (admitted.role === "secretariat") {
|
|
957
1160
|
const pass = chain.passes.at(-1);
|