@akagilnc/pi-workflow-roles 0.1.4021 → 0.1.4062

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 (74) hide show
  1. package/dist/acp-host/production-host.js +682 -355
  2. package/dist/doctor-auditor.js +1 -1
  3. package/dist/dossier-resolution.js +21 -8
  4. package/dist/gatekeeper-role.js +34 -27
  5. package/dist/headless-host/description.js +7 -8
  6. package/dist/headless-host/production-host.js +709 -369
  7. package/dist/host-contracts.js +16 -0
  8. package/dist/inspector-contracts.js +8 -0
  9. package/dist/navigator-attendance.js +2 -0
  10. package/dist/navigator-public-session.js +18 -34
  11. package/dist/navigator-session-contracts.js +19 -0
  12. package/dist/packaged-role-registry.js +1 -1
  13. package/dist/pi/known-failure.js +3 -4
  14. package/dist/pi/role-turn-host.js +43 -13
  15. package/dist/public-cli/auto-resume.js +20 -7
  16. package/dist/public-cli/case-dossier-delivery.js +60 -1
  17. package/dist/public-cli/diarist-run.js +9 -23
  18. package/dist/public-cli/inspector-run.js +17 -3
  19. package/dist/public-cli/instruction-seat-run.js +12 -7
  20. package/dist/public-cli/judge-run.js +3 -1
  21. package/dist/public-cli/main.js +440 -222
  22. package/dist/public-cli/notary-run.js +7 -4
  23. package/dist/public-cli/post-admission.js +66 -59
  24. package/dist/public-cli/run-lifecycle.js +3 -0
  25. package/dist/public-cli/settlement.js +78 -58
  26. package/dist/public-cli/terminal.js +1 -11
  27. package/dist/public-role-summons.js +8 -0
  28. package/dist/submission-ledger.js +139 -19
  29. package/dist/user-dialogue-stdin.js +33 -0
  30. package/extensions/role-runtime.ts +1 -0
  31. package/package.json +1 -1
  32. package/resources/836-deleted-machine-instruction-inventory.md +1 -1
  33. package/src/doctor-auditor.ts +1 -1
  34. package/src/dossier-resolution.ts +26 -8
  35. package/src/external-host-turn-loop.ts +9 -0
  36. package/src/gatekeeper-pass-envelope.ts +6 -0
  37. package/src/gatekeeper-role.ts +47 -28
  38. package/src/headless-host/description.ts +8 -11
  39. package/src/headless-host/role-turn-host.ts +20 -5
  40. package/src/host-contracts.ts +30 -7
  41. package/src/inspector-contracts.ts +7 -0
  42. package/src/judge-role.ts +4 -0
  43. package/src/navigator-attendance.ts +2 -0
  44. package/src/navigator-public-session.ts +19 -55
  45. package/src/navigator-session-contracts.ts +39 -0
  46. package/src/navigator-work-context.ts +3 -4
  47. package/src/notary-role.ts +1 -0
  48. package/src/packaged-role-registry.ts +1 -1
  49. package/src/pi/adapter.ts +19 -4
  50. package/src/pi/known-failure.ts +3 -4
  51. package/src/pi/role-turn-host.ts +48 -14
  52. package/src/public-cli/auto-resume.ts +57 -20
  53. package/src/public-cli/case-dossier-delivery.ts +86 -1
  54. package/src/public-cli/cli.ts +2 -2
  55. package/src/public-cli/collector-run.ts +3 -1
  56. package/src/public-cli/countersign-run.ts +34 -27
  57. package/src/public-cli/diarist-run.ts +10 -27
  58. package/src/public-cli/inspector-run.ts +26 -2
  59. package/src/public-cli/instruction-seat-run.ts +20 -10
  60. package/src/public-cli/invocation.ts +2 -0
  61. package/src/public-cli/judge-run.ts +3 -1
  62. package/src/public-cli/notary-run.ts +13 -4
  63. package/src/public-cli/post-admission.ts +74 -65
  64. package/src/public-cli/reviewer-run.ts +3 -1
  65. package/src/public-cli/run-lifecycle.ts +3 -0
  66. package/src/public-cli/settlement.ts +105 -80
  67. package/src/public-cli/terminal.ts +7 -16
  68. package/src/public-role-summons.ts +31 -7
  69. package/src/role-envelope.ts +9 -30
  70. package/src/role-runtime-dependencies.ts +1 -0
  71. package/src/role-runtime.ts +95 -19
  72. package/src/submission-ledger.ts +195 -26
  73. package/src/user-dialogue-stdin.ts +37 -0
  74. package/src/worker-role.ts +4 -0
@@ -15,6 +15,7 @@ import { readSitianRecords, resolveSitianRecordPath, sitianReport } from "../sit
15
15
 
16
16
  import {
17
17
  hasFreshAttemptSubmission,
18
+ readAttemptScopedSubmissionRows,
18
19
  readRecordedSubmissionRows,
19
20
  readRecordedSubmissions,
20
21
  } from "../submission-ledger.ts";
@@ -56,28 +57,22 @@ import {
56
57
  } from "../package-contracts/judge-output.ts";
57
58
  import {
58
59
  COLLECTOR_OUTPUT_TOOL,
59
- type CollectorReceipt,
60
60
  } from "../package-contracts/collector-output.ts";
61
61
  import {
62
62
  CODER_OUTPUT_TOOL_NAME,
63
63
  FIXER_OUTPUT_TOOL_NAME,
64
- type CoderOutput,
65
- type FixerOutput,
66
64
  } from "../package-contracts/worker-output.ts";
67
65
 
68
66
  import {
69
67
  DOCTOR_OUTPUT_TOOL_NAME,
70
68
  type DoctorCaseCost,
71
- type DoctorOutput,
72
69
  } from "../doctor-contracts.ts";
73
70
  import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "../dossier-resolution.ts";
74
71
  import {
75
72
  REVIEWER_OUTPUT_TOOL_NAME,
76
- type ReviewerIntent,
77
73
  } from "../package-contracts/reviewer-output.ts";
78
74
  import {
79
75
  MERGER_OUTPUT_TOOL_NAME,
80
- type MergerOutput,
81
76
  } from "../merger-contracts.ts";
82
77
  import {
83
78
  NOTARY_OUTPUT_TOOL_NAME,
@@ -152,7 +147,7 @@ import {
152
147
  } from "./invocation.ts";
153
148
 
154
149
  /** Ledger reads use the run's machine home — not ambient process HOME (child write vs parent settle). */
155
- function sealedLedgerHome(admitted: AdmittedRoleInvocation): string {
150
+ function sealedLedgerHome(admitted: Pick<AdmittedRoleInvocation, "runDirectory">): string {
156
151
  return homeFromRunDirectory(admitted.runDirectory);
157
152
  }
158
153
 
@@ -165,7 +160,7 @@ export type SettlementCourtScope = {
165
160
  };
166
161
 
167
162
  function ledgerReadScope(
168
- admitted: AdmittedRoleInvocation,
163
+ admitted: Pick<AdmittedRoleInvocation, "runDirectory">,
169
164
  scope?: SettlementCourtScope,
170
165
  ): { home: string; attemptId?: string } {
171
166
  return {
@@ -178,12 +173,22 @@ function ledgerReadScope(
178
173
 
179
174
  function roleOutcomeFromRows(
180
175
  role: TerminalRoleName,
181
- rows: readonly { readonly role: TerminalRoleName; readonly kind: "accepted" | "audit-escalation"; readonly accepted: unknown }[],
176
+ rows: readonly {
177
+ readonly role?: TerminalRoleName;
178
+ readonly kind: "accepted" | "audit-escalation" | "correctable-rejection" | "infrastructure" | "candidate";
179
+ readonly accepted: unknown;
180
+ }[],
182
181
  ): Extract<TerminalRoleOutcome, { kind: "accepted" | "audit_escalation" }> | undefined {
183
182
  const mine = rows.filter((row) => row.role === role);
184
- if (mine.length === 0) return undefined;
183
+ // Terminal acceptance kind still only follows sealed / audit-escalation (#881):
184
+ // correctable-rejection / infrastructure / candidate stay payloads, not acceptance.
185
+ const terminal = mine.filter(
186
+ (row) => row.kind === "accepted" || row.kind === "audit-escalation",
187
+ );
188
+ if (terminal.length === 0) return undefined;
189
+ // Role-result block is the full recorded sequence for this seat, not a sole pick.
185
190
  const payloads = mine.map((row) => row.accepted);
186
- if (mine.some((row) => row.kind === "audit-escalation")) {
191
+ if (terminal.some((row) => row.kind === "audit-escalation")) {
187
192
  return { kind: "audit_escalation", role, status: "audit_escalation", payloads };
188
193
  }
189
194
  return { kind: "accepted", role, payloads };
@@ -194,6 +199,22 @@ async function sealedLedgerOutcome(
194
199
  role: TerminalRoleName,
195
200
  scope?: SettlementCourtScope,
196
201
  ): Promise<Extract<TerminalRoleOutcome, { kind: "accepted" | "audit_escalation" }> | undefined> {
202
+ const home = sealedLedgerHome(admitted);
203
+ // #879: when court scope is present, roleOutcome is this-court original only —
204
+ // never last-wins over an undivided historical array, and never falls back to
205
+ // full-run history when this court sealed zero rows. #836 presentation of full
206
+ // history stays on attachRecordedSubmissions / terminal.submissions (run-scoped).
207
+ if (scope?.courtAttemptId !== undefined && scope.courtAttemptId.length > 0) {
208
+ const thisCourt = await readAttemptScopedSubmissionRows(
209
+ admitted.projectRoot,
210
+ admitted.runId,
211
+ scope.courtAttemptId,
212
+ home,
213
+ );
214
+ // Scope present + zero this-court rows → no this-court outcome (not run history).
215
+ return roleOutcomeFromRows(role, thisCourt);
216
+ }
217
+ // No court scope: keep run-scoped ledger face (#836).
197
218
  const rows = await readRecordedSubmissionRows(
198
219
  admitted.projectRoot,
199
220
  admitted.runId,
@@ -227,7 +248,7 @@ export async function attemptProducedFreshSubmission(
227
248
 
228
249
  /** #836: every raw role payload in settle scope (调几次记几次). */
229
250
  export async function recordedSubmissionPayloads(
230
- admitted: AdmittedRoleInvocation,
251
+ admitted: Pick<AdmittedRoleInvocation, "projectRoot" | "runId" | "runDirectory">,
231
252
  scope?: SettlementCourtScope,
232
253
  ): Promise<readonly unknown[]> {
233
254
  return readRecordedSubmissions(
@@ -243,6 +264,8 @@ export function withSubmissions<T extends TerminalResult>(
243
264
  ): T {
244
265
  if (submissions.length === 0) return terminal;
245
266
  const roleOutcome = terminal.roleOutcome;
267
+ // #879 keeps an already scoped this-court result; #881 full run history is
268
+ // presented independently on terminal.submissions. Only fill a missing result payload.
246
269
  const withPayloads =
247
270
  roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation"
248
271
  ? { ...roleOutcome, payloads: (roleOutcome.payloads ?? []).length > 0 ? roleOutcome.payloads : submissions }
@@ -254,11 +277,18 @@ export function withSubmissions<T extends TerminalResult>(
254
277
 
255
278
  /** Attach full ledger submissions onto any settled terminal (#836). */
256
279
  export async function attachRecordedSubmissions<T extends TerminalResult>(
257
- admitted: AdmittedRoleInvocation,
280
+ admitted: Pick<AdmittedRoleInvocation, "projectRoot" | "runId" | "runDirectory">,
258
281
  terminal: T,
259
282
  scope?: SettlementCourtScope,
260
283
  ): Promise<T> {
261
- return withSubmissions(terminal, await recordedSubmissionPayloads(admitted, scope));
284
+ // #836 / #879: submissions presentation is always run-scoped history. Do not
285
+ // pass courtAttemptId here — roleOutcome already carries this-court payloads
286
+ // from sealedLedgerOutcome when scoped; withSubmissions keeps them.
287
+ void scope;
288
+ return withSubmissions(
289
+ terminal,
290
+ await recordedSubmissionPayloads(admitted, undefined),
291
+ );
262
292
  }
263
293
 
264
294
  /**
@@ -313,7 +343,6 @@ import {
313
343
  exitCodeForTerminalOutcome,
314
344
  formatTerminalResult,
315
345
  isLawfulTypedTerminalOutcome,
316
- lastRolePayloadRecord,
317
346
  recommendationNavigatorFact,
318
347
  type ControlledFailureCause,
319
348
  type TerminalArtifactRef,
@@ -334,9 +363,10 @@ export {
334
363
  isLawfulTypedTerminalOutcome,
335
364
  };
336
365
 
337
- /** Preserved post-admission failure cause (not a role Receipt). */
366
+ /** Preserved post-admission failure (not a role Receipt). */
338
367
  export type ControlledFailure = {
339
- readonly cause: ControlledFailureCause;
368
+ /** Typed class only when confirmed; omitted when unknown (#881 — no fabricated label). */
369
+ readonly cause?: ControlledFailureCause;
340
370
  readonly diagnostic: string;
341
371
  readonly identity?: {
342
372
  readonly name?: string;
@@ -464,8 +494,7 @@ function isTypedActivationError(
464
494
  cause === "activation" ||
465
495
  cause === "session" ||
466
496
  cause === "output" ||
467
- cause === "timeout" ||
468
- cause === "unrecognized"
497
+ cause === "timeout"
469
498
  );
470
499
  }
471
500
 
@@ -494,21 +523,20 @@ export function projectThrownFailureLeaf(error: unknown): ControlledFailure {
494
523
  }
495
524
  return {
496
525
  cause: error.knownCause,
497
- diagnostic: error.message || error.name || "unrecognized exception",
526
+ diagnostic: error.message || error.name || "exception",
498
527
  identity,
499
528
  ...(error.details === undefined ? {} : { details: error.details }),
500
529
  };
501
530
  }
502
531
  if (error instanceof Error) {
503
532
  const identity = thrownIdentity(error);
533
+ // No typed confirmation → keep original diagnostic/identity; do not mint a class (#881).
504
534
  return {
505
- cause: "unrecognized",
506
- diagnostic: error.message || error.name || "unrecognized exception",
535
+ diagnostic: error.message || error.name || "exception",
507
536
  identity,
508
537
  };
509
538
  }
510
539
  return {
511
- cause: "unrecognized",
512
540
  diagnostic: String(error),
513
541
  };
514
542
  }
@@ -539,7 +567,7 @@ function classifyThrownFailure(error: unknown): ControlledFailure {
539
567
  ...leaves.slice(1).map((leaf) => {
540
568
  const secondary = projectThrownFailureLeaf(leaf);
541
569
  return {
542
- cause: secondary.cause,
570
+ ...(secondary.cause === undefined ? {} : { cause: secondary.cause }),
543
571
  diagnostic: secondary.diagnostic,
544
572
  ...(secondary.identity === undefined ? {} : { identity: secondary.identity }),
545
573
  ...(secondary.details === undefined ? {} : { details: secondary.details }),
@@ -547,7 +575,7 @@ function classifyThrownFailure(error: unknown): ControlledFailure {
547
575
  }),
548
576
  ];
549
577
  return {
550
- cause: primary.cause,
578
+ ...(primary.cause === undefined ? {} : { cause: primary.cause }),
551
579
  diagnostic: primary.diagnostic,
552
580
  ...(primary.identity === undefined ? {} : { identity: primary.identity }),
553
581
  details: {
@@ -574,7 +602,7 @@ function withKnownDetails(
574
602
  }
575
603
 
576
604
  /**
577
- * Classify a controlled post-admission failure without washing unrecognized identities.
605
+ * Classify a controlled post-admission failure without washing original identities.
578
606
  * Cause classes are closed; diagnostic text retains the original identity when known.
579
607
  *
580
608
  * Order: thrown → knownCause → timeout → activation (nonzero) → session → output.
@@ -588,7 +616,7 @@ export function classifyPostAdmissionFailure(input: {
588
616
  stderr: string;
589
617
  /**
590
618
  * Caught post-admission exception. Presence (own key) is distinct from value:
591
- * JavaScript permits `throw undefined`, which must stay unrecognized rather than
619
+ * JavaScript permits `throw undefined`, which must keep the original thrown fact rather than
592
620
  * being washed into activation/null-exit paths that treat missing thrown as absence.
593
621
  */
594
622
  thrown?: unknown;
@@ -642,6 +670,37 @@ export function classifyPostAdmissionFailure(input: {
642
670
  : { identity: input.knownIdentity }),
643
671
  };
644
672
  }
673
+ // #881: original failure testimony without a typed class — keep diagnostic/identity;
674
+ // do not fall through to activation/output wash that would mint a substitute class.
675
+ // Bare knownDetails alone is secondary evidence for later branches (withKnownDetails),
676
+ // not a stand-in primary failure record.
677
+ if (
678
+ (input.knownDiagnostic !== undefined && input.knownDiagnostic.trim() !== "") ||
679
+ input.knownIdentity !== undefined
680
+ ) {
681
+ const diagnostic =
682
+ input.knownDiagnostic !== undefined && input.knownDiagnostic.trim() !== ""
683
+ ? input.knownDiagnostic
684
+ : conciseChildDiagnostic(input.stderr, "role run failed");
685
+ const { timedOut: _knownTimedOut, ...knownDetails } =
686
+ input.knownDetails ?? {};
687
+ const remoteCode = knownDetails.code;
688
+ return withKnownDetails(
689
+ {
690
+ diagnostic,
691
+ details: {
692
+ ...knownDetails,
693
+ ...(remoteCode === undefined ? {} : { code: remoteCode }),
694
+ exitCode: input.code,
695
+ ...(input.timedOut ? { timedOut: true as const } : {}),
696
+ },
697
+ ...(input.knownIdentity === undefined
698
+ ? {}
699
+ : { identity: input.knownIdentity }),
700
+ },
701
+ undefined,
702
+ );
703
+ }
645
704
  if (input.timedOut) {
646
705
  return withKnownDetails(
647
706
  {
@@ -699,7 +758,7 @@ export function explicitInternalKnownFailureClassificationInput(
699
758
  ) {
700
759
  if (failure === undefined) return {};
701
760
  return {
702
- knownCause: failure.cause,
761
+ ...(failure.cause === undefined ? {} : { knownCause: failure.cause }),
703
762
  ...(failure.identity === undefined ? {} : { knownIdentity: failure.identity }),
704
763
  ...(failure.diagnostic === undefined ? {} : { knownDiagnostic: failure.diagnostic }),
705
764
  ...(failure.details === undefined ? {} : { knownDetails: failure.details }),
@@ -1137,10 +1196,20 @@ function complianceFailureFromAuditorVolumes(
1137
1196
  if (entry?.type !== "custom" || entry.customType !== AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE || !isRecord(entry.data)) continue;
1138
1197
  const parent = isRecord(entry.data.parent) ? entry.data.parent : undefined;
1139
1198
  const failure = isRecord(entry.data.failure) ? entry.data.failure : undefined;
1140
- if (parent?.sessionId !== parentId || parent.sessionFile !== sessionFile || parent.attemptEntryId !== attemptEntryId || (failure?.cause !== "provider" && failure?.cause !== "unrecognized")) continue;
1199
+ if (parent?.sessionId !== parentId || parent.sessionFile !== sessionFile || parent.attemptEntryId !== attemptEntryId) continue;
1200
+ // #881: keep the recorded failure as written — typed cause when present, else raw diagnostic only.
1201
+ if (failure === undefined) continue;
1141
1202
  const identity = isRecord(failure.identity) ? failure.identity : undefined;
1203
+ const typedCause =
1204
+ failure.cause === "provider" ||
1205
+ failure.cause === "activation" ||
1206
+ failure.cause === "session" ||
1207
+ failure.cause === "output" ||
1208
+ failure.cause === "timeout"
1209
+ ? (failure.cause as ControlledFailureCause)
1210
+ : undefined;
1142
1211
  return {
1143
- cause: failure.cause === "provider" ? "provider" : "unrecognized",
1212
+ ...(typedCause === undefined ? {} : { cause: typedCause }),
1144
1213
  ...(identity === undefined ? {} : { identity: {
1145
1214
  ...(typeof identity.name === "string" ? { name: identity.name } : {}),
1146
1215
  ...(typeof identity.code === "string" || typeof identity.code === "number" ? { code: identity.code } : {}),
@@ -2314,7 +2383,6 @@ export async function publishCoderArtifacts(
2314
2383
  coordinates: DurablePrincipalCoordinates,
2315
2384
  options: {
2316
2385
  readonly methodProvenance?: PackagedMethodSkillProvenance;
2317
- readonly coderOutput?: CoderOutput;
2318
2386
  } = {},
2319
2387
  ): Promise<TerminalArtifactRef[]> {
2320
2388
  await appendRunAttemptHistory({ role: admitted.role, runId: admitted.runId, sessionFile: coordinates.sessionFile }, roleOutcome);
@@ -2329,9 +2397,6 @@ export async function publishCoderArtifacts(
2329
2397
  runId: admitted.runId,
2330
2398
  phase: admitted.phase,
2331
2399
  outcome: roleOutcome,
2332
- ...(options.coderOutput === undefined
2333
- ? {}
2334
- : { receipt: options.coderOutput }),
2335
2400
  },
2336
2401
  null,
2337
2402
  2,
@@ -2487,10 +2552,6 @@ async function settleLawfulCoderTerminalResult(
2487
2552
  const ledgerOutcome = await closedLedgerOutcome(admitted, "coder", scope);
2488
2553
  if (ledgerOutcome === undefined) return undefined;
2489
2554
  const roleOutcome: TerminalRoleOutcome = ledgerOutcome;
2490
- const output: CoderOutput | undefined =
2491
- ledgerOutcome.kind === "accepted"
2492
- ? (lastRolePayloadRecord(ledgerOutcome.payloads ?? []) as CoderOutput | undefined)
2493
- : undefined;
2494
2555
  const coordinates = coordinatesFromAdmitted(authority, admitted);
2495
2556
  const entries = await readLawfulSettlementEntries(coordinates.sessionFile) ?? [];
2496
2557
  const navigator = extractNavigatorFact(entries);
@@ -2499,7 +2560,6 @@ async function settleLawfulCoderTerminalResult(
2499
2560
  roleOutcome,
2500
2561
  coordinates,
2501
2562
  {
2502
- ...(output === undefined ? {} : { coderOutput: output }),
2503
2563
  ...(options.methodProvenance === undefined
2504
2564
  ? {}
2505
2565
  : { methodProvenance: options.methodProvenance }),
@@ -2590,7 +2650,6 @@ export async function publishFixerArtifacts(
2590
2650
  options: {
2591
2651
  readonly methodProvenance: PackagedMethodSkillProvenance;
2592
2652
  readonly methodInvocations?: readonly ObservedPackagedMethodSkillInvocation[];
2593
- readonly fixerOutput?: FixerOutput;
2594
2653
  },
2595
2654
  ): Promise<TerminalArtifactRef[]> {
2596
2655
  await appendRunAttemptHistory({ role: admitted.role, runId: admitted.runId, sessionFile: coordinates.sessionFile }, roleOutcome);
@@ -2605,9 +2664,6 @@ export async function publishFixerArtifacts(
2605
2664
  runId: admitted.runId,
2606
2665
  phase: admitted.phase,
2607
2666
  outcome: roleOutcome,
2608
- ...(options.fixerOutput === undefined
2609
- ? {}
2610
- : { receipt: options.fixerOutput }),
2611
2667
  },
2612
2668
  null,
2613
2669
  2,
@@ -2666,10 +2722,6 @@ async function settleLawfulFixerTerminalResult(
2666
2722
  const ledgerOutcome = await closedLedgerOutcome(admitted, "fixer", scope);
2667
2723
  if (ledgerOutcome === undefined) return undefined;
2668
2724
  const roleOutcome: TerminalRoleOutcome = ledgerOutcome;
2669
- const output: FixerOutput | undefined =
2670
- ledgerOutcome.kind === "accepted"
2671
- ? (lastRolePayloadRecord(ledgerOutcome.payloads ?? []) as FixerOutput | undefined)
2672
- : undefined;
2673
2725
  const coordinates = coordinatesFromAdmitted(authority, admitted);
2674
2726
  const { sessionDirectory, sessionFile } = coordinates;
2675
2727
  const entries = await readLawfulSettlementEntries(sessionFile) ?? [];
@@ -2685,7 +2737,6 @@ async function settleLawfulFixerTerminalResult(
2685
2737
  roleOutcome,
2686
2738
  coordinates,
2687
2739
  {
2688
- ...(output === undefined ? {} : { fixerOutput: output }),
2689
2740
  methodProvenance: options.methodProvenance,
2690
2741
  methodInvocations,
2691
2742
  },
@@ -2725,9 +2776,6 @@ export async function publishCollectorArtifacts(
2725
2776
  admitted: AdmittedCollectorInvocation,
2726
2777
  roleOutcome: TerminalRoleOutcome,
2727
2778
  coordinates: DurablePrincipalCoordinates,
2728
- options: {
2729
- readonly collectorReceipt?: CollectorReceipt;
2730
- } = {},
2731
2779
  ): Promise<TerminalArtifactRef[]> {
2732
2780
  await appendRunAttemptHistory({ role: admitted.role, runId: admitted.runId, sessionFile: coordinates.sessionFile }, roleOutcome);
2733
2781
  const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
@@ -2740,9 +2788,6 @@ export async function publishCollectorArtifacts(
2740
2788
  role: "collector",
2741
2789
  runId: admitted.runId,
2742
2790
  outcome: roleOutcome,
2743
- ...(options.collectorReceipt === undefined
2744
- ? {}
2745
- : { receipt: options.collectorReceipt }),
2746
2791
  },
2747
2792
  null,
2748
2793
  2,
@@ -2810,14 +2855,12 @@ async function settleLawfulCollectorTerminalResult(
2810
2855
  }
2811
2856
  return undefined;
2812
2857
  }
2813
- const receipt = (lastRolePayloadRecord(roleOutcome.payloads ?? []) ?? {}) as CollectorReceipt;
2814
2858
  const accepted: LawfulCollectorRoleOutcome = roleOutcome;
2815
2859
  const navigator = extractNavigatorFact(entries);
2816
2860
  const artifacts = await publishCollectorArtifacts(
2817
2861
  admitted,
2818
2862
  accepted,
2819
2863
  coordinates,
2820
- { collectorReceipt: receipt },
2821
2864
  );
2822
2865
  return attachRecordedSubmissions(
2823
2866
  admitted,
@@ -2902,7 +2945,6 @@ export async function publishDoctorArtifacts(
2902
2945
  roleOutcome: TerminalRoleOutcome,
2903
2946
  coordinates: DurablePrincipalCoordinates,
2904
2947
  options: {
2905
- readonly doctorOutput?: DoctorOutput;
2906
2948
  readonly cost?: DoctorCaseCost;
2907
2949
  readonly auditNoReceipt?: unknown;
2908
2950
  } = {},
@@ -2918,10 +2960,6 @@ export async function publishDoctorArtifacts(
2918
2960
  role: "doctor",
2919
2961
  runId: admitted.runId,
2920
2962
  outcome: roleOutcome,
2921
- ...(options.doctorOutput === undefined
2922
- ? {}
2923
- : { receipt: options.doctorOutput }),
2924
- // Independent machine fields beside the receipt — not merged into it.
2925
2963
  ...(options.cost === undefined ? {} : { cost: options.cost }),
2926
2964
  ...(options.auditNoReceipt === undefined ? {} : { auditNoReceipt: options.auditNoReceipt }),
2927
2965
  },
@@ -2987,7 +3025,6 @@ async function settleLawfulDoctorTerminalResult(
2987
3025
  sessionDirectory,
2988
3026
  );
2989
3027
  }
2990
- const output = (lastRolePayloadRecord(sealed.payloads ?? []) ?? {}) as DoctorOutput;
2991
3028
  const roleOutcome = sealed;
2992
3029
  const navigator = extractNavigatorFact(entries);
2993
3030
  const cost = extractDoctorCandidateCostFact(entries);
@@ -2997,7 +3034,6 @@ async function settleLawfulDoctorTerminalResult(
2997
3034
  roleOutcome,
2998
3035
  coordinates,
2999
3036
  {
3000
- doctorOutput: output,
3001
3037
  ...(cost === undefined ? {} : { cost }),
3002
3038
  ...(auditNoReceipt === undefined ? {} : { auditNoReceipt }),
3003
3039
  },
@@ -3433,7 +3469,6 @@ export async function publishReviewerArtifacts(
3433
3469
  options: {
3434
3470
  readonly methodProvenance: PackagedMethodSkillProvenance;
3435
3471
  readonly methodInvocations?: readonly ObservedPackagedMethodSkillInvocation[];
3436
- readonly reviewerReceipt?: ReviewerIntent;
3437
3472
  },
3438
3473
  ): Promise<TerminalArtifactRef[]> {
3439
3474
  await appendRunAttemptHistory({ role: admitted.role, runId: admitted.runId, sessionFile: coordinates.sessionFile }, roleOutcome);
@@ -3447,9 +3482,6 @@ export async function publishReviewerArtifacts(
3447
3482
  role: "reviewer",
3448
3483
  runId: admitted.runId,
3449
3484
  outcome: roleOutcome,
3450
- ...(options.reviewerReceipt === undefined
3451
- ? {}
3452
- : { receipt: options.reviewerReceipt }),
3453
3485
  },
3454
3486
  null,
3455
3487
  2,
@@ -3509,7 +3541,6 @@ async function settleLawfulReviewerTerminalResult(
3509
3541
  const coordinates = coordinatesFromAdmitted(authority, admitted);
3510
3542
  const { sessionDirectory, sessionFile } = coordinates;
3511
3543
  const entries = await readLawfulSettlementEntries(sessionFile) ?? [];
3512
- const receipt = lastRolePayloadRecord(sealed.payloads ?? []) as ReviewerIntent | undefined;
3513
3544
  const roleOutcome: LawfulReviewerRoleOutcome = sealed;
3514
3545
  const navigator = extractNavigatorFact(entries);
3515
3546
  const methodInvocations = extractReviewerMethodInvocations(entries, {
@@ -3523,7 +3554,6 @@ async function settleLawfulReviewerTerminalResult(
3523
3554
  roleOutcome,
3524
3555
  coordinates,
3525
3556
  {
3526
- ...(receipt === undefined ? {} : { reviewerReceipt: receipt }),
3527
3557
  methodProvenance: options.methodProvenance,
3528
3558
  methodInvocations,
3529
3559
  },
@@ -3612,7 +3642,6 @@ export async function publishMergerArtifacts(
3612
3642
  options: {
3613
3643
  readonly methodProvenance: PackagedMethodSkillProvenance;
3614
3644
  readonly methodInvocations?: readonly ObservedPackagedMethodSkillInvocation[];
3615
- readonly mergerOutput?: MergerOutput;
3616
3645
  },
3617
3646
  ): Promise<TerminalArtifactRef[]> {
3618
3647
  await appendRunAttemptHistory({ role: admitted.role, runId: admitted.runId, sessionFile: coordinates.sessionFile }, roleOutcome);
@@ -3626,9 +3655,6 @@ export async function publishMergerArtifacts(
3626
3655
  role: "merger",
3627
3656
  runId: admitted.runId,
3628
3657
  outcome: roleOutcome,
3629
- ...(options.mergerOutput === undefined
3630
- ? {}
3631
- : { receipt: options.mergerOutput }),
3632
3658
  },
3633
3659
  null,
3634
3660
  2,
@@ -3711,7 +3737,6 @@ async function settleLawfulMergerTerminalResult(
3711
3737
  accepted,
3712
3738
  coordinates,
3713
3739
  {
3714
- mergerOutput: (lastRolePayloadRecord(accepted.payloads ?? []) ?? {}) as MergerOutput,
3715
3740
  methodProvenance: options.methodProvenance,
3716
3741
  methodInvocations,
3717
3742
  },
@@ -3941,7 +3966,7 @@ export async function publishFailureArtifacts(
3941
3966
  kind: "error",
3942
3967
  role: admitted.role,
3943
3968
  runId: admitted.runId,
3944
- cause: failure.cause,
3969
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
3945
3970
  diagnostic: failure.diagnostic,
3946
3971
  ...(failure.identity === undefined ? {} : { identity: failure.identity }),
3947
3972
  ...(failure.details === undefined ? {} : { details: failure.details }),
@@ -3966,7 +3991,7 @@ export async function publishFailureArtifacts(
3966
3991
  sha256: a.sha256,
3967
3992
  byteLength: a.byteLength,
3968
3993
  })),
3969
- failureCause: failure.cause,
3994
+ ...(failure.cause === undefined ? {} : { failureCause: failure.cause }),
3970
3995
  };
3971
3996
  const evidenceWrite = await writeFailureJsonRetainingCause(
3972
3997
  evidenceCandidates,
@@ -4045,7 +4070,7 @@ export async function settleFailureTerminalResult(
4045
4070
  // Private durable artifacts retain the original diagnostic identity (including run ID).
4046
4071
  const artifacts = await publishFailureArtifacts(admitted, failure, authority);
4047
4072
  const decisiveFacts: Record<string, unknown> = {
4048
- cause: failure.cause,
4073
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
4049
4074
  diagnostic: failure.diagnostic,
4050
4075
  };
4051
4076
  if (failure.identity?.name !== undefined) {
@@ -4065,7 +4090,7 @@ export async function settleFailureTerminalResult(
4065
4090
  const roleOutcome: TerminalRoleOutcome = {
4066
4091
  kind: "failure",
4067
4092
  role: admitted.role,
4068
- cause: failure.cause,
4093
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
4069
4094
  diagnostic: failure.diagnostic,
4070
4095
  decisiveFacts,
4071
4096
  };
@@ -4082,7 +4107,7 @@ export async function settleFailureTerminalResult(
4082
4107
  const roleOutcome: TerminalRoleOutcome = {
4083
4108
  kind: "failure",
4084
4109
  role: admitted.role,
4085
- cause: failure.cause,
4110
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
4086
4111
  diagnostic: failure.diagnostic,
4087
4112
  decisiveFacts,
4088
4113
  };
@@ -4122,7 +4147,7 @@ export function presentFailureTerminal(
4122
4147
  io.stdout(formatTerminalResult(terminal));
4123
4148
  if (terminal.roleOutcome.kind === "failure") {
4124
4149
  io.stderr(formatFailureStderrDiagnostic({
4125
- cause: terminal.roleOutcome.cause,
4150
+ ...(terminal.roleOutcome.cause === undefined ? {} : { cause: terminal.roleOutcome.cause }),
4126
4151
  diagnostic: terminal.roleOutcome.diagnostic,
4127
4152
  }));
4128
4153
  return;
@@ -73,8 +73,12 @@ export type TerminalRoleOutcome =
73
73
  | {
74
74
  kind: "failure";
75
75
  role: TerminalRoleName;
76
- /** Typed cause class — never a fabricated role Receipt status. */
77
- cause: ControlledFailureCause;
76
+ /**
77
+ * Typed cause class when a typed fact confirms it.
78
+ * Omitted when unknown — original diagnostic + error artifact carry the fact (#881).
79
+ * Never a fabricated "unrecognized" label.
80
+ */
81
+ cause?: ControlledFailureCause;
78
82
  /** Original diagnostic identity retained for the caller. */
79
83
  diagnostic: string;
80
84
  decisiveFacts: Readonly<Record<string, unknown>>;
@@ -175,19 +179,6 @@ export function roleResultPayloads(outcome: TerminalRoleOutcome): readonly unkno
175
179
  return [];
176
180
  }
177
181
 
178
- /** Last object payload the role actually wrote. No field remapping. */
179
- export function lastRolePayloadRecord(
180
- payloads: readonly unknown[],
181
- ): Record<string, unknown> | undefined {
182
- for (let index = payloads.length - 1; index >= 0; index -= 1) {
183
- const payload = payloads[index];
184
- if (typeof payload === "object" && payload !== null && !Array.isArray(payload)) {
185
- return payload as Record<string, unknown>;
186
- }
187
- }
188
- return undefined;
189
- }
190
-
191
182
  export type TerminalResult = {
192
183
  roleOutcome: TerminalRoleOutcome;
193
184
  navigator: TerminalNavigatorFact;
@@ -253,7 +244,7 @@ export function formatTerminalResult(result: TerminalResult): string {
253
244
  lines.push("role\toutcome\tstatus");
254
245
  const outcomeStatus =
255
246
  result.roleOutcome.kind === "failure"
256
- ? result.roleOutcome.cause
247
+ ? result.roleOutcome.cause ?? ""
257
248
  : result.roleOutcome.kind === "accepted"
258
249
  ? "accepted"
259
250
  : result.roleOutcome.status;
@@ -62,6 +62,12 @@ export type PublicSummonRequest = {
62
62
  * summons.instruction. Never folded into argv / parent-run lookup keys.
63
63
  */
64
64
  readonly reviewReask?: string;
65
+ /**
66
+ * #879 same-parent (and first-mint) gate summons: verbatim parent-submission body.
67
+ * Rides summons.instruction / first-mint prompt when reviewReask is absent.
68
+ * Never folded into argv / parent-run lookup keys (binding pointer stays pure).
69
+ */
70
+ readonly gateReviewInstruction?: string;
65
71
  /**
66
72
  * Pi-adapter inject (tests). Used only as the `pi` row when composing the
67
73
  * adapter table — never as an override of the child seat's selected host.
@@ -302,6 +308,10 @@ export async function summonPublicRole(
302
308
  ...(options.signal === undefined ? {} : { signal: options.signal }),
303
309
  // #753: reask rides the existing notary same-ticket resume summons.instruction.
304
310
  ...(options.reviewReask === undefined ? {} : { reviewReask: options.reviewReask }),
311
+ // #879: verbatim submission body on officer dialogue content channel.
312
+ ...(options.gateReviewInstruction === undefined
313
+ ? {}
314
+ : { gateReviewInstruction: options.gateReviewInstruction }),
305
315
  ...(options.boundTicketNumber === undefined
306
316
  ? {}
307
317
  : { boundTicketNumber: options.boundTicketNumber }),
@@ -430,6 +440,12 @@ export async function summonGateOfficer(options: {
430
440
  * Never concatenated into argv (inspector parentRunPath is the pure 卷宗指针).
431
441
  */
432
442
  readonly reask?: string;
443
+ /**
444
+ * In-flight parent 交卷 body (tool-call arguments). Relayed verbatim as officer
445
+ * dialogue content when reask is absent (#879). Binding pointer stays on
446
+ * --source-run / 卷宗指针 argv — never a content substitute.
447
+ */
448
+ readonly submission?: unknown;
433
449
  /** Pi-adapter inject — forwarded to summonPublicRole (not a parent-host override). */
434
450
  readonly roleTurnHost?: RoleTurnHost;
435
451
  readonly hostAdapters?: readonly NamedRoleTurnHostAdapter[];
@@ -444,8 +460,13 @@ export async function summonGateOfficer(options: {
444
460
  }
445
461
  // Officer host is seat-owned only (#821). Parent invocation.json.host stays a
446
462
  // hostTransition consumer in post-admission — not an officer override channel.
447
- // #836: officers get the whole parent run directory pointer; code does not
448
- // inject a picked toolCall leaf as the resume instruction.
463
+ // #879: binding pointer = parent run directory; dialogue content = submission body.
464
+ // Conclusion re-ask keeps sole ownership of reviewReask when present.
465
+ let gateReviewInstruction: string | undefined;
466
+ if (options.reask === undefined && options.submission !== undefined) {
467
+ const { readableGateItem } = await import("./readable-gate-item.ts");
468
+ gateReviewInstruction = readableGateItem(options.submission);
469
+ }
449
470
  const common = {
450
471
  cwd: options.cwd,
451
472
  ...(home === undefined ? {} : { home }),
@@ -453,13 +474,16 @@ export async function summonGateOfficer(options: {
453
474
  ...(options.io === undefined ? {} : { io: options.io }),
454
475
  ...(options.signal === undefined ? {} : { signal: options.signal }),
455
476
  ...(options.reask === undefined ? {} : { reviewReask: options.reask }),
477
+ ...(gateReviewInstruction === undefined
478
+ ? {}
479
+ : { gateReviewInstruction }),
456
480
  ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
457
481
  ...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
458
482
  ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
459
483
  } as const;
460
484
  if (options.officer === "notary") {
461
- // #753: reask rides summonPublicRole → runPublicNotary summons.instruction
462
- // (same resume seam as ordinary same-parent re-summons — no parallel stack).
485
+ // #753/#879: reask or verbatim body rides summons.instruction / first-mint prompt.
486
+ // Binding stays --source-run (never folded into content).
463
487
  return summonPublicRole({
464
488
  role: "notary",
465
489
  argv: ["--source-run", options.sourceRunDirectory, "--project", options.cwd],
@@ -468,7 +492,7 @@ export async function summonGateOfficer(options: {
468
492
  }
469
493
  if (options.officer === "auditor") {
470
494
  // #756: judge compliance path — same queue law as notary/inspector.
471
- // #836: auditor kickoff is path pointer (同察院), not「本 run 卷宗已就绪」.
495
+ // Binding pointer on argv; dialogue content rides gateReviewInstruction.
472
496
  return summonPublicRole({
473
497
  role: "auditor",
474
498
  argv: [
@@ -481,8 +505,8 @@ export async function summonGateOfficer(options: {
481
505
  ...common,
482
506
  });
483
507
  }
484
- // Inspector: argv stays pure 卷宗指针 (#747 parentRunPath); reask rides env only.
485
- // #836 resume 请重读 is added on the resume projection face, not here.
508
+ // Inspector: argv stays pure 卷宗指针 (#747 parentRunPath lookup key).
509
+ // Content (body/reask) rides env → summons.instruction / first-mint prompt.
486
510
  return summonPublicRole({
487
511
  role: "inspector",
488
512
  argv: [`卷宗指针:${options.sourceRunDirectory}`],