@akagilnc/pi-workflow-roles 0.1.5406 → 0.1.5475
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/CLAUDE.md +1 -1
- package/README.md +42 -55
- package/README.zh-CN.md +59 -73
- package/THIRD_PARTY_NOTICES.md +4 -7
- package/dist/acp-host/production-host.js +8261 -9073
- package/dist/collector-tool-schemas.js +1 -1
- package/dist/compliance-transport.js +2 -5
- package/dist/engine-detour-usage.js +3 -30
- package/dist/gatekeeper-role.js +4 -18
- package/dist/headless-host/description.js +0 -209
- package/dist/headless-host/production-host.js +6973 -8035
- package/dist/host-contracts.js +8 -0
- package/dist/migrate-book-topology.js +30 -27
- package/dist/packaged-role-registry.js +1 -9
- package/dist/pi/role-turn-host.js +37 -19
- package/dist/public-cli/auto-resume.js +29 -66
- package/dist/public-cli/countersign-run.js +4 -18
- package/dist/public-cli/instruction-seat-run.js +59 -76
- package/dist/public-cli/main.js +9844 -10720
- package/dist/public-cli/option-definitions.js +1 -1
- package/dist/public-cli/post-admission.js +204 -285
- package/dist/public-cli/process-cancel.js +8 -0
- package/dist/public-cli/run-lifecycle.js +24 -94
- package/dist/public-cli/settlement.js +614 -1524
- package/dist/public-cli/terminal.js +23 -53
- package/dist/public-role-summons.js +3 -8
- package/dist/receipt-delivery-policy.js +11 -12
- package/dist/run-terminal-artifacts.js +19 -14
- package/dist/serialize-thrown-value.js +7 -1
- package/dist/submission-ledger.js +21 -89
- package/package.json +1 -1
- package/packets/fixer-repair.md +3 -1
- package/packets/judge-apply.md +7 -16
- package/packets/judge-authority.md +7 -17
- package/packets/judge-plan.md +7 -18
- package/packets/judge-review.md +8 -22
- package/packets/judge-submission.md +4 -7
- package/resources/collector-bot-handbook.md +2 -2
- package/resources/diarist-collect.md +2 -2
- package/resources/engine-dispatch.md +2 -2
- package/resources/navigator-route-playbook.md +1 -1
- package/resources/secretariat-ticket-identity.md +1 -1
- package/scripts/build-package.mjs +0 -2
- package/souls/coder-output-guide.md +3 -3
- package/souls/coder.md +2 -3
- package/souls/doctor-auditor.md +0 -3
- package/souls/fixer-auditor.md +0 -2
- package/souls/fixer-output-guide.md +3 -3
- package/souls/fixer.md +2 -2
- package/souls/gate-output-guide.md +1 -1
- package/souls/quality-law.md +2 -0
- package/src/acp-host/role-turn-host.ts +168 -75
- package/src/collector-tool-schemas.ts +1 -1
- package/src/compliance-transport.ts +3 -6
- package/src/engine-detour-usage.ts +3 -33
- package/src/external-host-turn-loop.ts +81 -36
- package/src/gatekeeper-role.ts +5 -20
- 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 +13 -10
- package/src/navigator-public-session.ts +1 -1
- package/src/navigator-session-contracts.ts +1 -1
- package/src/package-contracts/gatekeeper-output.ts +1 -1
- package/src/package-contracts/navigator-output.ts +1 -1
- package/src/package-contracts/terminating-infrastructure.ts +1 -1
- package/src/packaged-role-registry.ts +2 -10
- package/src/pi/adapter.ts +0 -3
- package/src/pi/role-turn-host.ts +42 -28
- package/src/prepared-role-turn.ts +2 -0
- package/src/public-cli/auto-resume.ts +27 -66
- package/src/public-cli/countersign-run.ts +4 -22
- package/src/public-cli/instruction-seat-run.ts +79 -80
- package/src/public-cli/option-definitions.ts +1 -1
- package/src/public-cli/post-admission.ts +264 -340
- package/src/public-cli/process-cancel.ts +9 -0
- package/src/public-cli/run-lifecycle.ts +25 -126
- package/src/public-cli/settlement.ts +792 -1912
- package/src/public-cli/terminal.ts +29 -69
- package/src/public-role-summons.ts +3 -8
- package/src/receipt-delivery-policy.ts +11 -12
- package/src/role-envelope.ts +16 -9
- package/src/role-runtime.ts +14 -81
- package/src/run-terminal-artifacts.ts +21 -17
- package/src/serialize-thrown-value.ts +7 -1
- package/src/submission-ledger.ts +30 -116
- 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
|
@@ -73,45 +73,36 @@ import {
|
|
|
73
73
|
import type { CredentialProviders, SeatModelConfig } from "./config.ts";
|
|
74
74
|
import {
|
|
75
75
|
clearCurrentCourt,
|
|
76
|
-
clearTypedProviderHttpObservation,
|
|
77
76
|
describeErrorIdentity,
|
|
78
77
|
markRunRunning,
|
|
79
78
|
readCurrentCourt,
|
|
80
79
|
recordCurrentCourt,
|
|
81
|
-
renderResumeCommand,
|
|
82
80
|
type CurrentCourtState,
|
|
83
81
|
type RunWriterLease,
|
|
84
|
-
type TypedProviderHttpObservation,
|
|
85
82
|
} from "./run-lifecycle.ts";
|
|
86
83
|
import {
|
|
87
84
|
processCancelDiagnostic,
|
|
88
85
|
processCancelSignalName,
|
|
89
86
|
type CatchableProcessSignal,
|
|
90
87
|
} from "./process-cancel.ts";
|
|
91
|
-
import { homeFromRunDirectory } from "../activation-ledger-topology.ts";
|
|
92
88
|
import { DEFAULT_ROLE_TURN_HOST } from "../host-descriptions.ts";
|
|
93
89
|
import { recordRunStart } from "../host-session-record.ts";
|
|
94
90
|
import {
|
|
95
|
-
attemptProducedFreshSubmission,
|
|
96
91
|
classifyPostAdmissionFailure,
|
|
97
|
-
controlledFailureInputFromResolution,
|
|
98
92
|
exitCodeForTerminalOutcome,
|
|
99
93
|
explicitInternalKnownFailureClassificationInput,
|
|
100
94
|
formatCliDiagnostic,
|
|
101
95
|
formatErrorCauseDetail,
|
|
102
|
-
|
|
103
|
-
inspectJudgeSession,
|
|
96
|
+
ledgerReadScope,
|
|
104
97
|
isLawfulTypedTerminalOutcome,
|
|
105
|
-
presentFailureTerminal,
|
|
106
|
-
publishedFailureErrorPath,
|
|
107
98
|
presentStructuralRejection,
|
|
108
|
-
resolveAuditedRunnerFailureResolution,
|
|
109
|
-
resolveControlledFailureResumeObservation,
|
|
110
99
|
settleFailureTerminalResult,
|
|
111
100
|
settleHostEndedNoReceipt,
|
|
101
|
+
noteSettlementFault,
|
|
112
102
|
attachRecordedSubmissions,
|
|
113
|
-
|
|
103
|
+
retainPackageFault,
|
|
114
104
|
type ControlledFailure,
|
|
105
|
+
type PackageSideFact,
|
|
115
106
|
type SettlementCourtScope,
|
|
116
107
|
} from "./settlement.ts";
|
|
117
108
|
import type { CliIo } from "./cli-io.ts";
|
|
@@ -123,9 +114,9 @@ import {
|
|
|
123
114
|
import {
|
|
124
115
|
ensureRealArtifactsDirectory,
|
|
125
116
|
persistReturnedRunState,
|
|
117
|
+
presentTerminal,
|
|
126
118
|
runWithAutoResumeLoop,
|
|
127
119
|
TurnDispatchedFailure,
|
|
128
|
-
writeHardenedArtifactFile,
|
|
129
120
|
} from "./auto-resume.ts";
|
|
130
121
|
|
|
131
122
|
import { isRecord, errorText } from "../unknown-value.ts";
|
|
@@ -152,11 +143,6 @@ function projectRelocatedTurnIdentity(
|
|
|
152
143
|
}
|
|
153
144
|
}
|
|
154
145
|
if (result.terminal !== undefined) {
|
|
155
|
-
rewritePublishedFailureErrorPath(
|
|
156
|
-
result.terminal,
|
|
157
|
-
relocation.oldRunDirectory,
|
|
158
|
-
relocation.newRunDirectory,
|
|
159
|
-
);
|
|
160
146
|
for (const artifact of result.terminal.artifacts) {
|
|
161
147
|
artifact.path = rewrite(artifact.path) as string;
|
|
162
148
|
}
|
|
@@ -188,6 +174,7 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
|
|
|
188
174
|
persistRunState: boolean;
|
|
189
175
|
deferredPersist: { needsPersist?: true } | Record<string, never>;
|
|
190
176
|
invocationScopeId: string | undefined;
|
|
177
|
+
notePackageFault?: (diagnostic: string) => void | Promise<void>;
|
|
191
178
|
}): Promise<{
|
|
192
179
|
exitCode: number;
|
|
193
180
|
admitted: A;
|
|
@@ -203,7 +190,13 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
|
|
|
203
190
|
timedOut: input.result.timedOut,
|
|
204
191
|
code: input.result.code,
|
|
205
192
|
stderr: input.result.stderr,
|
|
206
|
-
|
|
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,
|
|
207
200
|
},
|
|
208
201
|
input.invocationScopeId,
|
|
209
202
|
),
|
|
@@ -211,6 +204,7 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
|
|
|
211
204
|
input.env.principalAuthority,
|
|
212
205
|
input.io,
|
|
213
206
|
input.persistRunState,
|
|
207
|
+
input.notePackageFault,
|
|
214
208
|
);
|
|
215
209
|
return {
|
|
216
210
|
...failed,
|
|
@@ -259,41 +253,32 @@ export const POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE =
|
|
|
259
253
|
* zero durable trace (#840 bounce class 2); a healthy session never gets a
|
|
260
254
|
* redundant second copy (#840 bounce class 1: 同一业务规则只保留一个权威实现).
|
|
261
255
|
*/
|
|
262
|
-
async function recordBestEffortPostDispatchDiagnostic
|
|
263
|
-
admitted:
|
|
264
|
-
env: PostAdmissionEnv,
|
|
256
|
+
async function recordBestEffortPostDispatchDiagnostic(
|
|
257
|
+
admitted: Pick<AdmittedRoleInvocation, "runDirectory" | "principal">,
|
|
258
|
+
env: Pick<PostAdmissionEnv, "sessionAppender" | "principalAuthority">,
|
|
265
259
|
diagnostic: string,
|
|
266
|
-
io: CliIo,
|
|
260
|
+
io: Pick<CliIo, "stderr">,
|
|
267
261
|
): Promise<void> {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
262
|
+
await retainPackageFault({
|
|
263
|
+
runDirectory: admitted.runDirectory,
|
|
264
|
+
diagnostic,
|
|
265
|
+
appendSession: (payload) => env.sessionAppender(
|
|
272
266
|
env.principalAuthority,
|
|
273
267
|
admitted.principal,
|
|
274
268
|
POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE,
|
|
275
269
|
payload,
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
try {
|
|
290
|
-
io.stderr(formatCliDiagnostic(diagnostic));
|
|
291
|
-
if (retentionFailure !== undefined) {
|
|
292
|
-
io.stderr(formatCliDiagnostic(retentionFailure));
|
|
293
|
-
}
|
|
294
|
-
} catch {
|
|
295
|
-
// Presentation is best-effort beside an already-formed host terminal.
|
|
296
|
-
}
|
|
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);
|
|
297
282
|
}
|
|
298
283
|
|
|
299
284
|
export type PostAdmissionEnv = {
|
|
@@ -353,18 +338,15 @@ export type PostAdmissionAdapters<
|
|
|
353
338
|
A extends AdmittedRoleInvocation = AdmittedRoleInvocation,
|
|
354
339
|
T extends TerminalResult = TerminalResult,
|
|
355
340
|
> = {
|
|
341
|
+
/** A present Terminal already carries run history from settlement. */
|
|
356
342
|
trySettle: (
|
|
357
343
|
admitted: A,
|
|
358
344
|
authority: DurablePrincipalAuthority,
|
|
359
|
-
/** Current
|
|
360
|
-
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 },
|
|
361
347
|
) => Promise<T | undefined>;
|
|
362
348
|
/** Default: isLawfulTypedTerminalOutcome(terminal.roleOutcome). */
|
|
363
349
|
shouldPresentSettled?: (terminal: T) => boolean;
|
|
364
|
-
resolveRunnerKnownFailure?: (input: {
|
|
365
|
-
result: RoleTurnResult;
|
|
366
|
-
sessionFile: string;
|
|
367
|
-
}) => Promise<RoleTurnKnownFailure | undefined>;
|
|
368
350
|
/** A child office may pause this run with its unchanged terminal before a host turn starts. */
|
|
369
351
|
beforeDispatch?: (admitted: A, lease?: RunWriterLease) => Promise<T | void> | T | void;
|
|
370
352
|
/**
|
|
@@ -395,13 +377,22 @@ export type ControlledFailureInput = {
|
|
|
395
377
|
knownDiagnostic?: string;
|
|
396
378
|
/** Secondary evidence already owned by the typed production failure channel. */
|
|
397
379
|
knownDetails?: Readonly<Record<string, unknown>>;
|
|
398
|
-
typedHttpObservationSettled?: true;
|
|
399
|
-
typedHttpObservation?: TypedProviderHttpObservation;
|
|
400
380
|
/**
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
|
|
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.
|
|
405
396
|
*/
|
|
406
397
|
skipRunStateWrite?: boolean;
|
|
407
398
|
/** Public-invocation scope from the shared Host envelope (#537). */
|
|
@@ -460,6 +451,12 @@ export async function resolveResumeMethodMaterialAdapters<
|
|
|
460
451
|
input.emptyAdapters,
|
|
461
452
|
input.authority,
|
|
462
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
|
+
}),
|
|
463
460
|
);
|
|
464
461
|
return { kind: "terminal", ...terminal };
|
|
465
462
|
}
|
|
@@ -475,45 +472,41 @@ export async function presentControlledFailure<
|
|
|
475
472
|
authority: DurablePrincipalAuthority,
|
|
476
473
|
io: CliIo,
|
|
477
474
|
persistRunState = true,
|
|
475
|
+
notePackageFault?: (diagnostic: string) => void | Promise<void>,
|
|
478
476
|
): Promise<{
|
|
479
477
|
exitCode: number;
|
|
480
478
|
admitted: A;
|
|
481
479
|
terminal: TerminalResult;
|
|
482
480
|
}> {
|
|
483
481
|
const hasThrown = Object.hasOwn(failureInput, "thrown");
|
|
484
|
-
const
|
|
485
|
-
runDirectory: admitted.runDirectory,
|
|
486
|
-
...(failureInput.typedHttpObservationSettled === true
|
|
487
|
-
? {
|
|
488
|
-
typedHttpObservationSettled: true as const,
|
|
489
|
-
...(failureInput.typedHttpObservation === undefined
|
|
490
|
-
? {}
|
|
491
|
-
: { typedHttpObservation: failureInput.typedHttpObservation }),
|
|
492
|
-
}
|
|
493
|
-
: {}),
|
|
494
|
-
});
|
|
495
|
-
const knownFailure =
|
|
496
|
-
failureInput.knownFailure ?? resumeObservation.observationReadFailure;
|
|
497
|
-
const session =
|
|
498
|
-
!hasThrown &&
|
|
499
|
-
!failureInput.timedOut &&
|
|
500
|
-
knownFailure === undefined &&
|
|
501
|
-
failureInput.knownCause === undefined &&
|
|
502
|
-
admitted.principal !== undefined
|
|
503
|
-
? await inspectJudgeSession(authority.decode(admitted.principal).sessionFile)
|
|
504
|
-
: undefined;
|
|
482
|
+
const knownFailure = failureInput.knownFailure;
|
|
505
483
|
// knownFailure channel owns details when present; otherwise caller knownDetails.
|
|
506
484
|
const fromKnownFailure =
|
|
507
485
|
explicitInternalKnownFailureClassificationInput(knownFailure);
|
|
508
|
-
|
|
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({
|
|
509
493
|
timedOut: failureInput.timedOut,
|
|
510
494
|
code: failureInput.code,
|
|
511
495
|
stderr: failureInput.stderr,
|
|
512
496
|
...(hasThrown ? { thrown: failureInput.thrown } : {}),
|
|
513
|
-
|
|
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
|
|
514
502
|
? {}
|
|
515
|
-
: {
|
|
516
|
-
...fromKnownFailure
|
|
503
|
+
: { knownCause: fromKnownFailure.knownCause }),
|
|
504
|
+
...(fromKnownFailure.knownIdentity === undefined
|
|
505
|
+
? {}
|
|
506
|
+
: { knownIdentity: fromKnownFailure.knownIdentity }),
|
|
507
|
+
...(fromKnownFailure.knownDiagnostic === undefined
|
|
508
|
+
? {}
|
|
509
|
+
: { knownDiagnostic: fromKnownFailure.knownDiagnostic }),
|
|
517
510
|
...(failureInput.knownCause === undefined
|
|
518
511
|
? {}
|
|
519
512
|
: { knownCause: failureInput.knownCause }),
|
|
@@ -523,23 +516,35 @@ export async function presentControlledFailure<
|
|
|
523
516
|
...(failureInput.knownDiagnostic === undefined
|
|
524
517
|
? {}
|
|
525
518
|
: { knownDiagnostic: failureInput.knownDiagnostic }),
|
|
526
|
-
...(
|
|
519
|
+
...(failureInput.signal === undefined ? {} : { signal: failureInput.signal }),
|
|
520
|
+
...(failureInput.cancelName === undefined ? {} : { cancelName: failureInput.cancelName }),
|
|
521
|
+
...(failureInput.packageFact === undefined
|
|
522
|
+
? {}
|
|
523
|
+
: { packageFact: failureInput.packageFact }),
|
|
527
524
|
});
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
525
|
+
const failure = failureInput.stderr.length === 0 || failureInput.stderr === classified.diagnostic
|
|
526
|
+
? classified
|
|
527
|
+
: { ...classified, stderr: failureInput.stderr };
|
|
531
528
|
|
|
532
529
|
if (persistRunState && !failureInput.skipRunStateWrite) {
|
|
533
|
-
|
|
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
|
+
}
|
|
534
540
|
}
|
|
535
541
|
|
|
536
542
|
let publishedErrorPath: string | undefined;
|
|
537
543
|
let terminal: TerminalResult;
|
|
538
544
|
try {
|
|
539
545
|
const settled = await settleFailureTerminalResult(admitted, failure, authority, {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
: {}),
|
|
546
|
+
recordAttemptHistory: true,
|
|
547
|
+
...(notePackageFault === undefined ? {} : { notePackageFault }),
|
|
543
548
|
...(failureInput.invocationScopeId === undefined ||
|
|
544
549
|
failureInput.invocationScopeId.length === 0
|
|
545
550
|
? {}
|
|
@@ -553,7 +558,7 @@ export async function presentControlledFailure<
|
|
|
553
558
|
}
|
|
554
559
|
throw error;
|
|
555
560
|
}
|
|
556
|
-
|
|
561
|
+
await presentTerminal(terminal, io, admitted.runDirectory);
|
|
557
562
|
return {
|
|
558
563
|
exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
|
|
559
564
|
admitted,
|
|
@@ -582,6 +587,7 @@ async function settleAfterTurnStarted<
|
|
|
582
587
|
authority: DurablePrincipalAuthority,
|
|
583
588
|
io: CliIo,
|
|
584
589
|
persistRunState: boolean,
|
|
590
|
+
notePackageFault?: (diagnostic: string) => void | Promise<void>,
|
|
585
591
|
): Promise<{ exitCode: number; admitted: A; terminal: T }> {
|
|
586
592
|
try {
|
|
587
593
|
return (await presentControlledFailure(
|
|
@@ -591,6 +597,7 @@ async function settleAfterTurnStarted<
|
|
|
591
597
|
authority,
|
|
592
598
|
io,
|
|
593
599
|
persistRunState,
|
|
600
|
+
notePackageFault,
|
|
594
601
|
)) as { exitCode: number; admitted: A; terminal: T };
|
|
595
602
|
} catch (error) {
|
|
596
603
|
throw new TurnDispatchedFailure(error);
|
|
@@ -601,47 +608,32 @@ async function settleAfterTurnStarted<
|
|
|
601
608
|
* Persist run-state for a result dispatchPostAdmissionTurn deferred
|
|
602
609
|
* (needsPersist — station-child / resumable auto-resume loop, #416/#840):
|
|
603
610
|
* that write must land outside the loop's own retried-dispatch try, but its
|
|
604
|
-
* failure
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
* classify/artifact/Terminal (#836 r12 class 3), and never lawful/non-lawful
|
|
608
|
-
* settling differently (#836 r13 class 2: a caller's io here may be a no-op,
|
|
609
|
-
* so a stderr-only trace is never seen and the failure is otherwise
|
|
610
|
-
* swallowed — 失败诚实宪法 真因必须落痕). Both cases settle through
|
|
611
|
-
* settleAfterTurnStarted, which attaches the run's already-recorded ledger
|
|
612
|
-
* submissions to the new failure terminal and returns skipAutoResume so the
|
|
613
|
-
* 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.
|
|
614
614
|
*/
|
|
615
615
|
async function settleDeferredPersist<
|
|
616
616
|
A extends AdmittedRoleInvocation,
|
|
617
617
|
T extends TerminalResult,
|
|
618
618
|
>(
|
|
619
619
|
admitted: A,
|
|
620
|
-
|
|
621
|
-
adapters: PostAdmissionAdapters<A, T>,
|
|
620
|
+
env: PostAdmissionEnv,
|
|
622
621
|
io: CliIo,
|
|
623
622
|
result: DispatchOutcomeFor<A, T>,
|
|
624
623
|
): Promise<DispatchOutcomeFor<A, T>> {
|
|
625
624
|
if (result.needsPersist !== true || result.terminal === undefined) return result;
|
|
626
625
|
const { needsPersist: _needsPersist, ...settledResult } = result;
|
|
627
|
-
const lawful = isLawfulTypedTerminalOutcome(result.terminal.roleOutcome);
|
|
628
626
|
try {
|
|
629
|
-
await persistReturnedRunState(admitted
|
|
627
|
+
await persistReturnedRunState(admitted);
|
|
630
628
|
return settledResult;
|
|
631
629
|
} catch (error) {
|
|
632
|
-
|
|
630
|
+
await recordBestEffortPostDispatchDiagnostic(
|
|
633
631
|
admitted,
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
authority,
|
|
632
|
+
env,
|
|
633
|
+
`run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
637
634
|
io,
|
|
638
|
-
true,
|
|
639
635
|
);
|
|
640
|
-
return
|
|
641
|
-
...failed,
|
|
642
|
-
turnDispatched: true as const,
|
|
643
|
-
skipAutoResume: true as const,
|
|
644
|
-
};
|
|
636
|
+
return settledResult;
|
|
645
637
|
}
|
|
646
638
|
}
|
|
647
639
|
|
|
@@ -740,21 +732,16 @@ async function settleCompletedHostTurn<
|
|
|
740
732
|
persistRunState: boolean;
|
|
741
733
|
deferredPersist: { needsPersist?: true } | Record<string, never>;
|
|
742
734
|
finishAfterTurn: (result: DispatchOutcomeFor<A, T>) => Promise<DispatchOutcomeFor<A, T>>;
|
|
735
|
+
courtScope: SettlementCourtScope & { notePackageFault: (diagnostic: string) => Promise<void> };
|
|
736
|
+
markPending: (terminal: T) => void;
|
|
743
737
|
}): Promise<{ kind: "settled"; outcome: DispatchOutcomeFor<A, T> } | { kind: "still-missing" }> {
|
|
744
738
|
const { admitted, env, io, adapters, shouldPresent, request, result, persistRunState, deferredPersist } = input;
|
|
745
739
|
const finishAfterTurn = input.finishAfterTurn;
|
|
746
740
|
|
|
747
|
-
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
// dropped silently (catch-and-continue with no trace is a defect) — but
|
|
752
|
-
// it also must never become THE controlling failure and wash out a
|
|
753
|
-
// primary child signal that already arrived on `result`, or an
|
|
754
|
-
// already-sealed accepted payload (#836: a secondary failure must not
|
|
755
|
-
// overwrite an already-known cause or an already-recorded leg). It rides
|
|
756
|
-
// beside whatever the real classification below determines, and only
|
|
757
|
-
// becomes the reported failure itself when nothing else is wrong.
|
|
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.
|
|
758
745
|
//
|
|
759
746
|
// Everything below runs after the host turn genuinely started (#840 r9
|
|
760
747
|
// 判词 class 1 boundary — 覆盖 executeTurn 已启动后至返回带 turnDispatched
|
|
@@ -780,12 +767,9 @@ async function settleCompletedHostTurn<
|
|
|
780
767
|
);
|
|
781
768
|
} catch (error) {
|
|
782
769
|
stderrLogWriteFailure = error;
|
|
783
|
-
// Best-effort:
|
|
784
|
-
//
|
|
785
|
-
//
|
|
786
|
-
// every auto-resume attempt's io is dummyIo (#840 r9 判词 class 1). It
|
|
787
|
-
// still rides beside the classification below and, if nothing else is
|
|
788
|
-
// wrong, becomes the reported failure itself (#836).
|
|
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.
|
|
789
773
|
await recordBestEffortPostDispatchDiagnostic(
|
|
790
774
|
admitted,
|
|
791
775
|
env,
|
|
@@ -794,151 +778,51 @@ async function settleCompletedHostTurn<
|
|
|
794
778
|
);
|
|
795
779
|
}
|
|
796
780
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
// accepted-settlement cleanup all settle through this one catch — a
|
|
802
|
-
// throw from any of them (including the session-file decode itself)
|
|
803
|
-
// still routes through settleAfterTurnStarted's TurnDispatchedFailure
|
|
804
|
-
// instead of losing turnDispatched to an uncaught throw. The facts are
|
|
805
|
-
// resolved before trySettle (#836) so an already-accepted settlement is
|
|
806
|
-
// never presented over a real current host/runner failure signal or a
|
|
807
|
-
// real stderr.log durable-write failure — both stay a real failure with
|
|
808
|
-
// the recorded payload riding beside it, not replacing it.
|
|
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.
|
|
809
785
|
let settled: T | undefined;
|
|
810
786
|
let settledOutcome:
|
|
811
787
|
| { exitCode: number; admitted: A; terminal: T; turnDispatched: true }
|
|
812
788
|
| undefined;
|
|
813
|
-
|
|
814
|
-
|
|
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;
|
|
815
800
|
try {
|
|
816
|
-
|
|
817
|
-
admitted.
|
|
818
|
-
|
|
819
|
-
: "";
|
|
820
|
-
const runnerKnownFailure =
|
|
821
|
-
adapters.resolveRunnerKnownFailure !== undefined && sessionFile !== ""
|
|
822
|
-
? await adapters.resolveRunnerKnownFailure({ result, sessionFile })
|
|
823
|
-
: result.knownFailure;
|
|
824
|
-
resolution = await resolveAuditedRunnerFailureResolution({
|
|
825
|
-
runner: runnerKnownFailure,
|
|
826
|
-
sessionFile,
|
|
827
|
-
credential: undefined,
|
|
828
|
-
runDirectory: admitted.runDirectory,
|
|
829
|
-
});
|
|
830
|
-
// A direct, current signal from the host/runner itself (timeout / host
|
|
831
|
-
// knownFailure / runner knownFailure / missing credential) is a real
|
|
832
|
-
// problem regardless of what else already settled — a settled accepted
|
|
833
|
-
// outcome must not paper over it (it rides beside the recorded payload
|
|
834
|
-
// via `submissions`, never replacing the payload). The audited
|
|
835
|
-
// resolution's own typed session read (malformed JSONL, provider-stop,
|
|
836
|
-
// an illegal/unsealed accepted status, a stale/superseded typed-HTTP
|
|
837
|
-
// observation, ...) makes a run with no sealed acceptance a true
|
|
838
|
-
// failure too — it must not be discarded into a lawful no_receipt just
|
|
839
|
-
// because the raw runner signal alone looked clean. But it must not
|
|
840
|
-
// retroactively invalidate an acceptance that already sealed this turn
|
|
841
|
-
// — a resolved 429 observed earlier in the same session is exactly
|
|
842
|
-
// that.
|
|
843
|
-
const processCancelName = processCancelSignalName(env.signal);
|
|
844
|
-
const directHostFailureSignal =
|
|
845
|
-
result.timedOut
|
|
846
|
-
|| result.knownFailure !== undefined
|
|
847
|
-
|| runnerKnownFailure !== undefined
|
|
848
|
-
|| processCancelName !== undefined;
|
|
849
|
-
hostSignalFailed =
|
|
850
|
-
directHostFailureSignal
|
|
851
|
-
|| (result.code !== null && result.code !== 0)
|
|
852
|
-
|| resolution.knownFailure !== undefined;
|
|
853
|
-
settled = await adapters.trySettle(admitted, env.principalAuthority, courtScope);
|
|
854
|
-
if (settled !== undefined) {
|
|
855
|
-
settled = await attachRecordedSubmissions(admitted, settled, courtScope) as T;
|
|
856
|
-
}
|
|
857
|
-
// #836 r12 class 2: an accepted/audit_escalation settlement can be
|
|
858
|
-
// entirely a prior attempt's stale payload (courtAttempt is a
|
|
859
|
-
// recording tag, not a visibility gate — attachRecordedSubmissions
|
|
860
|
-
// above still surfaces that historical payload honestly either way).
|
|
861
|
-
// Consume the ledger's own subject.attemptId (submission-ledger.ts)
|
|
862
|
-
// only to learn whether *this* attempt itself produced a fresh seal —
|
|
863
|
-
// never to filter what presents.
|
|
864
|
-
const settledIsFreshThisAttempt =
|
|
865
|
-
settled === undefined
|
|
866
|
-
|| (settled.roleOutcome.kind !== "accepted" && settled.roleOutcome.kind !== "audit_escalation")
|
|
867
|
-
? true
|
|
868
|
-
: await attemptProducedFreshSubmission(admitted, courtScope);
|
|
869
|
-
// A lawful settled outcome already reached this turn takes precedence
|
|
870
|
-
// over a later bare exit-code / session-inspection signal (trailing
|
|
871
|
-
// nonzero exit, late stderr noise, a stale already-superseded
|
|
872
|
-
// typed-HTTP observation, ...) — but never over a direct current
|
|
873
|
-
// host/runner failure signal, nor over a real stderr.log durable-write
|
|
874
|
-
// failure (confirmed infrastructure trouble, not weak/bare evidence),
|
|
875
|
-
// both of which stay a real failure with the recorded payload riding
|
|
876
|
-
// beside it, not replacing it (#836: never kill an already-recorded
|
|
877
|
-
// leg, but never wash a real failure away either). Note: the narrower
|
|
878
|
-
// directHostFailureSignal gates acceptance here; the broader
|
|
879
|
-
// hostSignalFailed (adds bare exit code / resolution.knownFailure)
|
|
880
|
-
// only gates the no-settlement fallback below — except when the
|
|
881
|
-
// settlement itself is entirely stale (no fresh seal this attempt),
|
|
882
|
-
// where a bare nonzero exit / resolution failure must not be outranked
|
|
883
|
-
// by someone else's earlier success (#836 r12 class 2).
|
|
884
|
-
const staleAcceptanceOutranksRealFailure = !settledIsFreshThisAttempt && hostSignalFailed;
|
|
885
|
-
// #855: re-read cancel after trySettle/attach awaits — a signal in this
|
|
886
|
-
// window must not land as lawful accepted.
|
|
887
|
-
const cancelAfterSettle = processCancelSignalName(env.signal);
|
|
888
|
-
if (
|
|
889
|
-
settled !== undefined
|
|
890
|
-
&& shouldPresent(settled)
|
|
891
|
-
&& !directHostFailureSignal
|
|
892
|
-
&& !staleAcceptanceOutranksRealFailure
|
|
893
|
-
&& stderrLogWriteFailure === undefined
|
|
894
|
-
&& cancelAfterSettle === undefined
|
|
895
|
-
) {
|
|
896
|
-
// This court sealed — drop open-court pointer (bare resume no longer continues it).
|
|
801
|
+
if (!hostSignalFailed) {
|
|
802
|
+
settled = await adapters.trySettle(admitted, env.principalAuthority, { ...courtScope, previewOnly: true });
|
|
803
|
+
const cancelAfterSettle = processCancelSignalName(env.signal);
|
|
897
804
|
if (
|
|
898
|
-
settled
|
|
899
|
-
|
|
900
|
-
|
|
805
|
+
settled !== undefined
|
|
806
|
+
&& shouldPresent(settled)
|
|
807
|
+
&& cancelAfterSettle === undefined
|
|
901
808
|
) {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
// Preserve the cleanup failure as a post-dispatch diagnostic; the
|
|
909
|
-
// accepted settlement remains authoritative (#840 r9 判词 class 1).
|
|
910
|
-
await recordBestEffortPostDispatchDiagnostic(
|
|
911
|
-
admitted,
|
|
912
|
-
env,
|
|
913
|
-
`current-court cleanup failed after accepted settlement (best-effort continue): ${describeErrorIdentity(error)}`,
|
|
914
|
-
io,
|
|
915
|
-
);
|
|
916
|
-
}
|
|
809
|
+
settledOutcome = {
|
|
810
|
+
exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
|
|
811
|
+
admitted,
|
|
812
|
+
terminal: settled,
|
|
813
|
+
turnDispatched: true as const,
|
|
814
|
+
};
|
|
917
815
|
}
|
|
918
|
-
settledOutcome = {
|
|
919
|
-
exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
|
|
920
|
-
admitted,
|
|
921
|
-
terminal: settled,
|
|
922
|
-
turnDispatched: true as const,
|
|
923
|
-
};
|
|
924
816
|
}
|
|
925
817
|
} catch (error) {
|
|
926
|
-
//
|
|
927
|
-
//
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
code: result.code,
|
|
933
|
-
stderr: result.stderr,
|
|
934
|
-
thrown: error,
|
|
935
|
-
}, request.invocationScopeId),
|
|
936
|
-
adapters,
|
|
937
|
-
env.principalAuthority,
|
|
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)}`,
|
|
938
824
|
io,
|
|
939
|
-
persistRunState,
|
|
940
825
|
);
|
|
941
|
-
return { kind: "settled", outcome: await finishAfterTurn({ ...settledFailure, turnDispatched: true as const, ...deferredPersist }) };
|
|
942
826
|
}
|
|
943
827
|
if (settledOutcome !== undefined) {
|
|
944
828
|
// #855: final cancel re-read before lawful seal — settlement window may
|
|
@@ -956,12 +840,13 @@ async function settleCompletedHostTurn<
|
|
|
956
840
|
persistRunState,
|
|
957
841
|
deferredPersist,
|
|
958
842
|
invocationScopeId: request.invocationScopeId,
|
|
843
|
+
notePackageFault: courtScope.notePackageFault,
|
|
959
844
|
}),
|
|
960
845
|
) };
|
|
961
846
|
}
|
|
962
847
|
if (persistRunState) {
|
|
963
848
|
try {
|
|
964
|
-
await persistReturnedRunState(admitted
|
|
849
|
+
await persistReturnedRunState(admitted);
|
|
965
850
|
} catch (error) {
|
|
966
851
|
await recordBestEffortPostDispatchDiagnostic(
|
|
967
852
|
admitted,
|
|
@@ -971,17 +856,16 @@ async function settleCompletedHostTurn<
|
|
|
971
856
|
);
|
|
972
857
|
}
|
|
973
858
|
}
|
|
974
|
-
//
|
|
859
|
+
// Persist + present is the caller's stop seam (auto-resume loop /
|
|
975
860
|
// manual resume), not this retried host-turn function.
|
|
976
861
|
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
862
|
+
input.markPending(settledOutcome.terminal);
|
|
977
863
|
return { kind: "settled", outcome: await finishAfterTurn({ ...settledOutcome, ...deferredPersist }) };
|
|
978
864
|
}
|
|
979
865
|
|
|
980
866
|
// Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
|
|
981
867
|
if (hostSignalFailed) {
|
|
982
|
-
const
|
|
983
|
-
const processCancelName = processCancelSignalName(env.signal);
|
|
984
|
-
const stderrLogWriteDetails =
|
|
868
|
+
const stderrLogWriteFact =
|
|
985
869
|
stderrLogWriteFailure === undefined
|
|
986
870
|
? undefined
|
|
987
871
|
: { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
|
|
@@ -991,28 +875,23 @@ async function settleCompletedHostTurn<
|
|
|
991
875
|
timedOut: result.timedOut,
|
|
992
876
|
code: result.code,
|
|
993
877
|
stderr: result.stderr,
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
//
|
|
1000
|
-
//
|
|
1001
|
-
...(
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
knownFailure: {
|
|
1006
|
-
...resolutionInput.knownFailure,
|
|
1007
|
-
details: { ...(resolutionInput.knownFailure.details ?? {}), ...stderrLogWriteDetails },
|
|
1008
|
-
},
|
|
1009
|
-
}
|
|
1010
|
-
: { knownDetails: stderrLogWriteDetails }),
|
|
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 }),
|
|
1011
889
|
}, request.invocationScopeId),
|
|
1012
890
|
adapters,
|
|
1013
891
|
env.principalAuthority,
|
|
1014
892
|
io,
|
|
1015
893
|
persistRunState,
|
|
894
|
+
courtScope.notePackageFault,
|
|
1016
895
|
);
|
|
1017
896
|
return { kind: "settled", outcome: await finishAfterTurn(
|
|
1018
897
|
withProcessCancelSkipAutoResume(
|
|
@@ -1022,28 +901,6 @@ async function settleCompletedHostTurn<
|
|
|
1022
901
|
) };
|
|
1023
902
|
}
|
|
1024
903
|
|
|
1025
|
-
// Nothing else was wrong, but the durable stderr mirror itself failed to
|
|
1026
|
-
// write — that is the real (infrastructure) problem in this case, not a
|
|
1027
|
-
// lawful absence of a receipt. Honest and loud, not silently no_receipt.
|
|
1028
|
-
if (stderrLogWriteFailure !== undefined) {
|
|
1029
|
-
const failed = await settleAfterTurnStarted(
|
|
1030
|
-
admitted,
|
|
1031
|
-
withEngineDetourInvocationScope({
|
|
1032
|
-
timedOut: false,
|
|
1033
|
-
code: result.code,
|
|
1034
|
-
stderr: result.stderr,
|
|
1035
|
-
thrown: stderrLogWriteFailure,
|
|
1036
|
-
}, request.invocationScopeId),
|
|
1037
|
-
adapters,
|
|
1038
|
-
env.principalAuthority,
|
|
1039
|
-
io,
|
|
1040
|
-
persistRunState,
|
|
1041
|
-
);
|
|
1042
|
-
return { kind: "settled", outcome: await finishAfterTurn({ ...failed, turnDispatched: true as const, ...deferredPersist }) };
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// No receipt at all after a clean host turn: lawful no_receipt, but the
|
|
1046
|
-
// caller owns whether to催交 first (票面 3).
|
|
1047
904
|
return { kind: "still-missing" };
|
|
1048
905
|
}
|
|
1049
906
|
|
|
@@ -1074,6 +931,24 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1074
931
|
* lease release — success and failure alike. When a primary failure terminal already
|
|
1075
932
|
* exists, keep that cause and leave relocate failure on the shared diagnostic channel.
|
|
1076
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
|
+
|
|
1077
952
|
let afterDispatchApplied = false;
|
|
1078
953
|
const receiptDelivery = input.receiptDelivery ?? createReceiptDeliveryPolicy(env.autoResumeLimit);
|
|
1079
954
|
let turnDispatched = false;
|
|
@@ -1083,12 +958,13 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1083
958
|
// again here would double-deliver on one run. External hosts (ACP / headless
|
|
1084
959
|
// CLI) have no such in-child loop, so the AK execution seam owns it for them.
|
|
1085
960
|
const akSeamDeliversReceipt =
|
|
1086
|
-
|
|
961
|
+
env.host !== undefined && env.host !== "" && env.host !== DEFAULT_ROLE_TURN_HOST;
|
|
1087
962
|
const finishAfterTurn = async (result: DispatchOutcomeFor<A, T>): Promise<DispatchOutcomeFor<A, T>> => {
|
|
1088
963
|
if (afterDispatchApplied) return result;
|
|
1089
964
|
afterDispatchApplied = true;
|
|
1090
|
-
// #855: re-read cancel before
|
|
1091
|
-
//
|
|
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
|
|
1092
968
|
// may already be terminal from a prior lawful persist; do not reopen as resumable.
|
|
1093
969
|
const cancelBeforeFinish = processCancelSignalName(env.signal);
|
|
1094
970
|
if (
|
|
@@ -1105,6 +981,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1105
981
|
code: null,
|
|
1106
982
|
stderr: "",
|
|
1107
983
|
knownDiagnostic: processCancelDiagnostic(cancelBeforeFinish),
|
|
984
|
+
cancelName: cancelBeforeFinish,
|
|
1108
985
|
skipRunStateWrite: true,
|
|
1109
986
|
},
|
|
1110
987
|
request.invocationScopeId,
|
|
@@ -1113,12 +990,51 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1113
990
|
env.principalAuthority,
|
|
1114
991
|
io,
|
|
1115
992
|
persistRunState,
|
|
993
|
+
courtScope.notePackageFault,
|
|
1116
994
|
)),
|
|
1117
995
|
turnDispatched: true as const,
|
|
1118
996
|
skipAutoResume: true as const,
|
|
1119
997
|
...deferredPersist,
|
|
1120
998
|
};
|
|
1121
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
|
+
}
|
|
1122
1038
|
try {
|
|
1123
1039
|
// #858 / #1071: the first identifiable ticketNumber field declaration
|
|
1124
1040
|
// files an unbound run (integer, digit string, or leading #N token).
|
|
@@ -1130,7 +1046,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1130
1046
|
const rows = await readRecordedSubmissionRows(
|
|
1131
1047
|
admitted.projectRoot,
|
|
1132
1048
|
admitted.runId,
|
|
1133
|
-
|
|
1049
|
+
ledgerReadScope(admitted),
|
|
1134
1050
|
);
|
|
1135
1051
|
let ticketNumber: number | undefined;
|
|
1136
1052
|
for (const row of rows) {
|
|
@@ -1185,6 +1101,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1185
1101
|
env.principalAuthority,
|
|
1186
1102
|
io,
|
|
1187
1103
|
persistRunState,
|
|
1104
|
+
courtScope.notePackageFault,
|
|
1188
1105
|
)) as { exitCode: number; admitted: A; terminal: T },
|
|
1189
1106
|
...(result.turnDispatched === true ? { turnDispatched: true as const } : {}),
|
|
1190
1107
|
...(result.skipAutoResume === true ? { skipAutoResume: true as const } : {}),
|
|
@@ -1195,7 +1112,6 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1195
1112
|
try {
|
|
1196
1113
|
// #987: do not pre-block or classify host dispatch from AK credential
|
|
1197
1114
|
// catalog facts. Per-invocation runner/host evidence owns provider identity.
|
|
1198
|
-
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
1199
1115
|
// The authoritative host write (markRunRunning) is delayed to just before
|
|
1200
1116
|
// executeTurn — not merely past beforeDispatch (#840 r9 判词 class 2). Any
|
|
1201
1117
|
// pre-turn retry (beforeDispatch, continuation assembly) must not commit a host
|
|
@@ -1223,6 +1139,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1223
1139
|
env.principalAuthority,
|
|
1224
1140
|
io,
|
|
1225
1141
|
persistRunState,
|
|
1142
|
+
courtScope.notePackageFault,
|
|
1226
1143
|
)) as { exitCode: number; admitted: A; terminal: T };
|
|
1227
1144
|
return { ...settled, ...deferredPersist };
|
|
1228
1145
|
}
|
|
@@ -1265,6 +1182,10 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1265
1182
|
|
|
1266
1183
|
let result: RoleTurnResult;
|
|
1267
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.
|
|
1268
1189
|
recordRunStart(admitted.runDirectory);
|
|
1269
1190
|
turnDispatched = true;
|
|
1270
1191
|
result = await env.roleTurnHost.executeTurn(turnRequest);
|
|
@@ -1276,15 +1197,16 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1276
1197
|
timedOut: false,
|
|
1277
1198
|
code: null,
|
|
1278
1199
|
stderr: "",
|
|
1279
|
-
thrown
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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,
|
|
1283
1204
|
}, request.invocationScopeId),
|
|
1284
1205
|
adapters,
|
|
1285
1206
|
env.principalAuthority,
|
|
1286
1207
|
io,
|
|
1287
1208
|
persistRunState,
|
|
1209
|
+
courtScope.notePackageFault,
|
|
1288
1210
|
);
|
|
1289
1211
|
return await finishAfterTurn(
|
|
1290
1212
|
withProcessCancelSkipAutoResume(
|
|
@@ -1307,6 +1229,8 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1307
1229
|
persistRunState,
|
|
1308
1230
|
deferredPersist,
|
|
1309
1231
|
finishAfterTurn,
|
|
1232
|
+
courtScope,
|
|
1233
|
+
markPending: (terminal) => { pendingSettlement = "sealed"; pendingTerminal = terminal; },
|
|
1310
1234
|
});
|
|
1311
1235
|
if (settledTurn.kind === "settled") return settledTurn.outcome;
|
|
1312
1236
|
|
|
@@ -1383,6 +1307,8 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1383
1307
|
persistRunState: false,
|
|
1384
1308
|
deferredPersist,
|
|
1385
1309
|
finishAfterTurn,
|
|
1310
|
+
courtScope,
|
|
1311
|
+
markPending: (terminal) => { pendingSettlement = "sealed"; pendingTerminal = terminal; },
|
|
1386
1312
|
});
|
|
1387
1313
|
// The delivery turn's own outcome IS the run's outcome, except when it
|
|
1388
1314
|
// simply still has no receipt — that is the loop's own case to keep going.
|
|
@@ -1395,9 +1321,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1395
1321
|
// NOT write; this one, which ends the run, must.
|
|
1396
1322
|
if (persistRunState && deliveryTurn.outcome.terminal !== undefined) {
|
|
1397
1323
|
try {
|
|
1398
|
-
await persistReturnedRunState(admitted
|
|
1399
|
-
lawful: isLawfulTypedTerminalOutcome(deliveryTurn.outcome.terminal.roleOutcome),
|
|
1400
|
-
});
|
|
1324
|
+
await persistReturnedRunState(admitted);
|
|
1401
1325
|
} catch (error) {
|
|
1402
1326
|
await recordBestEffortPostDispatchDiagnostic(
|
|
1403
1327
|
admitted,
|
|
@@ -1411,7 +1335,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1411
1335
|
}
|
|
1412
1336
|
}
|
|
1413
1337
|
|
|
1414
|
-
const noReceiptScope =
|
|
1338
|
+
const noReceiptScope = { ...courtScope, previewOnly: true as const };
|
|
1415
1339
|
const noReceipt = await attachRecordedSubmissions(
|
|
1416
1340
|
admitted,
|
|
1417
1341
|
await settleHostEndedNoReceipt(
|
|
@@ -1436,12 +1360,13 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1436
1360
|
persistRunState,
|
|
1437
1361
|
deferredPersist,
|
|
1438
1362
|
invocationScopeId: request.invocationScopeId,
|
|
1363
|
+
notePackageFault: courtScope.notePackageFault,
|
|
1439
1364
|
}),
|
|
1440
1365
|
);
|
|
1441
1366
|
}
|
|
1442
1367
|
if (persistRunState) {
|
|
1443
1368
|
try {
|
|
1444
|
-
await persistReturnedRunState(admitted
|
|
1369
|
+
await persistReturnedRunState(admitted);
|
|
1445
1370
|
} catch (error) {
|
|
1446
1371
|
await recordBestEffortPostDispatchDiagnostic(
|
|
1447
1372
|
admitted,
|
|
@@ -1452,6 +1377,8 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1452
1377
|
}
|
|
1453
1378
|
}
|
|
1454
1379
|
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
1380
|
+
pendingSettlement = "no_receipt";
|
|
1381
|
+
pendingTerminal = noReceipt;
|
|
1455
1382
|
return await finishAfterTurn({
|
|
1456
1383
|
exitCode: exitCodeForTerminalOutcome(noReceipt.roleOutcome),
|
|
1457
1384
|
admitted,
|
|
@@ -1555,7 +1482,6 @@ export function resumeTurnRequestProjectionOptions(
|
|
|
1555
1482
|
kind: "resume",
|
|
1556
1483
|
prompt,
|
|
1557
1484
|
},
|
|
1558
|
-
...(request.message === undefined ? {} : { courtAttemptId: randomUUID() }),
|
|
1559
1485
|
...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
|
|
1560
1486
|
// #1132: one configured ceiling, already resolved by the caller (#422).
|
|
1561
1487
|
deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
|
|
@@ -1657,8 +1583,7 @@ async function runSettledAutoResumeLoop<
|
|
|
1657
1583
|
});
|
|
1658
1584
|
return settleDeferredPersist(
|
|
1659
1585
|
input.admitted,
|
|
1660
|
-
input.env
|
|
1661
|
-
adapters,
|
|
1586
|
+
input.env,
|
|
1662
1587
|
attemptIo,
|
|
1663
1588
|
result,
|
|
1664
1589
|
);
|
|
@@ -1851,13 +1776,13 @@ export async function runPostAdmissionSeatResume<
|
|
|
1851
1776
|
}
|
|
1852
1777
|
}
|
|
1853
1778
|
|
|
1854
|
-
// Open court continue, or new court for
|
|
1855
|
-
// (clause 0 新庭可再交卷; #833). Bare resume
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
) {
|
|
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) {
|
|
1861
1786
|
const courtAttemptId =
|
|
1862
1787
|
openCourtAttemptId ??
|
|
1863
1788
|
(turnRequest.courtAttemptId !== undefined &&
|
|
@@ -2010,8 +1935,7 @@ export function showResumeErrorPointer(
|
|
|
2010
1935
|
terminal: TerminalResult | undefined,
|
|
2011
1936
|
): void {
|
|
2012
1937
|
if (exitCode === 0 || terminal === undefined) return;
|
|
2013
|
-
const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path
|
|
2014
|
-
?? publishedFailureErrorPath(terminal);
|
|
1938
|
+
const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path;
|
|
2015
1939
|
if (publishedError !== undefined) writeResumeFailurePointer(io, publishedError);
|
|
2016
1940
|
}
|
|
2017
1941
|
|
|
@@ -2233,7 +2157,7 @@ export async function runPostAdmissionManualResume<
|
|
|
2233
2157
|
// through its own settleAfterTurnStarted-backed branches; a lawful result
|
|
2234
2158
|
// only ever reaches this point once that persist has already succeeded
|
|
2235
2159
|
// (#836 r12 class 3 dedup — one persist owner, not a second here).
|
|
2236
|
-
|
|
2160
|
+
await presentTerminal(result.terminal, io, admitted.runDirectory);
|
|
2237
2161
|
}
|
|
2238
2162
|
if (result.terminal !== undefined) {
|
|
2239
2163
|
(result.terminal as { autoResumeCount?: number }).autoResumeCount = 0;
|