@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
@@ -8,6 +8,7 @@
8
8
  import type { NoReceiptLifecycleFacts } from "../receipt-delivery-policy.ts";
9
9
  import type { ControlledFailureCause } from "../host-contracts.ts";
10
10
  import type { PackagedRole } from "../packaged-role-registry.ts";
11
+ import { serializeThrownValue } from "../serialize-thrown-value.ts";
11
12
 
12
13
  export type { ControlledFailureCause } from "../host-contracts.ts";
13
14
 
@@ -25,11 +26,11 @@ export type TerminalArtifactRef = {
25
26
  /** Public callable roles that currently produce Terminal outcomes. */
26
27
  export type TerminalRoleName = PackagedRole;
27
28
 
28
- export type NoReceiptTerminalOutcome = NoReceiptLifecycleFacts & {
29
+ export type NoReceiptTerminalOutcome = Partial<NoReceiptLifecycleFacts> & {
29
30
  kind: "no_receipt";
30
31
  role: TerminalRoleName;
31
32
  status: "no-accepted-receipt";
32
- decisiveFacts: NoReceiptLifecycleFacts & Readonly<Record<string, unknown>>;
33
+ decisiveFacts: Partial<NoReceiptLifecycleFacts> & Readonly<Record<string, unknown>>;
33
34
  };
34
35
 
35
36
  export type TerminalRoleOutcome =
@@ -56,7 +57,7 @@ export type TerminalRoleOutcome =
56
57
  role: TerminalRoleName;
57
58
  /**
58
59
  * Typed cause class when a typed fact confirms it.
59
- * Omitted when unknown — original diagnostic + error artifact carry the fact (#881).
60
+ * Omitted when unknown — the original diagnostic retains the fact (#881).
60
61
  * Never a fabricated "unrecognized" label.
61
62
  */
62
63
  cause?: ControlledFailureCause;
@@ -102,12 +103,6 @@ export type TerminalNavigatorFact =
102
103
  advisoryDiagnostic?: string;
103
104
  };
104
105
 
105
- /** Present only when a controlled failure is v1-resumable (typed HTTP 429). */
106
- export type TerminalResume = {
107
- /** Complete public command; run ID is revealed only here. */
108
- readonly command: string;
109
- };
110
-
111
106
  /** Current English gate seat faces projected on Terminal (#478). */
112
107
  export type TerminalGateSeat = "gatekeeper" | "inspector" | "notary";
113
108
 
@@ -147,38 +142,21 @@ export type TerminalGateFact = {
147
142
  };
148
143
 
149
144
  /**
150
- * One admitted Role run's typed Terminal aggregate.
151
- * Resumable failures carry `resume` and must not re-disclose the run ID via
152
- * top-level `runId` or public artifact path components — only `resume.command`.
153
- * #416: autoResumeCount (0..2) is a call-local observation of how many in-place
154
- * auto-resumes occurred during this single LLM call; it is not persisted to
155
- * run-state.json and does not participate in limit decisions.
156
- */
157
- /**
158
- * Current-result payloads on a terminal — accepted/audit role result, or a
159
- * rare intentional failure face. Run history is TerminalResult.submissions
160
- * (#836 / #953), not this helper.
145
+ * Parent-facing current reply: a court's own accepted/audit payloads only.
146
+ * Run-scoped history stays on TerminalResult.submissions (#879 / #836), and a
147
+ * failure face never answers as this court's reply (#953).
161
148
  */
162
- export function roleResultPayloads(outcome: TerminalRoleOutcome): readonly unknown[] {
163
- if (outcome.kind === "accepted" || outcome.kind === "audit_escalation") return outcome.payloads ?? [];
164
- if (outcome.kind === "failure") return outcome.payloads ?? [];
165
- return [];
149
+ export function currentReplyRows(terminal: TerminalResult | undefined): readonly unknown[] {
150
+ const outcome = terminal?.roleOutcome;
151
+ return outcome?.kind === "accepted" || outcome?.kind === "audit_escalation"
152
+ ? outcome.payloads ?? []
153
+ : [];
166
154
  }
167
155
 
168
156
  /**
169
- * Prefer non-empty current-result payloads; an empty array must not shadow
170
- * top-level submissions (#953 / #836). Callers that need failure history must
171
- * read TerminalResult.submissions directly — do not coalesce into failure.payloads.
157
+ * One admitted Role run's typed Terminal aggregate.
158
+ * autoResumeCount is call-local (#416).
172
159
  */
173
- export function coalesceSubmissionRows(
174
- payloads: readonly unknown[] | undefined,
175
- submissions: readonly unknown[] | undefined,
176
- ): readonly unknown[] {
177
- if (payloads !== undefined && payloads.length > 0) return payloads;
178
- if (submissions !== undefined && submissions.length > 0) return submissions;
179
- return payloads ?? submissions ?? [];
180
- }
181
-
182
160
  export type TerminalResult = {
183
161
  roleOutcome: TerminalRoleOutcome;
184
162
  /** Default Reviewer parent: original child Terminals, keyed by frozen axis. */
@@ -207,22 +185,14 @@ export type TerminalResult = {
207
185
  /** Call-local auto-resume observation (0..2) for this single LLM call; read-only, not persisted. */
208
186
  autoResumeCount?: number;
209
187
  } & (
210
- | {
211
- /** Resumable failure: run ID appears only inside resume.command. */
212
- resume: TerminalResume;
213
- runId?: undefined;
214
- batch?: undefined;
215
- }
216
188
  | {
217
189
  runId: string;
218
- resume?: undefined;
219
190
  batch?: undefined;
220
191
  }
221
192
  | {
222
193
  /** Deterministic public batch projection; no parent Role run exists. */
223
194
  batch: "reviewer";
224
195
  runId?: undefined;
225
- resume?: undefined;
226
196
  }
227
197
  );
228
198
 
@@ -283,7 +253,7 @@ export function formatTerminalResult(result: TerminalResult): string {
283
253
  for (const [key, value] of Object.entries(facts)) {
284
254
  if (value === undefined) continue;
285
255
  const rendered =
286
- typeof value === "string" ? value : JSON.stringify(value);
256
+ typeof value === "string" ? value : serializeThrownValue(value);
287
257
  lines.push(`fact\t${encodeTerminalField(key)}\t${encodeTerminalField(rendered)}`);
288
258
  }
289
259
  }
@@ -328,10 +298,7 @@ export function formatTerminalResult(result: TerminalResult): string {
328
298
  }
329
299
  }
330
300
  }
331
- if (result.resume !== undefined) {
332
- // Resumable failure: run ID is revealed only inside the complete resume command.
333
- lines.push(`resume\t${encodeTerminalField(result.resume.command)}`);
334
- } else if (result.runId !== undefined) {
301
+ if (result.runId !== undefined) {
335
302
  lines.push(`run\t${encodeTerminalField(result.runId)}`);
336
303
  }
337
304
  if (result.autoResumeCount !== undefined) {
@@ -339,26 +306,19 @@ export function formatTerminalResult(result: TerminalResult): string {
339
306
  }
340
307
  // Role-result block: original payloads, newest first for humans (#961).
341
308
  // Typed payloads/submissions stay ledger order; only this presentation reverses.
342
- // #953: failure history is the top-level submissions carrier (#836) — present
343
- // as recorded-submission, never as this-turn submission/receipt.
344
- if (result.roleOutcome.kind === "failure") {
345
- const recorded = result.submissions ?? [];
346
- for (let i = recorded.length - 1; i >= 0; i -= 1) {
347
- const payload = recorded[i]!;
348
- const rendered =
349
- typeof payload === "string" ? payload : JSON.stringify(payload);
350
- lines.push(`recorded-submission\t${encodeTerminalField(rendered)}`);
351
- }
352
- } else {
353
- const payloads = result.roleOutcome.kind === "accepted" ||
354
- result.roleOutcome.kind === "audit_escalation"
355
- ? coalesceSubmissionRows(result.submissions, result.roleOutcome.payloads)
356
- : result.submissions ?? [];
357
- for (let i = payloads.length - 1; i >= 0; i -= 1) {
358
- const payload = payloads[i]!;
359
- const rendered =
360
- typeof payload === "string" ? payload : JSON.stringify(payload);
361
- lines.push(`submission\t${encodeTerminalField(rendered)}`);
309
+ // Present the current result and the run history on distinct faces (#879 / #836).
310
+ // Neither a prior court's submission nor a failed turn's history is this reply.
311
+ const current = result.roleOutcome.kind === "failure"
312
+ ? result.roleOutcome.payloads ?? []
313
+ : currentReplyRows(result);
314
+ for (const [label, rows] of [
315
+ ["submission", current],
316
+ ["recorded-submission", result.submissions ?? []],
317
+ ] as const) {
318
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
319
+ const payload = rows[i]!;
320
+ const rendered = typeof payload === "string" ? payload : JSON.stringify(payload);
321
+ lines.push(`${label}\t${encodeTerminalField(rendered)}`);
362
322
  }
363
323
  }
364
324
  return `${lines.join("\n")}\n`;
@@ -27,6 +27,7 @@ import type { TerminalResult } from "./public-cli/terminal.ts";
27
27
  import type { HostSelectionFailure, NamedRoleTurnHostAdapter } from "./public-cli/role-turn-host-resolution.ts";
28
28
 
29
29
  import { isRecord, errorText } from "./unknown-value.ts";
30
+ import { serializeThrownValue } from "./serialize-thrown-value.ts";
30
31
 
31
32
  /** Env published by the parent activation so nested summons never re-derive root. */
32
33
  export const AK_ROLE_PACKAGE_ROOT_ENV = "AK_ROLE_PACKAGE_ROOT" as const;
@@ -588,15 +589,9 @@ export async function summonParallelReviewerLenses(options: {
588
589
  readonly completeness: PublicSummonResult;
589
590
  readonly correctness: PublicSummonResult;
590
591
  }> {
591
- const describeFailure = (error: unknown): string => {
592
- if (error instanceof AggregateError) {
593
- return [error.message, ...error.errors.map(describeFailure)].join("\n");
594
- }
595
- return errorText(error);
596
- };
597
592
  const failedResult = (error: unknown): PublicSummonResult => ({
598
593
  exitCode: 1,
599
- stderr: describeFailure(error),
594
+ stderr: serializeThrownValue(error),
600
595
  });
601
596
  const dualFailure = (error: unknown) => {
602
597
  const failure = failedResult(error);
@@ -720,7 +715,7 @@ export async function summonParallelReviewerLenses(options: {
720
715
  ].join("\n");
721
716
  }
722
717
  } catch (error) {
723
- sealDiagnostic = `Reviewer target final seal failed:\n${describeFailure(error)}`;
718
+ sealDiagnostic = `Reviewer target final seal failed:\n${serializeThrownValue(error)}`;
724
719
  }
725
720
  if (sealDiagnostic !== undefined) {
726
721
  const withSealFailure = (result: PublicSummonResult): PublicSummonResult => ({
@@ -83,18 +83,13 @@ export function parseNoReceiptLifecycleFacts(input: unknown): NoReceiptLifecycle
83
83
  && typeof item.reason === "string")) {
84
84
  throw new TypeError("malformed no-receipt lifecycle facts");
85
85
  }
86
- return {
86
+ return noReceiptLifecycleFacts({
87
87
  terminalToolCalled: input.terminalToolCalled,
88
- rejectedReceipts: input.rejectedReceipts.map((item) => ({
89
- reason: item.reason as string,
90
- diagnosticAvailable: (item.reason as string).trim() !== "",
91
- })),
88
+ rejectedReceipts: input.rejectedReceipts as { reason: string }[],
92
89
  deliveryTurns: deliveryTurnsFact(input.deliveryTurns),
93
- sessionCompletion: "settled-without-accepted-receipt",
94
90
  runPointer: input.runPointer,
95
91
  attemptPointer: input.attemptPointer,
96
- acceptedReceipt: false,
97
- };
92
+ });
98
93
  }
99
94
 
100
95
  /**
@@ -168,10 +163,14 @@ export function createReceiptDeliveryPolicy(limit?: number) {
168
163
  * larger of the two seams: a nested zero must not wipe prompts this layer
169
164
  * already sent, and adding the two counts would bill one send twice.
170
165
  */
171
- recordNestedNoReceipt(facts: NoReceiptLifecycleFacts) {
172
- terminalToolCalled = terminalToolCalled || facts.terminalToolCalled;
173
- rejectedReceipts.push(...facts.rejectedReceipts);
174
- deliveryTurns = Math.max(deliveryTurns, facts.deliveryTurns);
166
+ recordNestedNoReceipt(facts: Partial<NoReceiptLifecycleFacts>) {
167
+ if (typeof facts.terminalToolCalled === "boolean") {
168
+ terminalToolCalled = terminalToolCalled || facts.terminalToolCalled;
169
+ }
170
+ if (facts.rejectedReceipts !== undefined) {
171
+ rejectedReceipts.push(...facts.rejectedReceipts);
172
+ }
173
+ if (facts.deliveryTurns !== undefined) deliveryTurns = Math.max(deliveryTurns, facts.deliveryTurns);
175
174
  closed = true;
176
175
  },
177
176
  nextAction(): "accepted" | "request-delivery" | "no-receipt" {
@@ -35,6 +35,8 @@ import {
35
35
  extractInfrastructureFailureEvidence,
36
36
  } from "./navigator-invocation-identity.ts";
37
37
  import { serializeThrownValue } from "./serialize-thrown-value.ts";
38
+ import { retainPackageFault } from "./public-cli/settlement.ts";
39
+ import { describeErrorIdentity } from "./public-cli/run-lifecycle.ts";
38
40
 
39
41
  import { isRecord, errorText } from "./unknown-value.ts";
40
42
 
@@ -557,14 +559,14 @@ export async function prepareRoleEnvelope(options: {
557
559
  const dispose = async (): Promise<void> => {
558
560
  if (disposed) return;
559
561
  disposed = true;
560
- const cleanupFailures: unknown[] = [];
562
+ const requiredFailures: unknown[] = [];
561
563
  // session_shutdown may still append package durable entries (navigator
562
564
  // attendance when agent_settled was skipped). Drain only after those
563
565
  // handlers — single terminal judgment via settleDurableWrites (#959).
564
566
  try {
565
567
  await emit("session_shutdown", {});
566
568
  } catch (error) {
567
- cleanupFailures.push(error);
569
+ requiredFailures.push(error);
568
570
  }
569
571
  try {
570
572
  // Always drain post-shutdown writes. Only throw when the host has not
@@ -572,7 +574,7 @@ export async function prepareRoleEnvelope(options: {
572
574
  const alreadyHanded = durableFailureHandedToCloseRound;
573
575
  const durableFailure = await settleDurableWrites();
574
576
  if (durableFailure !== undefined && !alreadyHanded) {
575
- cleanupFailures.push(
577
+ requiredFailures.push(
576
578
  Object.assign(
577
579
  new Error(durableFailure.failure.diagnostic ?? "durable session entry flush failed"),
578
580
  { code: durableFailure.failure.identity?.code ?? "durable-session-write-failed" },
@@ -580,7 +582,7 @@ export async function prepareRoleEnvelope(options: {
580
582
  );
581
583
  }
582
584
  } catch (error) {
583
- cleanupFailures.push(error);
585
+ requiredFailures.push(error);
584
586
  }
585
587
  try {
586
588
  const closeAll = (server as unknown as { closeAllConnections?: () => void }).closeAllConnections;
@@ -589,12 +591,17 @@ export async function prepareRoleEnvelope(options: {
589
591
  server.close((error) => (error ? reject(error) : resolve()));
590
592
  });
591
593
  } catch (error) {
592
- cleanupFailures.push(error);
594
+ // Closing the relay is ordinary cleanup, not required envelope work.
595
+ await retainPackageFault({
596
+ runDirectory: request.runDirectory,
597
+ diagnostic: `relay close failed beside envelope shutdown: ${describeErrorIdentity(error)}`,
598
+ error,
599
+ });
593
600
  }
594
- if (cleanupFailures.length === 1) throw cleanupFailures[0];
595
- if (cleanupFailures.length > 1) {
596
- throw new AggregateError(cleanupFailures, "ACP envelope dispose cleanup failures", {
597
- cause: cleanupFailures[0],
601
+ if (requiredFailures.length === 1) throw requiredFailures[0];
602
+ if (requiredFailures.length > 1) {
603
+ throw new AggregateError(requiredFailures, "Required envelope shutdown failures", {
604
+ cause: requiredFailures[0],
598
605
  });
599
606
  }
600
607
  };
@@ -111,7 +111,6 @@ import {
111
111
  NAVIGATOR_INVOCATION_ENTRY,
112
112
  resolveLifecycleInvocationPrincipal,
113
113
  } from "./navigator-invocation-identity.ts";
114
- import { recordTypedProviderHttpStatus } from "./typed-provider-http.ts";
115
114
  import { NAVIGATOR_POST_ROLE_GRACE_MS, raceNavigatorGrace } from "./public-cli/settlement.ts";
116
115
  import { PACKAGED_ROLE_REGISTRY, isOfficerReviewSeat, packagedRoleActivationFlags, packagedRoleInputFlag, packagedRoleMetadata, packagedRoleOutputTool, packagedRolePhaseFlag, type PackagedRole } from "./packaged-role-registry.ts";
117
116
  import {
@@ -308,7 +307,7 @@ export type { GatekeeperResult, GatekeeperSubject, SubmissionGateNonPassResult,
308
307
  import { ParentQueueReaskError, unreadableDiscriminatorNotice } from "./submission-errors.ts";
309
308
  import { REVIEW_QUEUE_STATUSES } from "./review-submission.ts";
310
309
 
311
- import { isRecord, errorText, isEnoent } from "./unknown-value.ts";
310
+ import { isRecord, errorText } from "./unknown-value.ts";
312
311
 
313
312
  export {
314
313
  DOCTOR_EVIDENCE_TOOL_NAME,
@@ -1056,9 +1055,6 @@ export function createRoleRuntimeExtension(
1056
1055
  // #1132: one ceiling for this turn, closed over from the execution seam.
1057
1056
  let receiptDelivery = createReceiptDeliveryPolicy(deliveryLimit);
1058
1057
  let noReceiptRecorded = false;
1059
- // Public-run fetch observation.
1060
- let priorFetch: typeof globalThis.fetch | undefined;
1061
- let fetchWrapped = false;
1062
1058
  /** Envelope-owned: abort/teardown attendance without re-blocking the parent court (#959). */
1063
1059
  const disposeNavigatorAttendanceNonBlocking = (
1064
1060
  attendance: NavigatorAttendanceDependency | undefined,
@@ -1433,35 +1429,31 @@ export function createRoleRuntimeExtension(
1433
1429
  roleHost.on("session_shutdown", async () => {
1434
1430
  // #351: stop OAuth keepalive first so shutdown yields zero further ticks.
1435
1431
  envelopeHost.stopKeepalive();
1436
- if (fetchWrapped && priorFetch !== undefined) {
1437
- globalThis.fetch = priorFetch;
1438
- priorFetch = undefined;
1439
- fetchWrapped = false;
1440
- }
1441
1432
  // Flush any still-pending affirmative attendance before teardown.
1442
1433
  // Grace-timeout paths normally emit on agent_settled; abort can skip that hook.
1443
1434
  const presentation = pendingNavigatorPresentation;
1444
1435
  pendingNavigatorPresentation = undefined;
1445
- if (presentation !== undefined) {
1446
- try {
1436
+ try {
1437
+ if (presentation !== undefined) {
1438
+ // Required attendance persistence must reach its failure owner, not be
1439
+ // discarded as cleanup or merged with an existing host report.
1447
1440
  await envelopeHost.sendMessage({
1448
1441
  customType: NAVIGATOR_EVENT_TYPE,
1449
1442
  content: formatNavigatorReport(presentation.report),
1450
1443
  display: true,
1451
1444
  details: presentation.event,
1452
1445
  }, { triggerTurn: false });
1453
- } catch {
1454
- // Teardown must not mask the original role failure cause.
1455
1446
  }
1447
+ } finally {
1448
+ // Same non-blocking dispose as post-role grace: awaiting here re-blocked the
1449
+ // parent court for 43–270s after unavailable was already projected (#959 reopen).
1450
+ const attendanceToDispose = navigatorAttendance;
1451
+ navigatorAttendance = undefined;
1452
+ pendingNavigatorSettlement = undefined;
1453
+ disposeNavigatorAttendanceNonBlocking(attendanceToDispose);
1454
+ pendingInfrastructureFailures.clear();
1455
+ pendingSubmissionNonPassByToolCallId.clear();
1456
1456
  }
1457
- // Same non-blocking dispose as post-role grace: awaiting here re-blocked the
1458
- // parent court for 43–270s after unavailable was already projected (#959 reopen).
1459
- const attendanceToDispose = navigatorAttendance;
1460
- navigatorAttendance = undefined;
1461
- pendingNavigatorSettlement = undefined;
1462
- disposeNavigatorAttendanceNonBlocking(attendanceToDispose);
1463
- pendingInfrastructureFailures.clear();
1464
- pendingSubmissionNonPassByToolCallId.clear();
1465
1457
  });
1466
1458
 
1467
1459
  const hostActions = {
@@ -1649,66 +1641,7 @@ export function createRoleRuntimeExtension(
1649
1641
  const clock = dependencies.activationClock ?? (() => new Date().toISOString());
1650
1642
  const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
1651
1643
 
1652
- // Public Role run: record typed non-success HTTP for error evidence + v1 resume.
1653
- // Same observation owner → typed-provider-http
1654
- // sidecar (settlement already merges observation.httpStatus into knownFailure).
1655
- // after_provider_response covers the success-path onResponse face; fetch wrap covers
1656
- // non-2xx Responses where openai-completions throws before onResponse (#675).
1657
- const recordHttpObservation = async (
1658
- status: number,
1659
- provider: string,
1660
- ctx: HostContext,
1661
- ): Promise<void> => {
1662
- const runDir = runDirectoryFromHostContext(ctx);
1663
- if (runDir === undefined) return;
1664
- try {
1665
- await recordTypedProviderHttpStatus(runDir, { httpStatus: status, provider });
1666
- } catch (error) {
1667
- if (status >= 200 && status < 300 && isEnoent(error)) return;
1668
- failInfrastructure(error, ctx);
1669
- }
1670
- };
1671
- roleHost.on("after_provider_response", async (event, ctx) => {
1672
- const status = event.status;
1673
- if (typeof status !== "number") return;
1674
- const fromCtx = ctx.model?.provider;
1675
- const provider =
1676
- typeof fromCtx === "string" && fromCtx.trim() !== ""
1677
- ? fromCtx
1678
- : "unknown";
1679
- await recordHttpObservation(status, provider, ctx);
1680
- });
1681
-
1682
1644
  roleHost.on("session_start", async (event, ctx) => {
1683
- // Scope fetch observation to this public run.
1684
- if (!fetchWrapped && typeof globalThis.fetch === "function") {
1685
- priorFetch = globalThis.fetch.bind(globalThis);
1686
- const underlying = priorFetch;
1687
- globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
1688
- const response = await underlying(input, init);
1689
- const runDir = runDirectoryFromHostContext(ctx);
1690
- if (
1691
- runDir !== undefined
1692
- && typeof response?.status === "number"
1693
- && (response.status < 200 || response.status >= 300)
1694
- ) {
1695
- const provider =
1696
- typeof ctx.model?.provider === "string" && ctx.model.provider.trim() !== ""
1697
- ? ctx.model.provider
1698
- : "unknown";
1699
- try {
1700
- await recordTypedProviderHttpStatus(runDir, {
1701
- httpStatus: response.status,
1702
- provider,
1703
- });
1704
- } catch {
1705
- // Observation must not break the provider stream.
1706
- }
1707
- }
1708
- return response;
1709
- }) as typeof globalThis.fetch;
1710
- fetchWrapped = true;
1711
- }
1712
1645
  admitted = false;
1713
1646
  selectedRole = undefined;
1714
1647
  roleReferenceMaterials = "";
@@ -1,21 +1,25 @@
1
1
  /**
2
- * Canonical reader for run-directory typed terminal artifacts.
3
- * Layout owner is settlement publish*Artifacts (report.json / error.json /
4
- * audit-incomplete.json under artifacts/, plus the same publisher's durable
5
- * failure fallbacks). This module only reads presence and structural
2
+ * Canonical layout and reader for run-directory typed terminal artifacts.
3
+ * Settlement publish*Artifacts uses these names (report.json / error.json /
4
+ * audit-incomplete.json under artifacts/, plus durable failure fallbacks).
5
+ * The reader only checks presence and structural
6
6
  * readability — it does not re-derive role outcomes or invent a second
7
7
  * candidate algorithm.
8
8
  */
9
+ import { randomUUID } from "node:crypto";
9
10
  import { readdir, readFile } from "node:fs/promises";
10
11
  import { basename, dirname, join } from "node:path";
11
12
 
12
13
  import { roleRunArtifactsDirectory } from "./role-run-placement.ts";
13
-
14
14
  import { isRecord, errorText, isMissingPathError } from "./unknown-value.ts";
15
15
 
16
+ export const RUN_TERMINAL_REPORT_FILE = "report.json" as const;
17
+ export const RUN_TERMINAL_ERROR_FILE = "error.json" as const;
18
+ export const RUN_TERMINAL_EVIDENCE_FILE = "evidence.json" as const;
19
+ export const RUN_TERMINAL_ERROR_SETTLEMENT_FILE = "error.settlement.json" as const;
16
20
  export const RUN_TERMINAL_ARTIFACT_FILES = [
17
- "report.json",
18
- "error.json",
21
+ RUN_TERMINAL_REPORT_FILE,
22
+ RUN_TERMINAL_ERROR_FILE,
19
23
  "audit-incomplete.json",
20
24
  ] as const;
21
25
 
@@ -28,13 +32,18 @@ export type RunTerminalArtifactFile = (typeof RUN_TERMINAL_ARTIFACT_FILES)[numbe
28
32
  * Relative to the run directory.
29
33
  */
30
34
  export const RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS = [
31
- "artifacts/error.settlement.json",
32
- "error.settlement.json",
35
+ `artifacts/${RUN_TERMINAL_ERROR_SETTLEMENT_FILE}`,
36
+ RUN_TERMINAL_ERROR_SETTLEMENT_FILE,
33
37
  ] as const;
34
38
 
35
- /** Unique open-ended failure names: error.<uuid>.json (publisher stem + uuid). */
36
- const UNIQUE_ERROR_FALLBACK_NAME =
37
- /^error\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.json$/i;
39
+ /** Publisher and reader share the open-ended error face stem. */
40
+ export const UNIQUE_ERROR_FALLBACK_STEM = "error";
41
+ export function uniqueErrorFallbackName(): string {
42
+ return `${UNIQUE_ERROR_FALLBACK_STEM}.${randomUUID()}.json`;
43
+ }
44
+ const UNIQUE_ERROR_FALLBACK_NAME = new RegExp(
45
+ `^${UNIQUE_ERROR_FALLBACK_STEM}\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.json$`, "i",
46
+ );
38
47
 
39
48
  export type RunTerminalArtifactRead =
40
49
  | { readonly status: "absent" }
@@ -266,8 +275,3 @@ export async function readRunTerminalArtifact(
266
275
  }
267
276
  return { status: "absent" };
268
277
  }
269
-
270
- /** Test/helper: basename face of a unique fallback path, if any. */
271
- export function isUniqueErrorFallbackName(name: string): boolean {
272
- return UNIQUE_ERROR_FALLBACK_NAME.test(basename(name));
273
- }
@@ -2,5 +2,11 @@ import { inspect } from "node:util";
2
2
 
3
3
  /** Record the whole thrown value without invoking its getters or custom formatter. */
4
4
  export function serializeThrownValue(value: unknown): string {
5
- return inspect(value, { depth: 10, showHidden: true, customInspect: false });
5
+ return inspect(value, {
6
+ depth: null,
7
+ maxArrayLength: null,
8
+ maxStringLength: null,
9
+ showHidden: true,
10
+ customInspect: false,
11
+ });
6
12
  }