@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.
- package/dist/acp-host/production-host.js +208 -107
- package/dist/headless-host/production-host.js +208 -107
- package/dist/navigator-attendance.js +2 -0
- package/dist/navigator-public-session.js +18 -34
- package/dist/navigator-session-contracts.js +19 -0
- package/dist/pi/known-failure.js +3 -4
- package/dist/public-cli/auto-resume.js +20 -7
- package/dist/public-cli/diarist-run.js +9 -23
- package/dist/public-cli/instruction-seat-run.js +3 -1
- package/dist/public-cli/judge-run.js +3 -1
- package/dist/public-cli/main.js +203 -112
- package/dist/public-cli/settlement.js +64 -61
- package/dist/public-cli/terminal.js +1 -11
- package/dist/submission-ledger.js +109 -11
- package/package.json +1 -1
- package/src/host-contracts.ts +8 -4
- package/src/navigator-attendance.ts +2 -0
- package/src/navigator-public-session.ts +19 -55
- package/src/navigator-session-contracts.ts +39 -0
- package/src/pi/known-failure.ts +3 -4
- package/src/public-cli/auto-resume.ts +57 -20
- package/src/public-cli/cli.ts +2 -2
- package/src/public-cli/collector-run.ts +3 -1
- package/src/public-cli/countersign-run.ts +34 -27
- package/src/public-cli/diarist-run.ts +10 -27
- package/src/public-cli/instruction-seat-run.ts +3 -1
- package/src/public-cli/judge-run.ts +3 -1
- package/src/public-cli/reviewer-run.ts +3 -1
- package/src/public-cli/settlement.ts +83 -84
- package/src/public-cli/terminal.ts +7 -16
- package/src/submission-ledger.ts +147 -13
|
@@ -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
|
-
|
|
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 (
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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,
|
|
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 || "
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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.
|
|
221
|
+
if (payload?.type !== "outcome" || payload.accepted === undefined)
|
|
137
222
|
continue;
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
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
|
-
|
|
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
|
|
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
package/src/host-contracts.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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,
|