@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.
Files changed (93) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +42 -55
  3. package/README.zh-CN.md +59 -73
  4. package/THIRD_PARTY_NOTICES.md +4 -7
  5. package/dist/acp-host/production-host.js +8261 -9073
  6. package/dist/collector-tool-schemas.js +1 -1
  7. package/dist/compliance-transport.js +2 -5
  8. package/dist/engine-detour-usage.js +3 -30
  9. package/dist/gatekeeper-role.js +4 -18
  10. package/dist/headless-host/description.js +0 -209
  11. package/dist/headless-host/production-host.js +6973 -8035
  12. package/dist/host-contracts.js +8 -0
  13. package/dist/migrate-book-topology.js +30 -27
  14. package/dist/packaged-role-registry.js +1 -9
  15. package/dist/pi/role-turn-host.js +37 -19
  16. package/dist/public-cli/auto-resume.js +29 -66
  17. package/dist/public-cli/countersign-run.js +4 -18
  18. package/dist/public-cli/instruction-seat-run.js +59 -76
  19. package/dist/public-cli/main.js +9844 -10720
  20. package/dist/public-cli/option-definitions.js +1 -1
  21. package/dist/public-cli/post-admission.js +204 -285
  22. package/dist/public-cli/process-cancel.js +8 -0
  23. package/dist/public-cli/run-lifecycle.js +24 -94
  24. package/dist/public-cli/settlement.js +614 -1524
  25. package/dist/public-cli/terminal.js +23 -53
  26. package/dist/public-role-summons.js +3 -8
  27. package/dist/receipt-delivery-policy.js +11 -12
  28. package/dist/run-terminal-artifacts.js +19 -14
  29. package/dist/serialize-thrown-value.js +7 -1
  30. package/dist/submission-ledger.js +21 -89
  31. package/package.json +1 -1
  32. package/packets/fixer-repair.md +3 -1
  33. package/packets/judge-apply.md +7 -16
  34. package/packets/judge-authority.md +7 -17
  35. package/packets/judge-plan.md +7 -18
  36. package/packets/judge-review.md +8 -22
  37. package/packets/judge-submission.md +4 -7
  38. package/resources/collector-bot-handbook.md +2 -2
  39. package/resources/diarist-collect.md +2 -2
  40. package/resources/engine-dispatch.md +2 -2
  41. package/resources/navigator-route-playbook.md +1 -1
  42. package/resources/secretariat-ticket-identity.md +1 -1
  43. package/scripts/build-package.mjs +0 -2
  44. package/souls/coder-output-guide.md +3 -3
  45. package/souls/coder.md +2 -3
  46. package/souls/doctor-auditor.md +0 -3
  47. package/souls/fixer-auditor.md +0 -2
  48. package/souls/fixer-output-guide.md +3 -3
  49. package/souls/fixer.md +2 -2
  50. package/souls/gate-output-guide.md +1 -1
  51. package/souls/quality-law.md +2 -0
  52. package/src/acp-host/role-turn-host.ts +168 -75
  53. package/src/collector-tool-schemas.ts +1 -1
  54. package/src/compliance-transport.ts +3 -6
  55. package/src/engine-detour-usage.ts +3 -33
  56. package/src/external-host-turn-loop.ts +81 -36
  57. package/src/gatekeeper-role.ts +5 -20
  58. package/src/headless-host/description.ts +4 -214
  59. package/src/headless-host/production-host.ts +2 -1
  60. package/src/headless-host/role-turn-host.ts +153 -186
  61. package/src/host-contracts.ts +13 -10
  62. package/src/navigator-public-session.ts +1 -1
  63. package/src/navigator-session-contracts.ts +1 -1
  64. package/src/package-contracts/gatekeeper-output.ts +1 -1
  65. package/src/package-contracts/navigator-output.ts +1 -1
  66. package/src/package-contracts/terminating-infrastructure.ts +1 -1
  67. package/src/packaged-role-registry.ts +2 -10
  68. package/src/pi/adapter.ts +0 -3
  69. package/src/pi/role-turn-host.ts +42 -28
  70. package/src/prepared-role-turn.ts +2 -0
  71. package/src/public-cli/auto-resume.ts +27 -66
  72. package/src/public-cli/countersign-run.ts +4 -22
  73. package/src/public-cli/instruction-seat-run.ts +79 -80
  74. package/src/public-cli/option-definitions.ts +1 -1
  75. package/src/public-cli/post-admission.ts +264 -340
  76. package/src/public-cli/process-cancel.ts +9 -0
  77. package/src/public-cli/run-lifecycle.ts +25 -126
  78. package/src/public-cli/settlement.ts +792 -1912
  79. package/src/public-cli/terminal.ts +29 -69
  80. package/src/public-role-summons.ts +3 -8
  81. package/src/receipt-delivery-policy.ts +11 -12
  82. package/src/role-envelope.ts +16 -9
  83. package/src/role-runtime.ts +14 -81
  84. package/src/run-terminal-artifacts.ts +21 -17
  85. package/src/serialize-thrown-value.ts +7 -1
  86. package/src/submission-ledger.ts +30 -116
  87. package/dist/audit-escalation.js +0 -7
  88. package/dist/pi/known-failure.js +0 -51
  89. package/dist/typed-provider-http.js +0 -57
  90. package/dist/upstream-error-testimony.js +0 -19
  91. package/src/pi/known-failure.ts +0 -76
  92. package/src/typed-provider-http.ts +0 -89
  93. package/src/upstream-error-testimony.ts +0 -45
@@ -37,14 +37,13 @@ function isStationChildOfficerDialogue(role, env) {
37
37
  return env.stationChild === true && isOfficerReviewSeat(role);
38
38
  }
39
39
  import { mintEngineDetourInvocationScope, withEngineDetourInvocationScope, } from "../engine-detour-usage.js";
40
- import { clearCurrentCourt, clearTypedProviderHttpObservation, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, renderResumeCommand, } from "./run-lifecycle.js";
40
+ import { clearCurrentCourt, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, } from "./run-lifecycle.js";
41
41
  import { processCancelDiagnostic, processCancelSignalName, } from "./process-cancel.js";
42
- import { homeFromRunDirectory } from "../activation-ledger-topology.js";
43
42
  import { DEFAULT_ROLE_TURN_HOST } from "../host-descriptions.js";
44
43
  import { recordRunStart } from "../host-session-record.js";
45
- import { attemptProducedFreshSubmission, classifyPostAdmissionFailure, controlledFailureInputFromResolution, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatErrorCauseDetail, formatTerminalResult, inspectJudgeSession, isLawfulTypedTerminalOutcome, presentFailureTerminal, publishedFailureErrorPath, presentStructuralRejection, resolveAuditedRunnerFailureResolution, resolveControlledFailureResumeObservation, settleFailureTerminalResult, settleHostEndedNoReceipt, attachRecordedSubmissions, rewritePublishedFailureErrorPath, } from "./settlement.js";
44
+ import { classifyPostAdmissionFailure, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatErrorCauseDetail, ledgerReadScope, isLawfulTypedTerminalOutcome, presentStructuralRejection, settleFailureTerminalResult, settleHostEndedNoReceipt, noteSettlementFault, attachRecordedSubmissions, retainPackageFault, } from "./settlement.js";
46
45
  import {} from "./terminal.js";
47
- import { ensureRealArtifactsDirectory, persistReturnedRunState, runWithAutoResumeLoop, TurnDispatchedFailure, writeHardenedArtifactFile, } from "./auto-resume.js";
46
+ import { ensureRealArtifactsDirectory, persistReturnedRunState, presentTerminal, runWithAutoResumeLoop, TurnDispatchedFailure, } from "./auto-resume.js";
48
47
  import { isRecord, errorText } from "../unknown-value.js";
49
48
  function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
50
49
  const rewrite = (value) => rewriteRunDirectoryPathValue(value, relocation.oldRunDirectory, relocation.newRunDirectory);
@@ -60,7 +59,6 @@ function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
60
59
  }
61
60
  }
62
61
  if (result.terminal !== undefined) {
63
- rewritePublishedFailureErrorPath(result.terminal, relocation.oldRunDirectory, relocation.newRunDirectory);
64
62
  for (const artifact of result.terminal.artifacts) {
65
63
  artifact.path = rewrite(artifact.path);
66
64
  }
@@ -82,8 +80,14 @@ async function settleProcessCancelAfterTurn(input) {
82
80
  timedOut: input.result.timedOut,
83
81
  code: input.result.code,
84
82
  stderr: input.result.stderr,
85
- knownDiagnostic: processCancelDiagnostic(input.cancelName),
86
- }, input.invocationScopeId), input.adapters, input.env.principalAuthority, input.io, input.persistRunState);
83
+ ...(input.result.signal === undefined ? {} : { signal: input.result.signal }),
84
+ // A host report already on this result stays the report. The cancel
85
+ // name is wording only when the host gave none.
86
+ ...(input.result.knownFailure === undefined
87
+ ? { knownDiagnostic: processCancelDiagnostic(input.cancelName) }
88
+ : { knownFailure: input.result.knownFailure }),
89
+ cancelName: input.cancelName,
90
+ }, input.invocationScopeId), input.adapters, input.env.principalAuthority, input.io, input.persistRunState, input.notePackageFault);
87
91
  return {
88
92
  ...failed,
89
93
  turnDispatched: true,
@@ -127,33 +131,16 @@ export const POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE = "ak_post_admission_c
127
131
  * redundant second copy (#840 bounce class 1: 同一业务规则只保留一个权威实现).
128
132
  */
129
133
  async function recordBestEffortPostDispatchDiagnostic(admitted, env, diagnostic, io) {
130
- const payload = { diagnostic, recordedAt: new Date().toISOString() };
131
- let retentionFailure;
132
- try {
133
- await env.sessionAppender(env.principalAuthority, admitted.principal, POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE, payload);
134
- }
135
- catch (appendError) {
136
- try {
137
- const artifactsDir = await ensureRealArtifactsDirectory(admitted.runDirectory);
138
- await writeHardenedArtifactFile(artifactsDir, "post-admission-diagnostic", {
139
- version: 1,
140
- ...payload,
141
- });
142
- }
143
- catch (artifactError) {
144
- retentionFailure =
145
- `post-dispatch diagnostic durable retention failed on both channels (best-effort continue): dossier=${describeErrorIdentity(appendError)}; artifact=${describeErrorIdentity(artifactError)}`;
146
- }
147
- }
148
- try {
149
- io.stderr(formatCliDiagnostic(diagnostic));
150
- if (retentionFailure !== undefined) {
151
- io.stderr(formatCliDiagnostic(retentionFailure));
152
- }
153
- }
154
- catch {
155
- // Presentation is best-effort beside an already-formed host terminal.
156
- }
134
+ await retainPackageFault({
135
+ runDirectory: admitted.runDirectory,
136
+ diagnostic,
137
+ appendSession: (payload) => env.sessionAppender(env.principalAuthority, admitted.principal, POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE, payload),
138
+ stderr: (text) => io.stderr(text),
139
+ });
140
+ }
141
+ /** The one package-fault receiver, bound to this admitted run's session and io. */
142
+ export function packageFaultNoteFor(admitted, env, io) {
143
+ return (diagnostic) => recordBestEffortPostDispatchDiagnostic(admitted, env, diagnostic, io);
157
144
  }
158
145
  /**
159
146
  * Factory-seat resume method-material face (#833): one authority for
@@ -175,42 +162,42 @@ export async function resolveResumeMethodMaterialAdapters(input) {
175
162
  stderr: "",
176
163
  thrown: error,
177
164
  ...(input.knownCause === undefined ? {} : { knownCause: input.knownCause }),
178
- }, input.emptyAdapters, input.authority, { ...input.io, omitFailureStderrDiagnostic: true });
165
+ }, input.emptyAdapters, input.authority, { ...input.io, omitFailureStderrDiagnostic: true }, true, (diagnostic) => retainPackageFault({
166
+ runDirectory: input.admitted.runDirectory,
167
+ diagnostic,
168
+ stderr: (text) => input.io.stderr(text),
169
+ }));
179
170
  return { kind: "terminal", ...terminal };
180
171
  }
181
172
  }
182
- export async function presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState = true) {
173
+ export async function presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState = true, notePackageFault) {
183
174
  const hasThrown = Object.hasOwn(failureInput, "thrown");
184
- const resumeObservation = await resolveControlledFailureResumeObservation({
185
- runDirectory: admitted.runDirectory,
186
- ...(failureInput.typedHttpObservationSettled === true
187
- ? {
188
- typedHttpObservationSettled: true,
189
- ...(failureInput.typedHttpObservation === undefined
190
- ? {}
191
- : { typedHttpObservation: failureInput.typedHttpObservation }),
192
- }
193
- : {}),
194
- });
195
- const knownFailure = failureInput.knownFailure ?? resumeObservation.observationReadFailure;
196
- const session = !hasThrown &&
197
- !failureInput.timedOut &&
198
- knownFailure === undefined &&
199
- failureInput.knownCause === undefined &&
200
- admitted.principal !== undefined
201
- ? await inspectJudgeSession(authority.decode(admitted.principal).sessionFile)
202
- : undefined;
175
+ const knownFailure = failureInput.knownFailure;
203
176
  // knownFailure channel owns details when present; otherwise caller knownDetails.
204
177
  const fromKnownFailure = explicitInternalKnownFailureClassificationInput(knownFailure);
205
- const failure = classifyPostAdmissionFailure({
178
+ // Merge the two detail sources so a key carried by either survives; a later
179
+ // spread would otherwise drop the earlier one's keys entirely.
180
+ const callerDetails = failureInput.knownDetails === undefined && fromKnownFailure.knownDetails === undefined
181
+ ? undefined
182
+ : { ...(failureInput.knownDetails ?? {}), ...(fromKnownFailure.knownDetails ?? {}) };
183
+ const classified = classifyPostAdmissionFailure({
206
184
  timedOut: failureInput.timedOut,
207
185
  code: failureInput.code,
208
186
  stderr: failureInput.stderr,
209
187
  ...(hasThrown ? { thrown: failureInput.thrown } : {}),
210
- ...(failureInput.knownDetails === undefined
188
+ // Both detail sources are real; merging keeps each one's keys instead of
189
+ // letting the later spread drop the earlier one.
190
+ ...(callerDetails === undefined ? {} : { knownDetails: callerDetails }),
191
+ // The knownFailure channel's own cause/identity/diagnostic ride with it.
192
+ ...(fromKnownFailure.knownCause === undefined
193
+ ? {}
194
+ : { knownCause: fromKnownFailure.knownCause }),
195
+ ...(fromKnownFailure.knownIdentity === undefined
196
+ ? {}
197
+ : { knownIdentity: fromKnownFailure.knownIdentity }),
198
+ ...(fromKnownFailure.knownDiagnostic === undefined
211
199
  ? {}
212
- : { knownDetails: failureInput.knownDetails }),
213
- ...fromKnownFailure,
200
+ : { knownDiagnostic: fromKnownFailure.knownDiagnostic }),
214
201
  ...(failureInput.knownCause === undefined
215
202
  ? {}
216
203
  : { knownCause: failureInput.knownCause }),
@@ -220,20 +207,30 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
220
207
  ...(failureInput.knownDiagnostic === undefined
221
208
  ? {}
222
209
  : { knownDiagnostic: failureInput.knownDiagnostic }),
223
- ...(session === undefined ? {} : { session }),
210
+ ...(failureInput.signal === undefined ? {} : { signal: failureInput.signal }),
211
+ ...(failureInput.cancelName === undefined ? {} : { cancelName: failureInput.cancelName }),
212
+ ...(failureInput.packageFact === undefined
213
+ ? {}
214
+ : { packageFact: failureInput.packageFact }),
224
215
  });
225
- // #665 / #416: typed 429 with a run principal offers resume; the host decides availability.
226
- const resumable = admitted.principal !== undefined && resumeObservation.typedHttp429 !== undefined;
216
+ const failure = failureInput.stderr.length === 0 || failureInput.stderr === classified.diagnostic
217
+ ? classified
218
+ : { ...classified, stderr: failureInput.stderr };
227
219
  if (persistRunState && !failureInput.skipRunStateWrite) {
228
- await persistReturnedRunState(admitted);
220
+ try {
221
+ await persistReturnedRunState(admitted);
222
+ }
223
+ catch (error) {
224
+ // The host failure is already classified. A run-state write stays beside it.
225
+ await noteSettlementFault(admitted.runDirectory, notePackageFault === undefined ? undefined : { notePackageFault }, `run-state persistence failed beside host terminal: ${describeErrorIdentity(error)}`);
226
+ }
229
227
  }
230
228
  let publishedErrorPath;
231
229
  let terminal;
232
230
  try {
233
231
  const settled = await settleFailureTerminalResult(admitted, failure, authority, {
234
- ...(resumable
235
- ? { resume: { command: renderResumeCommand(admitted.runId) } }
236
- : {}),
232
+ recordAttemptHistory: true,
233
+ ...(notePackageFault === undefined ? {} : { notePackageFault }),
237
234
  ...(failureInput.invocationScopeId === undefined ||
238
235
  failureInput.invocationScopeId.length === 0
239
236
  ? {}
@@ -248,7 +245,7 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
248
245
  }
249
246
  throw error;
250
247
  }
251
- presentFailureTerminal(terminal, io);
248
+ await presentTerminal(terminal, io, admitted.runDirectory);
252
249
  return {
253
250
  exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
254
251
  admitted,
@@ -266,9 +263,9 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
266
263
  * the true cause through its own existing dispatch-exception machinery once
267
264
  * the retry budget is exhausted.
268
265
  */
269
- async function settleAfterTurnStarted(admitted, failureInput, adapters, authority, io, persistRunState) {
266
+ async function settleAfterTurnStarted(admitted, failureInput, adapters, authority, io, persistRunState, notePackageFault) {
270
267
  try {
271
- return (await presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState));
268
+ return (await presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState, notePackageFault));
272
269
  }
273
270
  catch (error) {
274
271
  throw new TurnDispatchedFailure(error);
@@ -278,33 +275,21 @@ async function settleAfterTurnStarted(admitted, failureInput, adapters, authorit
278
275
  * Persist run-state for a result dispatchPostAdmissionTurn deferred
279
276
  * (needsPersist — station-child / resumable auto-resume loop, #416/#840):
280
277
  * that write must land outside the loop's own retried-dispatch try, but its
281
- * failure still settles through the single existing controlled-failure
282
- * authority (settleAfterTurnStarted / presentControlledFailure, ADR 0080:
283
- * one settlement disposition owner) — never a second hand-rolled
284
- * classify/artifact/Terminal (#836 r12 class 3), and never lawful/non-lawful
285
- * settling differently (#836 r13 class 2: a caller's io here may be a no-op,
286
- * so a stderr-only trace is never seen and the failure is otherwise
287
- * swallowed — 失败诚实宪法 真因必须落痕). Both cases settle through
288
- * settleAfterTurnStarted, which attaches the run's already-recorded ledger
289
- * submissions to the new failure terminal and returns skipAutoResume so the
290
- * caller presents it once and stops — never re-entering auto-resume.
278
+ * failure is a package note beside the terminal already settled. It does
279
+ * not replace that terminal or open another attempt. The note is durable
280
+ * because the caller's io may be a no-op.
291
281
  */
292
- async function settleDeferredPersist(admitted, authority, adapters, io, result) {
282
+ async function settleDeferredPersist(admitted, env, io, result) {
293
283
  if (result.needsPersist !== true || result.terminal === undefined)
294
284
  return result;
295
285
  const { needsPersist: _needsPersist, ...settledResult } = result;
296
- const lawful = isLawfulTypedTerminalOutcome(result.terminal.roleOutcome);
297
286
  try {
298
- await persistReturnedRunState(admitted, lawful ? { lawful: true } : undefined);
287
+ await persistReturnedRunState(admitted);
299
288
  return settledResult;
300
289
  }
301
290
  catch (error) {
302
- const failed = await settleAfterTurnStarted(admitted, { timedOut: false, code: null, stderr: "", thrown: error, skipRunStateWrite: true }, adapters, authority, io, true);
303
- return {
304
- ...failed,
305
- turnDispatched: true,
306
- skipAutoResume: true,
307
- };
291
+ await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
292
+ return settledResult;
308
293
  }
309
294
  }
310
295
  /**
@@ -371,17 +356,10 @@ async function buildReceiptDeliveryRequest(input) {
371
356
  async function settleCompletedHostTurn(input) {
372
357
  const { admitted, env, io, adapters, shouldPresent, request, result, persistRunState, deferredPersist } = input;
373
358
  const finishAfterTurn = input.finishAfterTurn;
374
- // `result.stderr` stays live in memory for the real classification below
375
- // regardless of whether this durable mirror write succeeds. A write
376
- // failure here (unwritable run directory, stderr.log occupied as a
377
- // directory, ...) is a real infrastructure problem and must never be
378
- // dropped silently (catch-and-continue with no trace is a defect) — but
379
- // it also must never become THE controlling failure and wash out a
380
- // primary child signal that already arrived on `result`, or an
381
- // already-sealed accepted payload (#836: a secondary failure must not
382
- // overwrite an already-known cause or an already-recorded leg). It rides
383
- // beside whatever the real classification below determines, and only
384
- // becomes the reported failure itself when nothing else is wrong.
359
+ const courtScope = input.courtScope;
360
+ // `result.stderr` stays live in memory for host-failure settlement
361
+ // whether or not this mirror write succeeds. A write failure is noted.
362
+ // It does not replace the host terminal or an already-sealed acceptance.
385
363
  //
386
364
  // Everything below runs after the host turn genuinely started (#840 r9
387
365
  // 判词 class 1 boundary — 覆盖 executeTurn 已启动后至返回带 turnDispatched
@@ -404,138 +382,47 @@ async function settleCompletedHostTurn(input) {
404
382
  }
405
383
  catch (error) {
406
384
  stderrLogWriteFailure = error;
407
- // Best-effort: the turn's own stderr capture is secondary to lawful /
408
- // controlled-failure settlement below, but the failure itself must
409
- // still leave a real trace (失败诚实宪法 真因必须落痕) — durably, since
410
- // every auto-resume attempt's io is dummyIo (#840 r9 判词 class 1). It
411
- // still rides beside the classification below and, if nothing else is
412
- // wrong, becomes the reported failure itself (#836).
385
+ // Best-effort: stderr.log is secondary to the host terminal. A write
386
+ // failure leaves a durable note and, on a host failure, rides in
387
+ // packageFact. It does not become the cause.
413
388
  await recordBestEffortPostDispatchDiagnostic(admitted, env, `stderr.log write failed (best-effort continue): ${describeErrorIdentity(error)}`, io);
414
389
  }
415
- const courtScope = settlementScopeForTurn(request);
416
- // Single complete boundary (#840 r9 判词 class 1): resolving the
417
- // host/runner failure facts, trySettle, its shouldPresent gate, and the
418
- // accepted-settlement cleanup all settle through this one catch — a
419
- // throw from any of them (including the session-file decode itself)
420
- // still routes through settleAfterTurnStarted's TurnDispatchedFailure
421
- // instead of losing turnDispatched to an uncaught throw. The facts are
422
- // resolved before trySettle (#836) so an already-accepted settlement is
423
- // never presented over a real current host/runner failure signal or a
424
- // real stderr.log durable-write failure — both stay a real failure with
425
- // the recorded payload riding beside it, not replacing it.
390
+ // Host facts are resolved before trySettle. A host failure skips trySettle
391
+ // so a later read cannot replace that report. A clean host whose settlement
392
+ // read throws is noted above and is not turned into a failure terminal.
393
+ // stderr.log and run-state writes are notes beside the host terminal.
426
394
  let settled;
427
395
  let settledOutcome;
428
- let hostSignalFailed = false;
429
- let resolution;
396
+ // The host CLI's own report: timeout, declared failure, cancel, or exit.
397
+ // A null code is a signal death (Node close). No session or history read
398
+ // takes part (owner ea321c6d / 4743ade7).
399
+ const processCancelName = processCancelSignalName(env.signal);
400
+ const hostSignalFailed = result.timedOut
401
+ || result.knownFailure !== undefined
402
+ || processCancelName !== undefined
403
+ || result.code === null
404
+ || result.code !== 0;
405
+ const hostReportedFailure = result.knownFailure;
430
406
  try {
431
- const sessionFile = admitted.principal !== undefined
432
- ? env.principalAuthority.decode(admitted.principal).sessionFile
433
- : "";
434
- const runnerKnownFailure = adapters.resolveRunnerKnownFailure !== undefined && sessionFile !== ""
435
- ? await adapters.resolveRunnerKnownFailure({ result, sessionFile })
436
- : result.knownFailure;
437
- resolution = await resolveAuditedRunnerFailureResolution({
438
- runner: runnerKnownFailure,
439
- sessionFile,
440
- credential: undefined,
441
- runDirectory: admitted.runDirectory,
442
- });
443
- // A direct, current signal from the host/runner itself (timeout / host
444
- // knownFailure / runner knownFailure / missing credential) is a real
445
- // problem regardless of what else already settled — a settled accepted
446
- // outcome must not paper over it (it rides beside the recorded payload
447
- // via `submissions`, never replacing the payload). The audited
448
- // resolution's own typed session read (malformed JSONL, provider-stop,
449
- // an illegal/unsealed accepted status, a stale/superseded typed-HTTP
450
- // observation, ...) makes a run with no sealed acceptance a true
451
- // failure too — it must not be discarded into a lawful no_receipt just
452
- // because the raw runner signal alone looked clean. But it must not
453
- // retroactively invalidate an acceptance that already sealed this turn
454
- // — a resolved 429 observed earlier in the same session is exactly
455
- // that.
456
- const processCancelName = processCancelSignalName(env.signal);
457
- const directHostFailureSignal = result.timedOut
458
- || result.knownFailure !== undefined
459
- || runnerKnownFailure !== undefined
460
- || processCancelName !== undefined;
461
- hostSignalFailed =
462
- directHostFailureSignal
463
- || (result.code !== null && result.code !== 0)
464
- || resolution.knownFailure !== undefined;
465
- settled = await adapters.trySettle(admitted, env.principalAuthority, courtScope);
466
- if (settled !== undefined) {
467
- settled = await attachRecordedSubmissions(admitted, settled, courtScope);
468
- }
469
- // #836 r12 class 2: an accepted/audit_escalation settlement can be
470
- // entirely a prior attempt's stale payload (courtAttempt is a
471
- // recording tag, not a visibility gate — attachRecordedSubmissions
472
- // above still surfaces that historical payload honestly either way).
473
- // Consume the ledger's own subject.attemptId (submission-ledger.ts)
474
- // only to learn whether *this* attempt itself produced a fresh seal —
475
- // never to filter what presents.
476
- const settledIsFreshThisAttempt = settled === undefined
477
- || (settled.roleOutcome.kind !== "accepted" && settled.roleOutcome.kind !== "audit_escalation")
478
- ? true
479
- : await attemptProducedFreshSubmission(admitted, courtScope);
480
- // A lawful settled outcome already reached this turn takes precedence
481
- // over a later bare exit-code / session-inspection signal (trailing
482
- // nonzero exit, late stderr noise, a stale already-superseded
483
- // typed-HTTP observation, ...) — but never over a direct current
484
- // host/runner failure signal, nor over a real stderr.log durable-write
485
- // failure (confirmed infrastructure trouble, not weak/bare evidence),
486
- // both of which stay a real failure with the recorded payload riding
487
- // beside it, not replacing it (#836: never kill an already-recorded
488
- // leg, but never wash a real failure away either). Note: the narrower
489
- // directHostFailureSignal gates acceptance here; the broader
490
- // hostSignalFailed (adds bare exit code / resolution.knownFailure)
491
- // only gates the no-settlement fallback below — except when the
492
- // settlement itself is entirely stale (no fresh seal this attempt),
493
- // where a bare nonzero exit / resolution failure must not be outranked
494
- // by someone else's earlier success (#836 r12 class 2).
495
- const staleAcceptanceOutranksRealFailure = !settledIsFreshThisAttempt && hostSignalFailed;
496
- // #855: re-read cancel after trySettle/attach awaits — a signal in this
497
- // window must not land as lawful accepted.
498
- const cancelAfterSettle = processCancelSignalName(env.signal);
499
- if (settled !== undefined
500
- && shouldPresent(settled)
501
- && !directHostFailureSignal
502
- && !staleAcceptanceOutranksRealFailure
503
- && stderrLogWriteFailure === undefined
504
- && cancelAfterSettle === undefined) {
505
- // This court sealed — drop open-court pointer (bare resume no longer continues it).
506
- if (settled.roleOutcome.kind === "accepted" &&
507
- request.courtAttemptId !== undefined &&
508
- request.courtAttemptId.length > 0) {
509
- try {
510
- await clearCurrentCourt(admitted.runDirectory, request.courtAttemptId);
511
- }
512
- catch (error) {
513
- // Settlement already sealed accepted — a cleanup failure here must
514
- // not erase that fact or make the caller replay this court's
515
- // summons over already-delivered work (#840 已交劳动只整理终局不重做).
516
- // Preserve the cleanup failure as a post-dispatch diagnostic; the
517
- // accepted settlement remains authoritative (#840 r9 判词 class 1).
518
- await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue): ${describeErrorIdentity(error)}`, io);
519
- }
407
+ if (!hostSignalFailed) {
408
+ settled = await adapters.trySettle(admitted, env.principalAuthority, { ...courtScope, previewOnly: true });
409
+ const cancelAfterSettle = processCancelSignalName(env.signal);
410
+ if (settled !== undefined
411
+ && shouldPresent(settled)
412
+ && cancelAfterSettle === undefined) {
413
+ settledOutcome = {
414
+ exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
415
+ admitted,
416
+ terminal: settled,
417
+ turnDispatched: true,
418
+ };
520
419
  }
521
- settledOutcome = {
522
- exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
523
- admitted,
524
- terminal: settled,
525
- turnDispatched: true,
526
- };
527
420
  }
528
421
  }
529
422
  catch (error) {
530
- // Settle (or its shouldPresent gate, or the failure-fact resolution
531
- // above) throw is a real failure fact — never swallow into undefined.
532
- const settledFailure = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
533
- timedOut: false,
534
- code: result.code,
535
- stderr: result.stderr,
536
- thrown: error,
537
- }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
538
- return { kind: "settled", outcome: await finishAfterTurn({ ...settledFailure, turnDispatched: true, ...deferredPersist }) };
423
+ // The host did not fail. A settlement read here is a package fault and
424
+ // must not become the terminal.
425
+ await recordBestEffortPostDispatchDiagnostic(admitted, env, `settlement read failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
539
426
  }
540
427
  if (settledOutcome !== undefined) {
541
428
  // #855: final cancel re-read before lawful seal — settlement window may
@@ -552,66 +439,46 @@ async function settleCompletedHostTurn(input) {
552
439
  persistRunState,
553
440
  deferredPersist,
554
441
  invocationScopeId: request.invocationScopeId,
442
+ notePackageFault: courtScope.notePackageFault,
555
443
  })) };
556
444
  }
557
445
  if (persistRunState) {
558
446
  try {
559
- await persistReturnedRunState(admitted, { lawful: true });
447
+ await persistReturnedRunState(admitted);
560
448
  }
561
449
  catch (error) {
562
450
  await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
563
451
  }
564
452
  }
565
- // Lawful persist + present is the caller's stop seam (auto-resume loop /
453
+ // Persist + present is the caller's stop seam (auto-resume loop /
566
454
  // manual resume), not this retried host-turn function.
567
455
  // #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
456
+ input.markPending(settledOutcome.terminal);
568
457
  return { kind: "settled", outcome: await finishAfterTurn({ ...settledOutcome, ...deferredPersist }) };
569
458
  }
570
459
  // Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
571
460
  if (hostSignalFailed) {
572
- const resolutionInput = controlledFailureInputFromResolution(resolution);
573
- const processCancelName = processCancelSignalName(env.signal);
574
- const stderrLogWriteDetails = stderrLogWriteFailure === undefined
461
+ const stderrLogWriteFact = stderrLogWriteFailure === undefined
575
462
  ? undefined
576
463
  : { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
577
464
  const failed = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
578
465
  timedOut: result.timedOut,
579
466
  code: result.code,
580
467
  stderr: result.stderr,
581
- ...resolutionInput,
582
- ...(processCancelName === undefined
583
- ? {}
584
- : { knownDiagnostic: processCancelDiagnostic(processCancelName) }),
585
- // Secondary fact only — never the cause. Rides on whichever channel
586
- // classification actually reads (knownFailure.details owns it when
587
- // a knownFailure exists; the top-level knownDetails otherwise).
588
- ...(stderrLogWriteDetails === undefined
589
- ? {}
590
- : resolutionInput.knownFailure !== undefined
591
- ? {
592
- knownFailure: {
593
- ...resolutionInput.knownFailure,
594
- details: { ...(resolutionInput.knownFailure.details ?? {}), ...stderrLogWriteDetails },
595
- },
596
- }
597
- : { knownDetails: stderrLogWriteDetails }),
598
- }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
468
+ // A signal-killed child is a fact of its own. The synthetic line is
469
+ // only a fallback for when the host declared no failure of its own —
470
+ // it must never displace words the host actually reported.
471
+ ...(result.signal === undefined ? {} : { signal: result.signal }),
472
+ ...(hostReportedFailure === undefined ? {} : { knownFailure: hostReportedFailure }),
473
+ // The cancellation is the host's own report of why this call ended;
474
+ // it supplies the wording only when the host declared nothing else.
475
+ ...(processCancelName === undefined ? {} : { cancelName: processCancelName }),
476
+ // Secondary fact only — never the cause, and never merged into the
477
+ // host's own details.
478
+ ...(stderrLogWriteFact === undefined ? {} : { packageFact: stderrLogWriteFact }),
479
+ }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault);
599
480
  return { kind: "settled", outcome: await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal)) };
600
481
  }
601
- // Nothing else was wrong, but the durable stderr mirror itself failed to
602
- // write — that is the real (infrastructure) problem in this case, not a
603
- // lawful absence of a receipt. Honest and loud, not silently no_receipt.
604
- if (stderrLogWriteFailure !== undefined) {
605
- const failed = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
606
- timedOut: false,
607
- code: result.code,
608
- stderr: result.stderr,
609
- thrown: stderrLogWriteFailure,
610
- }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
611
- return { kind: "settled", outcome: await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist }) };
612
- }
613
- // No receipt at all after a clean host turn: lawful no_receipt, but the
614
- // caller owns whether to催交 first (票面 3).
615
482
  return { kind: "still-missing" };
616
483
  }
617
484
  export async function dispatchPostAdmissionTurn(input) {
@@ -625,6 +492,18 @@ export async function dispatchPostAdmissionTurn(input) {
625
492
  * lease release — success and failure alike. When a primary failure terminal already
626
493
  * exists, keep that cause and leave relocate failure on the shared diagnostic channel.
627
494
  */
495
+ const courtScope = {
496
+ ...settlementScopeForTurn(request),
497
+ notePackageFault: (diagnostic) => recordBestEffortPostDispatchDiagnostic(admitted, env, diagnostic, io),
498
+ };
499
+ let pendingSettlement;
500
+ /**
501
+ * The terminal this pending publication belongs to. A later real failure
502
+ * (e.g. a cancel re-read) replaces `result.terminal` with its own settled
503
+ * failure; that failure is already durable, so the superseded preview must
504
+ * not publish over it (失败诚实宪法:接住可以,洗白不行).
505
+ */
506
+ let pendingTerminal;
628
507
  let afterDispatchApplied = false;
629
508
  const receiptDelivery = input.receiptDelivery ?? createReceiptDeliveryPolicy(env.autoResumeLimit);
630
509
  let turnDispatched = false;
@@ -638,8 +517,9 @@ export async function dispatchPostAdmissionTurn(input) {
638
517
  if (afterDispatchApplied)
639
518
  return result;
640
519
  afterDispatchApplied = true;
641
- // #855: re-read cancel before afterDispatch finalization — never present
642
- // lawful success after a catchable process signal. skipRunStateWrite: run-state
520
+ // #855: re-read cancel before committing the terminal and afterDispatch.
521
+ // A signal after the history-first commit cannot retroactively erase it.
522
+ // skipRunStateWrite: run-state
643
523
  // may already be terminal from a prior lawful persist; do not reopen as resumable.
644
524
  const cancelBeforeFinish = processCancelSignalName(env.signal);
645
525
  if (cancelBeforeFinish !== undefined &&
@@ -651,13 +531,43 @@ export async function dispatchPostAdmissionTurn(input) {
651
531
  code: null,
652
532
  stderr: "",
653
533
  knownDiagnostic: processCancelDiagnostic(cancelBeforeFinish),
534
+ cancelName: cancelBeforeFinish,
654
535
  skipRunStateWrite: true,
655
- }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState)),
536
+ }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault)),
656
537
  turnDispatched: true,
657
538
  skipAutoResume: true,
658
539
  ...deferredPersist,
659
540
  };
660
541
  }
542
+ // The last cancellation check precedes the one durable settlement. A preview
543
+ // never publishes pointers or history; a superseding failure owns this turn —
544
+ // `result.terminal === pendingTerminal` is what makes the publication this
545
+ // turn's own rather than a superseded one.
546
+ if (pendingSettlement !== undefined &&
547
+ pendingTerminal !== undefined &&
548
+ result.terminal === pendingTerminal) {
549
+ try {
550
+ const terminal = pendingSettlement === "sealed"
551
+ ? await adapters.trySettle(admitted, env.principalAuthority, { ...courtScope, recordAttemptHistory: true })
552
+ : await attachRecordedSubmissions(admitted, await settleHostEndedNoReceipt(admitted, env.principalAuthority, { ...courtScope, recordAttemptHistory: true }, receiptDelivery.issuedDeliveryRequests()), courtScope);
553
+ if (terminal === undefined)
554
+ throw new Error("settled host attempt vanished before publication");
555
+ result = { ...result, terminal, exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome) };
556
+ if (pendingSettlement === "sealed" && terminal.roleOutcome.kind === "accepted"
557
+ && request.courtAttemptId !== undefined && request.courtAttemptId.length > 0) {
558
+ try {
559
+ await clearCurrentCourt(admitted.runDirectory, request.courtAttemptId);
560
+ }
561
+ catch (error) {
562
+ // The accepted attempt is already durable; cleanup is diagnostic only.
563
+ await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue): ${describeErrorIdentity(error)}`, io);
564
+ }
565
+ }
566
+ }
567
+ catch (error) {
568
+ await recordBestEffortPostDispatchDiagnostic(admitted, env, `terminal publication failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
569
+ }
570
+ }
661
571
  try {
662
572
  // #858 / #1071: the first identifiable ticketNumber field declaration
663
573
  // files an unbound run (integer, digit string, or leading #N token).
@@ -666,7 +576,7 @@ export async function dispatchPostAdmissionTurn(input) {
666
576
  // Later receipts remain untouched; prose note/report is never consulted;
667
577
  // this seam does not adjudicate a ticket change.
668
578
  if (admitted.ticketNumber === undefined) {
669
- const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, { home: homeFromRunDirectory(admitted.runDirectory), sessionParent: join(admitted.runDirectory, "session", "session.jsonl") });
579
+ const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, ledgerReadScope(admitted));
670
580
  let ticketNumber;
671
581
  for (const row of rows) {
672
582
  if (row.role !== admitted.role
@@ -708,7 +618,7 @@ export async function dispatchPostAdmissionTurn(input) {
708
618
  code: null,
709
619
  stderr: "",
710
620
  thrown: error,
711
- }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState)),
621
+ }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault)),
712
622
  ...(result.turnDispatched === true ? { turnDispatched: true } : {}),
713
623
  ...(result.skipAutoResume === true ? { skipAutoResume: true } : {}),
714
624
  ...deferredPersist,
@@ -718,7 +628,6 @@ export async function dispatchPostAdmissionTurn(input) {
718
628
  try {
719
629
  // #987: do not pre-block or classify host dispatch from AK credential
720
630
  // catalog facts. Per-invocation runner/host evidence owns provider identity.
721
- await clearTypedProviderHttpObservation(admitted.runDirectory);
722
631
  // The authoritative host write (markRunRunning) is delayed to just before
723
632
  // executeTurn — not merely past beforeDispatch (#840 r9 判词 class 2). Any
724
633
  // pre-turn retry (beforeDispatch, continuation assembly) must not commit a host
@@ -740,7 +649,7 @@ export async function dispatchPostAdmissionTurn(input) {
740
649
  code: null,
741
650
  stderr: "",
742
651
  thrown: error,
743
- }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState));
652
+ }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault));
744
653
  return { ...settled, ...deferredPersist };
745
654
  }
746
655
  }
@@ -772,6 +681,10 @@ export async function dispatchPostAdmissionTurn(input) {
772
681
  // auto-resume re-enters this dispatch and must reuse the same scope.
773
682
  let result;
774
683
  try {
684
+ // A bare `ak-role resume` only forwards the caller's instruction to the
685
+ // host CLI's native resume. Nothing here may gate, redirect or reshape
686
+ // that dispatch on prior conclusions, row counts or report presence;
687
+ // the authoritative post-turn settlement reads whatever really happened.
775
688
  recordRunStart(admitted.runDirectory);
776
689
  turnDispatched = true;
777
690
  result = await env.roleTurnHost.executeTurn(turnRequest);
@@ -782,10 +695,11 @@ export async function dispatchPostAdmissionTurn(input) {
782
695
  timedOut: false,
783
696
  code: null,
784
697
  stderr: "",
785
- thrown: processCancelName === undefined
786
- ? error
787
- : new Error(processCancelDiagnostic(processCancelName), { cause: error }),
788
- }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
698
+ // The thrown value is the host's report. A cancel received on this
699
+ // call is a second fact; it must not become a new Error in its place.
700
+ ...(processCancelName === undefined ? {} : { cancelName: processCancelName }),
701
+ thrown: error,
702
+ }, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState, courtScope.notePackageFault);
789
703
  return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...settled, turnDispatched: true, ...deferredPersist }, env.signal));
790
704
  }
791
705
  // #1132: settle this turn through the ONE post-turn settlement authority,
@@ -801,6 +715,8 @@ export async function dispatchPostAdmissionTurn(input) {
801
715
  persistRunState,
802
716
  deferredPersist,
803
717
  finishAfterTurn,
718
+ courtScope,
719
+ markPending: (terminal) => { pendingSettlement = "sealed"; pendingTerminal = terminal; },
804
720
  });
805
721
  if (settledTurn.kind === "settled")
806
722
  return settledTurn.outcome;
@@ -863,6 +779,8 @@ export async function dispatchPostAdmissionTurn(input) {
863
779
  persistRunState: false,
864
780
  deferredPersist,
865
781
  finishAfterTurn,
782
+ courtScope,
783
+ markPending: (terminal) => { pendingSettlement = "sealed"; pendingTerminal = terminal; },
866
784
  });
867
785
  // The delivery turn's own outcome IS the run's outcome, except when it
868
786
  // simply still has no receipt — that is the loop's own case to keep going.
@@ -875,9 +793,7 @@ export async function dispatchPostAdmissionTurn(input) {
875
793
  // NOT write; this one, which ends the run, must.
876
794
  if (persistRunState && deliveryTurn.outcome.terminal !== undefined) {
877
795
  try {
878
- await persistReturnedRunState(admitted, {
879
- lawful: isLawfulTypedTerminalOutcome(deliveryTurn.outcome.terminal.roleOutcome),
880
- });
796
+ await persistReturnedRunState(admitted);
881
797
  }
882
798
  catch (error) {
883
799
  await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside催交 terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
@@ -886,7 +802,7 @@ export async function dispatchPostAdmissionTurn(input) {
886
802
  return await finishAfterTurn(deliveryTurn.outcome);
887
803
  }
888
804
  }
889
- const noReceiptScope = settlementScopeForTurn(request);
805
+ const noReceiptScope = { ...courtScope, previewOnly: true };
890
806
  const noReceipt = await attachRecordedSubmissions(admitted, await settleHostEndedNoReceipt(admitted, env.principalAuthority, noReceiptScope, receiptDelivery.issuedDeliveryRequests()), noReceiptScope);
891
807
  // #855: cancel during no_receipt settlement window is not lawful success.
892
808
  const noReceiptCancel = processCancelSignalName(env.signal);
@@ -901,17 +817,20 @@ export async function dispatchPostAdmissionTurn(input) {
901
817
  persistRunState,
902
818
  deferredPersist,
903
819
  invocationScopeId: request.invocationScopeId,
820
+ notePackageFault: courtScope.notePackageFault,
904
821
  }));
905
822
  }
906
823
  if (persistRunState) {
907
824
  try {
908
- await persistReturnedRunState(admitted, { lawful: true });
825
+ await persistReturnedRunState(admitted);
909
826
  }
910
827
  catch (error) {
911
828
  await recordBestEffortPostDispatchDiagnostic(admitted, env, `run-state persistence failed beside host terminal (best-effort continue): ${describeErrorIdentity(error)}`, io);
912
829
  }
913
830
  }
914
831
  // #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
832
+ pendingSettlement = "no_receipt";
833
+ pendingTerminal = noReceipt;
915
834
  return await finishAfterTurn({
916
835
  exitCode: exitCodeForTerminalOutcome(noReceipt.roleOutcome),
917
836
  admitted,
@@ -1008,7 +927,6 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
1008
927
  kind: "resume",
1009
928
  prompt,
1010
929
  },
1011
- ...(request.message === undefined ? {} : { courtAttemptId: randomUUID() }),
1012
930
  ...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
1013
931
  // #1132: one configured ceiling, already resolved by the caller (#422).
1014
932
  deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
@@ -1083,7 +1001,7 @@ async function runSettledAutoResumeLoop(input) {
1083
1001
  ? {}
1084
1002
  : { effectiveEngine: input.effectiveEngine }),
1085
1003
  });
1086
- return settleDeferredPersist(input.admitted, input.env.principalAuthority, adapters, attemptIo, result);
1004
+ return settleDeferredPersist(input.admitted, input.env, attemptIo, result);
1087
1005
  },
1088
1006
  }),
1089
1007
  });
@@ -1204,11 +1122,13 @@ export async function runPostAdmissionSeatResume(input) {
1204
1122
  };
1205
1123
  }
1206
1124
  }
1207
- // Open court continue, or new court for summons / message re-review
1208
- // (clause 0 新庭可再交卷; #833). Bare resume without open court omits id.
1209
- if (openCourtAttemptId !== undefined ||
1210
- request.summons !== undefined ||
1211
- request.message !== undefined) {
1125
+ // Open court continue, or a new court for a real re-summons
1126
+ // (clause 0 新庭可再交卷; #833). Bare resume — with or without caller
1127
+ // words — carries no summons and no open court, so it mints none:
1128
+ // the package must not turn ordinary continuation bytes into an audit
1129
+ // court whose attempt scope would reshape settlement (#1032 修订票:
1130
+ // 此要求不延伸到没有该传召庭次的裸 resume).
1131
+ if (openCourtAttemptId !== undefined || request.summons !== undefined) {
1212
1132
  const courtAttemptId = openCourtAttemptId ??
1213
1133
  (turnRequest.courtAttemptId !== undefined &&
1214
1134
  turnRequest.courtAttemptId.length > 0
@@ -1328,8 +1248,7 @@ function writeResumeFailurePointer(io, errorPath) {
1328
1248
  export function showResumeErrorPointer(io, exitCode, terminal) {
1329
1249
  if (exitCode === 0 || terminal === undefined)
1330
1250
  return;
1331
- const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path
1332
- ?? publishedFailureErrorPath(terminal);
1251
+ const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path;
1333
1252
  if (publishedError !== undefined)
1334
1253
  writeResumeFailurePointer(io, publishedError);
1335
1254
  }
@@ -1470,7 +1389,7 @@ export async function runPostAdmissionManualResume(input) {
1470
1389
  // through its own settleAfterTurnStarted-backed branches; a lawful result
1471
1390
  // only ever reaches this point once that persist has already succeeded
1472
1391
  // (#836 r12 class 3 dedup — one persist owner, not a second here).
1473
- io.stdout(formatTerminalResult(result.terminal));
1392
+ await presentTerminal(result.terminal, io, admitted.runDirectory);
1474
1393
  }
1475
1394
  if (result.terminal !== undefined) {
1476
1395
  result.terminal.autoResumeCount = 0;