@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
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { ExplicitInternalActivationError } from "../host-contracts.ts";
|
|
|
25
25
|
import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.ts";
|
|
26
26
|
import { projectActivationFlags } from "../role-activation-flags.ts";
|
|
27
27
|
import { encodeUserDialogueStdin } from "../user-dialogue-stdin.ts";
|
|
28
|
+
import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.ts";
|
|
28
29
|
|
|
29
30
|
/** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
|
|
30
31
|
const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
|
|
@@ -403,6 +404,12 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
403
404
|
// Selected host axis (#537 / ADR 0082): omit must not inherit a parent env value.
|
|
404
405
|
if (request.host === undefined || request.host.trim() === "") delete env.AK_ROLE_HOST;
|
|
405
406
|
else env.AK_ROLE_HOST = request.host.trim();
|
|
407
|
+
// #1132: the one effective delivery-request ceiling travels to the child
|
|
408
|
+
// on its own env so the in-child role runtime and the worker submission
|
|
409
|
+
// gate count with the same configured number the AK seam resolved. Omit
|
|
410
|
+
// must not inherit a parent value (same rule as the other AK_ROLE_ axes).
|
|
411
|
+
if (request.deliveryRequestLimit === undefined) delete env[RECEIPT_DELIVERY_LIMIT_ENV];
|
|
412
|
+
else env[RECEIPT_DELIVERY_LIMIT_ENV] = String(request.deliveryRequestLimit);
|
|
406
413
|
applyEngineChildEnv(env, request.engine);
|
|
407
414
|
// Nested auditor dossier tool binds the parent run pointer when published.
|
|
408
415
|
if (
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -1234,6 +1234,7 @@ export async function runAkRole(
|
|
|
1234
1234
|
while (
|
|
1235
1235
|
current.exitCode === 0 && current.admitted?.correlationId !== undefined
|
|
1236
1236
|
&& current.terminal?.roleOutcome.kind === "accepted"
|
|
1237
|
+
&& current.terminal.roleOutcome.decisiveFacts?.directionUnsettled !== true
|
|
1237
1238
|
&& !latestPayloadEscalated(current.terminal.roleOutcome)
|
|
1238
1239
|
) {
|
|
1239
1240
|
const parentRunId = current.admitted.correlationId;
|
|
@@ -6,15 +6,18 @@
|
|
|
6
6
|
import { dirname, resolve } from "node:path";
|
|
7
7
|
import { isUnboundRunDirectory } from "../role-run-placement.ts";
|
|
8
8
|
import { AUDITOR_DOSSIER_PROMPT } from "../compliance-transport.ts";
|
|
9
|
-
import { bookDirectOfficerRunPointer } from "../archivist-record-pointer.ts";
|
|
9
|
+
import { bookDirectOfficerRunPointer, readDirectOfficerRunPointer } from "../archivist-record-pointer.ts";
|
|
10
10
|
import { createPiDoctorAuditor } from "../doctor-auditor.ts";
|
|
11
11
|
|
|
12
12
|
import type { DurablePrincipalAuthority, HostContext, RoleTurnRequest } from "../host-contracts.ts";
|
|
13
|
-
import { officerConclusionReask, gateOfficerForSubject } from "../gatekeeper-role.ts";
|
|
13
|
+
import { officerConclusionReask, gateOfficerForSubject, type GateOfficer } from "../gatekeeper-role.ts";
|
|
14
|
+
import { receivedDiscriminator } from "../submission-errors.ts";
|
|
15
|
+
import { REVIEW_QUEUE_STATUSES } from "../review-submission.ts";
|
|
14
16
|
import { runJudgeGates } from "../judge-role.ts";
|
|
15
17
|
import { WORKER_DONE_STATUSES } from "../worker-submission-contracts.ts";
|
|
16
18
|
import { SECRETARIAT_GATE_OFFICER_ENTRY_TYPE } from "../secretariat-contracts.ts";
|
|
17
19
|
import { readableGateItem } from "../readable-gate-item.ts";
|
|
20
|
+
import { deliveryLimitFromConfig } from "../receipt-delivery-policy.ts";
|
|
18
21
|
import { runIdFromRunDirectory } from "../run-terminal-artifacts.ts";
|
|
19
22
|
import { createDefaultGateOfficerSummon, requireSubmissionGate } from "../submission-gate.ts";
|
|
20
23
|
import { readRecordedSubmissionRows } from "../submission-ledger.ts";
|
|
@@ -69,6 +72,7 @@ import {
|
|
|
69
72
|
loadResumablePublicRole,
|
|
70
73
|
markRunAdmitted,
|
|
71
74
|
parentRunPathFromGatePointerInstruction,
|
|
75
|
+
readCurrentCourt,
|
|
72
76
|
type PublicResumeRequest,
|
|
73
77
|
type RunWriterLease,
|
|
74
78
|
type SameTicketSummonsMaterials,
|
|
@@ -84,6 +88,7 @@ import {
|
|
|
84
88
|
import type { CliIo } from "./cli-io.ts";
|
|
85
89
|
import { warnMissingMethodSkills } from "./machine-method-skills.ts";
|
|
86
90
|
import {
|
|
91
|
+
coalesceSubmissionRows,
|
|
87
92
|
formatTerminalResult,
|
|
88
93
|
isLawfulTypedTerminalOutcome,
|
|
89
94
|
type TerminalResult,
|
|
@@ -91,7 +96,6 @@ import {
|
|
|
91
96
|
} from "./terminal.ts";
|
|
92
97
|
import {
|
|
93
98
|
admittedSeatTurnDetails,
|
|
94
|
-
projectPublicTurnAxes,
|
|
95
99
|
projectRoleTurnRequest,
|
|
96
100
|
requiredMethodSkills,
|
|
97
101
|
type RoleTurnRequestProjectionOptions,
|
|
@@ -116,7 +120,6 @@ type SeatRunResult = {
|
|
|
116
120
|
|
|
117
121
|
const AUDITED_ROLES = new Set<PackagedRole>(["judge", "fixer", "coder", "secretariat", "countersign", "doctor"]);
|
|
118
122
|
const SECRETARIAT_STATUS_REASK = "secretariatStatus 不是 converged、escalate 之一。请重新交卷,secretariatStatus 写明其一。";
|
|
119
|
-
const COUNTERSIGN_STATUS_REASK = "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。";
|
|
120
123
|
const WORKER_ROUTING_STATUSES: ReadonlySet<string> = new Set([
|
|
121
124
|
"planned", "completed", "refused", "partially_completed", "unfinished",
|
|
122
125
|
]);
|
|
@@ -127,11 +130,10 @@ const DOCTOR_STATUS_REASK =
|
|
|
127
130
|
"status 不是 completed、refused 之一。请重新交卷,status 写明其一。";
|
|
128
131
|
const POST_SUBMISSION_ROUTING: Partial<Record<PackagedRole, {
|
|
129
132
|
readonly statuses: ReadonlySet<string>;
|
|
130
|
-
readonly reask
|
|
133
|
+
readonly reask?: string;
|
|
131
134
|
}>> = {
|
|
132
135
|
judge: {
|
|
133
|
-
statuses:
|
|
134
|
-
reask: "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。",
|
|
136
|
+
statuses: REVIEW_QUEUE_STATUSES,
|
|
135
137
|
},
|
|
136
138
|
coder: {
|
|
137
139
|
statuses: WORKER_ROUTING_STATUSES,
|
|
@@ -161,12 +163,96 @@ function unreadablePostSubmissionStatus(
|
|
|
161
163
|
const route = POST_SUBMISSION_ROUTING[admitted.role];
|
|
162
164
|
if (route === undefined) return undefined;
|
|
163
165
|
const payload = terminal.roleOutcome.payloads?.at(-1);
|
|
164
|
-
const status =
|
|
165
|
-
? (payload as Record<string, unknown>).status
|
|
166
|
-
: undefined;
|
|
166
|
+
const status = receivedDiscriminator(payload, "status");
|
|
167
167
|
return typeof status === "string" && route.statuses.has(status)
|
|
168
168
|
? undefined
|
|
169
|
-
: route.reask;
|
|
169
|
+
: route.reask ?? officerConclusionReask(status);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* One more unreadable-status reask. This loop has its own counter and reads
|
|
174
|
+
* the same autoResumeLimit as the other loops. Exhaustion is not a readable
|
|
175
|
+
* status: the caller presents the volumes already in hand and does not advance
|
|
176
|
+
* review. ADR 0007 audit continues are not this shape.
|
|
177
|
+
*/
|
|
178
|
+
function withUnreadableReask(env: InstructionSeatRunEnv): InstructionSeatRunEnv | undefined {
|
|
179
|
+
const spent = env.unreadableReasksSpent ?? 0;
|
|
180
|
+
if (spent >= deliveryLimitFromConfig(env.autoResumeLimit)) return undefined;
|
|
181
|
+
return { ...env, unreadableReasksSpent: spent + 1 };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Budget spent on an unreadable status. Distinct from "status is readable". */
|
|
185
|
+
const UNREADABLE_REASK_EXHAUSTED = { unreadableReaskExhausted: true as const };
|
|
186
|
+
|
|
187
|
+
function isUnreadableReaskExhausted(
|
|
188
|
+
value: SeatRunResult | typeof UNREADABLE_REASK_EXHAUSTED | undefined,
|
|
189
|
+
): value is typeof UNREADABLE_REASK_EXHAUSTED {
|
|
190
|
+
return value === UNREADABLE_REASK_EXHAUSTED;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function presentOriginalVolume(
|
|
194
|
+
held: readonly string[],
|
|
195
|
+
terminal: TerminalResult | undefined,
|
|
196
|
+
io: CliIo,
|
|
197
|
+
): void {
|
|
198
|
+
// The held buffer was formatted before direction facts were attached.
|
|
199
|
+
if (terminal?.roleOutcome.decisiveFacts?.directionUnsettled === true) {
|
|
200
|
+
io.stdout(formatTerminalResult(terminal));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (held.length > 0) {
|
|
204
|
+
for (const value of held) io.stdout(value);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (terminal !== undefined) io.stdout(formatTerminalResult(terminal));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Runtime fact beside the original volume. Payloads stay as submitted. */
|
|
211
|
+
function withUnsettledDirection(
|
|
212
|
+
parent: TerminalResult,
|
|
213
|
+
officer?: TerminalResult,
|
|
214
|
+
): TerminalResult {
|
|
215
|
+
const outcome = parent.roleOutcome;
|
|
216
|
+
if (outcome.kind !== "accepted" && outcome.kind !== "audit_escalation") return parent;
|
|
217
|
+
const officerOutcome = officer?.roleOutcome;
|
|
218
|
+
const officerPayloads = coalesceSubmissionRows(
|
|
219
|
+
officer?.submissions,
|
|
220
|
+
officerOutcome !== undefined && officerOutcome.kind !== "no_receipt"
|
|
221
|
+
? officerOutcome.payloads
|
|
222
|
+
: undefined,
|
|
223
|
+
);
|
|
224
|
+
return {
|
|
225
|
+
...parent,
|
|
226
|
+
roleOutcome: {
|
|
227
|
+
...outcome,
|
|
228
|
+
decisiveFacts: {
|
|
229
|
+
...(outcome.decisiveFacts ?? {}),
|
|
230
|
+
directionUnsettled: true,
|
|
231
|
+
subsequentAudit: "incomplete",
|
|
232
|
+
...(officerOutcome === undefined ? {} : { officerRole: officerOutcome.role }),
|
|
233
|
+
...(officer?.runId === undefined ? {} : { officerRunId: officer.runId }),
|
|
234
|
+
...(officerPayloads.length === 0 ? {} : { officerPayloads }),
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function heldUnreadableTerminal(terminal: TerminalResult | undefined): boolean {
|
|
241
|
+
if (terminal?.roleOutcome.kind !== "accepted") return false;
|
|
242
|
+
if (latestPayloadEscalated(terminal.roleOutcome)) return false;
|
|
243
|
+
const status = latestQueueStatus(terminal);
|
|
244
|
+
return typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function presentUnsettledAudit(
|
|
248
|
+
parent: TerminalResult,
|
|
249
|
+
officer: TerminalResult | undefined,
|
|
250
|
+
io: CliIo,
|
|
251
|
+
): TerminalResult {
|
|
252
|
+
const terminal = withUnsettledDirection(parent, officer);
|
|
253
|
+
io.stdout(formatTerminalResult(terminal));
|
|
254
|
+
if (officer !== undefined) io.stdout(formatTerminalResult(officer));
|
|
255
|
+
return terminal;
|
|
170
256
|
}
|
|
171
257
|
|
|
172
258
|
async function reaskUnreadablePostSubmissionStatus(
|
|
@@ -174,12 +260,14 @@ async function reaskUnreadablePostSubmissionStatus(
|
|
|
174
260
|
terminal: TerminalResult | undefined,
|
|
175
261
|
env: InstructionSeatRunEnv,
|
|
176
262
|
io: CliIo,
|
|
177
|
-
|
|
178
|
-
|
|
263
|
+
message = unreadablePostSubmissionStatus(admitted, terminal),
|
|
264
|
+
): Promise<SeatRunResult | typeof UNREADABLE_REASK_EXHAUSTED | undefined> {
|
|
179
265
|
if (message === undefined) return undefined;
|
|
266
|
+
const next = withUnreadableReask(env);
|
|
267
|
+
if (next === undefined) return UNREADABLE_REASK_EXHAUSTED;
|
|
180
268
|
return runPublicInstructionSeatResume(
|
|
181
269
|
{ runId: admitted.runId, message },
|
|
182
|
-
|
|
270
|
+
next,
|
|
183
271
|
io,
|
|
184
272
|
);
|
|
185
273
|
}
|
|
@@ -375,6 +463,13 @@ async function dispatchAdmitted(
|
|
|
375
463
|
env,
|
|
376
464
|
io,
|
|
377
465
|
);
|
|
466
|
+
if (isUnreadableReaskExhausted(unreadableReask)) {
|
|
467
|
+
const terminal = result.terminal === undefined
|
|
468
|
+
? undefined
|
|
469
|
+
: withUnsettledDirection(result.terminal);
|
|
470
|
+
presentOriginalVolume(held, terminal, io);
|
|
471
|
+
return terminal === undefined ? result : { ...result, terminal };
|
|
472
|
+
}
|
|
378
473
|
if (unreadableReask !== undefined) return unreadableReask;
|
|
379
474
|
if (AUDITED_ROLES.has(admitted.role) && result.terminal?.roleOutcome.kind === "accepted") {
|
|
380
475
|
return auditSubmittedRole(result, env, io);
|
|
@@ -561,23 +656,17 @@ async function runCountersignBody(
|
|
|
561
656
|
await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
|
|
562
657
|
admitted = { ...admitted, sourceRunPath: gateParentRunPath };
|
|
563
658
|
}
|
|
564
|
-
const turnProjection
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
...pickEngineAxis(env),
|
|
576
|
-
packageRoot: env.packageRoot,
|
|
577
|
-
}),
|
|
578
|
-
),
|
|
579
|
-
},
|
|
580
|
-
};
|
|
659
|
+
const turnProjection = roleTurnOptions(env, admitted, {
|
|
660
|
+
kind: "initial",
|
|
661
|
+
prompt: (env.reviewReask ?? env.gateReviewInstruction)
|
|
662
|
+
?? buildInstructionTransportPrompt(
|
|
663
|
+
admitted,
|
|
664
|
+
engineSessionMaterialFromOptions({
|
|
665
|
+
...pickEngineAxis(env),
|
|
666
|
+
packageRoot: env.packageRoot,
|
|
667
|
+
}),
|
|
668
|
+
),
|
|
669
|
+
});
|
|
581
670
|
const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
|
|
582
671
|
const result = await runPostAdmissionOneShot({
|
|
583
672
|
admitted,
|
|
@@ -869,7 +958,27 @@ export async function runPublicInstructionSeatResume(
|
|
|
869
958
|
},
|
|
870
959
|
afterAdmittedLoad: async (admitted) => {
|
|
871
960
|
await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
|
|
872
|
-
|
|
961
|
+
const adapters = seatAdapters(admitted, env);
|
|
962
|
+
if ((env.unreadableReasksSpent ?? 0) === 0 && env.reviewReask === undefined) {
|
|
963
|
+
return { kind: "continue" as const, adapters };
|
|
964
|
+
}
|
|
965
|
+
const original = await adapters.trySettle(
|
|
966
|
+
admitted, env.principalAuthority, await readCurrentCourt(admitted.runDirectory),
|
|
967
|
+
);
|
|
968
|
+
const unsettled = POST_SUBMISSION_ROUTING[admitted.role] === undefined
|
|
969
|
+
? heldUnreadableTerminal(original)
|
|
970
|
+
: unreadablePostSubmissionStatus(admitted, original) !== undefined;
|
|
971
|
+
// Select this reask's existing subject before dispatch. The common
|
|
972
|
+
// settlement then keeps it only when no fresh receipt supersedes it;
|
|
973
|
+
// actual failures still take their normal failure path.
|
|
974
|
+
return {
|
|
975
|
+
kind: "continue" as const,
|
|
976
|
+
adapters: original === undefined || !unsettled ? adapters : {
|
|
977
|
+
...adapters,
|
|
978
|
+
trySettle: async (seat, authority, scope) =>
|
|
979
|
+
await adapters.trySettle(seat, authority, scope) ?? original,
|
|
980
|
+
},
|
|
981
|
+
};
|
|
873
982
|
},
|
|
874
983
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
875
984
|
});
|
|
@@ -881,6 +990,13 @@ export async function runPublicInstructionSeatResume(
|
|
|
881
990
|
env,
|
|
882
991
|
io,
|
|
883
992
|
);
|
|
993
|
+
if (isUnreadableReaskExhausted(unreadableReask)) {
|
|
994
|
+
const terminal = result.terminal === undefined
|
|
995
|
+
? undefined
|
|
996
|
+
: withUnsettledDirection(result.terminal);
|
|
997
|
+
presentOriginalVolume(held, terminal, io);
|
|
998
|
+
return terminal === undefined ? result : { ...result, terminal };
|
|
999
|
+
}
|
|
884
1000
|
if (unreadableReask !== undefined) return unreadableReask;
|
|
885
1001
|
if (result.admitted !== undefined && AUDITED_ROLES.has(result.admitted.role)
|
|
886
1002
|
&& result.terminal?.roleOutcome.kind === "accepted") {
|
|
@@ -908,12 +1024,15 @@ export async function runPublicInstructionSeatResume(
|
|
|
908
1024
|
return resume();
|
|
909
1025
|
}
|
|
910
1026
|
|
|
911
|
-
const QUEUE_CONCLUSIONS = new Set(["converged", "continue", "escalate"]);
|
|
912
1027
|
const GATE_CHILD_ROLES = new Set(["notary", "auditor", "inspector", "countersign"]);
|
|
913
1028
|
|
|
914
1029
|
/**
|
|
915
1030
|
* A gate child's conclusion outside the three states goes back to that child.
|
|
916
|
-
* The words are the existing officer re-ask.
|
|
1031
|
+
* The words are the existing officer re-ask. Each reask spends this loop's own
|
|
1032
|
+
* copy of the configured ceiling; exhaustion keeps the terminal already in hand.
|
|
1033
|
+
* A host failure stops here.
|
|
1034
|
+
* Countersign already spends that ceiling inside its own seat, or the summoning
|
|
1035
|
+
* gate does. This loop does not open a second one for the same conclusion.
|
|
917
1036
|
*/
|
|
918
1037
|
async function queueConclusionFromChild(
|
|
919
1038
|
child: AdmittedRoleInvocation,
|
|
@@ -925,26 +1044,45 @@ async function queueConclusionFromChild(
|
|
|
925
1044
|
| undefined
|
|
926
1045
|
> {
|
|
927
1046
|
if (!GATE_CHILD_ROLES.has(child.role)) return undefined;
|
|
928
|
-
|
|
1047
|
+
if (child.role === "countersign") {
|
|
1048
|
+
const terminal = await trySettlePublicSeat(
|
|
1049
|
+
child, env.principalAuthority, await readCurrentCourt(child.runDirectory),
|
|
1050
|
+
);
|
|
1051
|
+
const status = latestQueueStatus(terminal);
|
|
1052
|
+
if (terminal !== undefined && typeof status === "string" && REVIEW_QUEUE_STATUSES.has(status)) {
|
|
1053
|
+
return { admitted: child, terminal, status };
|
|
1054
|
+
}
|
|
1055
|
+
return { stop: { exitCode: 0, ...(terminal === undefined ? {} : { terminal }) } };
|
|
1056
|
+
}
|
|
1057
|
+
let current: AdmittedRoleInvocation = child;
|
|
1058
|
+
// Notary, auditor, and inspector have no seat-local status reask. This loop
|
|
1059
|
+
// is their one budget. The child resume keeps the caller's env so delivery,
|
|
1060
|
+
// failure recovery, and other reask loops stay separate.
|
|
1061
|
+
let budgetEnv: InstructionSeatRunEnv = { ...env, unreadableReasksSpent: 0 };
|
|
929
1062
|
for (;;) {
|
|
930
1063
|
const terminal = await trySettlePublicSeat(
|
|
931
1064
|
current,
|
|
932
1065
|
env.principalAuthority,
|
|
933
|
-
|
|
1066
|
+
await readCurrentCourt(current.runDirectory),
|
|
934
1067
|
);
|
|
935
1068
|
const status = latestQueueStatus(terminal);
|
|
936
|
-
if (terminal !== undefined && status
|
|
1069
|
+
if (terminal !== undefined && typeof status === "string" && REVIEW_QUEUE_STATUSES.has(status)) {
|
|
937
1070
|
return { admitted: current, terminal, status };
|
|
938
1071
|
}
|
|
939
|
-
const reasked = await
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1072
|
+
const reasked = await reaskUnreadablePostSubmissionStatus(
|
|
1073
|
+
current, terminal, budgetEnv, io, officerConclusionReask(status),
|
|
1074
|
+
);
|
|
1075
|
+
if (reasked === undefined || isUnreadableReaskExhausted(reasked)) {
|
|
1076
|
+
return { stop: { exitCode: 0, ...(terminal === undefined ? {} : { terminal }) } };
|
|
1077
|
+
}
|
|
1078
|
+
budgetEnv = withUnreadableReask(budgetEnv)!;
|
|
1079
|
+
if (reasked.exitCode !== 0 || reasked.admitted === undefined || reasked.terminal === undefined
|
|
1080
|
+
|| reasked.terminal.roleOutcome.kind === "no_receipt"
|
|
1081
|
+
|| reasked.terminal.roleOutcome.decisiveFacts?.directionUnsettled === true) {
|
|
944
1082
|
return { stop: reasked };
|
|
945
1083
|
}
|
|
946
1084
|
const reaskedStatus = latestQueueStatus(reasked.terminal);
|
|
947
|
-
if (reaskedStatus
|
|
1085
|
+
if (typeof reaskedStatus === "string" && REVIEW_QUEUE_STATUSES.has(reaskedStatus)) {
|
|
948
1086
|
return { admitted: reasked.admitted, terminal: reasked.terminal, status: reaskedStatus };
|
|
949
1087
|
}
|
|
950
1088
|
if (latestPayloadEscalated(reasked.terminal.roleOutcome)) return { stop: reasked };
|
|
@@ -965,7 +1103,24 @@ export async function continueParentAfterChild(
|
|
|
965
1103
|
}
|
|
966
1104
|
const admitted = loaded.admitted;
|
|
967
1105
|
const resolved = await queueConclusionFromChild(child, env, io);
|
|
968
|
-
if (resolved !== undefined && "stop" in resolved)
|
|
1106
|
+
if (resolved !== undefined && "stop" in resolved) {
|
|
1107
|
+
const stopped = resolved.stop;
|
|
1108
|
+
if (
|
|
1109
|
+
stopped.exitCode === 0
|
|
1110
|
+
&& heldUnreadableTerminal(stopped.terminal)
|
|
1111
|
+
&& stopped.terminal !== undefined
|
|
1112
|
+
) {
|
|
1113
|
+
const parentTerminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
|
|
1114
|
+
if (
|
|
1115
|
+
parentTerminal?.roleOutcome.kind === "accepted"
|
|
1116
|
+
|| parentTerminal?.roleOutcome.kind === "audit_escalation"
|
|
1117
|
+
) {
|
|
1118
|
+
const terminal = presentUnsettledAudit(parentTerminal, stopped.terminal, io);
|
|
1119
|
+
return { exitCode: 0, admitted, terminal };
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return stopped;
|
|
1123
|
+
}
|
|
969
1124
|
if (resolved !== undefined && resolved.status === "escalate") {
|
|
970
1125
|
return { exitCode: 0, admitted: resolved.admitted, terminal: resolved.terminal };
|
|
971
1126
|
}
|
|
@@ -973,26 +1128,61 @@ export async function continueParentAfterChild(
|
|
|
973
1128
|
if (AUDITED_ROLES.has(admitted.role) && resolved.status === "converged") {
|
|
974
1129
|
const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
|
|
975
1130
|
if (terminal?.roleOutcome.kind !== "accepted") throw new Error("pending submission is not recorded");
|
|
976
|
-
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted
|
|
1131
|
+
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
|
|
977
1132
|
}
|
|
978
1133
|
return runPublicInstructionSeatResume({
|
|
979
1134
|
runId: parentRunId,
|
|
980
1135
|
message: readableGateItem(latestQueuePayload(resolved.terminal)),
|
|
981
|
-
}, { ...env,
|
|
1136
|
+
}, { ...env, unreadableReasksSpent: 0 }, io);
|
|
982
1137
|
}
|
|
983
1138
|
const result = await dispatchAdmitted(admitted, env, { ...io, omitFailureStderrDiagnostic: true });
|
|
984
1139
|
showResumeErrorPointer(io, result.exitCode, result.terminal);
|
|
985
1140
|
return result;
|
|
986
1141
|
}
|
|
987
1142
|
|
|
1143
|
+
/**
|
|
1144
|
+
* A gate is already passed only when an officer run reviewed this submission
|
|
1145
|
+
* and its latest queue word is converged. The resumed seat's role is not itself
|
|
1146
|
+
* a pass. Unknown or unreadable evidence does not skip the gate.
|
|
1147
|
+
*/
|
|
1148
|
+
async function officerPassedCurrentSubmission(input: {
|
|
1149
|
+
readonly env: InstructionSeatRunEnv;
|
|
1150
|
+
readonly parentSessionFile: string;
|
|
1151
|
+
readonly officer: GateOfficer;
|
|
1152
|
+
readonly toolCallId: string;
|
|
1153
|
+
}): Promise<boolean> {
|
|
1154
|
+
const { env, officer } = input;
|
|
1155
|
+
const pointer = await readDirectOfficerRunPointer(input.parentSessionFile, officer);
|
|
1156
|
+
if (pointer?.runDirectory === undefined) return false;
|
|
1157
|
+
if (pointer.submissionToolCallId !== input.toolCallId) return false;
|
|
1158
|
+
const officerRunId = runIdFromRunDirectory(pointer.runDirectory);
|
|
1159
|
+
if (officerRunId === undefined) return false;
|
|
1160
|
+
let admitted: AdmittedRoleInvocation;
|
|
1161
|
+
try {
|
|
1162
|
+
const loaded = await loadResumablePublicRole(
|
|
1163
|
+
env.home,
|
|
1164
|
+
officerRunId,
|
|
1165
|
+
env.principalAuthority,
|
|
1166
|
+
);
|
|
1167
|
+
admitted = loaded.admitted;
|
|
1168
|
+
} catch {
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
1171
|
+
if (admitted.role !== officer) return false;
|
|
1172
|
+
// A summon binding identifies the requested review, not its outcome. An open
|
|
1173
|
+
// court (including a failed host turn) must not borrow a previous court's seal.
|
|
1174
|
+
const court = await readCurrentCourt(admitted.runDirectory);
|
|
1175
|
+
const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, court);
|
|
1176
|
+
return latestQueueStatus(terminal) === "converged";
|
|
1177
|
+
}
|
|
1178
|
+
|
|
988
1179
|
/** Finished submissions enter the existing audit gate after their tool call has returned. */
|
|
989
1180
|
async function auditSubmittedRole(
|
|
990
1181
|
turn: SeatRunResult,
|
|
991
1182
|
env: InstructionSeatRunEnv,
|
|
992
1183
|
io: CliIo,
|
|
993
|
-
|
|
1184
|
+
resumedOfficer?: AdmittedRoleInvocation,
|
|
994
1185
|
passedReceipt?: unknown,
|
|
995
|
-
passedRunId?: string,
|
|
996
1186
|
): Promise<SeatRunResult> {
|
|
997
1187
|
const admitted = turn.admitted;
|
|
998
1188
|
if (admitted === undefined || !AUDITED_ROLES.has(admitted.role)
|
|
@@ -1002,13 +1192,28 @@ async function auditSubmittedRole(
|
|
|
1002
1192
|
const record = isRecord(accepted)
|
|
1003
1193
|
? accepted as Record<string, unknown> : undefined;
|
|
1004
1194
|
const status = admitted.role === "secretariat" ? record?.secretariatStatus : record?.status;
|
|
1195
|
+
// A gate summon already owns this conclusion's reasks. The seat must not
|
|
1196
|
+
// start another budget on the same run (parentRunPath is that summon).
|
|
1197
|
+
const gateOwnsStatusReask = typeof env.parentRunPath === "string" && env.parentRunPath.trim() !== "";
|
|
1005
1198
|
if (admitted.role === "secretariat" && status !== "converged" && status !== "escalate") {
|
|
1006
|
-
|
|
1007
|
-
|
|
1199
|
+
if (gateOwnsStatusReask) return turn;
|
|
1200
|
+
const next = withUnreadableReask(env);
|
|
1201
|
+
if (next === undefined) {
|
|
1202
|
+
const terminal = withUnsettledDirection(turn.terminal);
|
|
1203
|
+
io.stdout(formatTerminalResult(terminal));
|
|
1204
|
+
return { ...turn, terminal };
|
|
1205
|
+
}
|
|
1206
|
+
return runPublicInstructionSeatResume({ runId: admitted.runId, message: SECRETARIAT_STATUS_REASK }, next, io);
|
|
1008
1207
|
}
|
|
1009
|
-
if (admitted.role === "countersign" && (typeof status !== "string" || !
|
|
1010
|
-
|
|
1011
|
-
|
|
1208
|
+
if (admitted.role === "countersign" && (typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status))) {
|
|
1209
|
+
if (gateOwnsStatusReask) return turn;
|
|
1210
|
+
const next = withUnreadableReask(env);
|
|
1211
|
+
if (next === undefined) {
|
|
1212
|
+
const terminal = withUnsettledDirection(turn.terminal);
|
|
1213
|
+
io.stdout(formatTerminalResult(terminal));
|
|
1214
|
+
return { ...turn, terminal };
|
|
1215
|
+
}
|
|
1216
|
+
return runPublicInstructionSeatResume({ runId: admitted.runId, message: officerConclusionReask(status) }, next, io);
|
|
1012
1217
|
}
|
|
1013
1218
|
// Self-escalation is a valid open routing state for these seats only.
|
|
1014
1219
|
// Doctor's declared domain is completed|refused — an open "escalate" must
|
|
@@ -1044,11 +1249,18 @@ async function auditSubmittedRole(
|
|
|
1044
1249
|
},
|
|
1045
1250
|
abort() {},
|
|
1046
1251
|
};
|
|
1252
|
+
const passedThisSubmission = (officer: GateOfficer) => officerPassedCurrentSubmission({
|
|
1253
|
+
env,
|
|
1254
|
+
parentSessionFile: sessionFile,
|
|
1255
|
+
officer,
|
|
1256
|
+
toolCallId,
|
|
1257
|
+
});
|
|
1047
1258
|
if (admitted.role === "doctor") {
|
|
1048
|
-
if (
|
|
1259
|
+
if (!await passedThisSubmission("auditor")) {
|
|
1049
1260
|
let lastSummon: PublicSummonResult | undefined;
|
|
1050
1261
|
const decision = await createPiDoctorAuditor()({
|
|
1051
1262
|
context, submission: accepted,
|
|
1263
|
+
...(env.autoResumeLimit === undefined ? {} : { autoResumeLimit: env.autoResumeLimit }),
|
|
1052
1264
|
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
1053
1265
|
summonAuditor: async (subject, sourceRunDirectory, signal, reask, submission) => {
|
|
1054
1266
|
const summoned = await summonPublicRole({
|
|
@@ -1068,6 +1280,7 @@ async function auditSubmittedRole(
|
|
|
1068
1280
|
if (officerSession !== undefined) {
|
|
1069
1281
|
bookDirectOfficerRunPointer({
|
|
1070
1282
|
parentSessionFile: sessionFile, officer: "auditor", sessionFile: officerSession,
|
|
1283
|
+
submissionToolCallId: toolCallId,
|
|
1071
1284
|
...(summoned.runDirectory === undefined ? {} : { runDirectory: summoned.runDirectory }),
|
|
1072
1285
|
});
|
|
1073
1286
|
}
|
|
@@ -1078,7 +1291,15 @@ async function auditSubmittedRole(
|
|
|
1078
1291
|
if (decision.status === "continue") {
|
|
1079
1292
|
return runPublicInstructionSeatResume({
|
|
1080
1293
|
runId: admitted.runId, message: readableGateItem(decision.receipt ?? decision.violations),
|
|
1081
|
-
}, { ...env,
|
|
1294
|
+
}, { ...env, unreadableReasksSpent: 0 }, io);
|
|
1295
|
+
}
|
|
1296
|
+
if (decision.status === "received") {
|
|
1297
|
+
if (lastSummon?.terminal === undefined) {
|
|
1298
|
+
throw new Error("doctor audit kept an unreadable reply with no terminal");
|
|
1299
|
+
}
|
|
1300
|
+
if (turn.terminal === undefined) throw new Error("doctor submission has no terminal");
|
|
1301
|
+
const terminal = presentUnsettledAudit(turn.terminal, lastSummon.terminal, io);
|
|
1302
|
+
return { exitCode: 0, admitted, terminal };
|
|
1082
1303
|
}
|
|
1083
1304
|
if (decision.status !== "converged") {
|
|
1084
1305
|
if (lastSummon?.terminal !== undefined) {
|
|
@@ -1099,7 +1320,7 @@ async function auditSubmittedRole(
|
|
|
1099
1320
|
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
1100
1321
|
});
|
|
1101
1322
|
let chain: Awaited<ReturnType<typeof runJudgeGates>>;
|
|
1102
|
-
let officerRunId =
|
|
1323
|
+
let officerRunId = resumedOfficer?.runId;
|
|
1103
1324
|
const runGate = (subject: Parameters<typeof requireSubmissionGate>[0]["subject"]) =>
|
|
1104
1325
|
requireSubmissionGate({
|
|
1105
1326
|
context,
|
|
@@ -1107,6 +1328,7 @@ async function auditSubmittedRole(
|
|
|
1107
1328
|
toolCallId,
|
|
1108
1329
|
submission: accepted,
|
|
1109
1330
|
summonOfficer,
|
|
1331
|
+
...(env.autoResumeLimit === undefined ? {} : { autoResumeLimit: env.autoResumeLimit }),
|
|
1110
1332
|
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
1111
1333
|
hostActions: {
|
|
1112
1334
|
failInfrastructure(error): never { throw error; },
|
|
@@ -1115,8 +1337,7 @@ async function auditSubmittedRole(
|
|
|
1115
1337
|
});
|
|
1116
1338
|
if (admitted.role === "judge") {
|
|
1117
1339
|
chain = await runJudgeGates({
|
|
1118
|
-
gateAlreadyConverged: async (subject) =>
|
|
1119
|
-
|| (passedOfficer === "notary" && subject.kind === "judge_draft"),
|
|
1340
|
+
gateAlreadyConverged: async (subject) => passedThisSubmission(gateOfficerForSubject(subject)),
|
|
1120
1341
|
runGate,
|
|
1121
1342
|
});
|
|
1122
1343
|
} else {
|
|
@@ -1126,7 +1347,7 @@ async function auditSubmittedRole(
|
|
|
1126
1347
|
? { kind: "secretariat_verdict" as const }
|
|
1127
1348
|
: { kind: "countersign_verdict" as const };
|
|
1128
1349
|
const officer = gateOfficerForSubject(subject);
|
|
1129
|
-
if (
|
|
1350
|
+
if (await passedThisSubmission(officer)) {
|
|
1130
1351
|
chain = { status: "converged", passes: [] };
|
|
1131
1352
|
} else {
|
|
1132
1353
|
const pass = await runGate(subject);
|
|
@@ -1140,10 +1361,18 @@ async function auditSubmittedRole(
|
|
|
1140
1361
|
receipt: pass.receipt,
|
|
1141
1362
|
...(pass.runId === undefined ? {} : { runId: pass.runId }),
|
|
1142
1363
|
...(pass.runDirectory === undefined ? {} : { runDirectory: pass.runDirectory }),
|
|
1364
|
+
...(pass.terminal === undefined ? {} : { terminal: pass.terminal }),
|
|
1143
1365
|
}],
|
|
1144
1366
|
};
|
|
1145
1367
|
}
|
|
1146
1368
|
}
|
|
1369
|
+
if (chain.status === "needs_reask") {
|
|
1370
|
+
const officerTerminal = chain.passes.at(-1)?.terminal;
|
|
1371
|
+
if (officerTerminal === undefined) throw new Error("unreadable audit has no terminal result");
|
|
1372
|
+
if (turn.terminal === undefined) throw new Error("audited submission has no terminal");
|
|
1373
|
+
const terminal = presentUnsettledAudit(turn.terminal, officerTerminal, io);
|
|
1374
|
+
return { exitCode: 0, admitted, terminal };
|
|
1375
|
+
}
|
|
1147
1376
|
if (chain.status === "escalate") {
|
|
1148
1377
|
const escalation = chain.passes.at(-1);
|
|
1149
1378
|
const escalatedRunId = escalation?.runId
|
|
@@ -1151,7 +1380,9 @@ async function auditSubmittedRole(
|
|
|
1151
1380
|
? undefined : runIdFromRunDirectory(escalation.runDirectory));
|
|
1152
1381
|
if (escalatedRunId === undefined) throw new Error("escalated audit has no resumable run identity");
|
|
1153
1382
|
const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority);
|
|
1154
|
-
const terminal = await trySettlePublicSeat(
|
|
1383
|
+
const terminal = await trySettlePublicSeat(
|
|
1384
|
+
officer.admitted, env.principalAuthority, await readCurrentCourt(officer.admitted.runDirectory),
|
|
1385
|
+
);
|
|
1155
1386
|
if (terminal === undefined) throw new Error("escalated audit has no terminal result");
|
|
1156
1387
|
io.stdout(formatTerminalResult(terminal));
|
|
1157
1388
|
return { exitCode: 0, admitted: officer.admitted, terminal };
|
|
@@ -1160,7 +1391,7 @@ async function auditSubmittedRole(
|
|
|
1160
1391
|
return runPublicInstructionSeatResume({
|
|
1161
1392
|
runId: admitted.runId,
|
|
1162
1393
|
message: readableGateItem(chain.passes.at(-1)?.receipt),
|
|
1163
|
-
}, { ...env,
|
|
1394
|
+
}, { ...env, unreadableReasksSpent: 0 }, io);
|
|
1164
1395
|
}
|
|
1165
1396
|
if (admitted.role === "secretariat") {
|
|
1166
1397
|
const pass = chain.passes.at(-1);
|