@akagilnc/pi-workflow-roles 0.1.3414 → 0.1.3439

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 (61) hide show
  1. package/dist/archivist-record-entry.js +21 -12
  2. package/dist/archivist-record-topology.js +20 -0
  3. package/dist/auditor-dossier-tool.js +67 -76
  4. package/dist/canonical-json.js +66 -69
  5. package/dist/compliance-transport.js +99 -86
  6. package/dist/countersign-contracts.js +23 -25
  7. package/dist/doctor-contracts.js +169 -145
  8. package/dist/engine-detour-tool.js +83 -87
  9. package/dist/engine-detour.js +148 -183
  10. package/dist/evidence-child-executor.js +10 -1
  11. package/dist/exact-utf8.js +11 -10
  12. package/dist/git-object-id.js +10 -5
  13. package/dist/gleaner-left-contracts.js +33 -35
  14. package/dist/grok/production-host.js +229 -118
  15. package/dist/inspector-contracts.js +28 -31
  16. package/dist/institutional-resolution.js +101 -104
  17. package/dist/ledger-session-read.js +241 -0
  18. package/dist/merger-contracts.js +66 -56
  19. package/dist/notary-contracts.js +91 -100
  20. package/dist/open-tool-schema.js +32 -31
  21. package/dist/package-contracts/collector-output.js +56 -43
  22. package/dist/package-contracts/fixer-output.js +52 -50
  23. package/dist/package-contracts/fixer-packet.js +67 -64
  24. package/dist/package-contracts/gatekeeper-output.js +48 -56
  25. package/dist/package-contracts/judge-output.js +23 -20
  26. package/dist/package-contracts/navigator-output.js +35 -40
  27. package/dist/package-contracts/reviewer-output.js +94 -110
  28. package/dist/package-contracts/terminating-infrastructure.js +62 -90
  29. package/dist/package-contracts/worker-output.js +30 -11
  30. package/dist/package-resources/engine-material.js +58 -94
  31. package/dist/public-cli/config.js +420 -484
  32. package/dist/public-cli/main.js +1219 -524
  33. package/dist/public-cli/registry.js +107 -114
  34. package/dist/receipt-delivery-policy.js +74 -76
  35. package/dist/session-opening-materials.js +44 -47
  36. package/dist/sha256.js +5 -3
  37. package/dist/sitian-appender.js +204 -210
  38. package/dist/sitian-contracts.js +15 -19
  39. package/dist/sitian-facade.js +5 -10
  40. package/dist/sitian-reader.js +37 -46
  41. package/dist/stream-idle-guard.js +63 -71
  42. package/dist/ticket-seat-memory.js +244 -0
  43. package/package.json +1 -1
  44. package/scripts/build-package.mjs +41 -0
  45. package/src/analyst-gate-cycles-read.ts +152 -83
  46. package/src/analyst-ledger.ts +170 -43
  47. package/src/archivist-record-entry.ts +32 -11
  48. package/src/archivist-record-topology.ts +39 -0
  49. package/src/evidence-child-executor.ts +14 -1
  50. package/src/grok/production-host.ts +44 -9
  51. package/src/host-contracts.ts +18 -0
  52. package/src/host-transition-prior-native.ts +8 -1
  53. package/src/ledger-session-read.ts +56 -0
  54. package/src/pi/durable-principal.ts +6 -0
  55. package/src/public-cli/inspector-run.ts +21 -10
  56. package/src/public-cli/invocation.ts +4 -20
  57. package/src/public-cli/notary-run.ts +21 -4
  58. package/src/public-cli/post-admission.ts +333 -42
  59. package/src/public-cli/settlement.ts +266 -81
  60. package/src/session-opening-materials.ts +4 -2
  61. package/src/ticket-seat-memory.ts +454 -0
@@ -26,6 +26,11 @@ import type {
26
26
  SessionCustomEntryAppender,
27
27
  } from "../host-contracts.ts";
28
28
  import { projectHostTransitionPriorNative } from "../host-transition-prior-native.ts";
29
+ import {
30
+ isTicketSeatMemoryBound,
31
+ readTicketSeatMemoryLastHost,
32
+ writeTicketSeatMemoryLastHost,
33
+ } from "../ticket-seat-memory.ts";
29
34
  import type { CredentialProviders, SeatModelConfig } from "./config.ts";
30
35
  import {
31
36
  missingCredentialPreDispatchFailure,
@@ -54,10 +59,12 @@ import {
54
59
  isLawfulTypedTerminalOutcome,
55
60
  presentFailureTerminal,
56
61
  presentStructuralRejection,
62
+ projectThrownFailureLeaf,
57
63
  resolveAuditedRunnerFailureResolution,
58
64
  resolveControlledFailureResumeObservation,
59
65
  settleFailureTerminalResult,
60
66
  sealedAcceptanceRedispatchDisposition,
67
+ type ControlledFailure,
61
68
  } from "./settlement.ts";
62
69
  import type { CliIo } from "./cli-io.ts";
63
70
  import type { AdmittedRoleInvocation } from "./invocation.ts";
@@ -109,7 +116,6 @@ export type PostAdmissionAdapters<
109
116
  trySettle: (admitted: A, authority: DurablePrincipalAuthority) => Promise<T | undefined>;
110
117
  /** Default: isLawfulTypedTerminalOutcome(terminal.roleOutcome). */
111
118
  shouldPresentSettled?: (terminal: T) => boolean;
112
- trySettleSecondary?: (admitted: A, authority: DurablePrincipalAuthority) => Promise<TerminalResult | undefined>;
113
119
  resolveRunnerKnownFailure?: (input: {
114
120
  result: RoleTurnResult;
115
121
  sessionFile: string;
@@ -129,6 +135,8 @@ export type ControlledFailureInput = {
129
135
  readonly code?: string | number;
130
136
  };
131
137
  knownDiagnostic?: string;
138
+ /** Secondary evidence already owned by the typed production failure channel. */
139
+ knownDetails?: Readonly<Record<string, unknown>>;
132
140
  typedHttpObservationSettled?: true;
133
141
  typedHttpObservation?: TypedProviderHttpObservation;
134
142
  };
@@ -169,12 +177,19 @@ export async function presentControlledFailure<
169
177
  admitted.principal !== undefined
170
178
  ? await inspectJudgeSession(authority.decode(admitted.principal).sessionFile)
171
179
  : undefined;
180
+ // knownFailure channel owns details when present; otherwise caller knownDetails
181
+ // (e.g. last-host write concurrent leaf on timeout/activation dual failure).
182
+ const fromKnownFailure =
183
+ explicitInternalKnownFailureClassificationInput(knownFailure);
172
184
  const failure = classifyPostAdmissionFailure({
173
185
  timedOut: failureInput.timedOut,
174
186
  code: failureInput.code,
175
187
  stderr: failureInput.stderr,
176
188
  ...(hasThrown ? { thrown: failureInput.thrown } : {}),
177
- ...explicitInternalKnownFailureClassificationInput(knownFailure),
189
+ ...(failureInput.knownDetails === undefined
190
+ ? {}
191
+ : { knownDetails: failureInput.knownDetails }),
192
+ ...fromKnownFailure,
178
193
  ...(failureInput.knownCause === undefined
179
194
  ? {}
180
195
  : { knownCause: failureInput.knownCause }),
@@ -218,12 +233,125 @@ export async function presentControlledFailure<
218
233
  };
219
234
  }
220
235
 
221
- function presentSecondaryTerminal(terminal: TerminalResult, io: CliIo): void {
222
- if (terminal.roleOutcome.kind === "failure" || terminal.roleOutcome.kind === "no_receipt") {
223
- presentFailureTerminal(terminal, io);
224
- } else {
225
- io.stdout(formatTerminalResult(terminal));
236
+ /** Serialize one failure leaf for details.concurrentFailures (sole leaf owner = settlement). */
237
+ function concurrentFailureLeafRecord(leaf: ControlledFailure): {
238
+ readonly cause: ControlledFailureCause;
239
+ readonly diagnostic: string;
240
+ readonly identity?: { readonly name?: string; readonly code?: string | number };
241
+ readonly details?: Readonly<Record<string, unknown>>;
242
+ } {
243
+ return {
244
+ cause: leaf.cause,
245
+ diagnostic: leaf.diagnostic,
246
+ ...(leaf.identity === undefined ? {} : { identity: leaf.identity }),
247
+ ...(leaf.details === undefined ? {} : { details: leaf.details }),
248
+ };
249
+ }
250
+
251
+ function existingConcurrentFailureLeaves(
252
+ details: Readonly<Record<string, unknown>> | undefined,
253
+ ): unknown[] {
254
+ const value = details?.concurrentFailures;
255
+ return Array.isArray(value) ? [...value] : [];
256
+ }
257
+
258
+ /**
259
+ * Attach last-host write failure as concurrent secondary evidence on an existing
260
+ * returned-path ControlledFailureInput — append, never replace prior leaves.
261
+ */
262
+ function withLastHostWriteConcurrentFailure(
263
+ input: ControlledFailureInput,
264
+ writeError: unknown,
265
+ ): ControlledFailureInput {
266
+ const writeLeaf = concurrentFailureLeafRecord(projectThrownFailureLeaf(writeError));
267
+ if (input.knownFailure !== undefined) {
268
+ const failure = input.knownFailure;
269
+ return {
270
+ ...input,
271
+ knownFailure: {
272
+ cause: failure.cause,
273
+ ...(failure.identity === undefined ? {} : { identity: failure.identity }),
274
+ ...(failure.diagnostic === undefined
275
+ ? {}
276
+ : { diagnostic: failure.diagnostic }),
277
+ details: {
278
+ ...(failure.details ?? {}),
279
+ concurrentFailures: [
280
+ ...existingConcurrentFailureLeaves(failure.details),
281
+ writeLeaf,
282
+ ],
283
+ },
284
+ },
285
+ };
286
+ }
287
+ return {
288
+ ...input,
289
+ knownDetails: {
290
+ ...(input.knownDetails ?? {}),
291
+ concurrentFailures: [
292
+ ...existingConcurrentFailureLeaves(input.knownDetails),
293
+ writeLeaf,
294
+ ],
295
+ },
296
+ };
297
+ }
298
+
299
+ /** Settled failure roleOutcome already narrowed by kind at the sole call site. */
300
+ type SettledFailureOutcome = Extract<
301
+ TerminalResult["roleOutcome"],
302
+ { kind: "failure" }
303
+ >;
304
+
305
+ /**
306
+ * Rebuild the settled failure as knownFailure so concurrent last-host write can
307
+ * append without replacing cause/identity/diagnostic/details.
308
+ * Caller narrows by kind; no second runtime re-check.
309
+ */
310
+ function knownFailureFromSettledFailureOutcome(
311
+ outcome: SettledFailureOutcome,
312
+ ): RoleTurnKnownFailure {
313
+ const facts = outcome.decisiveFacts;
314
+ const secondary = facts.secondaryEvidence;
315
+ const details =
316
+ secondary !== undefined &&
317
+ typeof secondary === "object" &&
318
+ secondary !== null &&
319
+ !Array.isArray(secondary)
320
+ ? (secondary as Readonly<Record<string, unknown>>)
321
+ : undefined;
322
+ const identity: { name?: string; code?: string | number } = {};
323
+ if (typeof facts.errorName === "string") identity.name = facts.errorName;
324
+ if (
325
+ typeof facts.errorCode === "string" ||
326
+ typeof facts.errorCode === "number"
327
+ ) {
328
+ identity.code = facts.errorCode;
226
329
  }
330
+ return {
331
+ cause: outcome.cause,
332
+ diagnostic: outcome.diagnostic,
333
+ ...(Object.keys(identity).length > 0 ? { identity } : {}),
334
+ ...(details === undefined ? {} : { details }),
335
+ };
336
+ }
337
+
338
+ /**
339
+ * Presence envelope for a caught last-host write failure.
340
+ * Own-key presence is distinct from the caught value: `throw undefined` must stay a
341
+ * real failure fact (same rule as settlement thrown own-key), never a missing-write sentinel.
342
+ */
343
+ type LastHostWriteFailure = { readonly error: unknown };
344
+
345
+ /** Keep primary throw identity; attach last-host write as a real AggregateError leaf. */
346
+ function withOptionalLastHostWriteThrow(
347
+ primary: unknown,
348
+ writeFailure: LastHostWriteFailure | undefined,
349
+ message: string,
350
+ ): unknown {
351
+ if (writeFailure === undefined) return primary;
352
+ return new AggregateError([primary, writeFailure.error], message, {
353
+ cause: primary,
354
+ });
227
355
  }
228
356
 
229
357
  export async function dispatchPostAdmissionTurn<
@@ -261,19 +389,95 @@ export async function dispatchPostAdmissionTurn<
261
389
  }
262
390
  // #617 DK-4: capture previous invocation host before markRunRunning overwrites it.
263
391
  // Single authority projectHostTransitionPriorNative owns known-host prior native paths.
264
- const previousHost = await readInvocationHost(admitted.runDirectory);
392
+ // #636 ticket-seat memory: one last-host page owns both last host and Grok native-home run.
393
+ // Open is not turn return/throw: production Grok notes the open fact at bind success;
394
+ // last-host records when the host returned a result OR when native home actually opened.
395
+ // Pre-open throws claim nothing; post-open throws retain the opened run.
396
+ // Side effects are gated by explicit ticket+seat binding — never by directory-outside-run guessing.
397
+ let previousHost: string | undefined;
398
+ let previousRunDirectory: string | undefined;
399
+ let nativeHomeRunDirectory: string | undefined;
400
+ let openedNativeHomeRunDirectory: string | undefined;
265
401
  const liveHost = env.host;
266
- const hostTransition =
267
- previousHost !== undefined && liveHost !== undefined && admitted.principal !== undefined
268
- ? await projectHostTransitionPriorNative({
269
- previousHost,
270
- liveHost,
271
- runDirectory: admitted.runDirectory,
272
- piSessionFile: env.principalAuthority.decode(admitted.principal).sessionFile,
273
- })
274
- : undefined;
275
- const turnRequest: RoleTurnRequest =
276
- hostTransition === undefined ? request : { ...request, hostTransition };
402
+ const principalCoordinates =
403
+ admitted.principal === undefined
404
+ ? undefined
405
+ : env.principalAuthority.decode(admitted.principal);
406
+ const ticketSeatMemoryBound = isTicketSeatMemoryBound({
407
+ role: admitted.role,
408
+ ...(admitted.ticketNumber === undefined ? {} : { ticketNumber: admitted.ticketNumber }),
409
+ });
410
+ let turnRequest: RoleTurnRequest;
411
+ try {
412
+ // Ticket-seat host authority lives on the nest last-host page only.
413
+ // Per-run invocation.host must not override: old-run retry after a cross-host
414
+ // intermediate would otherwise keep the stale run host and drop the transition.
415
+ // Non-ticket-seat paths still read the per-run invocation mark (#617).
416
+ if (ticketSeatMemoryBound && principalCoordinates !== undefined) {
417
+ const lastHost = await readTicketSeatMemoryLastHost(
418
+ principalCoordinates.sessionDirectory,
419
+ );
420
+ if (lastHost !== undefined) {
421
+ previousHost = lastHost.host;
422
+ // runDirectory on last-host is the established Grok native isolation run when present
423
+ // (preserved across non-Grok hosts so return-to-Grok can reopen it).
424
+ if (
425
+ typeof lastHost.runDirectory === "string" &&
426
+ lastHost.runDirectory.length > 0
427
+ ) {
428
+ previousRunDirectory = lastHost.runDirectory;
429
+ if (
430
+ liveHost === "grok-build" &&
431
+ request.continuation.kind === "resume"
432
+ ) {
433
+ nativeHomeRunDirectory = lastHost.runDirectory;
434
+ }
435
+ }
436
+ }
437
+ } else {
438
+ previousHost = await readInvocationHost(admitted.runDirectory);
439
+ }
440
+ const hostTransition =
441
+ previousHost !== undefined && liveHost !== undefined && principalCoordinates !== undefined
442
+ ? await projectHostTransitionPriorNative({
443
+ previousHost,
444
+ liveHost,
445
+ runDirectory: admitted.runDirectory,
446
+ piSessionFile: principalCoordinates.sessionFile,
447
+ ...(previousRunDirectory === undefined ? {} : { previousRunDirectory }),
448
+ })
449
+ : undefined;
450
+ turnRequest = request;
451
+ if (hostTransition !== undefined) {
452
+ turnRequest = { ...turnRequest, hostTransition };
453
+ }
454
+ if (nativeHomeRunDirectory !== undefined) {
455
+ turnRequest = { ...turnRequest, nativeHomeRunDirectory };
456
+ }
457
+ // Capture the actual Grok open event from production isolation (bind success).
458
+ if (ticketSeatMemoryBound) {
459
+ turnRequest = {
460
+ ...turnRequest,
461
+ noteNativeHomeOpened: (runDirectory) => {
462
+ openedNativeHomeRunDirectory = runDirectory;
463
+ },
464
+ };
465
+ }
466
+ } catch (error) {
467
+ // last-host / prior-native IO is on the public one-shot path — controlled failure, not bare throw.
468
+ return (await presentControlledFailure(
469
+ admitted,
470
+ {
471
+ timedOut: false,
472
+ code: null,
473
+ stderr: "",
474
+ thrown: error,
475
+ },
476
+ adapters,
477
+ env.principalAuthority,
478
+ io,
479
+ )) as { exitCode: number; admitted: A; terminal: T };
480
+ }
277
481
 
278
482
  await markRunRunning(admitted.runDirectory, env.model, effectiveEngine, env.host);
279
483
  await clearTypedProviderHttpObservation(admitted.runDirectory);
@@ -298,17 +502,66 @@ export async function dispatchPostAdmissionTurn<
298
502
  }
299
503
  }
300
504
 
301
- let result: RoleTurnResult;
505
+ // Turn outcome and open fact are separate events. Discriminate throw value undefined.
506
+ let turnOutcome:
507
+ | { readonly kind: "returned"; readonly result: RoleTurnResult }
508
+ | { readonly kind: "thrown"; readonly error: unknown };
302
509
  try {
303
- result = await env.roleTurnHost.executeTurn(turnRequest);
510
+ turnOutcome = {
511
+ kind: "returned",
512
+ result: await env.roleTurnHost.executeTurn(turnRequest),
513
+ };
304
514
  } catch (error) {
515
+ turnOutcome = { kind: "thrown", error };
516
+ }
517
+
518
+ // last-host authority: host returned a turn result OR native home actually opened.
519
+ // Grok open fact comes from production bind (noteNativeHomeOpened), not from return/throw.
520
+ // Mock hosts that return without opening still record via the returned path.
521
+ // Pre-open throws: neither → no claim. Post-open throws: open fact → retain.
522
+ // Spans returned failure / same-run retry / return-to-Grok / post-open body+cleanup throws.
523
+ // last-host write failure is pending secondary evidence only — never forks settlement.
524
+ // Thrown dual: AggregateError leaves. Returned dual: same shared settle/resolve chain,
525
+ // write leaf attached at the single present boundary (ADR 0080 single-settlement-disposition).
526
+ // Presence is the envelope, not the caught value — `throw undefined` stays a failure fact.
527
+ let lastHostWriteFailure: LastHostWriteFailure | undefined;
528
+ const hostEngaged =
529
+ turnOutcome.kind === "returned" || openedNativeHomeRunDirectory !== undefined;
530
+ if (
531
+ hostEngaged &&
532
+ liveHost !== undefined &&
533
+ principalCoordinates !== undefined &&
534
+ ticketSeatMemoryBound
535
+ ) {
536
+ try {
537
+ await writeTicketSeatMemoryLastHost(
538
+ principalCoordinates.sessionDirectory,
539
+ liveHost,
540
+ liveHost === "grok-build"
541
+ ? (openedNativeHomeRunDirectory ??
542
+ nativeHomeRunDirectory ??
543
+ admitted.runDirectory)
544
+ : previousRunDirectory,
545
+ );
546
+ } catch (error) {
547
+ lastHostWriteFailure = { error };
548
+ }
549
+ }
550
+
551
+ if (turnOutcome.kind === "thrown") {
552
+ // Preserve original throw/cause (pre-open or post-open). last-host already
553
+ // retained open fact above when bind had succeeded; write fail stays concurrent.
305
554
  return (await presentControlledFailure(
306
555
  admitted,
307
556
  {
308
557
  timedOut: false,
309
558
  code: null,
310
559
  stderr: "",
311
- thrown: error,
560
+ thrown: withOptionalLastHostWriteThrow(
561
+ turnOutcome.error,
562
+ lastHostWriteFailure,
563
+ "host turn and ticket-seat last-host write failed",
564
+ ),
312
565
  },
313
566
  adapters,
314
567
  env.principalAuthority,
@@ -316,6 +569,8 @@ export async function dispatchPostAdmissionTurn<
316
569
  )) as { exitCode: number; admitted: A; terminal: T };
317
570
  }
318
571
 
572
+ const result = turnOutcome.result;
573
+
319
574
  try {
320
575
  await writeFile(
321
576
  join(admitted.runDirectory, "stderr.log"),
@@ -330,13 +585,18 @@ export async function dispatchPostAdmissionTurn<
330
585
  try {
331
586
  settled = await adapters.trySettle(admitted, env.principalAuthority);
332
587
  } catch (error) {
588
+ // Settle throw is a real failure fact — never swallow into undefined.
333
589
  return (await presentControlledFailure(
334
590
  admitted,
335
591
  {
336
592
  timedOut: false,
337
593
  code: result.code,
338
594
  stderr: result.stderr,
339
- thrown: error,
595
+ thrown: withOptionalLastHostWriteThrow(
596
+ error,
597
+ lastHostWriteFailure,
598
+ "settlement and ticket-seat last-host write failed",
599
+ ),
340
600
  },
341
601
  adapters,
342
602
  env.principalAuthority,
@@ -344,6 +604,44 @@ export async function dispatchPostAdmissionTurn<
344
604
  )) as { exitCode: number; admitted: A; terminal: T };
345
605
  }
346
606
  if (settled !== undefined && shouldPresent(settled)) {
607
+ if (lastHostWriteFailure !== undefined) {
608
+ if (settled.roleOutcome.kind === "failure") {
609
+ // Existing failure terminal stays primary; last-host write is concurrent only.
610
+ // shouldPresentSettled must not be read as "no existing failure".
611
+ return (await presentControlledFailure(
612
+ admitted,
613
+ withLastHostWriteConcurrentFailure(
614
+ {
615
+ timedOut: result.timedOut,
616
+ code: result.code,
617
+ stderr: result.stderr,
618
+ knownFailure: knownFailureFromSettledFailureOutcome(
619
+ settled.roleOutcome,
620
+ ),
621
+ },
622
+ lastHostWriteFailure.error,
623
+ ),
624
+ adapters,
625
+ env.principalAuthority,
626
+ io,
627
+ )) as { exitCode: number; admitted: A; terminal: T };
628
+ }
629
+ // No existing failure terminal — last-host write is the sole public failure.
630
+ // thrown key is always set so `throw undefined` stays own-key present.
631
+ return (await presentControlledFailure(
632
+ admitted,
633
+ {
634
+ timedOut: result.timedOut,
635
+ code: result.code,
636
+ stderr: result.stderr,
637
+ thrown: lastHostWriteFailure.error,
638
+ },
639
+ adapters,
640
+ env.principalAuthority,
641
+ io,
642
+ )) as { exitCode: number; admitted: A; terminal: T };
643
+ }
644
+ // last-host recorded after host returned a turn result (shared failure/retry/return fact).
347
645
  await markRunTerminal(admitted.runDirectory);
348
646
  io.stdout(formatTerminalResult(settled));
349
647
  return {
@@ -353,19 +651,6 @@ export async function dispatchPostAdmissionTurn<
353
651
  };
354
652
  }
355
653
 
356
- if (adapters.trySettleSecondary !== undefined) {
357
- const secondary = await adapters.trySettleSecondary(admitted, env.principalAuthority);
358
- if (secondary !== undefined) {
359
- await markRunTerminal(admitted.runDirectory);
360
- presentSecondaryTerminal(secondary, io);
361
- return {
362
- exitCode: exitCodeForTerminalOutcome(secondary.roleOutcome),
363
- admitted,
364
- terminal: secondary as unknown as T,
365
- };
366
- }
367
- }
368
-
369
654
  const sessionFile =
370
655
  admitted.principal !== undefined
371
656
  ? env.principalAuthority.decode(admitted.principal).sessionFile
@@ -385,14 +670,20 @@ export async function dispatchPostAdmissionTurn<
385
670
  credential: credentialFailure,
386
671
  runDirectory: admitted.runDirectory,
387
672
  });
673
+ const hostFailureInput: ControlledFailureInput = {
674
+ timedOut: result.timedOut,
675
+ code: result.code,
676
+ stderr: result.stderr,
677
+ ...controlledFailureInputFromResolution(resolution),
678
+ };
388
679
  return (await presentControlledFailure(
389
680
  admitted,
390
- {
391
- timedOut: result.timedOut,
392
- code: result.code,
393
- stderr: result.stderr,
394
- ...controlledFailureInputFromResolution(resolution),
395
- },
681
+ lastHostWriteFailure === undefined
682
+ ? hostFailureInput
683
+ : withLastHostWriteConcurrentFailure(
684
+ hostFailureInput,
685
+ lastHostWriteFailure.error,
686
+ ),
396
687
  adapters,
397
688
  env.principalAuthority,
398
689
  io,