@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
|
@@ -30,6 +30,10 @@ import { readDeclaredTicketNumber } from "../run-ticket-number.ts";
|
|
|
30
30
|
import { pathContainedIn } from "../activation-ledger-topology.ts";
|
|
31
31
|
import { pickEngineAxis } from "../package-resources/engine-material.ts";
|
|
32
32
|
import { readStoredHostSessionId } from "../session-identity.ts";
|
|
33
|
+
import {
|
|
34
|
+
createReceiptDeliveryPolicy,
|
|
35
|
+
deliveryLimitFromConfig,
|
|
36
|
+
} from "../receipt-delivery-policy.ts";
|
|
33
37
|
import { rewriteRunDirectoryPathValue } from "../role-run-relocation.ts";
|
|
34
38
|
|
|
35
39
|
import type {
|
|
@@ -69,44 +73,37 @@ import {
|
|
|
69
73
|
import type { CredentialProviders, SeatModelConfig } from "./config.ts";
|
|
70
74
|
import {
|
|
71
75
|
clearCurrentCourt,
|
|
72
|
-
clearTypedProviderHttpObservation,
|
|
73
76
|
describeErrorIdentity,
|
|
74
77
|
markRunRunning,
|
|
75
78
|
readCurrentCourt,
|
|
76
79
|
recordCurrentCourt,
|
|
77
|
-
renderResumeCommand,
|
|
78
80
|
type CurrentCourtState,
|
|
79
81
|
type RunWriterLease,
|
|
80
|
-
type TypedProviderHttpObservation,
|
|
81
82
|
} from "./run-lifecycle.ts";
|
|
82
83
|
import {
|
|
83
84
|
processCancelDiagnostic,
|
|
84
85
|
processCancelSignalName,
|
|
85
86
|
type CatchableProcessSignal,
|
|
86
87
|
} from "./process-cancel.ts";
|
|
87
|
-
import {
|
|
88
|
+
import { DEFAULT_ROLE_TURN_HOST } from "../host-descriptions.ts";
|
|
88
89
|
import { recordRunStart } from "../host-session-record.ts";
|
|
89
90
|
import {
|
|
90
|
-
attemptProducedFreshSubmission,
|
|
91
91
|
classifyPostAdmissionFailure,
|
|
92
|
-
controlledFailureInputFromResolution,
|
|
93
92
|
exitCodeForTerminalOutcome,
|
|
94
93
|
explicitInternalKnownFailureClassificationInput,
|
|
95
94
|
formatCliDiagnostic,
|
|
96
95
|
formatErrorCauseDetail,
|
|
97
|
-
|
|
98
|
-
inspectJudgeSession,
|
|
96
|
+
ledgerReadScope,
|
|
99
97
|
isLawfulTypedTerminalOutcome,
|
|
100
|
-
presentFailureTerminal,
|
|
101
|
-
publishedFailureErrorPath,
|
|
102
98
|
presentStructuralRejection,
|
|
103
|
-
resolveAuditedRunnerFailureResolution,
|
|
104
|
-
resolveControlledFailureResumeObservation,
|
|
105
99
|
settleFailureTerminalResult,
|
|
106
100
|
settleHostEndedNoReceipt,
|
|
101
|
+
noteSettlementFault,
|
|
107
102
|
attachRecordedSubmissions,
|
|
108
|
-
|
|
103
|
+
retainPackageFault,
|
|
109
104
|
type ControlledFailure,
|
|
105
|
+
type PackageSideFact,
|
|
106
|
+
type SettlementCourtScope,
|
|
110
107
|
} from "./settlement.ts";
|
|
111
108
|
import type { CliIo } from "./cli-io.ts";
|
|
112
109
|
import type { AdmittedRoleInvocation, RunDirectoryRelocation } from "./invocation.ts";
|
|
@@ -117,9 +114,9 @@ import {
|
|
|
117
114
|
import {
|
|
118
115
|
ensureRealArtifactsDirectory,
|
|
119
116
|
persistReturnedRunState,
|
|
117
|
+
presentTerminal,
|
|
120
118
|
runWithAutoResumeLoop,
|
|
121
119
|
TurnDispatchedFailure,
|
|
122
|
-
writeHardenedArtifactFile,
|
|
123
120
|
} from "./auto-resume.ts";
|
|
124
121
|
|
|
125
122
|
import { isRecord, errorText } from "../unknown-value.ts";
|
|
@@ -146,11 +143,6 @@ function projectRelocatedTurnIdentity(
|
|
|
146
143
|
}
|
|
147
144
|
}
|
|
148
145
|
if (result.terminal !== undefined) {
|
|
149
|
-
rewritePublishedFailureErrorPath(
|
|
150
|
-
result.terminal,
|
|
151
|
-
relocation.oldRunDirectory,
|
|
152
|
-
relocation.newRunDirectory,
|
|
153
|
-
);
|
|
154
146
|
for (const artifact of result.terminal.artifacts) {
|
|
155
147
|
artifact.path = rewrite(artifact.path) as string;
|
|
156
148
|
}
|
|
@@ -182,6 +174,7 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
|
|
|
182
174
|
persistRunState: boolean;
|
|
183
175
|
deferredPersist: { needsPersist?: true } | Record<string, never>;
|
|
184
176
|
invocationScopeId: string | undefined;
|
|
177
|
+
notePackageFault?: (diagnostic: string) => void | Promise<void>;
|
|
185
178
|
}): Promise<{
|
|
186
179
|
exitCode: number;
|
|
187
180
|
admitted: A;
|
|
@@ -197,7 +190,13 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
|
|
|
197
190
|
timedOut: input.result.timedOut,
|
|
198
191
|
code: input.result.code,
|
|
199
192
|
stderr: input.result.stderr,
|
|
200
|
-
|
|
193
|
+
...(input.result.signal === undefined ? {} : { signal: input.result.signal }),
|
|
194
|
+
// A host report already on this result stays the report. The cancel
|
|
195
|
+
// name is wording only when the host gave none.
|
|
196
|
+
...(input.result.knownFailure === undefined
|
|
197
|
+
? { knownDiagnostic: processCancelDiagnostic(input.cancelName) }
|
|
198
|
+
: { knownFailure: input.result.knownFailure }),
|
|
199
|
+
cancelName: input.cancelName,
|
|
201
200
|
},
|
|
202
201
|
input.invocationScopeId,
|
|
203
202
|
),
|
|
@@ -205,6 +204,7 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
|
|
|
205
204
|
input.env.principalAuthority,
|
|
206
205
|
input.io,
|
|
207
206
|
input.persistRunState,
|
|
207
|
+
input.notePackageFault,
|
|
208
208
|
);
|
|
209
209
|
return {
|
|
210
210
|
...failed,
|
|
@@ -253,41 +253,32 @@ export const POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE =
|
|
|
253
253
|
* zero durable trace (#840 bounce class 2); a healthy session never gets a
|
|
254
254
|
* redundant second copy (#840 bounce class 1: 同一业务规则只保留一个权威实现).
|
|
255
255
|
*/
|
|
256
|
-
async function recordBestEffortPostDispatchDiagnostic
|
|
257
|
-
admitted:
|
|
258
|
-
env: PostAdmissionEnv,
|
|
256
|
+
async function recordBestEffortPostDispatchDiagnostic(
|
|
257
|
+
admitted: Pick<AdmittedRoleInvocation, "runDirectory" | "principal">,
|
|
258
|
+
env: Pick<PostAdmissionEnv, "sessionAppender" | "principalAuthority">,
|
|
259
259
|
diagnostic: string,
|
|
260
|
-
io: CliIo,
|
|
260
|
+
io: Pick<CliIo, "stderr">,
|
|
261
261
|
): Promise<void> {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
await retainPackageFault({
|
|
263
|
+
runDirectory: admitted.runDirectory,
|
|
264
|
+
diagnostic,
|
|
265
|
+
appendSession: (payload) => env.sessionAppender(
|
|
266
266
|
env.principalAuthority,
|
|
267
267
|
admitted.principal,
|
|
268
268
|
POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE,
|
|
269
269
|
payload,
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
try {
|
|
284
|
-
io.stderr(formatCliDiagnostic(diagnostic));
|
|
285
|
-
if (retentionFailure !== undefined) {
|
|
286
|
-
io.stderr(formatCliDiagnostic(retentionFailure));
|
|
287
|
-
}
|
|
288
|
-
} catch {
|
|
289
|
-
// Presentation is best-effort beside an already-formed host terminal.
|
|
290
|
-
}
|
|
270
|
+
),
|
|
271
|
+
stderr: (text) => io.stderr(text),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** The one package-fault receiver, bound to this admitted run's session and io. */
|
|
276
|
+
export function packageFaultNoteFor(
|
|
277
|
+
admitted: Pick<AdmittedRoleInvocation, "runDirectory" | "principal">,
|
|
278
|
+
env: Pick<PostAdmissionEnv, "sessionAppender" | "principalAuthority">,
|
|
279
|
+
io: Pick<CliIo, "stderr">,
|
|
280
|
+
): (diagnostic: string) => Promise<void> {
|
|
281
|
+
return (diagnostic) => recordBestEffortPostDispatchDiagnostic(admitted, env, diagnostic, io);
|
|
291
282
|
}
|
|
292
283
|
|
|
293
284
|
export type PostAdmissionEnv = {
|
|
@@ -313,6 +304,11 @@ export type PostAdmissionEnv = {
|
|
|
313
304
|
principalAuthority: DurablePrincipalAuthority;
|
|
314
305
|
sessionAppender: SessionCustomEntryAppender;
|
|
315
306
|
autoResumeLimit?: number;
|
|
307
|
+
/**
|
|
308
|
+
* Unreadable-status reasks already issued on this chain (#1132). Each reask
|
|
309
|
+
* keeps this env's autoResumeLimit; the counter is the only thing that moves.
|
|
310
|
+
*/
|
|
311
|
+
unreadableReasksSpent?: number;
|
|
316
312
|
createRunId?: () => string;
|
|
317
313
|
/**
|
|
318
314
|
* Parent cancellation for a nested public summon (#675). Every dispatched turn
|
|
@@ -342,18 +338,15 @@ export type PostAdmissionAdapters<
|
|
|
342
338
|
A extends AdmittedRoleInvocation = AdmittedRoleInvocation,
|
|
343
339
|
T extends TerminalResult = TerminalResult,
|
|
344
340
|
> = {
|
|
341
|
+
/** A present Terminal already carries run history from settlement. */
|
|
345
342
|
trySettle: (
|
|
346
343
|
admitted: A,
|
|
347
344
|
authority: DurablePrincipalAuthority,
|
|
348
|
-
/** Current
|
|
349
|
-
scope?: { readonly courtAttemptId?: string },
|
|
345
|
+
/** Current host attempt; only this invocation records history (#419). */
|
|
346
|
+
scope?: { readonly courtAttemptId?: string; readonly recordAttemptHistory?: true; readonly previewOnly?: true },
|
|
350
347
|
) => Promise<T | undefined>;
|
|
351
348
|
/** Default: isLawfulTypedTerminalOutcome(terminal.roleOutcome). */
|
|
352
349
|
shouldPresentSettled?: (terminal: T) => boolean;
|
|
353
|
-
resolveRunnerKnownFailure?: (input: {
|
|
354
|
-
result: RoleTurnResult;
|
|
355
|
-
sessionFile: string;
|
|
356
|
-
}) => Promise<RoleTurnKnownFailure | undefined>;
|
|
357
350
|
/** A child office may pause this run with its unchanged terminal before a host turn starts. */
|
|
358
351
|
beforeDispatch?: (admitted: A, lease?: RunWriterLease) => Promise<T | void> | T | void;
|
|
359
352
|
/**
|
|
@@ -384,13 +377,22 @@ export type ControlledFailureInput = {
|
|
|
384
377
|
knownDiagnostic?: string;
|
|
385
378
|
/** Secondary evidence already owned by the typed production failure channel. */
|
|
386
379
|
knownDetails?: Readonly<Record<string, unknown>>;
|
|
387
|
-
typedHttpObservationSettled?: true;
|
|
388
|
-
typedHttpObservation?: TypedProviderHttpObservation;
|
|
389
380
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
|
|
381
|
+
* A real failure this package hit while handling the call (e.g. the
|
|
382
|
+
* stderr.log durable write). Recorded beside the host's report; never a
|
|
383
|
+
* cause, and never written into the host's open `details`.
|
|
384
|
+
*/
|
|
385
|
+
packageFact?: PackageSideFact;
|
|
386
|
+
/** The signal that killed the host child, when one did. */
|
|
387
|
+
signal?: string;
|
|
388
|
+
/**
|
|
389
|
+
* A catchable process-signal cancellation. Supplies fallback wording only —
|
|
390
|
+
* a host that reported a diagnostic of its own keeps it.
|
|
391
|
+
*/
|
|
392
|
+
readonly cancelName?: CatchableProcessSignal;
|
|
393
|
+
/**
|
|
394
|
+
* Set when the caller already attempted markRunTerminal and it is what threw
|
|
395
|
+
* `thrown` — presentControlledFailure must not retry that write.
|
|
394
396
|
*/
|
|
395
397
|
skipRunStateWrite?: boolean;
|
|
396
398
|
/** Public-invocation scope from the shared Host envelope (#537). */
|
|
@@ -449,6 +451,12 @@ export async function resolveResumeMethodMaterialAdapters<
|
|
|
449
451
|
input.emptyAdapters,
|
|
450
452
|
input.authority,
|
|
451
453
|
{ ...input.io, omitFailureStderrDiagnostic: true },
|
|
454
|
+
true,
|
|
455
|
+
(diagnostic) => retainPackageFault({
|
|
456
|
+
runDirectory: input.admitted.runDirectory,
|
|
457
|
+
diagnostic,
|
|
458
|
+
stderr: (text) => input.io.stderr(text),
|
|
459
|
+
}),
|
|
452
460
|
);
|
|
453
461
|
return { kind: "terminal", ...terminal };
|
|
454
462
|
}
|
|
@@ -464,45 +472,41 @@ export async function presentControlledFailure<
|
|
|
464
472
|
authority: DurablePrincipalAuthority,
|
|
465
473
|
io: CliIo,
|
|
466
474
|
persistRunState = true,
|
|
475
|
+
notePackageFault?: (diagnostic: string) => void | Promise<void>,
|
|
467
476
|
): Promise<{
|
|
468
477
|
exitCode: number;
|
|
469
478
|
admitted: A;
|
|
470
479
|
terminal: TerminalResult;
|
|
471
480
|
}> {
|
|
472
481
|
const hasThrown = Object.hasOwn(failureInput, "thrown");
|
|
473
|
-
const
|
|
474
|
-
runDirectory: admitted.runDirectory,
|
|
475
|
-
...(failureInput.typedHttpObservationSettled === true
|
|
476
|
-
? {
|
|
477
|
-
typedHttpObservationSettled: true as const,
|
|
478
|
-
...(failureInput.typedHttpObservation === undefined
|
|
479
|
-
? {}
|
|
480
|
-
: { typedHttpObservation: failureInput.typedHttpObservation }),
|
|
481
|
-
}
|
|
482
|
-
: {}),
|
|
483
|
-
});
|
|
484
|
-
const knownFailure =
|
|
485
|
-
failureInput.knownFailure ?? resumeObservation.observationReadFailure;
|
|
486
|
-
const session =
|
|
487
|
-
!hasThrown &&
|
|
488
|
-
!failureInput.timedOut &&
|
|
489
|
-
knownFailure === undefined &&
|
|
490
|
-
failureInput.knownCause === undefined &&
|
|
491
|
-
admitted.principal !== undefined
|
|
492
|
-
? await inspectJudgeSession(authority.decode(admitted.principal).sessionFile)
|
|
493
|
-
: undefined;
|
|
482
|
+
const knownFailure = failureInput.knownFailure;
|
|
494
483
|
// knownFailure channel owns details when present; otherwise caller knownDetails.
|
|
495
484
|
const fromKnownFailure =
|
|
496
485
|
explicitInternalKnownFailureClassificationInput(knownFailure);
|
|
497
|
-
|
|
486
|
+
// Merge the two detail sources so a key carried by either survives; a later
|
|
487
|
+
// spread would otherwise drop the earlier one's keys entirely.
|
|
488
|
+
const callerDetails =
|
|
489
|
+
failureInput.knownDetails === undefined && fromKnownFailure.knownDetails === undefined
|
|
490
|
+
? undefined
|
|
491
|
+
: { ...(failureInput.knownDetails ?? {}), ...(fromKnownFailure.knownDetails ?? {}) };
|
|
492
|
+
const classified = classifyPostAdmissionFailure({
|
|
498
493
|
timedOut: failureInput.timedOut,
|
|
499
494
|
code: failureInput.code,
|
|
500
495
|
stderr: failureInput.stderr,
|
|
501
496
|
...(hasThrown ? { thrown: failureInput.thrown } : {}),
|
|
502
|
-
|
|
497
|
+
// Both detail sources are real; merging keeps each one's keys instead of
|
|
498
|
+
// letting the later spread drop the earlier one.
|
|
499
|
+
...(callerDetails === undefined ? {} : { knownDetails: callerDetails }),
|
|
500
|
+
// The knownFailure channel's own cause/identity/diagnostic ride with it.
|
|
501
|
+
...(fromKnownFailure.knownCause === undefined
|
|
502
|
+
? {}
|
|
503
|
+
: { knownCause: fromKnownFailure.knownCause }),
|
|
504
|
+
...(fromKnownFailure.knownIdentity === undefined
|
|
505
|
+
? {}
|
|
506
|
+
: { knownIdentity: fromKnownFailure.knownIdentity }),
|
|
507
|
+
...(fromKnownFailure.knownDiagnostic === undefined
|
|
503
508
|
? {}
|
|
504
|
-
: {
|
|
505
|
-
...fromKnownFailure,
|
|
509
|
+
: { knownDiagnostic: fromKnownFailure.knownDiagnostic }),
|
|
506
510
|
...(failureInput.knownCause === undefined
|
|
507
511
|
? {}
|
|
508
512
|
: { knownCause: failureInput.knownCause }),
|
|
@@ -512,23 +516,35 @@ export async function presentControlledFailure<
|
|
|
512
516
|
...(failureInput.knownDiagnostic === undefined
|
|
513
517
|
? {}
|
|
514
518
|
: { knownDiagnostic: failureInput.knownDiagnostic }),
|
|
515
|
-
...(
|
|
519
|
+
...(failureInput.signal === undefined ? {} : { signal: failureInput.signal }),
|
|
520
|
+
...(failureInput.cancelName === undefined ? {} : { cancelName: failureInput.cancelName }),
|
|
521
|
+
...(failureInput.packageFact === undefined
|
|
522
|
+
? {}
|
|
523
|
+
: { packageFact: failureInput.packageFact }),
|
|
516
524
|
});
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
525
|
+
const failure = failureInput.stderr.length === 0 || failureInput.stderr === classified.diagnostic
|
|
526
|
+
? classified
|
|
527
|
+
: { ...classified, stderr: failureInput.stderr };
|
|
520
528
|
|
|
521
529
|
if (persistRunState && !failureInput.skipRunStateWrite) {
|
|
522
|
-
|
|
530
|
+
try {
|
|
531
|
+
await persistReturnedRunState(admitted);
|
|
532
|
+
} catch (error) {
|
|
533
|
+
// The host failure is already classified. A run-state write stays beside it.
|
|
534
|
+
await noteSettlementFault(
|
|
535
|
+
admitted.runDirectory,
|
|
536
|
+
notePackageFault === undefined ? undefined : { notePackageFault },
|
|
537
|
+
`run-state persistence failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
523
540
|
}
|
|
524
541
|
|
|
525
542
|
let publishedErrorPath: string | undefined;
|
|
526
543
|
let terminal: TerminalResult;
|
|
527
544
|
try {
|
|
528
545
|
const settled = await settleFailureTerminalResult(admitted, failure, authority, {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
: {}),
|
|
546
|
+
recordAttemptHistory: true,
|
|
547
|
+
...(notePackageFault === undefined ? {} : { notePackageFault }),
|
|
532
548
|
...(failureInput.invocationScopeId === undefined ||
|
|
533
549
|
failureInput.invocationScopeId.length === 0
|
|
534
550
|
? {}
|
|
@@ -542,7 +558,7 @@ export async function presentControlledFailure<
|
|
|
542
558
|
}
|
|
543
559
|
throw error;
|
|
544
560
|
}
|
|
545
|
-
|
|
561
|
+
await presentTerminal(terminal, io, admitted.runDirectory);
|
|
546
562
|
return {
|
|
547
563
|
exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
|
|
548
564
|
admitted,
|
|
@@ -571,6 +587,7 @@ async function settleAfterTurnStarted<
|
|
|
571
587
|
authority: DurablePrincipalAuthority,
|
|
572
588
|
io: CliIo,
|
|
573
589
|
persistRunState: boolean,
|
|
590
|
+
notePackageFault?: (diagnostic: string) => void | Promise<void>,
|
|
574
591
|
): Promise<{ exitCode: number; admitted: A; terminal: T }> {
|
|
575
592
|
try {
|
|
576
593
|
return (await presentControlledFailure(
|
|
@@ -580,6 +597,7 @@ async function settleAfterTurnStarted<
|
|
|
580
597
|
authority,
|
|
581
598
|
io,
|
|
582
599
|
persistRunState,
|
|
600
|
+
notePackageFault,
|
|
583
601
|
)) as { exitCode: number; admitted: A; terminal: T };
|
|
584
602
|
} catch (error) {
|
|
585
603
|
throw new TurnDispatchedFailure(error);
|
|
@@ -590,51 +608,300 @@ async function settleAfterTurnStarted<
|
|
|
590
608
|
* Persist run-state for a result dispatchPostAdmissionTurn deferred
|
|
591
609
|
* (needsPersist — station-child / resumable auto-resume loop, #416/#840):
|
|
592
610
|
* that write must land outside the loop's own retried-dispatch try, but its
|
|
593
|
-
* failure
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
* classify/artifact/Terminal (#836 r12 class 3), and never lawful/non-lawful
|
|
597
|
-
* settling differently (#836 r13 class 2: a caller's io here may be a no-op,
|
|
598
|
-
* so a stderr-only trace is never seen and the failure is otherwise
|
|
599
|
-
* swallowed — 失败诚实宪法 真因必须落痕). Both cases settle through
|
|
600
|
-
* settleAfterTurnStarted, which attaches the run's already-recorded ledger
|
|
601
|
-
* submissions to the new failure terminal and returns skipAutoResume so the
|
|
602
|
-
* caller presents it once and stops — never re-entering auto-resume.
|
|
611
|
+
* failure is a package note beside the terminal already settled. It does
|
|
612
|
+
* not replace that terminal or open another attempt. The note is durable
|
|
613
|
+
* because the caller's io may be a no-op.
|
|
603
614
|
*/
|
|
604
615
|
async function settleDeferredPersist<
|
|
605
616
|
A extends AdmittedRoleInvocation,
|
|
606
617
|
T extends TerminalResult,
|
|
607
618
|
>(
|
|
608
619
|
admitted: A,
|
|
609
|
-
|
|
610
|
-
adapters: PostAdmissionAdapters<A, T>,
|
|
620
|
+
env: PostAdmissionEnv,
|
|
611
621
|
io: CliIo,
|
|
612
|
-
result:
|
|
613
|
-
|
|
614
|
-
admitted: A;
|
|
615
|
-
terminal?: T;
|
|
616
|
-
skipAutoResume?: true;
|
|
617
|
-
turnDispatched?: true;
|
|
618
|
-
needsPersist?: true;
|
|
619
|
-
},
|
|
620
|
-
): Promise<typeof result> {
|
|
622
|
+
result: DispatchOutcomeFor<A, T>,
|
|
623
|
+
): Promise<DispatchOutcomeFor<A, T>> {
|
|
621
624
|
if (result.needsPersist !== true || result.terminal === undefined) return result;
|
|
622
625
|
const { needsPersist: _needsPersist, ...settledResult } = result;
|
|
623
|
-
const lawful = isLawfulTypedTerminalOutcome(result.terminal.roleOutcome);
|
|
624
626
|
try {
|
|
625
|
-
await persistReturnedRunState(admitted
|
|
627
|
+
await persistReturnedRunState(admitted);
|
|
626
628
|
return settledResult;
|
|
627
629
|
} catch (error) {
|
|
628
|
-
|
|
630
|
+
await recordBestEffortPostDispatchDiagnostic(
|
|
629
631
|
admitted,
|
|
630
|
-
|
|
632
|
+
env,
|
|
633
|
+
`run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
634
|
+
io,
|
|
635
|
+
);
|
|
636
|
+
return settledResult;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Court/invocation scope for settlement reads. The one construction of this
|
|
642
|
+
* shape for every turn (#1132 — the first turn and each 催交 turn share it).
|
|
643
|
+
*/
|
|
644
|
+
function settlementScopeForTurn(request: RoleTurnRequest): SettlementCourtScope | undefined {
|
|
645
|
+
const hasCourt = request.courtAttemptId !== undefined && request.courtAttemptId.length > 0;
|
|
646
|
+
const hasScope = request.invocationScopeId !== undefined && request.invocationScopeId.length > 0;
|
|
647
|
+
if (!hasCourt && !hasScope) return undefined;
|
|
648
|
+
return {
|
|
649
|
+
...(hasCourt ? { courtAttemptId: request.courtAttemptId as string } : {}),
|
|
650
|
+
...(hasScope ? { invocationScopeId: request.invocationScopeId as string } : {}),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* #1132: one 没交卷催交 turn, dispatched through the SAME settlement path as the
|
|
656
|
+
* first turn.
|
|
657
|
+
*
|
|
658
|
+
* There is deliberately no second settlement implementation here. A delivery
|
|
659
|
+
* turn is an ordinary host turn on this same run/session: this builds the
|
|
660
|
+
* host-neutral resume request (the adapter's stored native session id, ADR 0082;
|
|
661
|
+
* pi's existing delivery-state content reused verbatim — no new prompt wording,
|
|
662
|
+
* no new host capability). The caller settles it through the same
|
|
663
|
+
* `settleCompletedHostTurn` the first turn uses (ADR 0080): the runner/host
|
|
664
|
+
* failure re-read, the shouldPresent gate, the fresh-seal check, open-court
|
|
665
|
+
* cleanup, cancel re-reads, run-state persist, and the stderr mirror.
|
|
666
|
+
*
|
|
667
|
+
* The caller owns the live delivery policy; the request projects its next send.
|
|
668
|
+
*/
|
|
669
|
+
async function buildReceiptDeliveryRequest<A extends AdmittedRoleInvocation>(input: {
|
|
670
|
+
admitted: A;
|
|
671
|
+
env: PostAdmissionEnv;
|
|
672
|
+
request: RoleTurnRequest;
|
|
673
|
+
receiptDelivery: ReturnType<typeof createReceiptDeliveryPolicy>;
|
|
674
|
+
}): Promise<RoleTurnRequest> {
|
|
675
|
+
const { admitted, env, request, receiptDelivery } = input;
|
|
676
|
+
const hostSessionId = await readStoredHostSessionId(
|
|
677
|
+
env.host,
|
|
678
|
+
env.principalAuthority,
|
|
679
|
+
admitted.principal,
|
|
680
|
+
);
|
|
681
|
+
return {
|
|
682
|
+
...request,
|
|
683
|
+
continuation: {
|
|
684
|
+
kind: "resume",
|
|
685
|
+
prompt: JSON.stringify({
|
|
686
|
+
...receiptDelivery.deliveryState(),
|
|
687
|
+
deliveryTurns: receiptDelivery.issuedDeliveryRequests() + 1,
|
|
688
|
+
}),
|
|
689
|
+
...(hostSessionId === undefined ? {} : { hostSessionId }),
|
|
690
|
+
},
|
|
691
|
+
deliveryRequestLimit: receiptDelivery.limit,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** The one dispatch-result shape. */
|
|
696
|
+
type DispatchOutcomeFor<A extends AdmittedRoleInvocation, T extends TerminalResult> = {
|
|
697
|
+
exitCode: number;
|
|
698
|
+
admitted: A;
|
|
699
|
+
terminal?: T;
|
|
700
|
+
skipAutoResume?: true;
|
|
701
|
+
turnDispatched?: true;
|
|
702
|
+
needsPersist?: true;
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* #1132: settle ONE completed host turn — the single post-turn settlement
|
|
707
|
+
* authority shared by the first turn and by every 没交卷催交 turn (ADR 0080).
|
|
708
|
+
*
|
|
709
|
+
* Extracted deliberately instead of re-entering `dispatchPostAdmissionTurn`.
|
|
710
|
+
* The turn-BEFORE concerns (beforeDispatch / markRunRunning / relocate /
|
|
711
|
+
* afterDispatch / writer-lease release) belong to the outermost dispatch and
|
|
712
|
+
* run exactly once per run; everything from the stderr mirror onward is
|
|
713
|
+
* per-turn. A 催交 turn is a real host turn on this same run/session, so it
|
|
714
|
+
* settles through THIS function — not a reduced copy — and it re-runs neither
|
|
715
|
+
* the admission hooks nor the run-state terminal write.
|
|
716
|
+
*
|
|
717
|
+
* Returns `still-missing` when the turn simply produced no receipt: the caller
|
|
718
|
+
* (first turn or the 催交 loop) then decides between issuing another delivery
|
|
719
|
+
* request and settling no_receipt.
|
|
720
|
+
*/
|
|
721
|
+
async function settleCompletedHostTurn<
|
|
722
|
+
A extends AdmittedRoleInvocation,
|
|
723
|
+
T extends TerminalResult,
|
|
724
|
+
>(input: {
|
|
725
|
+
admitted: A;
|
|
726
|
+
env: PostAdmissionEnv;
|
|
727
|
+
io: CliIo;
|
|
728
|
+
adapters: PostAdmissionAdapters<A, T>;
|
|
729
|
+
shouldPresent: (terminal: T) => boolean;
|
|
730
|
+
request: RoleTurnRequest;
|
|
731
|
+
result: RoleTurnResult;
|
|
732
|
+
persistRunState: boolean;
|
|
733
|
+
deferredPersist: { needsPersist?: true } | Record<string, never>;
|
|
734
|
+
finishAfterTurn: (result: DispatchOutcomeFor<A, T>) => Promise<DispatchOutcomeFor<A, T>>;
|
|
735
|
+
courtScope: SettlementCourtScope & { notePackageFault: (diagnostic: string) => Promise<void> };
|
|
736
|
+
markPending: (terminal: T) => void;
|
|
737
|
+
}): Promise<{ kind: "settled"; outcome: DispatchOutcomeFor<A, T> } | { kind: "still-missing" }> {
|
|
738
|
+
const { admitted, env, io, adapters, shouldPresent, request, result, persistRunState, deferredPersist } = input;
|
|
739
|
+
const finishAfterTurn = input.finishAfterTurn;
|
|
740
|
+
|
|
741
|
+
const courtScope = input.courtScope;
|
|
742
|
+
// `result.stderr` stays live in memory for host-failure settlement
|
|
743
|
+
// whether or not this mirror write succeeds. A write failure is noted.
|
|
744
|
+
// It does not replace the host terminal or an already-sealed acceptance.
|
|
745
|
+
//
|
|
746
|
+
// Everything below runs after the host turn genuinely started (#840 r9
|
|
747
|
+
// 判词 class 1 boundary — 覆盖 executeTurn 已启动后至返回带 turnDispatched
|
|
748
|
+
// 结果前的全部异常). Each fallible step routes any failure through the
|
|
749
|
+
// single existing settlement authority (presentControlledFailure /
|
|
750
|
+
// settleFailureTerminalResult) rather than a second one (ADR 0080:
|
|
751
|
+
// one settlement disposition owner) — never by fabricating a terminal here.
|
|
752
|
+
// A cleanup step that runs only after a real settlement (clearCurrentCourt)
|
|
753
|
+
// is protected by logging and keeping that already-obtained result, never
|
|
754
|
+
// by discarding it (#840 已交劳动只整理终局不重做). A failure inside the
|
|
755
|
+
// settlement authority itself (presentControlledFailure's own reads) goes
|
|
756
|
+
// through settleAfterTurnStarted: still no fabricated terminal, but the
|
|
757
|
+
// re-thrown TurnDispatchedFailure still tells runWithAutoResumeLoop the
|
|
758
|
+
// turn genuinely started, so the next retry sends a resume payload — the
|
|
759
|
+
// true cause settles through the loop's own existing dispatch-exception
|
|
760
|
+
// machinery once the retry budget is exhausted.
|
|
761
|
+
let stderrLogWriteFailure: unknown;
|
|
762
|
+
try {
|
|
763
|
+
await writeFile(
|
|
764
|
+
join(admitted.runDirectory, "stderr.log"),
|
|
765
|
+
result.stderr,
|
|
766
|
+
"utf8",
|
|
767
|
+
);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
stderrLogWriteFailure = error;
|
|
770
|
+
// Best-effort: stderr.log is secondary to the host terminal. A write
|
|
771
|
+
// failure leaves a durable note and, on a host failure, rides in
|
|
772
|
+
// packageFact. It does not become the cause.
|
|
773
|
+
await recordBestEffortPostDispatchDiagnostic(
|
|
774
|
+
admitted,
|
|
775
|
+
env,
|
|
776
|
+
`stderr.log write failed (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
777
|
+
io,
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Host facts are resolved before trySettle. A host failure skips trySettle
|
|
782
|
+
// so a later read cannot replace that report. A clean host whose settlement
|
|
783
|
+
// read throws is noted above and is not turned into a failure terminal.
|
|
784
|
+
// stderr.log and run-state writes are notes beside the host terminal.
|
|
785
|
+
let settled: T | undefined;
|
|
786
|
+
let settledOutcome:
|
|
787
|
+
| { exitCode: number; admitted: A; terminal: T; turnDispatched: true }
|
|
788
|
+
| undefined;
|
|
789
|
+
// The host CLI's own report: timeout, declared failure, cancel, or exit.
|
|
790
|
+
// A null code is a signal death (Node close). No session or history read
|
|
791
|
+
// takes part (owner ea321c6d / 4743ade7).
|
|
792
|
+
const processCancelName = processCancelSignalName(env.signal);
|
|
793
|
+
const hostSignalFailed =
|
|
794
|
+
result.timedOut
|
|
795
|
+
|| result.knownFailure !== undefined
|
|
796
|
+
|| processCancelName !== undefined
|
|
797
|
+
|| result.code === null
|
|
798
|
+
|| result.code !== 0;
|
|
799
|
+
const hostReportedFailure = result.knownFailure;
|
|
800
|
+
try {
|
|
801
|
+
if (!hostSignalFailed) {
|
|
802
|
+
settled = await adapters.trySettle(admitted, env.principalAuthority, { ...courtScope, previewOnly: true });
|
|
803
|
+
const cancelAfterSettle = processCancelSignalName(env.signal);
|
|
804
|
+
if (
|
|
805
|
+
settled !== undefined
|
|
806
|
+
&& shouldPresent(settled)
|
|
807
|
+
&& cancelAfterSettle === undefined
|
|
808
|
+
) {
|
|
809
|
+
settledOutcome = {
|
|
810
|
+
exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
|
|
811
|
+
admitted,
|
|
812
|
+
terminal: settled,
|
|
813
|
+
turnDispatched: true as const,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
} catch (error) {
|
|
818
|
+
// The host did not fail. A settlement read here is a package fault and
|
|
819
|
+
// must not become the terminal.
|
|
820
|
+
await recordBestEffortPostDispatchDiagnostic(
|
|
821
|
+
admitted,
|
|
822
|
+
env,
|
|
823
|
+
`settlement read failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
824
|
+
io,
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
if (settledOutcome !== undefined) {
|
|
828
|
+
// #855: final cancel re-read before lawful seal — settlement window may
|
|
829
|
+
// have received SIGTERM/SIGINT/SIGHUP after the earlier snapshot.
|
|
830
|
+
const lateCancel = processCancelSignalName(env.signal);
|
|
831
|
+
if (lateCancel !== undefined) {
|
|
832
|
+
return { kind: "settled", outcome: await finishAfterTurn(
|
|
833
|
+
await settleProcessCancelAfterTurn({
|
|
834
|
+
admitted,
|
|
835
|
+
cancelName: lateCancel,
|
|
836
|
+
result,
|
|
837
|
+
adapters,
|
|
838
|
+
env,
|
|
839
|
+
io,
|
|
840
|
+
persistRunState,
|
|
841
|
+
deferredPersist,
|
|
842
|
+
invocationScopeId: request.invocationScopeId,
|
|
843
|
+
notePackageFault: courtScope.notePackageFault,
|
|
844
|
+
}),
|
|
845
|
+
) };
|
|
846
|
+
}
|
|
847
|
+
if (persistRunState) {
|
|
848
|
+
try {
|
|
849
|
+
await persistReturnedRunState(admitted);
|
|
850
|
+
} catch (error) {
|
|
851
|
+
await recordBestEffortPostDispatchDiagnostic(
|
|
852
|
+
admitted,
|
|
853
|
+
env,
|
|
854
|
+
`run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
855
|
+
io,
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
// Persist + present is the caller's stop seam (auto-resume loop /
|
|
860
|
+
// manual resume), not this retried host-turn function.
|
|
861
|
+
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
862
|
+
input.markPending(settledOutcome.terminal);
|
|
863
|
+
return { kind: "settled", outcome: await finishAfterTurn({ ...settledOutcome, ...deferredPersist }) };
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
|
|
867
|
+
if (hostSignalFailed) {
|
|
868
|
+
const stderrLogWriteFact =
|
|
869
|
+
stderrLogWriteFailure === undefined
|
|
870
|
+
? undefined
|
|
871
|
+
: { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
|
|
872
|
+
const failed = await settleAfterTurnStarted(
|
|
873
|
+
admitted,
|
|
874
|
+
withEngineDetourInvocationScope({
|
|
875
|
+
timedOut: result.timedOut,
|
|
876
|
+
code: result.code,
|
|
877
|
+
stderr: result.stderr,
|
|
878
|
+
// A signal-killed child is a fact of its own. The synthetic line is
|
|
879
|
+
// only a fallback for when the host declared no failure of its own —
|
|
880
|
+
// it must never displace words the host actually reported.
|
|
881
|
+
...(result.signal === undefined ? {} : { signal: result.signal }),
|
|
882
|
+
...(hostReportedFailure === undefined ? {} : { knownFailure: hostReportedFailure }),
|
|
883
|
+
// The cancellation is the host's own report of why this call ended;
|
|
884
|
+
// it supplies the wording only when the host declared nothing else.
|
|
885
|
+
...(processCancelName === undefined ? {} : { cancelName: processCancelName }),
|
|
886
|
+
// Secondary fact only — never the cause, and never merged into the
|
|
887
|
+
// host's own details.
|
|
888
|
+
...(stderrLogWriteFact === undefined ? {} : { packageFact: stderrLogWriteFact }),
|
|
889
|
+
}, request.invocationScopeId),
|
|
631
890
|
adapters,
|
|
632
|
-
|
|
891
|
+
env.principalAuthority,
|
|
633
892
|
io,
|
|
634
|
-
|
|
893
|
+
persistRunState,
|
|
894
|
+
courtScope.notePackageFault,
|
|
635
895
|
);
|
|
636
|
-
return {
|
|
896
|
+
return { kind: "settled", outcome: await finishAfterTurn(
|
|
897
|
+
withProcessCancelSkipAutoResume(
|
|
898
|
+
{ ...failed, turnDispatched: true as const, ...deferredPersist },
|
|
899
|
+
env.signal,
|
|
900
|
+
),
|
|
901
|
+
) };
|
|
637
902
|
}
|
|
903
|
+
|
|
904
|
+
return { kind: "still-missing" };
|
|
638
905
|
}
|
|
639
906
|
|
|
640
907
|
export async function dispatchPostAdmissionTurn<
|
|
@@ -650,39 +917,54 @@ export async function dispatchPostAdmissionTurn<
|
|
|
650
917
|
adapters: PostAdmissionAdapters<A, T>;
|
|
651
918
|
effectiveEngine?: string;
|
|
652
919
|
persistRunState?: boolean;
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
terminal?: T;
|
|
657
|
-
skipAutoResume?: true;
|
|
658
|
-
turnDispatched?: true;
|
|
659
|
-
needsPersist?: true;
|
|
660
|
-
}> {
|
|
920
|
+
/** Call-owned delivery policy survives every failure-recovery dispatch. */
|
|
921
|
+
receiptDelivery?: ReturnType<typeof createReceiptDeliveryPolicy>;
|
|
922
|
+
}): Promise<DispatchOutcomeFor<A, T>> {
|
|
661
923
|
const { admitted, env, io, request, lease, adapters, effectiveEngine } = input;
|
|
662
924
|
const persistRunState = input.persistRunState !== false;
|
|
663
925
|
const deferredPersist = persistRunState ? {} : { needsPersist: true as const };
|
|
664
926
|
const shouldPresent =
|
|
665
927
|
adapters.shouldPresentSettled ?? ((terminal: T) => isLawfulTypedTerminalOutcome(terminal.roleOutcome));
|
|
666
|
-
type DispatchOutcome = {
|
|
667
|
-
exitCode: number;
|
|
668
|
-
admitted: A;
|
|
669
|
-
terminal?: T;
|
|
670
|
-
skipAutoResume?: true;
|
|
671
|
-
turnDispatched?: true;
|
|
672
|
-
needsPersist?: true;
|
|
673
|
-
};
|
|
674
928
|
/**
|
|
675
929
|
* Once-only post-host-turn hook (Diarist bind/relocate, etc.) under the still-held
|
|
676
930
|
* writer lease. Every exit after the host turn has started must pass here before
|
|
677
931
|
* lease release — success and failure alike. When a primary failure terminal already
|
|
678
932
|
* exists, keep that cause and leave relocate failure on the shared diagnostic channel.
|
|
679
933
|
*/
|
|
934
|
+
const courtScope = {
|
|
935
|
+
...settlementScopeForTurn(request),
|
|
936
|
+
notePackageFault: (diagnostic: string) => recordBestEffortPostDispatchDiagnostic(
|
|
937
|
+
admitted,
|
|
938
|
+
env,
|
|
939
|
+
diagnostic,
|
|
940
|
+
io,
|
|
941
|
+
),
|
|
942
|
+
};
|
|
943
|
+
let pendingSettlement: "sealed" | "no_receipt" | undefined;
|
|
944
|
+
/**
|
|
945
|
+
* The terminal this pending publication belongs to. A later real failure
|
|
946
|
+
* (e.g. a cancel re-read) replaces `result.terminal` with its own settled
|
|
947
|
+
* failure; that failure is already durable, so the superseded preview must
|
|
948
|
+
* not publish over it (失败诚实宪法:接住可以,洗白不行).
|
|
949
|
+
*/
|
|
950
|
+
let pendingTerminal: T | undefined;
|
|
951
|
+
|
|
680
952
|
let afterDispatchApplied = false;
|
|
681
|
-
const
|
|
953
|
+
const receiptDelivery = input.receiptDelivery ?? createReceiptDeliveryPolicy(env.autoResumeLimit);
|
|
954
|
+
let turnDispatched = false;
|
|
955
|
+
// #1132: the AK-side 催交 covers hosts that do not deliver to themselves.
|
|
956
|
+
// Pi runs the package role runtime in-process, and that runtime already issues
|
|
957
|
+
// its own delivery requests at `agent_end` and records the real count — asking
|
|
958
|
+
// again here would double-deliver on one run. External hosts (ACP / headless
|
|
959
|
+
// CLI) have no such in-child loop, so the AK execution seam owns it for them.
|
|
960
|
+
const akSeamDeliversReceipt =
|
|
961
|
+
env.host !== undefined && env.host !== "" && env.host !== DEFAULT_ROLE_TURN_HOST;
|
|
962
|
+
const finishAfterTurn = async (result: DispatchOutcomeFor<A, T>): Promise<DispatchOutcomeFor<A, T>> => {
|
|
682
963
|
if (afterDispatchApplied) return result;
|
|
683
964
|
afterDispatchApplied = true;
|
|
684
|
-
// #855: re-read cancel before
|
|
685
|
-
//
|
|
965
|
+
// #855: re-read cancel before committing the terminal and afterDispatch.
|
|
966
|
+
// A signal after the history-first commit cannot retroactively erase it.
|
|
967
|
+
// skipRunStateWrite: run-state
|
|
686
968
|
// may already be terminal from a prior lawful persist; do not reopen as resumable.
|
|
687
969
|
const cancelBeforeFinish = processCancelSignalName(env.signal);
|
|
688
970
|
if (
|
|
@@ -699,6 +981,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
699
981
|
code: null,
|
|
700
982
|
stderr: "",
|
|
701
983
|
knownDiagnostic: processCancelDiagnostic(cancelBeforeFinish),
|
|
984
|
+
cancelName: cancelBeforeFinish,
|
|
702
985
|
skipRunStateWrite: true,
|
|
703
986
|
},
|
|
704
987
|
request.invocationScopeId,
|
|
@@ -707,12 +990,51 @@ export async function dispatchPostAdmissionTurn<
|
|
|
707
990
|
env.principalAuthority,
|
|
708
991
|
io,
|
|
709
992
|
persistRunState,
|
|
993
|
+
courtScope.notePackageFault,
|
|
710
994
|
)),
|
|
711
995
|
turnDispatched: true as const,
|
|
712
996
|
skipAutoResume: true as const,
|
|
713
997
|
...deferredPersist,
|
|
714
998
|
};
|
|
715
999
|
}
|
|
1000
|
+
// The last cancellation check precedes the one durable settlement. A preview
|
|
1001
|
+
// never publishes pointers or history; a superseding failure owns this turn —
|
|
1002
|
+
// `result.terminal === pendingTerminal` is what makes the publication this
|
|
1003
|
+
// turn's own rather than a superseded one.
|
|
1004
|
+
if (
|
|
1005
|
+
pendingSettlement !== undefined &&
|
|
1006
|
+
pendingTerminal !== undefined &&
|
|
1007
|
+
result.terminal === pendingTerminal
|
|
1008
|
+
) {
|
|
1009
|
+
try {
|
|
1010
|
+
const terminal = pendingSettlement === "sealed"
|
|
1011
|
+
? await adapters.trySettle(admitted, env.principalAuthority,
|
|
1012
|
+
{ ...courtScope, recordAttemptHistory: true })
|
|
1013
|
+
: await attachRecordedSubmissions(admitted,
|
|
1014
|
+
await settleHostEndedNoReceipt(admitted, env.principalAuthority,
|
|
1015
|
+
{ ...courtScope, recordAttemptHistory: true }, receiptDelivery.issuedDeliveryRequests()) as T,
|
|
1016
|
+
courtScope);
|
|
1017
|
+
if (terminal === undefined) throw new Error("settled host attempt vanished before publication");
|
|
1018
|
+
result = { ...result, terminal, exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome) };
|
|
1019
|
+
if (pendingSettlement === "sealed" && terminal.roleOutcome.kind === "accepted"
|
|
1020
|
+
&& request.courtAttemptId !== undefined && request.courtAttemptId.length > 0) {
|
|
1021
|
+
try {
|
|
1022
|
+
await clearCurrentCourt(admitted.runDirectory, request.courtAttemptId);
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
// The accepted attempt is already durable; cleanup is diagnostic only.
|
|
1025
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env,
|
|
1026
|
+
`current-court cleanup failed after accepted settlement (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
await recordBestEffortPostDispatchDiagnostic(
|
|
1031
|
+
admitted,
|
|
1032
|
+
env,
|
|
1033
|
+
`terminal publication failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
1034
|
+
io,
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
716
1038
|
try {
|
|
717
1039
|
// #858 / #1071: the first identifiable ticketNumber field declaration
|
|
718
1040
|
// files an unbound run (integer, digit string, or leading #N token).
|
|
@@ -724,7 +1046,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
724
1046
|
const rows = await readRecordedSubmissionRows(
|
|
725
1047
|
admitted.projectRoot,
|
|
726
1048
|
admitted.runId,
|
|
727
|
-
|
|
1049
|
+
ledgerReadScope(admitted),
|
|
728
1050
|
);
|
|
729
1051
|
let ticketNumber: number | undefined;
|
|
730
1052
|
for (const row of rows) {
|
|
@@ -779,6 +1101,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
779
1101
|
env.principalAuthority,
|
|
780
1102
|
io,
|
|
781
1103
|
persistRunState,
|
|
1104
|
+
courtScope.notePackageFault,
|
|
782
1105
|
)) as { exitCode: number; admitted: A; terminal: T },
|
|
783
1106
|
...(result.turnDispatched === true ? { turnDispatched: true as const } : {}),
|
|
784
1107
|
...(result.skipAutoResume === true ? { skipAutoResume: true as const } : {}),
|
|
@@ -789,7 +1112,6 @@ export async function dispatchPostAdmissionTurn<
|
|
|
789
1112
|
try {
|
|
790
1113
|
// #987: do not pre-block or classify host dispatch from AK credential
|
|
791
1114
|
// catalog facts. Per-invocation runner/host evidence owns provider identity.
|
|
792
|
-
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
793
1115
|
// The authoritative host write (markRunRunning) is delayed to just before
|
|
794
1116
|
// executeTurn — not merely past beforeDispatch (#840 r9 判词 class 2). Any
|
|
795
1117
|
// pre-turn retry (beforeDispatch, continuation assembly) must not commit a host
|
|
@@ -817,6 +1139,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
817
1139
|
env.principalAuthority,
|
|
818
1140
|
io,
|
|
819
1141
|
persistRunState,
|
|
1142
|
+
courtScope.notePackageFault,
|
|
820
1143
|
)) as { exitCode: number; admitted: A; terminal: T };
|
|
821
1144
|
return { ...settled, ...deferredPersist };
|
|
822
1145
|
}
|
|
@@ -859,7 +1182,12 @@ export async function dispatchPostAdmissionTurn<
|
|
|
859
1182
|
|
|
860
1183
|
let result: RoleTurnResult;
|
|
861
1184
|
try {
|
|
1185
|
+
// A bare `ak-role resume` only forwards the caller's instruction to the
|
|
1186
|
+
// host CLI's native resume. Nothing here may gate, redirect or reshape
|
|
1187
|
+
// that dispatch on prior conclusions, row counts or report presence;
|
|
1188
|
+
// the authoritative post-turn settlement reads whatever really happened.
|
|
862
1189
|
recordRunStart(admitted.runDirectory);
|
|
1190
|
+
turnDispatched = true;
|
|
863
1191
|
result = await env.roleTurnHost.executeTurn(turnRequest);
|
|
864
1192
|
} catch (error) {
|
|
865
1193
|
const processCancelName = processCancelSignalName(env.signal);
|
|
@@ -869,15 +1197,16 @@ export async function dispatchPostAdmissionTurn<
|
|
|
869
1197
|
timedOut: false,
|
|
870
1198
|
code: null,
|
|
871
1199
|
stderr: "",
|
|
872
|
-
thrown
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1200
|
+
// The thrown value is the host's report. A cancel received on this
|
|
1201
|
+
// call is a second fact; it must not become a new Error in its place.
|
|
1202
|
+
...(processCancelName === undefined ? {} : { cancelName: processCancelName }),
|
|
1203
|
+
thrown: error,
|
|
876
1204
|
}, request.invocationScopeId),
|
|
877
1205
|
adapters,
|
|
878
1206
|
env.principalAuthority,
|
|
879
1207
|
io,
|
|
880
1208
|
persistRunState,
|
|
1209
|
+
courtScope.notePackageFault,
|
|
881
1210
|
);
|
|
882
1211
|
return await finishAfterTurn(
|
|
883
1212
|
withProcessCancelSkipAutoResume(
|
|
@@ -887,320 +1216,135 @@ export async function dispatchPostAdmissionTurn<
|
|
|
887
1216
|
);
|
|
888
1217
|
}
|
|
889
1218
|
|
|
890
|
-
//
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
//
|
|
913
|
-
//
|
|
914
|
-
//
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1219
|
+
// #1132: settle this turn through the ONE post-turn settlement authority,
|
|
1220
|
+
// then — if it simply produced no receipt — 催交 before settling no_receipt.
|
|
1221
|
+
const settledTurn = await settleCompletedHostTurn({
|
|
1222
|
+
admitted,
|
|
1223
|
+
env,
|
|
1224
|
+
io,
|
|
1225
|
+
adapters,
|
|
1226
|
+
shouldPresent,
|
|
1227
|
+
request,
|
|
1228
|
+
result,
|
|
1229
|
+
persistRunState,
|
|
1230
|
+
deferredPersist,
|
|
1231
|
+
finishAfterTurn,
|
|
1232
|
+
courtScope,
|
|
1233
|
+
markPending: (terminal) => { pendingSettlement = "sealed"; pendingTerminal = terminal; },
|
|
1234
|
+
});
|
|
1235
|
+
if (settledTurn.kind === "settled") return settledTurn.outcome;
|
|
1236
|
+
|
|
1237
|
+
// #1132: 没交卷先回本人催交,不直接结为 no_receipt. The AK execution seam
|
|
1238
|
+
// owns the 催交 and its real count. Each request is a real host turn on this
|
|
1239
|
+
// same run/session, dispatched and settled through the same two seams as the
|
|
1240
|
+
// first turn — never a reduced copy. 催交 never substitutes for host failure
|
|
1241
|
+
// recovery: a delivery turn that fails keeps its true cause below.
|
|
1242
|
+
// The delivery budget is spent HERE, at the outermost level; only when it is
|
|
1243
|
+
// exhausted with still no receipt does the settlement seam record no_receipt.
|
|
1244
|
+
while (akSeamDeliversReceipt && receiptDelivery.nextAction() === "request-delivery") {
|
|
1245
|
+
// Assembly sits in the same post-start catch as the host call. A throw
|
|
1246
|
+
// here is a started turn: resume the session, and do not count a request
|
|
1247
|
+
// that never reached the host.
|
|
1248
|
+
let deliveryResult: RoleTurnResult;
|
|
1249
|
+
let deliveryTurnRequest: RoleTurnRequest;
|
|
1250
|
+
try {
|
|
1251
|
+
const deliveryRequest = await buildReceiptDeliveryRequest({
|
|
1252
|
+
admitted,
|
|
1253
|
+
env,
|
|
1254
|
+
// Host and the other axes already selected on this turn ride the
|
|
1255
|
+
// shared projection. The pre-projection request does not have them.
|
|
1256
|
+
request: turnRequest,
|
|
1257
|
+
receiptDelivery,
|
|
1258
|
+
});
|
|
1259
|
+
// The same native-resume projection the first turn uses (host adapter owns
|
|
1260
|
+
// the resume; AK only hands it the request — ADR 0082).
|
|
1261
|
+
deliveryTurnRequest = {
|
|
1262
|
+
...deliveryRequest,
|
|
1263
|
+
principal: admitted.principal,
|
|
1264
|
+
runDirectory: admitted.runDirectory,
|
|
1265
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
1266
|
+
};
|
|
1267
|
+
receiptDelivery.recordDeliveryRequest();
|
|
1268
|
+
deliveryResult = await env.roleTurnHost.executeTurn(deliveryTurnRequest);
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
// 催交 cannot substitute for host failure recovery: a real failure with
|
|
1271
|
+
// its true cause, through the one settlement authority (ADR 0080).
|
|
1272
|
+
// The turn started, so the caller's loop resumes instead of replaying
|
|
1273
|
+
// the initial payload. The issued count survives that re-entry.
|
|
1274
|
+
const settled = await settleAfterTurnStarted(
|
|
1275
|
+
admitted,
|
|
1276
|
+
withEngineDetourInvocationScope(
|
|
1277
|
+
{ timedOut: false, code: null, stderr: "", thrown: error },
|
|
1278
|
+
request.invocationScopeId,
|
|
1279
|
+
),
|
|
1280
|
+
adapters,
|
|
1281
|
+
env.principalAuthority,
|
|
1282
|
+
io,
|
|
1283
|
+
persistRunState,
|
|
1284
|
+
);
|
|
1285
|
+
return await finishAfterTurn(
|
|
1286
|
+
withProcessCancelSkipAutoResume(
|
|
1287
|
+
{
|
|
1288
|
+
...settled,
|
|
1289
|
+
turnDispatched: true as const,
|
|
1290
|
+
...deferredPersist,
|
|
1291
|
+
},
|
|
1292
|
+
env.signal,
|
|
1293
|
+
),
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
const deliveryTurn = await settleCompletedHostTurn({
|
|
933
1297
|
admitted,
|
|
934
1298
|
env,
|
|
935
|
-
|
|
936
|
-
io,
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
...(request.invocationScopeId === undefined ||
|
|
949
|
-
request.invocationScopeId.length === 0
|
|
950
|
-
? {}
|
|
951
|
-
: { invocationScopeId: request.invocationScopeId }),
|
|
952
|
-
};
|
|
953
|
-
|
|
954
|
-
// Single complete boundary (#840 r9 判词 class 1): resolving the
|
|
955
|
-
// host/runner failure facts, trySettle, its shouldPresent gate, and the
|
|
956
|
-
// accepted-settlement cleanup all settle through this one catch — a
|
|
957
|
-
// throw from any of them (including the session-file decode itself)
|
|
958
|
-
// still routes through settleAfterTurnStarted's TurnDispatchedFailure
|
|
959
|
-
// instead of losing turnDispatched to an uncaught throw. The facts are
|
|
960
|
-
// resolved before trySettle (#836) so an already-accepted settlement is
|
|
961
|
-
// never presented over a real current host/runner failure signal or a
|
|
962
|
-
// real stderr.log durable-write failure — both stay a real failure with
|
|
963
|
-
// the recorded payload riding beside it, not replacing it.
|
|
964
|
-
let settled: T | undefined;
|
|
965
|
-
let settledOutcome:
|
|
966
|
-
| { exitCode: number; admitted: A; terminal: T; turnDispatched: true }
|
|
967
|
-
| undefined;
|
|
968
|
-
let hostSignalFailed = false;
|
|
969
|
-
let resolution: Awaited<ReturnType<typeof resolveAuditedRunnerFailureResolution>> | undefined;
|
|
970
|
-
try {
|
|
971
|
-
const sessionFile =
|
|
972
|
-
admitted.principal !== undefined
|
|
973
|
-
? env.principalAuthority.decode(admitted.principal).sessionFile
|
|
974
|
-
: "";
|
|
975
|
-
const runnerKnownFailure =
|
|
976
|
-
adapters.resolveRunnerKnownFailure !== undefined && sessionFile !== ""
|
|
977
|
-
? await adapters.resolveRunnerKnownFailure({ result, sessionFile })
|
|
978
|
-
: result.knownFailure;
|
|
979
|
-
resolution = await resolveAuditedRunnerFailureResolution({
|
|
980
|
-
runner: runnerKnownFailure,
|
|
981
|
-
sessionFile,
|
|
982
|
-
credential: undefined,
|
|
983
|
-
runDirectory: admitted.runDirectory,
|
|
1299
|
+
// Only the final Terminal presents; intermediate delivery attempts stay quiet.
|
|
1300
|
+
io: { ...io, stdout: () => {} },
|
|
1301
|
+
adapters,
|
|
1302
|
+
shouldPresent,
|
|
1303
|
+
request: deliveryTurnRequest,
|
|
1304
|
+
result: deliveryResult,
|
|
1305
|
+
// Run-state terminal write belongs to the OUTERMOST turn only, so a
|
|
1306
|
+
// still-silent delivery attempt never makes the run look lawfully terminal.
|
|
1307
|
+
persistRunState: false,
|
|
1308
|
+
deferredPersist,
|
|
1309
|
+
finishAfterTurn,
|
|
1310
|
+
courtScope,
|
|
1311
|
+
markPending: (terminal) => { pendingSettlement = "sealed"; pendingTerminal = terminal; },
|
|
984
1312
|
});
|
|
985
|
-
//
|
|
986
|
-
//
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
// retroactively invalidate an acceptance that already sealed this turn
|
|
996
|
-
// — a resolved 429 observed earlier in the same session is exactly
|
|
997
|
-
// that.
|
|
998
|
-
const processCancelName = processCancelSignalName(env.signal);
|
|
999
|
-
const directHostFailureSignal =
|
|
1000
|
-
result.timedOut
|
|
1001
|
-
|| result.knownFailure !== undefined
|
|
1002
|
-
|| runnerKnownFailure !== undefined
|
|
1003
|
-
|| processCancelName !== undefined;
|
|
1004
|
-
hostSignalFailed =
|
|
1005
|
-
directHostFailureSignal
|
|
1006
|
-
|| (result.code !== null && result.code !== 0)
|
|
1007
|
-
|| resolution.knownFailure !== undefined;
|
|
1008
|
-
settled = await adapters.trySettle(admitted, env.principalAuthority, courtScope);
|
|
1009
|
-
if (settled !== undefined) {
|
|
1010
|
-
settled = await attachRecordedSubmissions(admitted, settled, courtScope) as T;
|
|
1011
|
-
}
|
|
1012
|
-
// #836 r12 class 2: an accepted/audit_escalation settlement can be
|
|
1013
|
-
// entirely a prior attempt's stale payload (courtAttempt is a
|
|
1014
|
-
// recording tag, not a visibility gate — attachRecordedSubmissions
|
|
1015
|
-
// above still surfaces that historical payload honestly either way).
|
|
1016
|
-
// Consume the ledger's own subject.attemptId (submission-ledger.ts)
|
|
1017
|
-
// only to learn whether *this* attempt itself produced a fresh seal —
|
|
1018
|
-
// never to filter what presents.
|
|
1019
|
-
const settledIsFreshThisAttempt =
|
|
1020
|
-
settled === undefined
|
|
1021
|
-
|| (settled.roleOutcome.kind !== "accepted" && settled.roleOutcome.kind !== "audit_escalation")
|
|
1022
|
-
? true
|
|
1023
|
-
: await attemptProducedFreshSubmission(admitted, courtScope);
|
|
1024
|
-
// A lawful settled outcome already reached this turn takes precedence
|
|
1025
|
-
// over a later bare exit-code / session-inspection signal (trailing
|
|
1026
|
-
// nonzero exit, late stderr noise, a stale already-superseded
|
|
1027
|
-
// typed-HTTP observation, ...) — but never over a direct current
|
|
1028
|
-
// host/runner failure signal, nor over a real stderr.log durable-write
|
|
1029
|
-
// failure (confirmed infrastructure trouble, not weak/bare evidence),
|
|
1030
|
-
// both of which stay a real failure with the recorded payload riding
|
|
1031
|
-
// beside it, not replacing it (#836: never kill an already-recorded
|
|
1032
|
-
// leg, but never wash a real failure away either). Note: the narrower
|
|
1033
|
-
// directHostFailureSignal gates acceptance here; the broader
|
|
1034
|
-
// hostSignalFailed (adds bare exit code / resolution.knownFailure)
|
|
1035
|
-
// only gates the no-settlement fallback below — except when the
|
|
1036
|
-
// settlement itself is entirely stale (no fresh seal this attempt),
|
|
1037
|
-
// where a bare nonzero exit / resolution failure must not be outranked
|
|
1038
|
-
// by someone else's earlier success (#836 r12 class 2).
|
|
1039
|
-
const staleAcceptanceOutranksRealFailure = !settledIsFreshThisAttempt && hostSignalFailed;
|
|
1040
|
-
// #855: re-read cancel after trySettle/attach awaits — a signal in this
|
|
1041
|
-
// window must not land as lawful accepted.
|
|
1042
|
-
const cancelAfterSettle = processCancelSignalName(env.signal);
|
|
1043
|
-
if (
|
|
1044
|
-
settled !== undefined
|
|
1045
|
-
&& shouldPresent(settled)
|
|
1046
|
-
&& !directHostFailureSignal
|
|
1047
|
-
&& !staleAcceptanceOutranksRealFailure
|
|
1048
|
-
&& stderrLogWriteFailure === undefined
|
|
1049
|
-
&& cancelAfterSettle === undefined
|
|
1050
|
-
) {
|
|
1051
|
-
// This court sealed — drop open-court pointer (bare resume no longer continues it).
|
|
1052
|
-
if (
|
|
1053
|
-
settled.roleOutcome.kind === "accepted" &&
|
|
1054
|
-
request.courtAttemptId !== undefined &&
|
|
1055
|
-
request.courtAttemptId.length > 0
|
|
1056
|
-
) {
|
|
1313
|
+
// The delivery turn's own outcome IS the run's outcome, except when it
|
|
1314
|
+
// simply still has no receipt — that is the loop's own case to keep going.
|
|
1315
|
+
if (deliveryTurn.kind === "settled") {
|
|
1316
|
+
// #1132: the delivery turn settles with persistRunState:false, so a run
|
|
1317
|
+
// that ends HERE (得卷 or a real failure) still needs the outermost
|
|
1318
|
+
// single run-state write. Without it the ledger says accepted while
|
|
1319
|
+
// run-state stays `running`, and every later resume entry (ADR 0080)
|
|
1320
|
+
// reads a finished run as live. The still-silent attempts above must
|
|
1321
|
+
// NOT write; this one, which ends the run, must.
|
|
1322
|
+
if (persistRunState && deliveryTurn.outcome.terminal !== undefined) {
|
|
1057
1323
|
try {
|
|
1058
|
-
await
|
|
1324
|
+
await persistReturnedRunState(admitted);
|
|
1059
1325
|
} catch (error) {
|
|
1060
|
-
// Settlement already sealed accepted — a cleanup failure here must
|
|
1061
|
-
// not erase that fact or make the caller replay this court's
|
|
1062
|
-
// summons over already-delivered work (#840 已交劳动只整理终局不重做).
|
|
1063
|
-
// Preserve the cleanup failure as a post-dispatch diagnostic; the
|
|
1064
|
-
// accepted settlement remains authoritative (#840 r9 判词 class 1).
|
|
1065
1326
|
await recordBestEffortPostDispatchDiagnostic(
|
|
1066
1327
|
admitted,
|
|
1067
1328
|
env,
|
|
1068
|
-
`
|
|
1329
|
+
`run-state persistence failed beside催交 terminal (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
1069
1330
|
io,
|
|
1070
1331
|
);
|
|
1071
1332
|
}
|
|
1072
1333
|
}
|
|
1073
|
-
|
|
1074
|
-
exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
|
|
1075
|
-
admitted,
|
|
1076
|
-
terminal: settled,
|
|
1077
|
-
turnDispatched: true as const,
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
} catch (error) {
|
|
1081
|
-
// Settle (or its shouldPresent gate, or the failure-fact resolution
|
|
1082
|
-
// above) throw is a real failure fact — never swallow into undefined.
|
|
1083
|
-
const settledFailure = await settleAfterTurnStarted(
|
|
1084
|
-
admitted,
|
|
1085
|
-
withEngineDetourInvocationScope({
|
|
1086
|
-
timedOut: false,
|
|
1087
|
-
code: result.code,
|
|
1088
|
-
stderr: result.stderr,
|
|
1089
|
-
thrown: error,
|
|
1090
|
-
}, request.invocationScopeId),
|
|
1091
|
-
adapters,
|
|
1092
|
-
env.principalAuthority,
|
|
1093
|
-
io,
|
|
1094
|
-
persistRunState,
|
|
1095
|
-
);
|
|
1096
|
-
return await finishAfterTurn({ ...settledFailure, turnDispatched: true as const, ...deferredPersist });
|
|
1097
|
-
}
|
|
1098
|
-
if (settledOutcome !== undefined) {
|
|
1099
|
-
// #855: final cancel re-read before lawful seal — settlement window may
|
|
1100
|
-
// have received SIGTERM/SIGINT/SIGHUP after the earlier snapshot.
|
|
1101
|
-
const lateCancel = processCancelSignalName(env.signal);
|
|
1102
|
-
if (lateCancel !== undefined) {
|
|
1103
|
-
return await finishAfterTurn(
|
|
1104
|
-
await settleProcessCancelAfterTurn({
|
|
1105
|
-
admitted,
|
|
1106
|
-
cancelName: lateCancel,
|
|
1107
|
-
result,
|
|
1108
|
-
adapters,
|
|
1109
|
-
env,
|
|
1110
|
-
io,
|
|
1111
|
-
persistRunState,
|
|
1112
|
-
deferredPersist,
|
|
1113
|
-
invocationScopeId: request.invocationScopeId,
|
|
1114
|
-
}),
|
|
1115
|
-
);
|
|
1116
|
-
}
|
|
1117
|
-
if (persistRunState) {
|
|
1118
|
-
try {
|
|
1119
|
-
await persistReturnedRunState(admitted, { lawful: true });
|
|
1120
|
-
} catch (error) {
|
|
1121
|
-
await recordBestEffortPostDispatchDiagnostic(
|
|
1122
|
-
admitted,
|
|
1123
|
-
env,
|
|
1124
|
-
`run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
1125
|
-
io,
|
|
1126
|
-
);
|
|
1127
|
-
}
|
|
1334
|
+
return await finishAfterTurn(deliveryTurn.outcome);
|
|
1128
1335
|
}
|
|
1129
|
-
// Lawful persist + present is the caller's stop seam (auto-resume loop /
|
|
1130
|
-
// manual resume), not this retried host-turn function.
|
|
1131
|
-
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
1132
|
-
return await finishAfterTurn({ ...settledOutcome, ...deferredPersist });
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
|
|
1136
|
-
if (hostSignalFailed) {
|
|
1137
|
-
const resolutionInput = controlledFailureInputFromResolution(resolution!);
|
|
1138
|
-
const processCancelName = processCancelSignalName(env.signal);
|
|
1139
|
-
const stderrLogWriteDetails =
|
|
1140
|
-
stderrLogWriteFailure === undefined
|
|
1141
|
-
? undefined
|
|
1142
|
-
: { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
|
|
1143
|
-
const failed = await settleAfterTurnStarted(
|
|
1144
|
-
admitted,
|
|
1145
|
-
withEngineDetourInvocationScope({
|
|
1146
|
-
timedOut: result.timedOut,
|
|
1147
|
-
code: result.code,
|
|
1148
|
-
stderr: result.stderr,
|
|
1149
|
-
...resolutionInput,
|
|
1150
|
-
...(processCancelName === undefined
|
|
1151
|
-
? {}
|
|
1152
|
-
: { knownDiagnostic: processCancelDiagnostic(processCancelName) }),
|
|
1153
|
-
// Secondary fact only — never the cause. Rides on whichever channel
|
|
1154
|
-
// classification actually reads (knownFailure.details owns it when
|
|
1155
|
-
// a knownFailure exists; the top-level knownDetails otherwise).
|
|
1156
|
-
...(stderrLogWriteDetails === undefined
|
|
1157
|
-
? {}
|
|
1158
|
-
: resolutionInput.knownFailure !== undefined
|
|
1159
|
-
? {
|
|
1160
|
-
knownFailure: {
|
|
1161
|
-
...resolutionInput.knownFailure,
|
|
1162
|
-
details: { ...(resolutionInput.knownFailure.details ?? {}), ...stderrLogWriteDetails },
|
|
1163
|
-
},
|
|
1164
|
-
}
|
|
1165
|
-
: { knownDetails: stderrLogWriteDetails }),
|
|
1166
|
-
}, request.invocationScopeId),
|
|
1167
|
-
adapters,
|
|
1168
|
-
env.principalAuthority,
|
|
1169
|
-
io,
|
|
1170
|
-
persistRunState,
|
|
1171
|
-
);
|
|
1172
|
-
return await finishAfterTurn(
|
|
1173
|
-
withProcessCancelSkipAutoResume(
|
|
1174
|
-
{ ...failed, turnDispatched: true as const, ...deferredPersist },
|
|
1175
|
-
env.signal,
|
|
1176
|
-
),
|
|
1177
|
-
);
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
// Nothing else was wrong, but the durable stderr mirror itself failed to
|
|
1181
|
-
// write — that is the real (infrastructure) problem in this case, not a
|
|
1182
|
-
// lawful absence of a receipt. Honest and loud, not silently no_receipt.
|
|
1183
|
-
if (stderrLogWriteFailure !== undefined) {
|
|
1184
|
-
const failed = await settleAfterTurnStarted(
|
|
1185
|
-
admitted,
|
|
1186
|
-
withEngineDetourInvocationScope({
|
|
1187
|
-
timedOut: false,
|
|
1188
|
-
code: result.code,
|
|
1189
|
-
stderr: result.stderr,
|
|
1190
|
-
thrown: stderrLogWriteFailure,
|
|
1191
|
-
}, request.invocationScopeId),
|
|
1192
|
-
adapters,
|
|
1193
|
-
env.principalAuthority,
|
|
1194
|
-
io,
|
|
1195
|
-
persistRunState,
|
|
1196
|
-
);
|
|
1197
|
-
return await finishAfterTurn({ ...failed, turnDispatched: true as const, ...deferredPersist });
|
|
1198
1336
|
}
|
|
1199
1337
|
|
|
1338
|
+
const noReceiptScope = { ...courtScope, previewOnly: true as const };
|
|
1200
1339
|
const noReceipt = await attachRecordedSubmissions(
|
|
1201
1340
|
admitted,
|
|
1202
|
-
await settleHostEndedNoReceipt(
|
|
1203
|
-
|
|
1341
|
+
await settleHostEndedNoReceipt(
|
|
1342
|
+
admitted,
|
|
1343
|
+
env.principalAuthority,
|
|
1344
|
+
noReceiptScope,
|
|
1345
|
+
receiptDelivery.issuedDeliveryRequests(),
|
|
1346
|
+
) as T,
|
|
1347
|
+
noReceiptScope,
|
|
1204
1348
|
);
|
|
1205
1349
|
// #855: cancel during no_receipt settlement window is not lawful success.
|
|
1206
1350
|
const noReceiptCancel = processCancelSignalName(env.signal);
|
|
@@ -1216,12 +1360,13 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1216
1360
|
persistRunState,
|
|
1217
1361
|
deferredPersist,
|
|
1218
1362
|
invocationScopeId: request.invocationScopeId,
|
|
1363
|
+
notePackageFault: courtScope.notePackageFault,
|
|
1219
1364
|
}),
|
|
1220
1365
|
);
|
|
1221
1366
|
}
|
|
1222
1367
|
if (persistRunState) {
|
|
1223
1368
|
try {
|
|
1224
|
-
await persistReturnedRunState(admitted
|
|
1369
|
+
await persistReturnedRunState(admitted);
|
|
1225
1370
|
} catch (error) {
|
|
1226
1371
|
await recordBestEffortPostDispatchDiagnostic(
|
|
1227
1372
|
admitted,
|
|
@@ -1232,6 +1377,8 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1232
1377
|
}
|
|
1233
1378
|
}
|
|
1234
1379
|
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
1380
|
+
pendingSettlement = "no_receipt";
|
|
1381
|
+
pendingTerminal = noReceipt;
|
|
1235
1382
|
return await finishAfterTurn({
|
|
1236
1383
|
exitCode: exitCodeForTerminalOutcome(noReceipt.roleOutcome),
|
|
1237
1384
|
admitted,
|
|
@@ -1239,6 +1386,11 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1239
1386
|
turnDispatched: true as const,
|
|
1240
1387
|
...deferredPersist,
|
|
1241
1388
|
});
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
if (turnDispatched && !(error instanceof TurnDispatchedFailure)) {
|
|
1391
|
+
throw new TurnDispatchedFailure(error);
|
|
1392
|
+
}
|
|
1393
|
+
throw error;
|
|
1242
1394
|
} finally {
|
|
1243
1395
|
// Public manual resume (#987) holds no package writer lease.
|
|
1244
1396
|
if (lease !== undefined) {
|
|
@@ -1261,6 +1413,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1261
1413
|
}
|
|
1262
1414
|
}
|
|
1263
1415
|
|
|
1416
|
+
|
|
1264
1417
|
/**
|
|
1265
1418
|
* Shared resume continuation projection (#471 / #600 / #633 / #637 / #755 / #879):
|
|
1266
1419
|
* seat-table model/engine/timeout axes, restored correlation, and either
|
|
@@ -1329,8 +1482,9 @@ export function resumeTurnRequestProjectionOptions(
|
|
|
1329
1482
|
kind: "resume",
|
|
1330
1483
|
prompt,
|
|
1331
1484
|
},
|
|
1332
|
-
...(request.message === undefined ? {} : { courtAttemptId: randomUUID() }),
|
|
1333
1485
|
...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
|
|
1486
|
+
// #1132: one configured ceiling, already resolved by the caller (#422).
|
|
1487
|
+
deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
|
|
1334
1488
|
};
|
|
1335
1489
|
}
|
|
1336
1490
|
|
|
@@ -1349,6 +1503,8 @@ export function roleTurnOptions(
|
|
|
1349
1503
|
: { correlationId }),
|
|
1350
1504
|
continuation,
|
|
1351
1505
|
...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
|
|
1506
|
+
// #1132: one configured ceiling, already resolved by the caller (#422).
|
|
1507
|
+
deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
|
|
1352
1508
|
};
|
|
1353
1509
|
}
|
|
1354
1510
|
|
|
@@ -1397,6 +1553,7 @@ async function runSettledAutoResumeLoop<
|
|
|
1397
1553
|
}): Promise<{ exitCode: number; admitted?: A; terminal?: T }> {
|
|
1398
1554
|
const adapters = withOnceSuccessfulBeforeDispatch(input.adapters);
|
|
1399
1555
|
const dispatchEnv = input.dispatchEnv ?? input.env;
|
|
1556
|
+
const receiptDelivery = createReceiptDeliveryPolicy(input.env.autoResumeLimit);
|
|
1400
1557
|
return runWithAutoResumeLoop({
|
|
1401
1558
|
admitted: input.admitted,
|
|
1402
1559
|
principalAuthority: input.env.principalAuthority,
|
|
@@ -1419,14 +1576,14 @@ async function runSettledAutoResumeLoop<
|
|
|
1419
1576
|
...(lease === undefined ? {} : { lease }),
|
|
1420
1577
|
adapters,
|
|
1421
1578
|
persistRunState: false,
|
|
1579
|
+
receiptDelivery,
|
|
1422
1580
|
...(input.effectiveEngine === undefined
|
|
1423
1581
|
? {}
|
|
1424
1582
|
: { effectiveEngine: input.effectiveEngine }),
|
|
1425
1583
|
});
|
|
1426
1584
|
return settleDeferredPersist(
|
|
1427
1585
|
input.admitted,
|
|
1428
|
-
input.env
|
|
1429
|
-
adapters,
|
|
1586
|
+
input.env,
|
|
1430
1587
|
attemptIo,
|
|
1431
1588
|
result,
|
|
1432
1589
|
);
|
|
@@ -1619,13 +1776,13 @@ export async function runPostAdmissionSeatResume<
|
|
|
1619
1776
|
}
|
|
1620
1777
|
}
|
|
1621
1778
|
|
|
1622
|
-
// Open court continue, or new court for
|
|
1623
|
-
// (clause 0 新庭可再交卷; #833). Bare resume
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
) {
|
|
1779
|
+
// Open court continue, or a new court for a real re-summons
|
|
1780
|
+
// (clause 0 新庭可再交卷; #833). Bare resume — with or without caller
|
|
1781
|
+
// words — carries no summons and no open court, so it mints none:
|
|
1782
|
+
// the package must not turn ordinary continuation bytes into an audit
|
|
1783
|
+
// court whose attempt scope would reshape settlement (#1032 修订票:
|
|
1784
|
+
// 此要求不延伸到没有该传召庭次的裸 resume).
|
|
1785
|
+
if (openCourtAttemptId !== undefined || request.summons !== undefined) {
|
|
1629
1786
|
const courtAttemptId =
|
|
1630
1787
|
openCourtAttemptId ??
|
|
1631
1788
|
(turnRequest.courtAttemptId !== undefined &&
|
|
@@ -1778,8 +1935,7 @@ export function showResumeErrorPointer(
|
|
|
1778
1935
|
terminal: TerminalResult | undefined,
|
|
1779
1936
|
): void {
|
|
1780
1937
|
if (exitCode === 0 || terminal === undefined) return;
|
|
1781
|
-
const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path
|
|
1782
|
-
?? publishedFailureErrorPath(terminal);
|
|
1938
|
+
const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path;
|
|
1783
1939
|
if (publishedError !== undefined) writeResumeFailurePointer(io, publishedError);
|
|
1784
1940
|
}
|
|
1785
1941
|
|
|
@@ -2001,7 +2157,7 @@ export async function runPostAdmissionManualResume<
|
|
|
2001
2157
|
// through its own settleAfterTurnStarted-backed branches; a lawful result
|
|
2002
2158
|
// only ever reaches this point once that persist has already succeeded
|
|
2003
2159
|
// (#836 r12 class 3 dedup — one persist owner, not a second here).
|
|
2004
|
-
|
|
2160
|
+
await presentTerminal(result.terminal, io, admitted.runDirectory);
|
|
2005
2161
|
}
|
|
2006
2162
|
if (result.terminal !== undefined) {
|
|
2007
2163
|
(result.terminal as { autoResumeCount?: number }).autoResumeCount = 0;
|