@akagilnc/pi-workflow-roles 0.1.4021 → 0.1.4035

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.
@@ -54,10 +54,14 @@ function ledgerReadScope(admitted, scope) {
54
54
  }
55
55
  function roleOutcomeFromRows(role, rows) {
56
56
  const mine = rows.filter((row) => row.role === role);
57
- if (mine.length === 0)
57
+ // Terminal acceptance kind still only follows sealed / audit-escalation (#881):
58
+ // correctable-rejection / infrastructure / candidate stay payloads, not acceptance.
59
+ const terminal = mine.filter((row) => row.kind === "accepted" || row.kind === "audit-escalation");
60
+ if (terminal.length === 0)
58
61
  return undefined;
62
+ // Role-result block is the full recorded sequence for this seat, not a sole pick.
59
63
  const payloads = mine.map((row) => row.accepted);
60
- if (mine.some((row) => row.kind === "audit-escalation")) {
64
+ if (terminal.some((row) => row.kind === "audit-escalation")) {
61
65
  return { kind: "audit_escalation", role, status: "audit_escalation", payloads };
62
66
  }
63
67
  return { kind: "accepted", role, payloads };
@@ -89,11 +93,11 @@ export function withSubmissions(terminal, submissions) {
89
93
  if (submissions.length === 0)
90
94
  return terminal;
91
95
  const roleOutcome = terminal.roleOutcome;
92
- const withPayloads = roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation"
93
- ? { ...roleOutcome, payloads: (roleOutcome.payloads ?? []).length > 0 ? roleOutcome.payloads : submissions }
94
- : roleOutcome.kind === "failure"
95
- ? { ...roleOutcome, payloads: roleOutcome.payloads ?? submissions }
96
- : roleOutcome;
96
+ // Full ledger sequence always wins the role-result block (#881): do not keep a
97
+ // narrower pre-filtered payloads array when attach brings the complete set.
98
+ const withPayloads = roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation" || roleOutcome.kind === "failure"
99
+ ? { ...roleOutcome, payloads: submissions }
100
+ : roleOutcome;
97
101
  return { ...terminal, roleOutcome: withPayloads, submissions };
98
102
  }
99
103
  /** Attach full ledger submissions onto any settled terminal (#836). */
@@ -133,7 +137,7 @@ async function closedLedgerOutcome(admitted, role, scope) {
133
137
  function coordinatesFromAdmitted(authority, admitted) {
134
138
  return authority.decode(admitted.principal);
135
139
  }
136
- import { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, lastRolePayloadRecord, recommendationNavigatorFact, } from "./terminal.js";
140
+ import { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, recommendationNavigatorFact, } from "./terminal.js";
137
141
  export { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, };
138
142
  /**
139
143
  * Host stderr as recorded — full bytes, no flood filter, no char clip (#836).
@@ -223,8 +227,7 @@ function isTypedActivationError(error) {
223
227
  cause === "activation" ||
224
228
  cause === "session" ||
225
229
  cause === "output" ||
226
- cause === "timeout" ||
227
- cause === "unrecognized");
230
+ cause === "timeout");
228
231
  }
229
232
  /** Flatten nested AggregateError leaves; non-aggregate values stay as one fact. */
230
233
  function flattenThrownFailureLeaves(error) {
@@ -250,21 +253,20 @@ export function projectThrownFailureLeaf(error) {
250
253
  }
251
254
  return {
252
255
  cause: error.knownCause,
253
- diagnostic: error.message || error.name || "unrecognized exception",
256
+ diagnostic: error.message || error.name || "exception",
254
257
  identity,
255
258
  ...(error.details === undefined ? {} : { details: error.details }),
256
259
  };
257
260
  }
258
261
  if (error instanceof Error) {
259
262
  const identity = thrownIdentity(error);
263
+ // No typed confirmation → keep original diagnostic/identity; do not mint a class (#881).
260
264
  return {
261
- cause: "unrecognized",
262
- diagnostic: error.message || error.name || "unrecognized exception",
265
+ diagnostic: error.message || error.name || "exception",
263
266
  identity,
264
267
  };
265
268
  }
266
269
  return {
267
- cause: "unrecognized",
268
270
  diagnostic: String(error),
269
271
  };
270
272
  }
@@ -294,7 +296,7 @@ function classifyThrownFailure(error) {
294
296
  ...leaves.slice(1).map((leaf) => {
295
297
  const secondary = projectThrownFailureLeaf(leaf);
296
298
  return {
297
- cause: secondary.cause,
299
+ ...(secondary.cause === undefined ? {} : { cause: secondary.cause }),
298
300
  diagnostic: secondary.diagnostic,
299
301
  ...(secondary.identity === undefined ? {} : { identity: secondary.identity }),
300
302
  ...(secondary.details === undefined ? {} : { details: secondary.details }),
@@ -302,7 +304,7 @@ function classifyThrownFailure(error) {
302
304
  }),
303
305
  ];
304
306
  return {
305
- cause: primary.cause,
307
+ ...(primary.cause === undefined ? {} : { cause: primary.cause }),
306
308
  diagnostic: primary.diagnostic,
307
309
  ...(primary.identity === undefined ? {} : { identity: primary.identity }),
308
310
  details: {
@@ -325,7 +327,7 @@ function withKnownDetails(failure, knownDetails) {
325
327
  };
326
328
  }
327
329
  /**
328
- * Classify a controlled post-admission failure without washing unrecognized identities.
330
+ * Classify a controlled post-admission failure without washing original identities.
329
331
  * Cause classes are closed; diagnostic text retains the original identity when known.
330
332
  *
331
333
  * Order: thrown → knownCause → timeout → activation (nonzero) → session → output.
@@ -365,6 +367,30 @@ export function classifyPostAdmissionFailure(input) {
365
367
  : { identity: input.knownIdentity }),
366
368
  };
367
369
  }
370
+ // #881: original failure testimony without a typed class — keep diagnostic/identity;
371
+ // do not fall through to activation/output wash that would mint a substitute class.
372
+ // Bare knownDetails alone is secondary evidence for later branches (withKnownDetails),
373
+ // not a stand-in primary failure record.
374
+ if ((input.knownDiagnostic !== undefined && input.knownDiagnostic.trim() !== "") ||
375
+ input.knownIdentity !== undefined) {
376
+ const diagnostic = input.knownDiagnostic !== undefined && input.knownDiagnostic.trim() !== ""
377
+ ? input.knownDiagnostic
378
+ : conciseChildDiagnostic(input.stderr, "role run failed");
379
+ const { timedOut: _knownTimedOut, ...knownDetails } = input.knownDetails ?? {};
380
+ const remoteCode = knownDetails.code;
381
+ return withKnownDetails({
382
+ diagnostic,
383
+ details: {
384
+ ...knownDetails,
385
+ ...(remoteCode === undefined ? {} : { code: remoteCode }),
386
+ exitCode: input.code,
387
+ ...(input.timedOut ? { timedOut: true } : {}),
388
+ },
389
+ ...(input.knownIdentity === undefined
390
+ ? {}
391
+ : { identity: input.knownIdentity }),
392
+ }, undefined);
393
+ }
368
394
  if (input.timedOut) {
369
395
  return withKnownDetails({
370
396
  cause: "timeout",
@@ -405,7 +431,7 @@ export function explicitInternalKnownFailureClassificationInput(failure) {
405
431
  if (failure === undefined)
406
432
  return {};
407
433
  return {
408
- knownCause: failure.cause,
434
+ ...(failure.cause === undefined ? {} : { knownCause: failure.cause }),
409
435
  ...(failure.identity === undefined ? {} : { knownIdentity: failure.identity }),
410
436
  ...(failure.diagnostic === undefined ? {} : { knownDiagnostic: failure.diagnostic }),
411
437
  ...(failure.details === undefined ? {} : { knownDetails: failure.details }),
@@ -755,11 +781,21 @@ function complianceFailureFromAuditorVolumes(volumes) {
755
781
  continue;
756
782
  const parent = isRecord(entry.data.parent) ? entry.data.parent : undefined;
757
783
  const failure = isRecord(entry.data.failure) ? entry.data.failure : undefined;
758
- if (parent?.sessionId !== parentId || parent.sessionFile !== sessionFile || parent.attemptEntryId !== attemptEntryId || (failure?.cause !== "provider" && failure?.cause !== "unrecognized"))
784
+ if (parent?.sessionId !== parentId || parent.sessionFile !== sessionFile || parent.attemptEntryId !== attemptEntryId)
785
+ continue;
786
+ // #881: keep the recorded failure as written — typed cause when present, else raw diagnostic only.
787
+ if (failure === undefined)
759
788
  continue;
760
789
  const identity = isRecord(failure.identity) ? failure.identity : undefined;
790
+ const typedCause = failure.cause === "provider" ||
791
+ failure.cause === "activation" ||
792
+ failure.cause === "session" ||
793
+ failure.cause === "output" ||
794
+ failure.cause === "timeout"
795
+ ? failure.cause
796
+ : undefined;
761
797
  return {
762
- cause: failure.cause === "provider" ? "provider" : "unrecognized",
798
+ ...(typedCause === undefined ? {} : { cause: typedCause }),
763
799
  ...(identity === undefined ? {} : { identity: {
764
800
  ...(typeof identity.name === "string" ? { name: identity.name } : {}),
765
801
  ...(typeof identity.code === "string" || typeof identity.code === "number" ? { code: identity.code } : {}),
@@ -1683,9 +1719,6 @@ export async function publishCoderArtifacts(admitted, roleOutcome, coordinates,
1683
1719
  runId: admitted.runId,
1684
1720
  phase: admitted.phase,
1685
1721
  outcome: roleOutcome,
1686
- ...(options.coderOutput === undefined
1687
- ? {}
1688
- : { receipt: options.coderOutput }),
1689
1722
  }, null, 2)}\n`, "utf8");
1690
1723
  await writeFile(evidencePath, `${JSON.stringify({
1691
1724
  runId: admitted.runId,
@@ -1789,14 +1822,10 @@ async function settleLawfulCoderTerminalResult(admitted, authority, options = {}
1789
1822
  if (ledgerOutcome === undefined)
1790
1823
  return undefined;
1791
1824
  const roleOutcome = ledgerOutcome;
1792
- const output = ledgerOutcome.kind === "accepted"
1793
- ? lastRolePayloadRecord(ledgerOutcome.payloads ?? [])
1794
- : undefined;
1795
1825
  const coordinates = coordinatesFromAdmitted(authority, admitted);
1796
1826
  const entries = await readLawfulSettlementEntries(coordinates.sessionFile) ?? [];
1797
1827
  const navigator = extractNavigatorFact(entries);
1798
1828
  const artifacts = await publishCoderArtifacts(admitted, roleOutcome, coordinates, {
1799
- ...(output === undefined ? {} : { coderOutput: output }),
1800
1829
  ...(options.methodProvenance === undefined
1801
1830
  ? {}
1802
1831
  : { methodProvenance: options.methodProvenance }),
@@ -1873,9 +1902,6 @@ export async function publishFixerArtifacts(admitted, roleOutcome, coordinates,
1873
1902
  runId: admitted.runId,
1874
1903
  phase: admitted.phase,
1875
1904
  outcome: roleOutcome,
1876
- ...(options.fixerOutput === undefined
1877
- ? {}
1878
- : { receipt: options.fixerOutput }),
1879
1905
  }, null, 2)}\n`, "utf8");
1880
1906
  await writeFile(evidencePath, `${JSON.stringify({
1881
1907
  runId: admitted.runId,
@@ -1910,9 +1936,6 @@ async function settleLawfulFixerTerminalResult(admitted, authority, options, sco
1910
1936
  if (ledgerOutcome === undefined)
1911
1937
  return undefined;
1912
1938
  const roleOutcome = ledgerOutcome;
1913
- const output = ledgerOutcome.kind === "accepted"
1914
- ? lastRolePayloadRecord(ledgerOutcome.payloads ?? [])
1915
- : undefined;
1916
1939
  const coordinates = coordinatesFromAdmitted(authority, admitted);
1917
1940
  const { sessionDirectory, sessionFile } = coordinates;
1918
1941
  const entries = await readLawfulSettlementEntries(sessionFile) ?? [];
@@ -1924,7 +1947,6 @@ async function settleLawfulFixerTerminalResult(admitted, authority, options, sco
1924
1947
  ],
1925
1948
  });
1926
1949
  const artifacts = await publishFixerArtifacts(admitted, roleOutcome, coordinates, {
1927
- ...(output === undefined ? {} : { fixerOutput: output }),
1928
1950
  methodProvenance: options.methodProvenance,
1929
1951
  methodInvocations,
1930
1952
  });
@@ -1943,7 +1965,7 @@ export async function settleFixerTerminalResult(admitted, authority, options, sc
1943
1965
  }
1944
1966
  return settled;
1945
1967
  }
1946
- export async function publishCollectorArtifacts(admitted, roleOutcome, coordinates, options = {}) {
1968
+ export async function publishCollectorArtifacts(admitted, roleOutcome, coordinates) {
1947
1969
  await appendRunAttemptHistory({ role: admitted.role, runId: admitted.runId, sessionFile: coordinates.sessionFile }, roleOutcome);
1948
1970
  const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
1949
1971
  const reportPath = join(artifactsDir, "report.json");
@@ -1952,9 +1974,6 @@ export async function publishCollectorArtifacts(admitted, roleOutcome, coordinat
1952
1974
  role: "collector",
1953
1975
  runId: admitted.runId,
1954
1976
  outcome: roleOutcome,
1955
- ...(options.collectorReceipt === undefined
1956
- ? {}
1957
- : { receipt: options.collectorReceipt }),
1958
1977
  }, null, 2)}\n`, "utf8");
1959
1978
  await writeFile(evidencePath, `${JSON.stringify({
1960
1979
  runId: admitted.runId,
@@ -2004,10 +2023,9 @@ async function settleLawfulCollectorTerminalResult(admitted, authority, scope) {
2004
2023
  }
2005
2024
  return undefined;
2006
2025
  }
2007
- const receipt = (lastRolePayloadRecord(roleOutcome.payloads ?? []) ?? {});
2008
2026
  const accepted = roleOutcome;
2009
2027
  const navigator = extractNavigatorFact(entries);
2010
- const artifacts = await publishCollectorArtifacts(admitted, accepted, coordinates, { collectorReceipt: receipt });
2028
+ const artifacts = await publishCollectorArtifacts(admitted, accepted, coordinates);
2011
2029
  return attachRecordedSubmissions(admitted, await withOptionalGateProjection({
2012
2030
  roleOutcome: accepted,
2013
2031
  navigator,
@@ -2069,10 +2087,6 @@ export async function publishDoctorArtifacts(admitted, roleOutcome, coordinates,
2069
2087
  role: "doctor",
2070
2088
  runId: admitted.runId,
2071
2089
  outcome: roleOutcome,
2072
- ...(options.doctorOutput === undefined
2073
- ? {}
2074
- : { receipt: options.doctorOutput }),
2075
- // Independent machine fields beside the receipt — not merged into it.
2076
2090
  ...(options.cost === undefined ? {} : { cost: options.cost }),
2077
2091
  ...(options.auditNoReceipt === undefined ? {} : { auditNoReceipt: options.auditNoReceipt }),
2078
2092
  }, null, 2)}\n`, "utf8");
@@ -2113,13 +2127,11 @@ async function settleLawfulDoctorTerminalResult(admitted, authority, scope) {
2113
2127
  runId: admitted.runId,
2114
2128
  }, sessionDirectory);
2115
2129
  }
2116
- const output = (lastRolePayloadRecord(sealed.payloads ?? []) ?? {});
2117
2130
  const roleOutcome = sealed;
2118
2131
  const navigator = extractNavigatorFact(entries);
2119
2132
  const cost = extractDoctorCandidateCostFact(entries);
2120
2133
  const auditNoReceipt = extractDoctorCandidateAuditNoReceiptFact(entries);
2121
2134
  const artifacts = await publishDoctorArtifacts(admitted, roleOutcome, coordinates, {
2122
- doctorOutput: output,
2123
2135
  ...(cost === undefined ? {} : { cost }),
2124
2136
  ...(auditNoReceipt === undefined ? {} : { auditNoReceipt }),
2125
2137
  });
@@ -2346,9 +2358,6 @@ export async function publishReviewerArtifacts(admitted, roleOutcome, coordinate
2346
2358
  role: "reviewer",
2347
2359
  runId: admitted.runId,
2348
2360
  outcome: roleOutcome,
2349
- ...(options.reviewerReceipt === undefined
2350
- ? {}
2351
- : { receipt: options.reviewerReceipt }),
2352
2361
  }, null, 2)}\n`, "utf8");
2353
2362
  await writeFile(evidencePath, `${JSON.stringify({
2354
2363
  runId: admitted.runId,
@@ -2384,7 +2393,6 @@ async function settleLawfulReviewerTerminalResult(admitted, authority, options,
2384
2393
  const coordinates = coordinatesFromAdmitted(authority, admitted);
2385
2394
  const { sessionDirectory, sessionFile } = coordinates;
2386
2395
  const entries = await readLawfulSettlementEntries(sessionFile) ?? [];
2387
- const receipt = lastRolePayloadRecord(sealed.payloads ?? []);
2388
2396
  const roleOutcome = sealed;
2389
2397
  const navigator = extractNavigatorFact(entries);
2390
2398
  const methodInvocations = extractReviewerMethodInvocations(entries, {
@@ -2394,7 +2402,6 @@ async function settleLawfulReviewerTerminalResult(admitted, authority, options,
2394
2402
  ],
2395
2403
  });
2396
2404
  const artifacts = await publishReviewerArtifacts(admitted, roleOutcome, coordinates, {
2397
- ...(receipt === undefined ? {} : { reviewerReceipt: receipt }),
2398
2405
  methodProvenance: options.methodProvenance,
2399
2406
  methodInvocations,
2400
2407
  });
@@ -2455,9 +2462,6 @@ export async function publishMergerArtifacts(admitted, roleOutcome, coordinates,
2455
2462
  role: "merger",
2456
2463
  runId: admitted.runId,
2457
2464
  outcome: roleOutcome,
2458
- ...(options.mergerOutput === undefined
2459
- ? {}
2460
- : { receipt: options.mergerOutput }),
2461
2465
  }, null, 2)}\n`, "utf8");
2462
2466
  await writeFile(evidencePath, `${JSON.stringify({
2463
2467
  runId: admitted.runId,
@@ -2513,7 +2517,6 @@ async function settleLawfulMergerTerminalResult(admitted, authority, options, sc
2513
2517
  allowedLocations: [options.methodSkillPath, options.methodSkillConfiguredPath],
2514
2518
  });
2515
2519
  const artifacts = await publishMergerArtifacts(admitted, accepted, coordinates, {
2516
- mergerOutput: (lastRolePayloadRecord(accepted.payloads ?? []) ?? {}),
2517
2520
  methodProvenance: options.methodProvenance,
2518
2521
  methodInvocations,
2519
2522
  });
@@ -2666,7 +2669,7 @@ export async function publishFailureArtifacts(admitted, failure, authority) {
2666
2669
  kind: "error",
2667
2670
  role: admitted.role,
2668
2671
  runId: admitted.runId,
2669
- cause: failure.cause,
2672
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
2670
2673
  diagnostic: failure.diagnostic,
2671
2674
  ...(failure.identity === undefined ? {} : { identity: failure.identity }),
2672
2675
  ...(failure.details === undefined ? {} : { details: failure.details }),
@@ -2683,7 +2686,7 @@ export async function publishFailureArtifacts(admitted, failure, authority) {
2683
2686
  sha256: a.sha256,
2684
2687
  byteLength: a.byteLength,
2685
2688
  })),
2686
- failureCause: failure.cause,
2689
+ ...(failure.cause === undefined ? {} : { failureCause: failure.cause }),
2687
2690
  };
2688
2691
  const evidenceWrite = await writeFailureJsonRetainingCause(evidenceCandidates, uniqueFallbackDirs, "evidence", evidencePayload,
2689
2692
  // Evidence records the same publication collisions observed placing the error body.
@@ -2750,7 +2753,7 @@ export async function settleFailureTerminalResult(admitted, failure, authority,
2750
2753
  // Private durable artifacts retain the original diagnostic identity (including run ID).
2751
2754
  const artifacts = await publishFailureArtifacts(admitted, failure, authority);
2752
2755
  const decisiveFacts = {
2753
- cause: failure.cause,
2756
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
2754
2757
  diagnostic: failure.diagnostic,
2755
2758
  };
2756
2759
  if (failure.identity?.name !== undefined) {
@@ -2770,7 +2773,7 @@ export async function settleFailureTerminalResult(admitted, failure, authority,
2770
2773
  const roleOutcome = {
2771
2774
  kind: "failure",
2772
2775
  role: admitted.role,
2773
- cause: failure.cause,
2776
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
2774
2777
  diagnostic: failure.diagnostic,
2775
2778
  decisiveFacts,
2776
2779
  };
@@ -2784,7 +2787,7 @@ export async function settleFailureTerminalResult(admitted, failure, authority,
2784
2787
  const roleOutcome = {
2785
2788
  kind: "failure",
2786
2789
  role: admitted.role,
2787
- cause: failure.cause,
2790
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
2788
2791
  diagnostic: failure.diagnostic,
2789
2792
  decisiveFacts,
2790
2793
  };
@@ -2811,7 +2814,7 @@ export function presentFailureTerminal(terminal, io) {
2811
2814
  io.stdout(formatTerminalResult(terminal));
2812
2815
  if (terminal.roleOutcome.kind === "failure") {
2813
2816
  io.stderr(formatFailureStderrDiagnostic({
2814
- cause: terminal.roleOutcome.cause,
2817
+ ...(terminal.roleOutcome.cause === undefined ? {} : { cause: terminal.roleOutcome.cause }),
2815
2818
  diagnostic: terminal.roleOutcome.diagnostic,
2816
2819
  }));
2817
2820
  return;
@@ -33,16 +33,6 @@ export function roleResultPayloads(outcome) {
33
33
  return outcome.payloads ?? [];
34
34
  return [];
35
35
  }
36
- /** Last object payload the role actually wrote. No field remapping. */
37
- export function lastRolePayloadRecord(payloads) {
38
- for (let index = payloads.length - 1; index >= 0; index -= 1) {
39
- const payload = payloads[index];
40
- if (typeof payload === "object" && payload !== null && !Array.isArray(payload)) {
41
- return payload;
42
- }
43
- }
44
- return undefined;
45
- }
46
36
  /**
47
37
  * Build a recommendation navigator fact. Model command is kept when present;
48
38
  * registry render is fallback only. Unknown seats stay recommendations (#836 B4.1).
@@ -71,7 +61,7 @@ export function formatTerminalResult(result) {
71
61
  const lines = [];
72
62
  lines.push("role\toutcome\tstatus");
73
63
  const outcomeStatus = result.roleOutcome.kind === "failure"
74
- ? result.roleOutcome.cause
64
+ ? result.roleOutcome.cause ?? ""
75
65
  : result.roleOutcome.kind === "accepted"
76
66
  ? "accepted"
77
67
  : result.roleOutcome.status;
@@ -110,8 +110,45 @@ function recordedRole(payload) {
110
110
  return payload.projection.role;
111
111
  return undefined;
112
112
  }
113
+ /** Prefer a more complete row for the same tool call without inventing a sole winner across calls. */
114
+ function rowRank(kind) {
115
+ switch (kind) {
116
+ case "accepted":
117
+ return 4;
118
+ case "audit-escalation":
119
+ return 3;
120
+ case "correctable-rejection":
121
+ case "infrastructure":
122
+ return 2;
123
+ case "candidate":
124
+ return 1;
125
+ }
126
+ }
127
+ /**
128
+ * Same-call identity for reader pairing only (#881 / #836).
129
+ * attemptId is already on the record (subject/payload); bare toolCallId alone
130
+ * collapses distinct court attempts that reused a host call id.
131
+ */
132
+ function submissionCallKey(attemptId, toolCallId) {
133
+ return `${attemptId ?? ""}\0${toolCallId}`;
134
+ }
135
+ function rowFromPayload(kind, payload, accepted, roleFallback) {
136
+ const role = recordedRole(payload) ?? roleFallback;
137
+ return {
138
+ ...(role === undefined ? {} : { role }),
139
+ kind,
140
+ accepted,
141
+ ...(typeof payload.toolCallId === "string" && payload.toolCallId.length > 0
142
+ ? { toolCallId: payload.toolCallId }
143
+ : {}),
144
+ };
145
+ }
113
146
  /**
114
- * All recorded role submissions in ledger order (#836 multi-submit).
147
+ * All recorded role submissions in ledger order (#836 multi-submit / #881).
148
+ * Projects every original payload — sealed, audit-escalation, correctable-rejection,
149
+ * infrastructure, and bare candidate — without outcome-class filtering.
150
+ * Same call (candidate + outcome both carrying params) appears once — keyed by
151
+ * recorded attemptId + toolCallId so distinct court attempts stay distinct (#881).
115
152
  * `accepted` is the original payload; never rebuilt from a status/facts envelope.
116
153
  */
117
154
  export async function readRecordedSubmissionRows(cwd, runId, homeOrScope) {
@@ -119,27 +156,85 @@ export async function readRecordedSubmissionRows(cwd, runId, homeOrScope) {
119
156
  const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
120
157
  const scoped = recordsForAttempt(owned, scope.attemptId);
121
158
  const out = [];
159
+ const indexByCall = new Map();
160
+ // Recover seat identity for historical non-sealed rows that omitted role (#881).
161
+ const roleByCall = new Map();
122
162
  for (const record of scoped) {
163
+ const payload = record.payload;
164
+ if (typeof payload?.toolCallId !== "string" || payload.toolCallId.length === 0)
165
+ continue;
166
+ const role = recordedRole(payload);
167
+ if (role !== undefined) {
168
+ roleByCall.set(submissionCallKey(recordAttemptId(record), payload.toolCallId), role);
169
+ }
170
+ }
171
+ const take = (row, callKey) => {
172
+ const toolCallId = row.toolCallId;
173
+ if (toolCallId !== undefined && callKey !== undefined) {
174
+ const existingIndex = indexByCall.get(callKey);
175
+ if (existingIndex !== undefined) {
176
+ const existing = out[existingIndex];
177
+ if (rowRank(row.kind) >= rowRank(existing.kind)) {
178
+ out[existingIndex] = {
179
+ ...row,
180
+ toolCallId,
181
+ // Keep a previously recovered role when the upgraded row still omits it.
182
+ ...(row.role === undefined && existing.role !== undefined ? { role: existing.role } : {}),
183
+ };
184
+ }
185
+ else if (existing.role === undefined && row.role !== undefined) {
186
+ out[existingIndex] = { ...existing, role: row.role };
187
+ }
188
+ return;
189
+ }
190
+ indexByCall.set(callKey, out.length);
191
+ }
192
+ out.push(row);
193
+ };
194
+ for (const record of scoped) {
195
+ const attemptId = recordAttemptId(record);
196
+ if (record.kind === "candidate") {
197
+ const payload = record.payload;
198
+ if (payload?.type !== "candidate" || payload.params === undefined)
199
+ continue;
200
+ const callKey = typeof payload.toolCallId === "string"
201
+ ? submissionCallKey(attemptId, payload.toolCallId)
202
+ : undefined;
203
+ const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
204
+ take(rowFromPayload("candidate", payload, payload.params, fallback), callKey);
205
+ continue;
206
+ }
123
207
  if (record.kind === "sealed") {
124
208
  const payload = record.payload;
125
209
  if (payload?.type !== "sealed" || payload.accepted === undefined)
126
210
  continue;
127
- const role = recordedRole(payload);
128
- if (role === undefined)
129
- continue;
130
- out.push({ role, kind: "accepted", accepted: payload.accepted });
211
+ const callKey = typeof payload.toolCallId === "string"
212
+ ? submissionCallKey(attemptId, payload.toolCallId)
213
+ : undefined;
214
+ const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
215
+ take(rowFromPayload("accepted", payload, payload.accepted, fallback), callKey);
131
216
  continue;
132
217
  }
133
218
  if (record.kind !== "outcome")
134
219
  continue;
135
220
  const payload = record.payload;
136
- if (payload?.type !== "outcome" || payload.outcome !== "audit-escalation" || payload.accepted === undefined) {
221
+ if (payload?.type !== "outcome" || payload.accepted === undefined)
137
222
  continue;
138
- }
139
- const role = recordedRole(payload);
140
- if (role === undefined)
223
+ const outcome = payload.outcome;
224
+ const kind = outcome === "audit-escalation"
225
+ ? "audit-escalation"
226
+ : outcome === "correctable-rejection"
227
+ ? "correctable-rejection"
228
+ : outcome === "infrastructure"
229
+ ? "infrastructure"
230
+ : undefined;
231
+ if (kind === undefined)
141
232
  continue;
142
- out.push({ role, kind: "audit-escalation", accepted: payload.accepted });
233
+ const callKey = typeof payload.toolCallId === "string"
234
+ ? submissionCallKey(attemptId, payload.toolCallId)
235
+ : undefined;
236
+ const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
237
+ take(rowFromPayload(kind, payload, payload.accepted, fallback), callKey);
143
238
  }
144
239
  return out;
145
240
  }
@@ -150,7 +245,7 @@ export async function readRecordedSubmissionRows(cwd, runId, homeOrScope) {
150
245
  export async function readRecordedSubmissions(cwd, runId, homeOrScope) {
151
246
  return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).map((row) => row.accepted);
152
247
  }
153
- /** True when the run has at least one recorded accepted or audit-escalation payload. */
248
+ /** True when the run has at least one recorded original payload (any outcome class). */
154
249
  export async function hasRecordedSubmission(cwd, runId, homeOrScope) {
155
250
  return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).length > 0;
156
251
  }
@@ -254,6 +349,7 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
254
349
  toolCallId,
255
350
  toolName: tool.name,
256
351
  sequence: ++state.sequence,
352
+ role,
257
353
  params,
258
354
  });
259
355
  let result;
@@ -274,6 +370,7 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
274
370
  outcome: "correctable-rejection",
275
371
  code: "typed-bounce",
276
372
  diagnostic: error instanceof Error ? error.message : String(error),
373
+ role,
277
374
  accepted: params,
278
375
  });
279
376
  throw error;
@@ -285,6 +382,7 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
285
382
  toolCallId,
286
383
  outcome: "infrastructure",
287
384
  diagnostic: error instanceof Error ? error.message : String(error),
385
+ role,
288
386
  accepted: params,
289
387
  });
290
388
  throw error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.4021",
3
+ "version": "0.1.4035",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -15,18 +15,22 @@ export type HostToolResult<T = unknown> = {
15
15
  /** Opaque host-owned identity persisted with a Role run. */
16
16
  export type DurablePrincipal = object & { readonly __durablePrincipal?: never };
17
17
 
18
- /** Controlled post-admission failure classes (ADR 0052 / #107). Owner = host contract. */
18
+ /**
19
+ * Controlled post-admission failure classes (ADR 0052 / #107). Owner = host contract.
20
+ * Closed set of typed facts only — never a fabricated "could not classify" label (#881).
21
+ * When no typed confirmation exists, omit cause and keep the original diagnostic / error pointer.
22
+ */
19
23
  export type ControlledFailureCause =
20
24
  | "activation"
21
25
  | "provider"
22
26
  | "session"
23
27
  | "output"
24
- | "timeout"
25
- | "unrecognized";
28
+ | "timeout";
26
29
 
27
30
  /** Production-owned typed failure carried on a resolved turn result. */
28
31
  export type RoleTurnKnownFailure = {
29
- readonly cause: ControlledFailureCause;
32
+ /** Present only when a typed fact confirms the class; omitted when unknown (#881). */
33
+ readonly cause?: ControlledFailureCause;
30
34
  readonly identity?: {
31
35
  readonly name?: string;
32
36
  readonly code?: string | number;
@@ -25,6 +25,7 @@ import {
25
25
  navigatorProviderFailure,
26
26
  navigatorProviderFailureFromDiagnostics,
27
27
  navigatorProviderFailureFromError,
28
+ navigatorProviderFailureFromPublicTerminal,
28
29
  navigatorProviderFailureFromStatus,
29
30
  navigatorUnavailableError,
30
31
  parseNavigatorModelSetting,
@@ -46,6 +47,7 @@ export {
46
47
  navigatorProviderFailure,
47
48
  navigatorProviderFailureFromDiagnostics,
48
49
  navigatorProviderFailureFromError,
50
+ navigatorProviderFailureFromPublicTerminal,
49
51
  navigatorProviderFailureFromStatus,
50
52
  navigatorUnavailableError,
51
53
  parseNavigatorModelSetting,