@akagilnc/pi-workflow-roles 0.1.5382 → 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 +2523 -1880
- package/dist/archivist-record-pointer.js +38 -0
- package/dist/compliance-transport.js +5 -0
- package/dist/doctor-auditor.js +1 -0
- package/dist/engine-detour-usage.js +6 -6
- package/dist/headless-host/production-host.js +1097 -454
- package/dist/judge-role.js +14 -1
- package/dist/migrate-book-topology.js +190 -74
- package/dist/navigator-attendance.js +10 -9
- package/dist/navigator-invocation-identity.js +18 -0
- 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 +250 -44
- package/dist/public-cli/main.js +929 -323
- 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/submission-errors.js +11 -1
- package/dist/submission-gate.js +27 -4
- package/package.json +1 -1
- package/src/archivist-record-pointer.ts +48 -0
- package/src/compliance-transport.ts +10 -2
- package/src/doctor-auditor.ts +3 -0
- package/src/engine-detour-usage.ts +7 -6
- package/src/external-host-turn-loop.ts +24 -12
- package/src/gatekeeper-role.ts +2 -2
- package/src/host-contracts.ts +7 -0
- package/src/judge-role.ts +24 -5
- package/src/navigator-attendance.ts +17 -12
- package/src/navigator-invocation-identity.ts +31 -0
- 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 +283 -49
- 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/role-envelope.ts +5 -1
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +65 -21
- package/src/submission-correctable-error.ts +5 -2
- package/src/submission-errors.ts +12 -1
- package/src/submission-gate.ts +34 -4
- package/src/worker-role.ts +19 -7
- package/src/worker-submission-gates.ts +29 -7
|
@@ -31,7 +31,10 @@ import {
|
|
|
31
31
|
} from "./navigator-session-contracts.js";
|
|
32
32
|
import { sitianReport } from "./sitian-facade.js";
|
|
33
33
|
import { issueRoot, subjectPath } from "./work-subject-identity.js";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
createReceiptDeliveryPolicy,
|
|
36
|
+
NO_RECEIPT_LIFECYCLE_ENTRY_TYPE
|
|
37
|
+
} from "./receipt-delivery-policy.js";
|
|
35
38
|
import { navigatorProseFromUnknown } from "./package-contracts/navigator-output.js";
|
|
36
39
|
import { persistNavigatorWorkBase } from "./navigator-work-base.js";
|
|
37
40
|
import { sha256Hex } from "./sha256.js";
|
|
@@ -250,8 +253,8 @@ function createNavigatorAttendance(options) {
|
|
|
250
253
|
try {
|
|
251
254
|
try {
|
|
252
255
|
if (disposed) throw navigatorUnavailableError("session", new Error("Navigator attendance was disposed"));
|
|
253
|
-
const delivery = createReceiptDeliveryPolicy();
|
|
254
|
-
const promptAllowingRejectedPrepare = async (text
|
|
256
|
+
const delivery = createReceiptDeliveryPolicy(options.deliveryRequestLimit);
|
|
257
|
+
const promptAllowingRejectedPrepare = async (text) => {
|
|
255
258
|
await persistNavigatorWorkBase(activeSession.recordPointer(), { subject, authority });
|
|
256
259
|
const entryStart = activeSession.entries().length;
|
|
257
260
|
prepareBatchRejected = false;
|
|
@@ -278,17 +281,15 @@ function createNavigatorAttendance(options) {
|
|
|
278
281
|
delivery.recordNestedNoReceipt(sessionNoReceipt);
|
|
279
282
|
return;
|
|
280
283
|
}
|
|
281
|
-
if (deliveryRequest && output === void 0) delivery.recordDeliveryRequest();
|
|
282
284
|
};
|
|
283
|
-
await promptAllowingRejectedPrepare(request
|
|
285
|
+
await promptAllowingRejectedPrepare(request);
|
|
284
286
|
if (boundSettlement !== void 0) {
|
|
285
287
|
while (output === void 0 && prepareBatchRejected && delivery.nextAction() === "request-delivery") {
|
|
286
|
-
|
|
288
|
+
delivery.recordDeliveryRequest();
|
|
289
|
+
await promptAllowingRejectedPrepare(JSON.stringify(delivery.deliveryState()));
|
|
287
290
|
}
|
|
288
291
|
if (output === void 0 && delivery.nextAction() === "request-delivery") {
|
|
289
|
-
|
|
290
|
-
delivery.recordDeliveryRequest();
|
|
291
|
-
}
|
|
292
|
+
delivery.closeBudget();
|
|
292
293
|
}
|
|
293
294
|
if (output === void 0 && delivery.nextAction() === "no-receipt" && activeSession.providerFailure?.() === void 0) {
|
|
294
295
|
const facts = delivery.facts({ runPointer: activeSession.recordPointer(), attemptPointer: invocationId });
|
|
@@ -86,6 +86,23 @@ function markerMatchesExpectedIdentity(marker, expected) {
|
|
|
86
86
|
}
|
|
87
87
|
return true;
|
|
88
88
|
}
|
|
89
|
+
function packagedRoleOutputRejectionReason(message) {
|
|
90
|
+
if (typeof message.toolName !== "string") return void 0;
|
|
91
|
+
if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName) && message.toolName !== "ak_submission_output") {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
if (message.isError !== true) return void 0;
|
|
95
|
+
if (classifyPackagedRoleTerminalResult(message).kind !== "nonterminal") return void 0;
|
|
96
|
+
const content = message.content;
|
|
97
|
+
if (typeof content === "string") return content.trim();
|
|
98
|
+
if (!Array.isArray(content)) return "";
|
|
99
|
+
return content.map((part) => {
|
|
100
|
+
if (isRecord(part) && part.type === "text" && typeof part.text === "string") {
|
|
101
|
+
return part.text;
|
|
102
|
+
}
|
|
103
|
+
return "";
|
|
104
|
+
}).join("").trim();
|
|
105
|
+
}
|
|
89
106
|
function classifyPackagedRoleTerminalResult(message) {
|
|
90
107
|
if (typeof message.toolName !== "string") return { kind: "nonterminal" };
|
|
91
108
|
if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName) && message.toolName !== "ak_submission_output") return { kind: "nonterminal" };
|
|
@@ -181,6 +198,7 @@ export {
|
|
|
181
198
|
isNavigatorInfrastructureFailureFact,
|
|
182
199
|
markerMatchesExpectedIdentity,
|
|
183
200
|
mintNavigatorInvocationId,
|
|
201
|
+
packagedRoleOutputRejectionReason,
|
|
184
202
|
parseInvocationMarkerIdentity,
|
|
185
203
|
resolveLifecycleInvocationPrincipal
|
|
186
204
|
};
|
|
@@ -14,6 +14,7 @@ import { ExplicitInternalActivationError } from "../host-contracts.js";
|
|
|
14
14
|
import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.js";
|
|
15
15
|
import { projectActivationFlags } from "../role-activation-flags.js";
|
|
16
16
|
import { encodeUserDialogueStdin } from "../user-dialogue-stdin.js";
|
|
17
|
+
import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.js";
|
|
17
18
|
/** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
|
|
18
19
|
const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
|
|
19
20
|
export function resolveInternalRoleEntrypoint(packageRoot) {
|
|
@@ -307,6 +308,14 @@ export function createPiRoleTurnHost(config) {
|
|
|
307
308
|
delete env.AK_ROLE_HOST;
|
|
308
309
|
else
|
|
309
310
|
env.AK_ROLE_HOST = request.host.trim();
|
|
311
|
+
// #1132: the one effective delivery-request ceiling travels to the child
|
|
312
|
+
// on its own env so the in-child role runtime and the worker submission
|
|
313
|
+
// gate count with the same configured number the AK seam resolved. Omit
|
|
314
|
+
// must not inherit a parent value (same rule as the other AK_ROLE_ axes).
|
|
315
|
+
if (request.deliveryRequestLimit === undefined)
|
|
316
|
+
delete env[RECEIPT_DELIVERY_LIMIT_ENV];
|
|
317
|
+
else
|
|
318
|
+
env[RECEIPT_DELIVERY_LIMIT_ENV] = String(request.deliveryRequestLimit);
|
|
310
319
|
applyEngineChildEnv(env, request.engine);
|
|
311
320
|
// Nested auditor dossier tool binds the parent run pointer when published.
|
|
312
321
|
if (typeof process.env.AK_ROLE_AUDITOR_SOURCE_RUN === "string"
|
package/dist/public-cli/cli.js
CHANGED
|
@@ -846,6 +846,7 @@ export async function runAkRole(argv, env) {
|
|
|
846
846
|
let current = result;
|
|
847
847
|
while (current.exitCode === 0 && current.admitted?.correlationId !== undefined
|
|
848
848
|
&& current.terminal?.roleOutcome.kind === "accepted"
|
|
849
|
+
&& current.terminal.roleOutcome.decisiveFacts?.directionUnsettled !== true
|
|
849
850
|
&& !latestPayloadEscalated(current.terminal.roleOutcome)) {
|
|
850
851
|
const parentRunId = current.admitted.correlationId;
|
|
851
852
|
resumeFailureContext = {
|
|
@@ -6,7 +6,7 @@
|
|
|
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
12
|
import { receivedDiscriminator } from "../submission-errors.js";
|
|
@@ -15,6 +15,7 @@ import { runJudgeGates } from "../judge-role.js";
|
|
|
15
15
|
import { WORKER_DONE_STATUSES } from "../worker-submission-contracts.js";
|
|
16
16
|
import { SECRETARIAT_GATE_OFFICER_ENTRY_TYPE } from "../secretariat-contracts.js";
|
|
17
17
|
import { readableGateItem } from "../readable-gate-item.js";
|
|
18
|
+
import { deliveryLimitFromConfig } from "../receipt-delivery-policy.js";
|
|
18
19
|
import { runIdFromRunDirectory } from "../run-terminal-artifacts.js";
|
|
19
20
|
import { createDefaultGateOfficerSummon, requireSubmissionGate } from "../submission-gate.js";
|
|
20
21
|
import { readRecordedSubmissionRows } from "../submission-ledger.js";
|
|
@@ -29,12 +30,12 @@ import { readAuditorResumeBinding } from "../auditor-soul.js";
|
|
|
29
30
|
import { CliUsageError } from "./cli-errors.js";
|
|
30
31
|
import { admitPublicRole, bindAdmittedTicketNumber, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
|
|
31
32
|
import { presentControlledFailure, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
|
|
32
|
-
import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, } from "./run-lifecycle.js";
|
|
33
|
+
import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, readCurrentCourt, } from "./run-lifecycle.js";
|
|
33
34
|
import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
|
|
34
35
|
import { presentStructuralRejection, readBoundSessionEntries, seatKnownFailureResolver, attachPostAuditProjection, trySettlePublicSeat, } from "./settlement.js";
|
|
35
36
|
import { warnMissingMethodSkills } from "./machine-method-skills.js";
|
|
36
|
-
import { formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
|
|
37
|
-
import { admittedSeatTurnDetails,
|
|
37
|
+
import { coalesceSubmissionRows, formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
|
|
38
|
+
import { admittedSeatTurnDetails, projectRoleTurnRequest, requiredMethodSkills, } from "./turn-request.js";
|
|
38
39
|
import { invokeCourtDiarist, latestPayloadEscalated, } from "./countersign-run.js";
|
|
39
40
|
import { isRecord, errorText } from "../unknown-value.js";
|
|
40
41
|
const AUDITED_ROLES = new Set(["judge", "fixer", "coder", "secretariat", "countersign", "doctor"]);
|
|
@@ -80,11 +81,83 @@ function unreadablePostSubmissionStatus(admitted, terminal) {
|
|
|
80
81
|
? undefined
|
|
81
82
|
: route.reask ?? officerConclusionReask(status);
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
153
|
+
}
|
|
154
|
+
async function reaskUnreadablePostSubmissionStatus(admitted, terminal, env, io, message = unreadablePostSubmissionStatus(admitted, terminal)) {
|
|
85
155
|
if (message === undefined)
|
|
86
156
|
return undefined;
|
|
87
|
-
|
|
157
|
+
const next = withUnreadableReask(env);
|
|
158
|
+
if (next === undefined)
|
|
159
|
+
return UNREADABLE_REASK_EXHAUSTED;
|
|
160
|
+
return runPublicInstructionSeatResume({ runId: admitted.runId, message }, next, io);
|
|
88
161
|
}
|
|
89
162
|
function roleRecord(role) {
|
|
90
163
|
const record = packagedRoleMetadata(role);
|
|
@@ -232,6 +305,13 @@ async function dispatchAdmitted(admitted, env, io) {
|
|
|
232
305
|
};
|
|
233
306
|
const result = await execute(env);
|
|
234
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
|
+
}
|
|
235
315
|
if (unreadableReask !== undefined)
|
|
236
316
|
return unreadableReask;
|
|
237
317
|
if (AUDITED_ROLES.has(admitted.role) && result.terminal?.roleOutcome.kind === "accepted") {
|
|
@@ -387,20 +467,14 @@ async function runCountersignBody(parsed, env, io) {
|
|
|
387
467
|
await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
|
|
388
468
|
admitted = { ...admitted, sourceRunPath: gateParentRunPath };
|
|
389
469
|
}
|
|
390
|
-
const turnProjection = {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
?? buildInstructionTransportPrompt(admitted, engineSessionMaterialFromOptions({
|
|
399
|
-
...pickEngineAxis(env),
|
|
400
|
-
packageRoot: env.packageRoot,
|
|
401
|
-
})),
|
|
402
|
-
},
|
|
403
|
-
};
|
|
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
|
+
});
|
|
404
478
|
const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
|
|
405
479
|
const result = await runPostAdmissionOneShot({
|
|
406
480
|
admitted,
|
|
@@ -677,13 +751,37 @@ export async function runPublicInstructionSeatResume(request, env, io) {
|
|
|
677
751
|
},
|
|
678
752
|
afterAdmittedLoad: async (admitted) => {
|
|
679
753
|
await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
|
|
680
|
-
|
|
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
|
+
};
|
|
681
772
|
},
|
|
682
773
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
683
774
|
});
|
|
684
775
|
const unreadableReask = result.admitted === undefined
|
|
685
776
|
? undefined
|
|
686
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
|
+
}
|
|
687
785
|
if (unreadableReask !== undefined)
|
|
688
786
|
return unreadableReask;
|
|
689
787
|
if (result.admitted !== undefined && AUDITED_ROLES.has(result.admitted.role)
|
|
@@ -717,23 +815,42 @@ export async function runPublicInstructionSeatResume(request, env, io) {
|
|
|
717
815
|
const GATE_CHILD_ROLES = new Set(["notary", "auditor", "inspector", "countersign"]);
|
|
718
816
|
/**
|
|
719
817
|
* A gate child's conclusion outside the three states goes back to that child.
|
|
720
|
-
* 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.
|
|
721
823
|
*/
|
|
722
824
|
async function queueConclusionFromChild(child, env, io) {
|
|
723
825
|
if (!GATE_CHILD_ROLES.has(child.role))
|
|
724
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
|
+
}
|
|
725
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 };
|
|
726
840
|
for (;;) {
|
|
727
|
-
const terminal = await trySettlePublicSeat(current, env.principalAuthority,
|
|
841
|
+
const terminal = await trySettlePublicSeat(current, env.principalAuthority, await readCurrentCourt(current.runDirectory));
|
|
728
842
|
const status = latestQueueStatus(terminal);
|
|
729
843
|
if (terminal !== undefined && typeof status === "string" && REVIEW_QUEUE_STATUSES.has(status)) {
|
|
730
844
|
return { admitted: current, terminal, status };
|
|
731
845
|
}
|
|
732
|
-
const reasked = await
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
736
|
-
|
|
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) {
|
|
737
854
|
return { stop: reasked };
|
|
738
855
|
}
|
|
739
856
|
const reaskedStatus = latestQueueStatus(reasked.terminal);
|
|
@@ -753,8 +870,20 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
|
|
|
753
870
|
}
|
|
754
871
|
const admitted = loaded.admitted;
|
|
755
872
|
const resolved = await queueConclusionFromChild(child, env, io);
|
|
756
|
-
if (resolved !== undefined && "stop" in resolved)
|
|
757
|
-
|
|
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
|
+
}
|
|
758
887
|
if (resolved !== undefined && resolved.status === "escalate") {
|
|
759
888
|
return { exitCode: 0, admitted: resolved.admitted, terminal: resolved.terminal };
|
|
760
889
|
}
|
|
@@ -763,19 +892,50 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
|
|
|
763
892
|
const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
|
|
764
893
|
if (terminal?.roleOutcome.kind !== "accepted")
|
|
765
894
|
throw new Error("pending submission is not recorded");
|
|
766
|
-
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted
|
|
895
|
+
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
|
|
767
896
|
}
|
|
768
897
|
return runPublicInstructionSeatResume({
|
|
769
898
|
runId: parentRunId,
|
|
770
899
|
message: readableGateItem(latestQueuePayload(resolved.terminal)),
|
|
771
|
-
}, { ...env,
|
|
900
|
+
}, { ...env, unreadableReasksSpent: 0 }, io);
|
|
772
901
|
}
|
|
773
902
|
const result = await dispatchAdmitted(admitted, env, { ...io, omitFailureStderrDiagnostic: true });
|
|
774
903
|
showResumeErrorPointer(io, result.exitCode, result.terminal);
|
|
775
904
|
return result;
|
|
776
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
|
+
}
|
|
777
937
|
/** Finished submissions enter the existing audit gate after their tool call has returned. */
|
|
778
|
-
async function auditSubmittedRole(turn, env, io,
|
|
938
|
+
async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt) {
|
|
779
939
|
const admitted = turn.admitted;
|
|
780
940
|
if (admitted === undefined || !AUDITED_ROLES.has(admitted.role)
|
|
781
941
|
|| turn.terminal?.roleOutcome.kind !== "accepted")
|
|
@@ -786,11 +946,30 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
786
946
|
const record = isRecord(accepted)
|
|
787
947
|
? accepted : undefined;
|
|
788
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() !== "";
|
|
789
952
|
if (admitted.role === "secretariat" && status !== "converged" && status !== "escalate") {
|
|
790
|
-
|
|
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);
|
|
791
962
|
}
|
|
792
963
|
if (admitted.role === "countersign" && (typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status))) {
|
|
793
|
-
|
|
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);
|
|
794
973
|
}
|
|
795
974
|
// Self-escalation is a valid open routing state for these seats only.
|
|
796
975
|
// Doctor's declared domain is completed|refused — an open "escalate" must
|
|
@@ -827,11 +1006,18 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
827
1006
|
},
|
|
828
1007
|
abort() { },
|
|
829
1008
|
};
|
|
1009
|
+
const passedThisSubmission = (officer) => officerPassedCurrentSubmission({
|
|
1010
|
+
env,
|
|
1011
|
+
parentSessionFile: sessionFile,
|
|
1012
|
+
officer,
|
|
1013
|
+
toolCallId,
|
|
1014
|
+
});
|
|
830
1015
|
if (admitted.role === "doctor") {
|
|
831
|
-
if (
|
|
1016
|
+
if (!await passedThisSubmission("auditor")) {
|
|
832
1017
|
let lastSummon;
|
|
833
1018
|
const decision = await createPiDoctorAuditor()({
|
|
834
1019
|
context, submission: accepted,
|
|
1020
|
+
...(env.autoResumeLimit === undefined ? {} : { autoResumeLimit: env.autoResumeLimit }),
|
|
835
1021
|
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
836
1022
|
summonAuditor: async (subject, sourceRunDirectory, signal, reask, submission) => {
|
|
837
1023
|
const summoned = await summonPublicRole({
|
|
@@ -851,6 +1037,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
851
1037
|
if (officerSession !== undefined) {
|
|
852
1038
|
bookDirectOfficerRunPointer({
|
|
853
1039
|
parentSessionFile: sessionFile, officer: "auditor", sessionFile: officerSession,
|
|
1040
|
+
submissionToolCallId: toolCallId,
|
|
854
1041
|
...(summoned.runDirectory === undefined ? {} : { runDirectory: summoned.runDirectory }),
|
|
855
1042
|
});
|
|
856
1043
|
}
|
|
@@ -861,7 +1048,16 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
861
1048
|
if (decision.status === "continue") {
|
|
862
1049
|
return runPublicInstructionSeatResume({
|
|
863
1050
|
runId: admitted.runId, message: readableGateItem(decision.receipt ?? decision.violations),
|
|
864
|
-
}, { ...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 };
|
|
865
1061
|
}
|
|
866
1062
|
if (decision.status !== "converged") {
|
|
867
1063
|
if (lastSummon?.terminal !== undefined) {
|
|
@@ -883,13 +1079,14 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
883
1079
|
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
884
1080
|
});
|
|
885
1081
|
let chain;
|
|
886
|
-
let officerRunId =
|
|
1082
|
+
let officerRunId = resumedOfficer?.runId;
|
|
887
1083
|
const runGate = (subject) => requireSubmissionGate({
|
|
888
1084
|
context,
|
|
889
1085
|
subject,
|
|
890
1086
|
toolCallId,
|
|
891
1087
|
submission: accepted,
|
|
892
1088
|
summonOfficer,
|
|
1089
|
+
...(env.autoResumeLimit === undefined ? {} : { autoResumeLimit: env.autoResumeLimit }),
|
|
893
1090
|
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
894
1091
|
hostActions: {
|
|
895
1092
|
failInfrastructure(error) { throw error; },
|
|
@@ -898,8 +1095,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
898
1095
|
});
|
|
899
1096
|
if (admitted.role === "judge") {
|
|
900
1097
|
chain = await runJudgeGates({
|
|
901
|
-
gateAlreadyConverged: async (subject) =>
|
|
902
|
-
|| (passedOfficer === "notary" && subject.kind === "judge_draft"),
|
|
1098
|
+
gateAlreadyConverged: async (subject) => passedThisSubmission(gateOfficerForSubject(subject)),
|
|
903
1099
|
runGate,
|
|
904
1100
|
});
|
|
905
1101
|
}
|
|
@@ -910,7 +1106,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
910
1106
|
? { kind: "secretariat_verdict" }
|
|
911
1107
|
: { kind: "countersign_verdict" };
|
|
912
1108
|
const officer = gateOfficerForSubject(subject);
|
|
913
|
-
if (
|
|
1109
|
+
if (await passedThisSubmission(officer)) {
|
|
914
1110
|
chain = { status: "converged", passes: [] };
|
|
915
1111
|
}
|
|
916
1112
|
else {
|
|
@@ -926,10 +1122,20 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
926
1122
|
receipt: pass.receipt,
|
|
927
1123
|
...(pass.runId === undefined ? {} : { runId: pass.runId }),
|
|
928
1124
|
...(pass.runDirectory === undefined ? {} : { runDirectory: pass.runDirectory }),
|
|
1125
|
+
...(pass.terminal === undefined ? {} : { terminal: pass.terminal }),
|
|
929
1126
|
}],
|
|
930
1127
|
};
|
|
931
1128
|
}
|
|
932
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
|
+
}
|
|
933
1139
|
if (chain.status === "escalate") {
|
|
934
1140
|
const escalation = chain.passes.at(-1);
|
|
935
1141
|
const escalatedRunId = escalation?.runId
|
|
@@ -938,7 +1144,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
938
1144
|
if (escalatedRunId === undefined)
|
|
939
1145
|
throw new Error("escalated audit has no resumable run identity");
|
|
940
1146
|
const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority);
|
|
941
|
-
const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority,
|
|
1147
|
+
const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority, await readCurrentCourt(officer.admitted.runDirectory));
|
|
942
1148
|
if (terminal === undefined)
|
|
943
1149
|
throw new Error("escalated audit has no terminal result");
|
|
944
1150
|
io.stdout(formatTerminalResult(terminal));
|
|
@@ -948,7 +1154,7 @@ async function auditSubmittedRole(turn, env, io, passedOfficer, passedReceipt, p
|
|
|
948
1154
|
return runPublicInstructionSeatResume({
|
|
949
1155
|
runId: admitted.runId,
|
|
950
1156
|
message: readableGateItem(chain.passes.at(-1)?.receipt),
|
|
951
|
-
}, { ...env,
|
|
1157
|
+
}, { ...env, unreadableReasksSpent: 0 }, io);
|
|
952
1158
|
}
|
|
953
1159
|
if (admitted.role === "secretariat") {
|
|
954
1160
|
const pass = chain.passes.at(-1);
|