@akagilnc/pi-workflow-roles 0.1.5382 → 0.1.5462
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 +4 -2
- package/README.zh-CN.md +4 -2
- package/dist/acp-host/production-host.js +8843 -9012
- package/dist/archivist-record-pointer.js +38 -0
- package/dist/compliance-transport.js +7 -5
- package/dist/doctor-auditor.js +1 -0
- package/dist/engine-detour-usage.js +9 -36
- package/dist/gatekeeper-role.js +4 -18
- package/dist/headless-host/description.js +0 -209
- package/dist/headless-host/production-host.js +7828 -8247
- package/dist/host-contracts.js +8 -0
- package/dist/judge-role.js +14 -1
- package/dist/migrate-book-topology.js +215 -96
- package/dist/navigator-attendance.js +10 -9
- package/dist/navigator-invocation-identity.js +18 -0
- package/dist/packaged-role-registry.js +1 -9
- package/dist/pi/role-turn-host.js +46 -19
- package/dist/public-cli/auto-resume.js +29 -66
- package/dist/public-cli/cli.js +1 -0
- package/dist/public-cli/countersign-run.js +4 -18
- package/dist/public-cli/instruction-seat-run.js +258 -69
- package/dist/public-cli/main.js +7488 -7758
- package/dist/public-cli/post-admission.js +451 -349
- package/dist/public-cli/process-cancel.js +8 -0
- package/dist/public-cli/run-lifecycle.js +24 -94
- package/dist/public-cli/settlement.js +624 -1471
- package/dist/public-cli/terminal.js +34 -53
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/public-role-summons.js +3 -8
- package/dist/receipt-delivery-policy.js +162 -28
- package/dist/run-terminal-artifacts.js +19 -14
- package/dist/serialize-thrown-value.js +7 -1
- package/dist/submission-errors.js +11 -1
- package/dist/submission-gate.js +27 -4
- package/dist/submission-ledger.js +21 -89
- package/package.json +1 -1
- package/scripts/build-package.mjs +0 -2
- package/src/acp-host/role-turn-host.ts +168 -75
- package/src/archivist-record-pointer.ts +48 -0
- package/src/compliance-transport.ts +13 -8
- package/src/doctor-auditor.ts +3 -0
- package/src/engine-detour-usage.ts +10 -39
- package/src/external-host-turn-loop.ts +100 -43
- package/src/gatekeeper-role.ts +7 -22
- package/src/headless-host/description.ts +4 -214
- package/src/headless-host/production-host.ts +2 -1
- package/src/headless-host/role-turn-host.ts +153 -186
- package/src/host-contracts.ts +20 -10
- package/src/judge-role.ts +24 -5
- package/src/navigator-attendance.ts +17 -12
- package/src/navigator-invocation-identity.ts +31 -0
- package/src/navigator-public-session.ts +1 -1
- package/src/navigator-session-contracts.ts +1 -1
- package/src/packaged-role-registry.ts +2 -10
- package/src/pi/adapter.ts +33 -11
- package/src/pi/role-turn-host.ts +49 -28
- package/src/prepared-role-turn.ts +2 -0
- package/src/public-cli/auto-resume.ts +27 -66
- package/src/public-cli/cli.ts +1 -0
- package/src/public-cli/countersign-run.ts +4 -22
- package/src/public-cli/instruction-seat-run.ts +307 -74
- package/src/public-cli/post-admission.ts +615 -459
- package/src/public-cli/process-cancel.ts +9 -0
- package/src/public-cli/run-lifecycle.ts +25 -126
- package/src/public-cli/settlement.ts +801 -1859
- package/src/public-cli/terminal.ts +42 -70
- package/src/public-cli/turn-request.ts +10 -1
- package/src/public-role-summons.ts +3 -8
- package/src/receipt-delivery-policy.ts +219 -28
- package/src/role-envelope.ts +21 -10
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +79 -102
- package/src/run-terminal-artifacts.ts +21 -17
- package/src/serialize-thrown-value.ts +7 -1
- package/src/submission-correctable-error.ts +5 -2
- package/src/submission-errors.ts +12 -1
- package/src/submission-gate.ts +34 -4
- package/src/submission-ledger.ts +30 -116
- package/src/worker-role.ts +19 -7
- package/src/worker-submission-gates.ts +29 -7
- package/dist/audit-escalation.js +0 -7
- package/dist/pi/known-failure.js +0 -51
- package/dist/typed-provider-http.js +0 -57
- package/dist/upstream-error-testimony.js +0 -19
- package/src/pi/known-failure.ts +0 -76
- package/src/typed-provider-http.ts +0 -89
- package/src/upstream-error-testimony.ts +0 -45
|
@@ -17,6 +17,7 @@ import { readDeclaredTicketNumber } from "../run-ticket-number.js";
|
|
|
17
17
|
import { pathContainedIn } from "../activation-ledger-topology.js";
|
|
18
18
|
import { pickEngineAxis } from "../package-resources/engine-material.js";
|
|
19
19
|
import { readStoredHostSessionId } from "../session-identity.js";
|
|
20
|
+
import { createReceiptDeliveryPolicy, deliveryLimitFromConfig, } from "../receipt-delivery-policy.js";
|
|
20
21
|
import { rewriteRunDirectoryPathValue } from "../role-run-relocation.js";
|
|
21
22
|
import { isOfficerReviewSeat } from "../packaged-role-registry.js";
|
|
22
23
|
/** Original error bytes, never relabeled — a secondary fact riding beside a classified cause. */
|
|
@@ -36,13 +37,13 @@ function isStationChildOfficerDialogue(role, env) {
|
|
|
36
37
|
return env.stationChild === true && isOfficerReviewSeat(role);
|
|
37
38
|
}
|
|
38
39
|
import { mintEngineDetourInvocationScope, withEngineDetourInvocationScope, } from "../engine-detour-usage.js";
|
|
39
|
-
import { clearCurrentCourt,
|
|
40
|
+
import { clearCurrentCourt, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, } from "./run-lifecycle.js";
|
|
40
41
|
import { processCancelDiagnostic, processCancelSignalName, } from "./process-cancel.js";
|
|
41
|
-
import {
|
|
42
|
+
import { DEFAULT_ROLE_TURN_HOST } from "../host-descriptions.js";
|
|
42
43
|
import { recordRunStart } from "../host-session-record.js";
|
|
43
|
-
import {
|
|
44
|
+
import { classifyPostAdmissionFailure, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatErrorCauseDetail, ledgerReadScope, isLawfulTypedTerminalOutcome, presentStructuralRejection, settleFailureTerminalResult, settleHostEndedNoReceipt, noteSettlementFault, attachRecordedSubmissions, retainPackageFault, } from "./settlement.js";
|
|
44
45
|
import {} from "./terminal.js";
|
|
45
|
-
import { ensureRealArtifactsDirectory, persistReturnedRunState, runWithAutoResumeLoop, TurnDispatchedFailure,
|
|
46
|
+
import { ensureRealArtifactsDirectory, persistReturnedRunState, presentTerminal, runWithAutoResumeLoop, TurnDispatchedFailure, } from "./auto-resume.js";
|
|
46
47
|
import { isRecord, errorText } from "../unknown-value.js";
|
|
47
48
|
function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
|
|
48
49
|
const rewrite = (value) => rewriteRunDirectoryPathValue(value, relocation.oldRunDirectory, relocation.newRunDirectory);
|
|
@@ -58,7 +59,6 @@ function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
if (result.terminal !== undefined) {
|
|
61
|
-
rewritePublishedFailureErrorPath(result.terminal, relocation.oldRunDirectory, relocation.newRunDirectory);
|
|
62
62
|
for (const artifact of result.terminal.artifacts) {
|
|
63
63
|
artifact.path = rewrite(artifact.path);
|
|
64
64
|
}
|
|
@@ -80,8 +80,14 @@ async function settleProcessCancelAfterTurn(input) {
|
|
|
80
80
|
timedOut: input.result.timedOut,
|
|
81
81
|
code: input.result.code,
|
|
82
82
|
stderr: input.result.stderr,
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
...(input.result.signal === undefined ? {} : { signal: input.result.signal }),
|
|
84
|
+
// A host report already on this result stays the report. The cancel
|
|
85
|
+
// name is wording only when the host gave none.
|
|
86
|
+
...(input.result.knownFailure === undefined
|
|
87
|
+
? { knownDiagnostic: processCancelDiagnostic(input.cancelName) }
|
|
88
|
+
: { knownFailure: input.result.knownFailure }),
|
|
89
|
+
cancelName: input.cancelName,
|
|
90
|
+
}, input.invocationScopeId), input.adapters, input.env.principalAuthority, input.io, input.persistRunState, input.notePackageFault);
|
|
85
91
|
return {
|
|
86
92
|
...failed,
|
|
87
93
|
turnDispatched: true,
|
|
@@ -125,33 +131,16 @@ export const POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE = "ak_post_admission_c
|
|
|
125
131
|
* redundant second copy (#840 bounce class 1: 同一业务规则只保留一个权威实现).
|
|
126
132
|
*/
|
|
127
133
|
async function recordBestEffortPostDispatchDiagnostic(admitted, env, diagnostic, io) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
...payload,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
catch (artifactError) {
|
|
142
|
-
retentionFailure =
|
|
143
|
-
`post-dispatch diagnostic durable retention failed on both channels (best-effort continue): dossier=${describeErrorIdentity(appendError)}; artifact=${describeErrorIdentity(artifactError)}`;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
try {
|
|
147
|
-
io.stderr(formatCliDiagnostic(diagnostic));
|
|
148
|
-
if (retentionFailure !== undefined) {
|
|
149
|
-
io.stderr(formatCliDiagnostic(retentionFailure));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
// Presentation is best-effort beside an already-formed host terminal.
|
|
154
|
-
}
|
|
134
|
+
await retainPackageFault({
|
|
135
|
+
runDirectory: admitted.runDirectory,
|
|
136
|
+
diagnostic,
|
|
137
|
+
appendSession: (payload) => env.sessionAppender(env.principalAuthority, admitted.principal, POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE, payload),
|
|
138
|
+
stderr: (text) => io.stderr(text),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/** The one package-fault receiver, bound to this admitted run's session and io. */
|
|
142
|
+
export function packageFaultNoteFor(admitted, env, io) {
|
|
143
|
+
return (diagnostic) => recordBestEffortPostDispatchDiagnostic(admitted, env, diagnostic, io);
|
|
155
144
|
}
|
|
156
145
|
/**
|
|
157
146
|
* Factory-seat resume method-material face (#833): one authority for
|
|
@@ -173,42 +162,42 @@ export async function resolveResumeMethodMaterialAdapters(input) {
|
|
|
173
162
|
stderr: "",
|
|
174
163
|
thrown: error,
|
|
175
164
|
...(input.knownCause === undefined ? {} : { knownCause: input.knownCause }),
|
|
176
|
-
}, input.emptyAdapters, input.authority, { ...input.io, omitFailureStderrDiagnostic: true })
|
|
165
|
+
}, input.emptyAdapters, input.authority, { ...input.io, omitFailureStderrDiagnostic: true }, true, (diagnostic) => retainPackageFault({
|
|
166
|
+
runDirectory: input.admitted.runDirectory,
|
|
167
|
+
diagnostic,
|
|
168
|
+
stderr: (text) => input.io.stderr(text),
|
|
169
|
+
}));
|
|
177
170
|
return { kind: "terminal", ...terminal };
|
|
178
171
|
}
|
|
179
172
|
}
|
|
180
|
-
export async function presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState = true) {
|
|
173
|
+
export async function presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState = true, notePackageFault) {
|
|
181
174
|
const hasThrown = Object.hasOwn(failureInput, "thrown");
|
|
182
|
-
const
|
|
183
|
-
runDirectory: admitted.runDirectory,
|
|
184
|
-
...(failureInput.typedHttpObservationSettled === true
|
|
185
|
-
? {
|
|
186
|
-
typedHttpObservationSettled: true,
|
|
187
|
-
...(failureInput.typedHttpObservation === undefined
|
|
188
|
-
? {}
|
|
189
|
-
: { typedHttpObservation: failureInput.typedHttpObservation }),
|
|
190
|
-
}
|
|
191
|
-
: {}),
|
|
192
|
-
});
|
|
193
|
-
const knownFailure = failureInput.knownFailure ?? resumeObservation.observationReadFailure;
|
|
194
|
-
const session = !hasThrown &&
|
|
195
|
-
!failureInput.timedOut &&
|
|
196
|
-
knownFailure === undefined &&
|
|
197
|
-
failureInput.knownCause === undefined &&
|
|
198
|
-
admitted.principal !== undefined
|
|
199
|
-
? await inspectJudgeSession(authority.decode(admitted.principal).sessionFile)
|
|
200
|
-
: undefined;
|
|
175
|
+
const knownFailure = failureInput.knownFailure;
|
|
201
176
|
// knownFailure channel owns details when present; otherwise caller knownDetails.
|
|
202
177
|
const fromKnownFailure = explicitInternalKnownFailureClassificationInput(knownFailure);
|
|
203
|
-
|
|
178
|
+
// Merge the two detail sources so a key carried by either survives; a later
|
|
179
|
+
// spread would otherwise drop the earlier one's keys entirely.
|
|
180
|
+
const callerDetails = failureInput.knownDetails === undefined && fromKnownFailure.knownDetails === undefined
|
|
181
|
+
? undefined
|
|
182
|
+
: { ...(failureInput.knownDetails ?? {}), ...(fromKnownFailure.knownDetails ?? {}) };
|
|
183
|
+
const classified = classifyPostAdmissionFailure({
|
|
204
184
|
timedOut: failureInput.timedOut,
|
|
205
185
|
code: failureInput.code,
|
|
206
186
|
stderr: failureInput.stderr,
|
|
207
187
|
...(hasThrown ? { thrown: failureInput.thrown } : {}),
|
|
208
|
-
|
|
188
|
+
// Both detail sources are real; merging keeps each one's keys instead of
|
|
189
|
+
// letting the later spread drop the earlier one.
|
|
190
|
+
...(callerDetails === undefined ? {} : { knownDetails: callerDetails }),
|
|
191
|
+
// The knownFailure channel's own cause/identity/diagnostic ride with it.
|
|
192
|
+
...(fromKnownFailure.knownCause === undefined
|
|
193
|
+
? {}
|
|
194
|
+
: { knownCause: fromKnownFailure.knownCause }),
|
|
195
|
+
...(fromKnownFailure.knownIdentity === undefined
|
|
209
196
|
? {}
|
|
210
|
-
: {
|
|
211
|
-
...fromKnownFailure
|
|
197
|
+
: { knownIdentity: fromKnownFailure.knownIdentity }),
|
|
198
|
+
...(fromKnownFailure.knownDiagnostic === undefined
|
|
199
|
+
? {}
|
|
200
|
+
: { knownDiagnostic: fromKnownFailure.knownDiagnostic }),
|
|
212
201
|
...(failureInput.knownCause === undefined
|
|
213
202
|
? {}
|
|
214
203
|
: { knownCause: failureInput.knownCause }),
|
|
@@ -218,20 +207,30 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
|
|
|
218
207
|
...(failureInput.knownDiagnostic === undefined
|
|
219
208
|
? {}
|
|
220
209
|
: { knownDiagnostic: failureInput.knownDiagnostic }),
|
|
221
|
-
...(
|
|
210
|
+
...(failureInput.signal === undefined ? {} : { signal: failureInput.signal }),
|
|
211
|
+
...(failureInput.cancelName === undefined ? {} : { cancelName: failureInput.cancelName }),
|
|
212
|
+
...(failureInput.packageFact === undefined
|
|
213
|
+
? {}
|
|
214
|
+
: { packageFact: failureInput.packageFact }),
|
|
222
215
|
});
|
|
223
|
-
|
|
224
|
-
|
|
216
|
+
const failure = failureInput.stderr.length === 0 || failureInput.stderr === classified.diagnostic
|
|
217
|
+
? classified
|
|
218
|
+
: { ...classified, stderr: failureInput.stderr };
|
|
225
219
|
if (persistRunState && !failureInput.skipRunStateWrite) {
|
|
226
|
-
|
|
220
|
+
try {
|
|
221
|
+
await persistReturnedRunState(admitted);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
// The host failure is already classified. A run-state write stays beside it.
|
|
225
|
+
await noteSettlementFault(admitted.runDirectory, notePackageFault === undefined ? undefined : { notePackageFault }, `run-state persistence failed beside host terminal: ${describeErrorIdentity(error)}`);
|
|
226
|
+
}
|
|
227
227
|
}
|
|
228
228
|
let publishedErrorPath;
|
|
229
229
|
let terminal;
|
|
230
230
|
try {
|
|
231
231
|
const settled = await settleFailureTerminalResult(admitted, failure, authority, {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
: {}),
|
|
232
|
+
recordAttemptHistory: true,
|
|
233
|
+
...(notePackageFault === undefined ? {} : { notePackageFault }),
|
|
235
234
|
...(failureInput.invocationScopeId === undefined ||
|
|
236
235
|
failureInput.invocationScopeId.length === 0
|
|
237
236
|
? {}
|
|
@@ -246,7 +245,7 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
|
|
|
246
245
|
}
|
|
247
246
|
throw error;
|
|
248
247
|
}
|
|
249
|
-
|
|
248
|
+
await presentTerminal(terminal, io, admitted.runDirectory);
|
|
250
249
|
return {
|
|
251
250
|
exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
|
|
252
251
|
admitted,
|
|
@@ -264,9 +263,9 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
|
|
|
264
263
|
* the true cause through its own existing dispatch-exception machinery once
|
|
265
264
|
* the retry budget is exhausted.
|
|
266
265
|
*/
|
|
267
|
-
async function settleAfterTurnStarted(admitted, failureInput, adapters, authority, io, persistRunState) {
|
|
266
|
+
async function settleAfterTurnStarted(admitted, failureInput, adapters, authority, io, persistRunState, notePackageFault) {
|
|
268
267
|
try {
|
|
269
|
-
return (await presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState));
|
|
268
|
+
return (await presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState, notePackageFault));
|
|
270
269
|
}
|
|
271
270
|
catch (error) {
|
|
272
271
|
throw new TurnDispatchedFailure(error);
|
|
@@ -276,30 +275,211 @@ async function settleAfterTurnStarted(admitted, failureInput, adapters, authorit
|
|
|
276
275
|
* Persist run-state for a result dispatchPostAdmissionTurn deferred
|
|
277
276
|
* (needsPersist — station-child / resumable auto-resume loop, #416/#840):
|
|
278
277
|
* that write must land outside the loop's own retried-dispatch try, but its
|
|
279
|
-
* failure
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* classify/artifact/Terminal (#836 r12 class 3), and never lawful/non-lawful
|
|
283
|
-
* settling differently (#836 r13 class 2: a caller's io here may be a no-op,
|
|
284
|
-
* so a stderr-only trace is never seen and the failure is otherwise
|
|
285
|
-
* swallowed — 失败诚实宪法 真因必须落痕). Both cases settle through
|
|
286
|
-
* settleAfterTurnStarted, which attaches the run's already-recorded ledger
|
|
287
|
-
* submissions to the new failure terminal and returns skipAutoResume so the
|
|
288
|
-
* caller presents it once and stops — never re-entering auto-resume.
|
|
278
|
+
* failure is a package note beside the terminal already settled. It does
|
|
279
|
+
* not replace that terminal or open another attempt. The note is durable
|
|
280
|
+
* because the caller's io may be a no-op.
|
|
289
281
|
*/
|
|
290
|
-
async function settleDeferredPersist(admitted,
|
|
282
|
+
async function settleDeferredPersist(admitted, env, io, result) {
|
|
291
283
|
if (result.needsPersist !== true || result.terminal === undefined)
|
|
292
284
|
return result;
|
|
293
285
|
const { needsPersist: _needsPersist, ...settledResult } = result;
|
|
294
|
-
const lawful = isLawfulTypedTerminalOutcome(result.terminal.roleOutcome);
|
|
295
286
|
try {
|
|
296
|
-
await persistReturnedRunState(admitted
|
|
287
|
+
await persistReturnedRunState(admitted);
|
|
297
288
|
return settledResult;
|
|
298
289
|
}
|
|
299
290
|
catch (error) {
|
|
300
|
-
|
|
301
|
-
return
|
|
291
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
292
|
+
return settledResult;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Court/invocation scope for settlement reads. The one construction of this
|
|
297
|
+
* shape for every turn (#1132 — the first turn and each 催交 turn share it).
|
|
298
|
+
*/
|
|
299
|
+
function settlementScopeForTurn(request) {
|
|
300
|
+
const hasCourt = request.courtAttemptId !== undefined && request.courtAttemptId.length > 0;
|
|
301
|
+
const hasScope = request.invocationScopeId !== undefined && request.invocationScopeId.length > 0;
|
|
302
|
+
if (!hasCourt && !hasScope)
|
|
303
|
+
return undefined;
|
|
304
|
+
return {
|
|
305
|
+
...(hasCourt ? { courtAttemptId: request.courtAttemptId } : {}),
|
|
306
|
+
...(hasScope ? { invocationScopeId: request.invocationScopeId } : {}),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* #1132: one 没交卷催交 turn, dispatched through the SAME settlement path as the
|
|
311
|
+
* first turn.
|
|
312
|
+
*
|
|
313
|
+
* There is deliberately no second settlement implementation here. A delivery
|
|
314
|
+
* turn is an ordinary host turn on this same run/session: this builds the
|
|
315
|
+
* host-neutral resume request (the adapter's stored native session id, ADR 0082;
|
|
316
|
+
* pi's existing delivery-state content reused verbatim — no new prompt wording,
|
|
317
|
+
* no new host capability). The caller settles it through the same
|
|
318
|
+
* `settleCompletedHostTurn` the first turn uses (ADR 0080): the runner/host
|
|
319
|
+
* failure re-read, the shouldPresent gate, the fresh-seal check, open-court
|
|
320
|
+
* cleanup, cancel re-reads, run-state persist, and the stderr mirror.
|
|
321
|
+
*
|
|
322
|
+
* The caller owns the live delivery policy; the request projects its next send.
|
|
323
|
+
*/
|
|
324
|
+
async function buildReceiptDeliveryRequest(input) {
|
|
325
|
+
const { admitted, env, request, receiptDelivery } = input;
|
|
326
|
+
const hostSessionId = await readStoredHostSessionId(env.host, env.principalAuthority, admitted.principal);
|
|
327
|
+
return {
|
|
328
|
+
...request,
|
|
329
|
+
continuation: {
|
|
330
|
+
kind: "resume",
|
|
331
|
+
prompt: JSON.stringify({
|
|
332
|
+
...receiptDelivery.deliveryState(),
|
|
333
|
+
deliveryTurns: receiptDelivery.issuedDeliveryRequests() + 1,
|
|
334
|
+
}),
|
|
335
|
+
...(hostSessionId === undefined ? {} : { hostSessionId }),
|
|
336
|
+
},
|
|
337
|
+
deliveryRequestLimit: receiptDelivery.limit,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* #1132: settle ONE completed host turn — the single post-turn settlement
|
|
342
|
+
* authority shared by the first turn and by every 没交卷催交 turn (ADR 0080).
|
|
343
|
+
*
|
|
344
|
+
* Extracted deliberately instead of re-entering `dispatchPostAdmissionTurn`.
|
|
345
|
+
* The turn-BEFORE concerns (beforeDispatch / markRunRunning / relocate /
|
|
346
|
+
* afterDispatch / writer-lease release) belong to the outermost dispatch and
|
|
347
|
+
* run exactly once per run; everything from the stderr mirror onward is
|
|
348
|
+
* per-turn. A 催交 turn is a real host turn on this same run/session, so it
|
|
349
|
+
* settles through THIS function — not a reduced copy — and it re-runs neither
|
|
350
|
+
* the admission hooks nor the run-state terminal write.
|
|
351
|
+
*
|
|
352
|
+
* Returns `still-missing` when the turn simply produced no receipt: the caller
|
|
353
|
+
* (first turn or the 催交 loop) then decides between issuing another delivery
|
|
354
|
+
* request and settling no_receipt.
|
|
355
|
+
*/
|
|
356
|
+
async function settleCompletedHostTurn(input) {
|
|
357
|
+
const { admitted, env, io, adapters, shouldPresent, request, result, persistRunState, deferredPersist } = input;
|
|
358
|
+
const finishAfterTurn = input.finishAfterTurn;
|
|
359
|
+
const courtScope = input.courtScope;
|
|
360
|
+
// `result.stderr` stays live in memory for host-failure settlement
|
|
361
|
+
// whether or not this mirror write succeeds. A write failure is noted.
|
|
362
|
+
// It does not replace the host terminal or an already-sealed acceptance.
|
|
363
|
+
//
|
|
364
|
+
// Everything below runs after the host turn genuinely started (#840 r9
|
|
365
|
+
// 判词 class 1 boundary — 覆盖 executeTurn 已启动后至返回带 turnDispatched
|
|
366
|
+
// 结果前的全部异常). Each fallible step routes any failure through the
|
|
367
|
+
// single existing settlement authority (presentControlledFailure /
|
|
368
|
+
// settleFailureTerminalResult) rather than a second one (ADR 0080:
|
|
369
|
+
// one settlement disposition owner) — never by fabricating a terminal here.
|
|
370
|
+
// A cleanup step that runs only after a real settlement (clearCurrentCourt)
|
|
371
|
+
// is protected by logging and keeping that already-obtained result, never
|
|
372
|
+
// by discarding it (#840 已交劳动只整理终局不重做). A failure inside the
|
|
373
|
+
// settlement authority itself (presentControlledFailure's own reads) goes
|
|
374
|
+
// through settleAfterTurnStarted: still no fabricated terminal, but the
|
|
375
|
+
// re-thrown TurnDispatchedFailure still tells runWithAutoResumeLoop the
|
|
376
|
+
// turn genuinely started, so the next retry sends a resume payload — the
|
|
377
|
+
// true cause settles through the loop's own existing dispatch-exception
|
|
378
|
+
// machinery once the retry budget is exhausted.
|
|
379
|
+
let stderrLogWriteFailure;
|
|
380
|
+
try {
|
|
381
|
+
await writeFile(join(admitted.runDirectory, "stderr.log"), result.stderr, "utf8");
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
stderrLogWriteFailure = error;
|
|
385
|
+
// Best-effort: stderr.log is secondary to the host terminal. A write
|
|
386
|
+
// failure leaves a durable note and, on a host failure, rides in
|
|
387
|
+
// packageFact. It does not become the cause.
|
|
388
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `stderr.log write failed (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
389
|
+
}
|
|
390
|
+
// Host facts are resolved before trySettle. A host failure skips trySettle
|
|
391
|
+
// so a later read cannot replace that report. A clean host whose settlement
|
|
392
|
+
// read throws is noted above and is not turned into a failure terminal.
|
|
393
|
+
// stderr.log and run-state writes are notes beside the host terminal.
|
|
394
|
+
let settled;
|
|
395
|
+
let settledOutcome;
|
|
396
|
+
// The host CLI's own report: timeout, declared failure, cancel, or exit.
|
|
397
|
+
// A null code is a signal death (Node close). No session or history read
|
|
398
|
+
// takes part (owner ea321c6d / 4743ade7).
|
|
399
|
+
const processCancelName = processCancelSignalName(env.signal);
|
|
400
|
+
const hostSignalFailed = result.timedOut
|
|
401
|
+
|| result.knownFailure !== undefined
|
|
402
|
+
|| processCancelName !== undefined
|
|
403
|
+
|| result.code === null
|
|
404
|
+
|| result.code !== 0;
|
|
405
|
+
const hostReportedFailure = result.knownFailure;
|
|
406
|
+
try {
|
|
407
|
+
if (!hostSignalFailed) {
|
|
408
|
+
settled = await adapters.trySettle(admitted, env.principalAuthority, { ...courtScope, previewOnly: true });
|
|
409
|
+
const cancelAfterSettle = processCancelSignalName(env.signal);
|
|
410
|
+
if (settled !== undefined
|
|
411
|
+
&& shouldPresent(settled)
|
|
412
|
+
&& cancelAfterSettle === undefined) {
|
|
413
|
+
settledOutcome = {
|
|
414
|
+
exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
|
|
415
|
+
admitted,
|
|
416
|
+
terminal: settled,
|
|
417
|
+
turnDispatched: true,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
// The host did not fail. A settlement read here is a package fault and
|
|
424
|
+
// must not become the terminal.
|
|
425
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `settlement read failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
426
|
+
}
|
|
427
|
+
if (settledOutcome !== undefined) {
|
|
428
|
+
// #855: final cancel re-read before lawful seal — settlement window may
|
|
429
|
+
// have received SIGTERM/SIGINT/SIGHUP after the earlier snapshot.
|
|
430
|
+
const lateCancel = processCancelSignalName(env.signal);
|
|
431
|
+
if (lateCancel !== undefined) {
|
|
432
|
+
return { kind: "settled", outcome: await finishAfterTurn(await settleProcessCancelAfterTurn({
|
|
433
|
+
admitted,
|
|
434
|
+
cancelName: lateCancel,
|
|
435
|
+
result,
|
|
436
|
+
adapters,
|
|
437
|
+
env,
|
|
438
|
+
io,
|
|
439
|
+
persistRunState,
|
|
440
|
+
deferredPersist,
|
|
441
|
+
invocationScopeId: request.invocationScopeId,
|
|
442
|
+
notePackageFault: courtScope.notePackageFault,
|
|
443
|
+
})) };
|
|
444
|
+
}
|
|
445
|
+
if (persistRunState) {
|
|
446
|
+
try {
|
|
447
|
+
await persistReturnedRunState(admitted);
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Persist + present is the caller's stop seam (auto-resume loop /
|
|
454
|
+
// manual resume), not this retried host-turn function.
|
|
455
|
+
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
456
|
+
input.markPending(settledOutcome.terminal);
|
|
457
|
+
return { kind: "settled", outcome: await finishAfterTurn({ ...settledOutcome, ...deferredPersist }) };
|
|
458
|
+
}
|
|
459
|
+
// Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
|
|
460
|
+
if (hostSignalFailed) {
|
|
461
|
+
const stderrLogWriteFact = stderrLogWriteFailure === undefined
|
|
462
|
+
? undefined
|
|
463
|
+
: { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
|
|
464
|
+
const failed = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
|
|
465
|
+
timedOut: result.timedOut,
|
|
466
|
+
code: result.code,
|
|
467
|
+
stderr: result.stderr,
|
|
468
|
+
// A signal-killed child is a fact of its own. The synthetic line is
|
|
469
|
+
// only a fallback for when the host declared no failure of its own —
|
|
470
|
+
// it must never displace words the host actually reported.
|
|
471
|
+
...(result.signal === undefined ? {} : { signal: result.signal }),
|
|
472
|
+
...(hostReportedFailure === undefined ? {} : { knownFailure: hostReportedFailure }),
|
|
473
|
+
// The cancellation is the host's own report of why this call ended;
|
|
474
|
+
// it supplies the wording only when the host declared nothing else.
|
|
475
|
+
...(processCancelName === undefined ? {} : { cancelName: processCancelName }),
|
|
476
|
+
// Secondary fact only — never the cause, and never merged into the
|
|
477
|
+
// host's own details.
|
|
478
|
+
...(stderrLogWriteFact === undefined ? {} : { packageFact: stderrLogWriteFact }),
|
|
479
|
+
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault);
|
|
480
|
+
return { kind: "settled", outcome: await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal)) };
|
|
302
481
|
}
|
|
482
|
+
return { kind: "still-missing" };
|
|
303
483
|
}
|
|
304
484
|
export async function dispatchPostAdmissionTurn(input) {
|
|
305
485
|
const { admitted, env, io, request, lease, adapters, effectiveEngine } = input;
|
|
@@ -312,13 +492,34 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
312
492
|
* lease release — success and failure alike. When a primary failure terminal already
|
|
313
493
|
* exists, keep that cause and leave relocate failure on the shared diagnostic channel.
|
|
314
494
|
*/
|
|
495
|
+
const courtScope = {
|
|
496
|
+
...settlementScopeForTurn(request),
|
|
497
|
+
notePackageFault: (diagnostic) => recordBestEffortPostDispatchDiagnostic(admitted, env, diagnostic, io),
|
|
498
|
+
};
|
|
499
|
+
let pendingSettlement;
|
|
500
|
+
/**
|
|
501
|
+
* The terminal this pending publication belongs to. A later real failure
|
|
502
|
+
* (e.g. a cancel re-read) replaces `result.terminal` with its own settled
|
|
503
|
+
* failure; that failure is already durable, so the superseded preview must
|
|
504
|
+
* not publish over it (失败诚实宪法:接住可以,洗白不行).
|
|
505
|
+
*/
|
|
506
|
+
let pendingTerminal;
|
|
315
507
|
let afterDispatchApplied = false;
|
|
508
|
+
const receiptDelivery = input.receiptDelivery ?? createReceiptDeliveryPolicy(env.autoResumeLimit);
|
|
509
|
+
let turnDispatched = false;
|
|
510
|
+
// #1132: the AK-side 催交 covers hosts that do not deliver to themselves.
|
|
511
|
+
// Pi runs the package role runtime in-process, and that runtime already issues
|
|
512
|
+
// its own delivery requests at `agent_end` and records the real count — asking
|
|
513
|
+
// again here would double-deliver on one run. External hosts (ACP / headless
|
|
514
|
+
// CLI) have no such in-child loop, so the AK execution seam owns it for them.
|
|
515
|
+
const akSeamDeliversReceipt = env.host !== undefined && env.host !== "" && env.host !== DEFAULT_ROLE_TURN_HOST;
|
|
316
516
|
const finishAfterTurn = async (result) => {
|
|
317
517
|
if (afterDispatchApplied)
|
|
318
518
|
return result;
|
|
319
519
|
afterDispatchApplied = true;
|
|
320
|
-
// #855: re-read cancel before
|
|
321
|
-
//
|
|
520
|
+
// #855: re-read cancel before committing the terminal and afterDispatch.
|
|
521
|
+
// A signal after the history-first commit cannot retroactively erase it.
|
|
522
|
+
// skipRunStateWrite: run-state
|
|
322
523
|
// may already be terminal from a prior lawful persist; do not reopen as resumable.
|
|
323
524
|
const cancelBeforeFinish = processCancelSignalName(env.signal);
|
|
324
525
|
if (cancelBeforeFinish !== undefined &&
|
|
@@ -330,13 +531,43 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
330
531
|
code: null,
|
|
331
532
|
stderr: "",
|
|
332
533
|
knownDiagnostic: processCancelDiagnostic(cancelBeforeFinish),
|
|
534
|
+
cancelName: cancelBeforeFinish,
|
|
333
535
|
skipRunStateWrite: true,
|
|
334
|
-
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState)),
|
|
536
|
+
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault)),
|
|
335
537
|
turnDispatched: true,
|
|
336
538
|
skipAutoResume: true,
|
|
337
539
|
...deferredPersist,
|
|
338
540
|
};
|
|
339
541
|
}
|
|
542
|
+
// The last cancellation check precedes the one durable settlement. A preview
|
|
543
|
+
// never publishes pointers or history; a superseding failure owns this turn —
|
|
544
|
+
// `result.terminal === pendingTerminal` is what makes the publication this
|
|
545
|
+
// turn's own rather than a superseded one.
|
|
546
|
+
if (pendingSettlement !== undefined &&
|
|
547
|
+
pendingTerminal !== undefined &&
|
|
548
|
+
result.terminal === pendingTerminal) {
|
|
549
|
+
try {
|
|
550
|
+
const terminal = pendingSettlement === "sealed"
|
|
551
|
+
? await adapters.trySettle(admitted, env.principalAuthority, { ...courtScope, recordAttemptHistory: true })
|
|
552
|
+
: await attachRecordedSubmissions(admitted, await settleHostEndedNoReceipt(admitted, env.principalAuthority, { ...courtScope, recordAttemptHistory: true }, receiptDelivery.issuedDeliveryRequests()), courtScope);
|
|
553
|
+
if (terminal === undefined)
|
|
554
|
+
throw new Error("settled host attempt vanished before publication");
|
|
555
|
+
result = { ...result, terminal, exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome) };
|
|
556
|
+
if (pendingSettlement === "sealed" && terminal.roleOutcome.kind === "accepted"
|
|
557
|
+
&& request.courtAttemptId !== undefined && request.courtAttemptId.length > 0) {
|
|
558
|
+
try {
|
|
559
|
+
await clearCurrentCourt(admitted.runDirectory, request.courtAttemptId);
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
// The accepted attempt is already durable; cleanup is diagnostic only.
|
|
563
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `terminal publication failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
340
571
|
try {
|
|
341
572
|
// #858 / #1071: the first identifiable ticketNumber field declaration
|
|
342
573
|
// files an unbound run (integer, digit string, or leading #N token).
|
|
@@ -345,7 +576,7 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
345
576
|
// Later receipts remain untouched; prose note/report is never consulted;
|
|
346
577
|
// this seam does not adjudicate a ticket change.
|
|
347
578
|
if (admitted.ticketNumber === undefined) {
|
|
348
|
-
const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId,
|
|
579
|
+
const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, ledgerReadScope(admitted));
|
|
349
580
|
let ticketNumber;
|
|
350
581
|
for (const row of rows) {
|
|
351
582
|
if (row.role !== admitted.role
|
|
@@ -387,7 +618,7 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
387
618
|
code: null,
|
|
388
619
|
stderr: "",
|
|
389
620
|
thrown: error,
|
|
390
|
-
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState)),
|
|
621
|
+
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault)),
|
|
391
622
|
...(result.turnDispatched === true ? { turnDispatched: true } : {}),
|
|
392
623
|
...(result.skipAutoResume === true ? { skipAutoResume: true } : {}),
|
|
393
624
|
...deferredPersist,
|
|
@@ -397,7 +628,6 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
397
628
|
try {
|
|
398
629
|
// #987: do not pre-block or classify host dispatch from AK credential
|
|
399
630
|
// catalog facts. Per-invocation runner/host evidence owns provider identity.
|
|
400
|
-
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
401
631
|
// The authoritative host write (markRunRunning) is delayed to just before
|
|
402
632
|
// executeTurn — not merely past beforeDispatch (#840 r9 判词 class 2). Any
|
|
403
633
|
// pre-turn retry (beforeDispatch, continuation assembly) must not commit a host
|
|
@@ -419,7 +649,7 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
419
649
|
code: null,
|
|
420
650
|
stderr: "",
|
|
421
651
|
thrown: error,
|
|
422
|
-
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState));
|
|
652
|
+
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault));
|
|
423
653
|
return { ...settled, ...deferredPersist };
|
|
424
654
|
}
|
|
425
655
|
}
|
|
@@ -451,7 +681,12 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
451
681
|
// auto-resume re-enters this dispatch and must reuse the same scope.
|
|
452
682
|
let result;
|
|
453
683
|
try {
|
|
684
|
+
// A bare `ak-role resume` only forwards the caller's instruction to the
|
|
685
|
+
// host CLI's native resume. Nothing here may gate, redirect or reshape
|
|
686
|
+
// that dispatch on prior conclusions, row counts or report presence;
|
|
687
|
+
// the authoritative post-turn settlement reads whatever really happened.
|
|
454
688
|
recordRunStart(admitted.runDirectory);
|
|
689
|
+
turnDispatched = true;
|
|
455
690
|
result = await env.roleTurnHost.executeTurn(turnRequest);
|
|
456
691
|
}
|
|
457
692
|
catch (error) {
|
|
@@ -460,263 +695,115 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
460
695
|
timedOut: false,
|
|
461
696
|
code: null,
|
|
462
697
|
stderr: "",
|
|
463
|
-
thrown
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
698
|
+
// The thrown value is the host's report. A cancel received on this
|
|
699
|
+
// call is a second fact; it must not become a new Error in its place.
|
|
700
|
+
...(processCancelName === undefined ? {} : { cancelName: processCancelName }),
|
|
701
|
+
thrown: error,
|
|
702
|
+
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault);
|
|
467
703
|
return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...settled, turnDispatched: true, ...deferredPersist }, env.signal));
|
|
468
704
|
}
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
? {}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
? {}
|
|
520
|
-
: { invocationScopeId: request.invocationScopeId }),
|
|
521
|
-
};
|
|
522
|
-
// Single complete boundary (#840 r9 判词 class 1): resolving the
|
|
523
|
-
// host/runner failure facts, trySettle, its shouldPresent gate, and the
|
|
524
|
-
// accepted-settlement cleanup all settle through this one catch — a
|
|
525
|
-
// throw from any of them (including the session-file decode itself)
|
|
526
|
-
// still routes through settleAfterTurnStarted's TurnDispatchedFailure
|
|
527
|
-
// instead of losing turnDispatched to an uncaught throw. The facts are
|
|
528
|
-
// resolved before trySettle (#836) so an already-accepted settlement is
|
|
529
|
-
// never presented over a real current host/runner failure signal or a
|
|
530
|
-
// real stderr.log durable-write failure — both stay a real failure with
|
|
531
|
-
// the recorded payload riding beside it, not replacing it.
|
|
532
|
-
let settled;
|
|
533
|
-
let settledOutcome;
|
|
534
|
-
let hostSignalFailed = false;
|
|
535
|
-
let resolution;
|
|
536
|
-
try {
|
|
537
|
-
const sessionFile = admitted.principal !== undefined
|
|
538
|
-
? env.principalAuthority.decode(admitted.principal).sessionFile
|
|
539
|
-
: "";
|
|
540
|
-
const runnerKnownFailure = adapters.resolveRunnerKnownFailure !== undefined && sessionFile !== ""
|
|
541
|
-
? await adapters.resolveRunnerKnownFailure({ result, sessionFile })
|
|
542
|
-
: result.knownFailure;
|
|
543
|
-
resolution = await resolveAuditedRunnerFailureResolution({
|
|
544
|
-
runner: runnerKnownFailure,
|
|
545
|
-
sessionFile,
|
|
546
|
-
credential: undefined,
|
|
547
|
-
runDirectory: admitted.runDirectory,
|
|
548
|
-
});
|
|
549
|
-
// A direct, current signal from the host/runner itself (timeout / host
|
|
550
|
-
// knownFailure / runner knownFailure / missing credential) is a real
|
|
551
|
-
// problem regardless of what else already settled — a settled accepted
|
|
552
|
-
// outcome must not paper over it (it rides beside the recorded payload
|
|
553
|
-
// via `submissions`, never replacing the payload). The audited
|
|
554
|
-
// resolution's own typed session read (malformed JSONL, provider-stop,
|
|
555
|
-
// an illegal/unsealed accepted status, a stale/superseded typed-HTTP
|
|
556
|
-
// observation, ...) makes a run with no sealed acceptance a true
|
|
557
|
-
// failure too — it must not be discarded into a lawful no_receipt just
|
|
558
|
-
// because the raw runner signal alone looked clean. But it must not
|
|
559
|
-
// retroactively invalidate an acceptance that already sealed this turn
|
|
560
|
-
// — a resolved 429 observed earlier in the same session is exactly
|
|
561
|
-
// that.
|
|
562
|
-
const processCancelName = processCancelSignalName(env.signal);
|
|
563
|
-
const directHostFailureSignal = result.timedOut
|
|
564
|
-
|| result.knownFailure !== undefined
|
|
565
|
-
|| runnerKnownFailure !== undefined
|
|
566
|
-
|| processCancelName !== undefined;
|
|
567
|
-
hostSignalFailed =
|
|
568
|
-
directHostFailureSignal
|
|
569
|
-
|| (result.code !== null && result.code !== 0)
|
|
570
|
-
|| resolution.knownFailure !== undefined;
|
|
571
|
-
settled = await adapters.trySettle(admitted, env.principalAuthority, courtScope);
|
|
572
|
-
if (settled !== undefined) {
|
|
573
|
-
settled = await attachRecordedSubmissions(admitted, settled, courtScope);
|
|
705
|
+
// #1132: settle this turn through the ONE post-turn settlement authority,
|
|
706
|
+
// then — if it simply produced no receipt — 催交 before settling no_receipt.
|
|
707
|
+
const settledTurn = await settleCompletedHostTurn({
|
|
708
|
+
admitted,
|
|
709
|
+
env,
|
|
710
|
+
io,
|
|
711
|
+
adapters,
|
|
712
|
+
shouldPresent,
|
|
713
|
+
request,
|
|
714
|
+
result,
|
|
715
|
+
persistRunState,
|
|
716
|
+
deferredPersist,
|
|
717
|
+
finishAfterTurn,
|
|
718
|
+
courtScope,
|
|
719
|
+
markPending: (terminal) => { pendingSettlement = "sealed"; pendingTerminal = terminal; },
|
|
720
|
+
});
|
|
721
|
+
if (settledTurn.kind === "settled")
|
|
722
|
+
return settledTurn.outcome;
|
|
723
|
+
// #1132: 没交卷先回本人催交,不直接结为 no_receipt. The AK execution seam
|
|
724
|
+
// owns the 催交 and its real count. Each request is a real host turn on this
|
|
725
|
+
// same run/session, dispatched and settled through the same two seams as the
|
|
726
|
+
// first turn — never a reduced copy. 催交 never substitutes for host failure
|
|
727
|
+
// recovery: a delivery turn that fails keeps its true cause below.
|
|
728
|
+
// The delivery budget is spent HERE, at the outermost level; only when it is
|
|
729
|
+
// exhausted with still no receipt does the settlement seam record no_receipt.
|
|
730
|
+
while (akSeamDeliversReceipt && receiptDelivery.nextAction() === "request-delivery") {
|
|
731
|
+
// Assembly sits in the same post-start catch as the host call. A throw
|
|
732
|
+
// here is a started turn: resume the session, and do not count a request
|
|
733
|
+
// that never reached the host.
|
|
734
|
+
let deliveryResult;
|
|
735
|
+
let deliveryTurnRequest;
|
|
736
|
+
try {
|
|
737
|
+
const deliveryRequest = await buildReceiptDeliveryRequest({
|
|
738
|
+
admitted,
|
|
739
|
+
env,
|
|
740
|
+
// Host and the other axes already selected on this turn ride the
|
|
741
|
+
// shared projection. The pre-projection request does not have them.
|
|
742
|
+
request: turnRequest,
|
|
743
|
+
receiptDelivery,
|
|
744
|
+
});
|
|
745
|
+
// The same native-resume projection the first turn uses (host adapter owns
|
|
746
|
+
// the resume; AK only hands it the request — ADR 0082).
|
|
747
|
+
deliveryTurnRequest = {
|
|
748
|
+
...deliveryRequest,
|
|
749
|
+
principal: admitted.principal,
|
|
750
|
+
runDirectory: admitted.runDirectory,
|
|
751
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
752
|
+
};
|
|
753
|
+
receiptDelivery.recordDeliveryRequest();
|
|
754
|
+
deliveryResult = await env.roleTurnHost.executeTurn(deliveryTurnRequest);
|
|
574
755
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
//
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
request.courtAttemptId.length > 0) {
|
|
756
|
+
catch (error) {
|
|
757
|
+
// 催交 cannot substitute for host failure recovery: a real failure with
|
|
758
|
+
// its true cause, through the one settlement authority (ADR 0080).
|
|
759
|
+
// The turn started, so the caller's loop resumes instead of replaying
|
|
760
|
+
// the initial payload. The issued count survives that re-entry.
|
|
761
|
+
const settled = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({ timedOut: false, code: null, stderr: "", thrown: error }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
|
|
762
|
+
return await finishAfterTurn(withProcessCancelSkipAutoResume({
|
|
763
|
+
...settled,
|
|
764
|
+
turnDispatched: true,
|
|
765
|
+
...deferredPersist,
|
|
766
|
+
}, env.signal));
|
|
767
|
+
}
|
|
768
|
+
const deliveryTurn = await settleCompletedHostTurn({
|
|
769
|
+
admitted,
|
|
770
|
+
env,
|
|
771
|
+
// Only the final Terminal presents; intermediate delivery attempts stay quiet.
|
|
772
|
+
io: { ...io, stdout: () => { } },
|
|
773
|
+
adapters,
|
|
774
|
+
shouldPresent,
|
|
775
|
+
request: deliveryTurnRequest,
|
|
776
|
+
result: deliveryResult,
|
|
777
|
+
// Run-state terminal write belongs to the OUTERMOST turn only, so a
|
|
778
|
+
// still-silent delivery attempt never makes the run look lawfully terminal.
|
|
779
|
+
persistRunState: false,
|
|
780
|
+
deferredPersist,
|
|
781
|
+
finishAfterTurn,
|
|
782
|
+
courtScope,
|
|
783
|
+
markPending: (terminal) => { pendingSettlement = "sealed"; pendingTerminal = terminal; },
|
|
784
|
+
});
|
|
785
|
+
// The delivery turn's own outcome IS the run's outcome, except when it
|
|
786
|
+
// simply still has no receipt — that is the loop's own case to keep going.
|
|
787
|
+
if (deliveryTurn.kind === "settled") {
|
|
788
|
+
// #1132: the delivery turn settles with persistRunState:false, so a run
|
|
789
|
+
// that ends HERE (得卷 or a real failure) still needs the outermost
|
|
790
|
+
// single run-state write. Without it the ledger says accepted while
|
|
791
|
+
// run-state stays `running`, and every later resume entry (ADR 0080)
|
|
792
|
+
// reads a finished run as live. The still-silent attempts above must
|
|
793
|
+
// NOT write; this one, which ends the run, must.
|
|
794
|
+
if (persistRunState && deliveryTurn.outcome.terminal !== undefined) {
|
|
615
795
|
try {
|
|
616
|
-
await
|
|
796
|
+
await persistReturnedRunState(admitted);
|
|
617
797
|
}
|
|
618
798
|
catch (error) {
|
|
619
|
-
|
|
620
|
-
// not erase that fact or make the caller replay this court's
|
|
621
|
-
// summons over already-delivered work (#840 已交劳动只整理终局不重做).
|
|
622
|
-
// Preserve the cleanup failure as a post-dispatch diagnostic; the
|
|
623
|
-
// accepted settlement remains authoritative (#840 r9 判词 class 1).
|
|
624
|
-
await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
799
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside催交 terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
625
800
|
}
|
|
626
801
|
}
|
|
627
|
-
|
|
628
|
-
exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
|
|
629
|
-
admitted,
|
|
630
|
-
terminal: settled,
|
|
631
|
-
turnDispatched: true,
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
catch (error) {
|
|
636
|
-
// Settle (or its shouldPresent gate, or the failure-fact resolution
|
|
637
|
-
// above) throw is a real failure fact — never swallow into undefined.
|
|
638
|
-
const settledFailure = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
|
|
639
|
-
timedOut: false,
|
|
640
|
-
code: result.code,
|
|
641
|
-
stderr: result.stderr,
|
|
642
|
-
thrown: error,
|
|
643
|
-
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
|
|
644
|
-
return await finishAfterTurn({ ...settledFailure, turnDispatched: true, ...deferredPersist });
|
|
645
|
-
}
|
|
646
|
-
if (settledOutcome !== undefined) {
|
|
647
|
-
// #855: final cancel re-read before lawful seal — settlement window may
|
|
648
|
-
// have received SIGTERM/SIGINT/SIGHUP after the earlier snapshot.
|
|
649
|
-
const lateCancel = processCancelSignalName(env.signal);
|
|
650
|
-
if (lateCancel !== undefined) {
|
|
651
|
-
return await finishAfterTurn(await settleProcessCancelAfterTurn({
|
|
652
|
-
admitted,
|
|
653
|
-
cancelName: lateCancel,
|
|
654
|
-
result,
|
|
655
|
-
adapters,
|
|
656
|
-
env,
|
|
657
|
-
io,
|
|
658
|
-
persistRunState,
|
|
659
|
-
deferredPersist,
|
|
660
|
-
invocationScopeId: request.invocationScopeId,
|
|
661
|
-
}));
|
|
662
|
-
}
|
|
663
|
-
if (persistRunState) {
|
|
664
|
-
try {
|
|
665
|
-
await persistReturnedRunState(admitted, { lawful: true });
|
|
666
|
-
}
|
|
667
|
-
catch (error) {
|
|
668
|
-
await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
669
|
-
}
|
|
802
|
+
return await finishAfterTurn(deliveryTurn.outcome);
|
|
670
803
|
}
|
|
671
|
-
// Lawful persist + present is the caller's stop seam (auto-resume loop /
|
|
672
|
-
// manual resume), not this retried host-turn function.
|
|
673
|
-
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
674
|
-
return await finishAfterTurn({ ...settledOutcome, ...deferredPersist });
|
|
675
|
-
}
|
|
676
|
-
// Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
|
|
677
|
-
if (hostSignalFailed) {
|
|
678
|
-
const resolutionInput = controlledFailureInputFromResolution(resolution);
|
|
679
|
-
const processCancelName = processCancelSignalName(env.signal);
|
|
680
|
-
const stderrLogWriteDetails = stderrLogWriteFailure === undefined
|
|
681
|
-
? undefined
|
|
682
|
-
: { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
|
|
683
|
-
const failed = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
|
|
684
|
-
timedOut: result.timedOut,
|
|
685
|
-
code: result.code,
|
|
686
|
-
stderr: result.stderr,
|
|
687
|
-
...resolutionInput,
|
|
688
|
-
...(processCancelName === undefined
|
|
689
|
-
? {}
|
|
690
|
-
: { knownDiagnostic: processCancelDiagnostic(processCancelName) }),
|
|
691
|
-
// Secondary fact only — never the cause. Rides on whichever channel
|
|
692
|
-
// classification actually reads (knownFailure.details owns it when
|
|
693
|
-
// a knownFailure exists; the top-level knownDetails otherwise).
|
|
694
|
-
...(stderrLogWriteDetails === undefined
|
|
695
|
-
? {}
|
|
696
|
-
: resolutionInput.knownFailure !== undefined
|
|
697
|
-
? {
|
|
698
|
-
knownFailure: {
|
|
699
|
-
...resolutionInput.knownFailure,
|
|
700
|
-
details: { ...(resolutionInput.knownFailure.details ?? {}), ...stderrLogWriteDetails },
|
|
701
|
-
},
|
|
702
|
-
}
|
|
703
|
-
: { knownDetails: stderrLogWriteDetails }),
|
|
704
|
-
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
|
|
705
|
-
return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal));
|
|
706
804
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
// lawful absence of a receipt. Honest and loud, not silently no_receipt.
|
|
710
|
-
if (stderrLogWriteFailure !== undefined) {
|
|
711
|
-
const failed = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
|
|
712
|
-
timedOut: false,
|
|
713
|
-
code: result.code,
|
|
714
|
-
stderr: result.stderr,
|
|
715
|
-
thrown: stderrLogWriteFailure,
|
|
716
|
-
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
|
|
717
|
-
return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
|
|
718
|
-
}
|
|
719
|
-
const noReceipt = await attachRecordedSubmissions(admitted, await settleHostEndedNoReceipt(admitted, env.principalAuthority, courtScope), courtScope);
|
|
805
|
+
const noReceiptScope = { ...courtScope, previewOnly: true };
|
|
806
|
+
const noReceipt = await attachRecordedSubmissions(admitted, await settleHostEndedNoReceipt(admitted, env.principalAuthority, noReceiptScope, receiptDelivery.issuedDeliveryRequests()), noReceiptScope);
|
|
720
807
|
// #855: cancel during no_receipt settlement window is not lawful success.
|
|
721
808
|
const noReceiptCancel = processCancelSignalName(env.signal);
|
|
722
809
|
if (noReceiptCancel !== undefined) {
|
|
@@ -730,17 +817,20 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
730
817
|
persistRunState,
|
|
731
818
|
deferredPersist,
|
|
732
819
|
invocationScopeId: request.invocationScopeId,
|
|
820
|
+
notePackageFault: courtScope.notePackageFault,
|
|
733
821
|
}));
|
|
734
822
|
}
|
|
735
823
|
if (persistRunState) {
|
|
736
824
|
try {
|
|
737
|
-
await persistReturnedRunState(admitted
|
|
825
|
+
await persistReturnedRunState(admitted);
|
|
738
826
|
}
|
|
739
827
|
catch (error) {
|
|
740
828
|
await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
741
829
|
}
|
|
742
830
|
}
|
|
743
831
|
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
832
|
+
pendingSettlement = "no_receipt";
|
|
833
|
+
pendingTerminal = noReceipt;
|
|
744
834
|
return await finishAfterTurn({
|
|
745
835
|
exitCode: exitCodeForTerminalOutcome(noReceipt.roleOutcome),
|
|
746
836
|
admitted,
|
|
@@ -749,6 +839,12 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
749
839
|
...deferredPersist,
|
|
750
840
|
});
|
|
751
841
|
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
if (turnDispatched && !(error instanceof TurnDispatchedFailure)) {
|
|
844
|
+
throw new TurnDispatchedFailure(error);
|
|
845
|
+
}
|
|
846
|
+
throw error;
|
|
847
|
+
}
|
|
752
848
|
finally {
|
|
753
849
|
// Public manual resume (#987) holds no package writer lease.
|
|
754
850
|
if (lease !== undefined) {
|
|
@@ -831,8 +927,9 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
|
|
|
831
927
|
kind: "resume",
|
|
832
928
|
prompt,
|
|
833
929
|
},
|
|
834
|
-
...(request.message === undefined ? {} : { courtAttemptId: randomUUID() }),
|
|
835
930
|
...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
|
|
931
|
+
// #1132: one configured ceiling, already resolved by the caller (#422).
|
|
932
|
+
deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
|
|
836
933
|
};
|
|
837
934
|
}
|
|
838
935
|
/** Shared new-turn and in-call auto-resume projection. Seat code supplies prompt and activation. */
|
|
@@ -846,6 +943,8 @@ export function roleTurnOptions(env, admitted, continuation) {
|
|
|
846
943
|
: { correlationId }),
|
|
847
944
|
continuation,
|
|
848
945
|
...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
|
|
946
|
+
// #1132: one configured ceiling, already resolved by the caller (#422).
|
|
947
|
+
deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
|
|
849
948
|
};
|
|
850
949
|
}
|
|
851
950
|
/**
|
|
@@ -875,6 +974,7 @@ async function dispatchAfterWriterLease(input) {
|
|
|
875
974
|
async function runSettledAutoResumeLoop(input) {
|
|
876
975
|
const adapters = withOnceSuccessfulBeforeDispatch(input.adapters);
|
|
877
976
|
const dispatchEnv = input.dispatchEnv ?? input.env;
|
|
977
|
+
const receiptDelivery = createReceiptDeliveryPolicy(input.env.autoResumeLimit);
|
|
878
978
|
return runWithAutoResumeLoop({
|
|
879
979
|
admitted: input.admitted,
|
|
880
980
|
principalAuthority: input.env.principalAuthority,
|
|
@@ -896,11 +996,12 @@ async function runSettledAutoResumeLoop(input) {
|
|
|
896
996
|
...(lease === undefined ? {} : { lease }),
|
|
897
997
|
adapters,
|
|
898
998
|
persistRunState: false,
|
|
999
|
+
receiptDelivery,
|
|
899
1000
|
...(input.effectiveEngine === undefined
|
|
900
1001
|
? {}
|
|
901
1002
|
: { effectiveEngine: input.effectiveEngine }),
|
|
902
1003
|
});
|
|
903
|
-
return settleDeferredPersist(input.admitted, input.env
|
|
1004
|
+
return settleDeferredPersist(input.admitted, input.env, attemptIo, result);
|
|
904
1005
|
},
|
|
905
1006
|
}),
|
|
906
1007
|
});
|
|
@@ -1021,11 +1122,13 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
1021
1122
|
};
|
|
1022
1123
|
}
|
|
1023
1124
|
}
|
|
1024
|
-
// Open court continue, or new court for
|
|
1025
|
-
// (clause 0 新庭可再交卷; #833). Bare resume
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1125
|
+
// Open court continue, or a new court for a real re-summons
|
|
1126
|
+
// (clause 0 新庭可再交卷; #833). Bare resume — with or without caller
|
|
1127
|
+
// words — carries no summons and no open court, so it mints none:
|
|
1128
|
+
// the package must not turn ordinary continuation bytes into an audit
|
|
1129
|
+
// court whose attempt scope would reshape settlement (#1032 修订票:
|
|
1130
|
+
// 此要求不延伸到没有该传召庭次的裸 resume).
|
|
1131
|
+
if (openCourtAttemptId !== undefined || request.summons !== undefined) {
|
|
1029
1132
|
const courtAttemptId = openCourtAttemptId ??
|
|
1030
1133
|
(turnRequest.courtAttemptId !== undefined &&
|
|
1031
1134
|
turnRequest.courtAttemptId.length > 0
|
|
@@ -1145,8 +1248,7 @@ function writeResumeFailurePointer(io, errorPath) {
|
|
|
1145
1248
|
export function showResumeErrorPointer(io, exitCode, terminal) {
|
|
1146
1249
|
if (exitCode === 0 || terminal === undefined)
|
|
1147
1250
|
return;
|
|
1148
|
-
const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path
|
|
1149
|
-
?? publishedFailureErrorPath(terminal);
|
|
1251
|
+
const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path;
|
|
1150
1252
|
if (publishedError !== undefined)
|
|
1151
1253
|
writeResumeFailurePointer(io, publishedError);
|
|
1152
1254
|
}
|
|
@@ -1287,7 +1389,7 @@ export async function runPostAdmissionManualResume(input) {
|
|
|
1287
1389
|
// through its own settleAfterTurnStarted-backed branches; a lawful result
|
|
1288
1390
|
// only ever reaches this point once that persist has already succeeded
|
|
1289
1391
|
// (#836 r12 class 3 dedup — one persist owner, not a second here).
|
|
1290
|
-
|
|
1392
|
+
await presentTerminal(result.terminal, io, admitted.runDirectory);
|
|
1291
1393
|
}
|
|
1292
1394
|
if (result.terminal !== undefined) {
|
|
1293
1395
|
result.terminal.autoResumeCount = 0;
|