@akagilnc/pi-workflow-roles 0.1.2383 → 0.1.2402
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/audit-escalation.js +0 -13
- package/dist/compliance-transport.js +28 -5
- package/dist/doctor-contracts.js +2 -9
- package/dist/dossier-resolution.js +21 -2
- package/dist/engine-detour-tool.js +10 -35
- package/dist/evidence-child-executor.js +12 -4
- package/dist/merger-contracts.js +2 -8
- package/dist/navigator-attendance.js +1 -9
- package/dist/navigator-invocation-identity.js +16 -4
- package/dist/notary-contracts.js +7 -25
- package/dist/package-contracts/judge-output.js +1 -4
- package/dist/package-contracts/reviewer-output.js +1 -7
- package/dist/package-contracts/terminating-tools.js +6 -11
- package/dist/public-cli/main.js +125 -475
- package/package.json +1 -1
- package/scripts/build-package.mjs +1 -2
- package/src/analyst-gate-cycles-read.ts +5 -8
- package/src/analyst-metric-families/acceptance-success-rework.ts +3 -6
- package/src/audit-escalation.ts +0 -25
- package/src/collector-role.ts +1 -8
- package/src/compliance-transport.ts +38 -6
- package/src/doctor-auditor.ts +3 -3
- package/src/doctor-contracts.ts +3 -18
- package/src/doctor-role.ts +1 -5
- package/src/dossier-resolution.ts +25 -4
- package/src/engine-detour-tool.ts +16 -51
- package/src/evidence-child-executor.ts +12 -4
- package/src/factory-board.ts +2 -4
- package/src/gatekeeper-role.ts +33 -43
- package/src/judge-auditor.ts +3 -3
- package/src/judge-role.ts +1 -8
- package/src/merger-contracts.ts +3 -17
- package/src/merger-role.ts +1 -8
- package/src/navigator-attendance.ts +1 -14
- package/src/navigator-invocation-identity.ts +34 -13
- package/src/notary-contracts.ts +9 -43
- package/src/notary-role.ts +3 -5
- package/src/package-contracts/judge-output.ts +2 -17
- package/src/package-contracts/reviewer-output.ts +2 -21
- package/src/package-contracts/terminating-tools.ts +5 -14
- package/src/package-contracts/worker-output.ts +2 -10
- package/src/public-cli/doctor-run.ts +0 -2
- package/src/public-cli/fixer-run.ts +0 -15
- package/src/public-cli/judge-run.ts +0 -16
- package/src/public-cli/notary-run.ts +1 -1
- package/src/public-cli/one-shot-dispatch.ts +1 -1
- package/src/public-cli/reviewer-run.ts +0 -15
- package/src/public-cli/settlement.ts +110 -368
- package/src/public-cli/terminal.ts +3 -71
- package/src/reviewer-auditor.ts +3 -3
- package/src/reviewer-role.ts +2 -11
- package/src/role-runtime.ts +43 -37
- package/src/worker-role.ts +2 -14
- package/dist/engine-labor-fallback.js +0 -136
- package/src/engine-labor-fallback.ts +0 -270
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* Controlled failures and audit human decisions settle here without washing causes.
|
|
5
5
|
*/
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import {
|
|
8
|
-
import { appendFile, lstat, mkdir, open, readFile, readdir, writeFile } from "node:fs/promises";
|
|
7
|
+
import { appendFile, lstat, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
9
8
|
import { dirname, join } from "node:path";
|
|
10
9
|
|
|
11
10
|
import {
|
|
@@ -32,7 +31,6 @@ import {
|
|
|
32
31
|
AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE,
|
|
33
32
|
COMPLIANCE_RESPONSE_ENTRY_TYPE,
|
|
34
33
|
readComplianceCandidate,
|
|
35
|
-
type ComplianceAuditIncomplete,
|
|
36
34
|
type ComplianceDecision,
|
|
37
35
|
} from "../compliance-transport.ts";
|
|
38
36
|
import {
|
|
@@ -41,11 +39,6 @@ import {
|
|
|
41
39
|
COLLECTOR_WAIT_TOOL,
|
|
42
40
|
} from "../collector-ledger.ts";
|
|
43
41
|
import { ENGINE_DETOUR_TOOL_NAME } from "../engine-detour.ts";
|
|
44
|
-
import {
|
|
45
|
-
readEngineLaborFallbackFieldFrom,
|
|
46
|
-
seatFallbackBaseStatus,
|
|
47
|
-
seatFallbackStatusHasLawfulEvidence,
|
|
48
|
-
} from "../engine-labor-fallback.ts";
|
|
49
42
|
import {
|
|
50
43
|
JUDGE_OUTPUT_TOOL_NAME,
|
|
51
44
|
type JudgeVerdict,
|
|
@@ -118,10 +111,8 @@ import {
|
|
|
118
111
|
formatTerminalResult,
|
|
119
112
|
isLawfulTypedTerminalOutcome,
|
|
120
113
|
recommendationNavigatorFact,
|
|
121
|
-
buildAuditIncompleteTerminalOutcome,
|
|
122
114
|
buildResidualIncompleteTerminalOutcome,
|
|
123
115
|
redactExactRunId,
|
|
124
|
-
type AuditIncompleteResidual,
|
|
125
116
|
type ControlledFailureCause,
|
|
126
117
|
type TerminalArtifactRef,
|
|
127
118
|
type TerminalMenxiaFact,
|
|
@@ -728,10 +719,16 @@ export async function readBoundEvidenceChildKnownFailure(
|
|
|
728
719
|
return undefined;
|
|
729
720
|
}
|
|
730
721
|
|
|
731
|
-
|
|
732
|
-
|
|
722
|
+
type BoundAuditorVolume = {
|
|
723
|
+
readonly entries: SessionEntry[];
|
|
724
|
+
readonly attemptEntryId?: string;
|
|
725
|
+
readonly parentId: string;
|
|
726
|
+
readonly sessionFile: string;
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
async function loadBoundAuditorVolumes(
|
|
733
730
|
sessionFile: string,
|
|
734
|
-
): Promise<
|
|
731
|
+
): Promise<readonly BoundAuditorVolume[] | undefined> {
|
|
735
732
|
let parentEntries: SessionEntry[];
|
|
736
733
|
try {
|
|
737
734
|
parentEntries = await readBoundSessionEntries(sessionFile);
|
|
@@ -773,7 +770,7 @@ export async function readBoundAuditorKnownFailure(
|
|
|
773
770
|
// first attempt's child after resume advanced latest, losing retentionFailure
|
|
774
771
|
// when retry had no compliance entry. Fix: ignore envelope for staleness and
|
|
775
772
|
// prefer any valid compliance failure before falling back to primary.
|
|
776
|
-
const
|
|
773
|
+
const valid: BoundAuditorVolume[] = [];
|
|
777
774
|
for (const file of names.filter((name) => name.endsWith(".jsonl")).sort().reverse()) {
|
|
778
775
|
let entries: SessionEntry[];
|
|
779
776
|
try {
|
|
@@ -788,10 +785,20 @@ export async function readBoundAuditorKnownFailure(
|
|
|
788
785
|
const attemptEntryId = typeof bindingParent?.attemptEntryId === "string" ? bindingParent.attemptEntryId : undefined;
|
|
789
786
|
const attemptEntryIndex = attemptEntryId === undefined ? -1 : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
|
|
790
787
|
if (bindingParent?.sessionId !== parentId || bindingParent.sessionFile !== sessionFile || attemptEntryIndex < latestParentUserIndex) continue;
|
|
791
|
-
|
|
788
|
+
valid.push({
|
|
789
|
+
entries,
|
|
790
|
+
parentId,
|
|
791
|
+
sessionFile,
|
|
792
|
+
...(attemptEntryId === undefined ? {} : { attemptEntryId }),
|
|
793
|
+
});
|
|
792
794
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
+
return valid;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function complianceFailureFromAuditorVolumes(
|
|
799
|
+
volumes: readonly BoundAuditorVolume[],
|
|
800
|
+
): ExplicitInternalKnownFailure | undefined {
|
|
801
|
+
for (const { entries, attemptEntryId, parentId, sessionFile } of volumes) {
|
|
795
802
|
const stop = extractSessionProviderStop(entries);
|
|
796
803
|
if (stop === undefined) continue;
|
|
797
804
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
@@ -812,8 +819,13 @@ export async function readBoundAuditorKnownFailure(
|
|
|
812
819
|
};
|
|
813
820
|
}
|
|
814
821
|
}
|
|
815
|
-
|
|
816
|
-
|
|
822
|
+
return undefined;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function providerStopFallbackFromAuditorVolumes(
|
|
826
|
+
volumes: readonly BoundAuditorVolume[],
|
|
827
|
+
): ExplicitInternalKnownFailure | undefined {
|
|
828
|
+
for (const { entries } of volumes) {
|
|
817
829
|
const stop = extractSessionProviderStop(entries);
|
|
818
830
|
if (stop === undefined) continue;
|
|
819
831
|
const primary = knownFailureFromProviderStop(stop)!;
|
|
@@ -828,6 +840,34 @@ export async function readBoundAuditorKnownFailure(
|
|
|
828
840
|
return undefined;
|
|
829
841
|
}
|
|
830
842
|
|
|
843
|
+
/** Recover a provider stop from the auditor child bound to the current parent attempt. */
|
|
844
|
+
export async function readBoundAuditorKnownFailure(
|
|
845
|
+
sessionFile: string,
|
|
846
|
+
): Promise<ExplicitInternalKnownFailure | undefined> {
|
|
847
|
+
const volumes = await loadBoundAuditorVolumes(sessionFile);
|
|
848
|
+
if (volumes === undefined) return undefined;
|
|
849
|
+
return complianceFailureFromAuditorVolumes(volumes)
|
|
850
|
+
?? providerStopFallbackFromAuditorVolumes(volumes);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/** Strong auditor tier only — retained compliance-failure entries, no provider-stop fallback. */
|
|
854
|
+
async function readBoundAuditorComplianceFailure(
|
|
855
|
+
sessionFile: string,
|
|
856
|
+
): Promise<ExplicitInternalKnownFailure | undefined> {
|
|
857
|
+
const volumes = await loadBoundAuditorVolumes(sessionFile);
|
|
858
|
+
if (volumes === undefined) return undefined;
|
|
859
|
+
return complianceFailureFromAuditorVolumes(volumes);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/** Weaker auditor tier: provider stop without a retained compliance-failure entry. */
|
|
863
|
+
async function readBoundAuditorProviderStopFallback(
|
|
864
|
+
sessionFile: string,
|
|
865
|
+
): Promise<ExplicitInternalKnownFailure | undefined> {
|
|
866
|
+
const volumes = await loadBoundAuditorVolumes(sessionFile);
|
|
867
|
+
if (volumes === undefined) return undefined;
|
|
868
|
+
return providerStopFallbackFromAuditorVolumes(volumes);
|
|
869
|
+
}
|
|
870
|
+
|
|
831
871
|
function typedFailedTerminatingToolKnownFailure(
|
|
832
872
|
entries: readonly SessionEntry[],
|
|
833
873
|
): ExplicitInternalKnownFailure | undefined {
|
|
@@ -850,11 +890,14 @@ function typedFailedTerminatingToolKnownFailure(
|
|
|
850
890
|
? message.content.find((part) => isRecord(part) && part.type === "text" && typeof part.text === "string")
|
|
851
891
|
: undefined;
|
|
852
892
|
const diagnostic = isRecord(textPart) ? textPart.text : undefined;
|
|
893
|
+
// Durable details already carry fact + typed evidence from envelope one-shot projection (#475).
|
|
894
|
+
// Do not re-parse retained compliance responses here.
|
|
895
|
+
const details = isRecord(message.details) ? message.details : classification.fact;
|
|
853
896
|
return {
|
|
854
897
|
cause: "output",
|
|
855
898
|
identity: { name: message.toolName, code: message.toolCallId },
|
|
856
899
|
...(typeof diagnostic === "string" && diagnostic.trim() !== "" ? { diagnostic } : {}),
|
|
857
|
-
details
|
|
900
|
+
details,
|
|
858
901
|
};
|
|
859
902
|
}
|
|
860
903
|
return undefined;
|
|
@@ -913,12 +956,13 @@ export async function resolveAuditedRunnerFailureResolution(input: {
|
|
|
913
956
|
});
|
|
914
957
|
}
|
|
915
958
|
}
|
|
916
|
-
// Bound auditor
|
|
917
|
-
// caused (retention EISDIR race). A typed terminating-tool
|
|
918
|
-
//
|
|
959
|
+
// Bound auditor compliance-failure retention outranks a parent failure that the
|
|
960
|
+
// auditor path itself caused (retention EISDIR race). A typed terminating-tool
|
|
961
|
+
// host failure is next — it outranks weaker auditor provider-stop fallback so
|
|
962
|
+
// parent failInfrastructure abort pollution cannot wash a real diagnostic (#475).
|
|
919
963
|
try {
|
|
920
|
-
const
|
|
921
|
-
if (
|
|
964
|
+
const auditorCompliance = await readBoundAuditorComplianceFailure(input.sessionFile);
|
|
965
|
+
if (auditorCompliance !== undefined) return resolutionOf(auditorCompliance);
|
|
922
966
|
} catch (error) {
|
|
923
967
|
const failure = sessionReadFailure(error, "failed to recover bound auditor failure");
|
|
924
968
|
return resolutionOf({
|
|
@@ -942,6 +986,17 @@ export async function resolveAuditedRunnerFailureResolution(input: {
|
|
|
942
986
|
});
|
|
943
987
|
}
|
|
944
988
|
}
|
|
989
|
+
try {
|
|
990
|
+
const auditorStop = await readBoundAuditorProviderStopFallback(input.sessionFile);
|
|
991
|
+
if (auditorStop !== undefined) return resolutionOf(auditorStop);
|
|
992
|
+
} catch (error) {
|
|
993
|
+
const failure = sessionReadFailure(error, "failed to recover bound auditor provider stop");
|
|
994
|
+
return resolutionOf({
|
|
995
|
+
cause: "session",
|
|
996
|
+
identity: thrownIdentity(failure),
|
|
997
|
+
diagnostic: failure.message || failure.name,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
945
1000
|
// Reviewer axis evidence-children are next: fixed two-axis dispatch fails
|
|
946
1001
|
// during activation with only child stops durable. Parent stop remains the
|
|
947
1002
|
// fallback; credential is last.
|
|
@@ -1110,10 +1165,8 @@ function judgeDecisiveFacts(
|
|
|
1110
1165
|
const facts: Record<string, unknown> = {
|
|
1111
1166
|
judgeStatus,
|
|
1112
1167
|
...auditNoReceiptDecisiveFact(verdict),
|
|
1113
|
-
// #380: spread sole-built field; do not re-key here (S1).
|
|
1114
|
-
...readEngineLaborFallbackFieldFrom(verdict),
|
|
1115
1168
|
};
|
|
1116
|
-
const statusBase =
|
|
1169
|
+
const statusBase = judgeStatus;
|
|
1117
1170
|
if (statusBase === "continue") {
|
|
1118
1171
|
const fix = safelyRead(verdict, "fix");
|
|
1119
1172
|
if (fix.readable && isRecord(fix.value)) {
|
|
@@ -1167,7 +1220,7 @@ function coderDecisiveFacts(output: CoderOutput): Record<string, unknown> {
|
|
|
1167
1220
|
if (status.readable && typeof status.value === "string") facts.coderStatus = status.value;
|
|
1168
1221
|
const statusBase =
|
|
1169
1222
|
status.readable && typeof status.value === "string"
|
|
1170
|
-
?
|
|
1223
|
+
? (status.value)
|
|
1171
1224
|
: undefined;
|
|
1172
1225
|
const remainingScope = safelyRead(candidate, "remainingScope");
|
|
1173
1226
|
if (statusBase === "unfinished" && remainingScope.readable && typeof remainingScope.value === "string") facts.remainingScope = remainingScope.value;
|
|
@@ -1187,7 +1240,7 @@ function fixerDecisiveFacts(output: FixerOutput): Record<string, unknown> {
|
|
|
1187
1240
|
if (status.readable && typeof status.value === "string") facts.fixerStatus = status.value;
|
|
1188
1241
|
const statusBase =
|
|
1189
1242
|
status.readable && typeof status.value === "string"
|
|
1190
|
-
?
|
|
1243
|
+
? (status.value)
|
|
1191
1244
|
: undefined;
|
|
1192
1245
|
const remainingScope = safelyRead(candidate, "remainingScope");
|
|
1193
1246
|
if ((statusBase === "unfinished" || statusBase === "refused") && remainingScope.readable && typeof remainingScope.value === "string") facts.remainingScope = remainingScope.value;
|
|
@@ -1280,7 +1333,7 @@ function doctorDecisiveFacts(output: DoctorOutput): Record<string, unknown> {
|
|
|
1280
1333
|
if (status.readable && typeof status.value === "string") facts.doctorStatus = status.value;
|
|
1281
1334
|
const statusBase =
|
|
1282
1335
|
status.readable && typeof status.value === "string"
|
|
1283
|
-
?
|
|
1336
|
+
? (status.value)
|
|
1284
1337
|
: undefined;
|
|
1285
1338
|
if (statusBase === "refused") {
|
|
1286
1339
|
const reason = safelyRead(candidate, "reason");
|
|
@@ -1329,8 +1382,6 @@ function reviewerDecisiveFacts(
|
|
|
1329
1382
|
amendmentAxes,
|
|
1330
1383
|
acceptedBatchPresent: acceptedBatch.readable && acceptedBatch.value !== undefined,
|
|
1331
1384
|
...auditNoReceiptDecisiveFact(candidate),
|
|
1332
|
-
// #380: spread sole-built field; do not re-key here (S1).
|
|
1333
|
-
...readEngineLaborFallbackFieldFrom(candidate),
|
|
1334
1385
|
};
|
|
1335
1386
|
if (status.readable && typeof status.value === "string") facts.reviewerStatus = status.value;
|
|
1336
1387
|
if (
|
|
@@ -1342,7 +1393,7 @@ function reviewerDecisiveFacts(
|
|
|
1342
1393
|
const diagnostic = safelyRead(candidate, "diagnostic");
|
|
1343
1394
|
const statusBase =
|
|
1344
1395
|
status.readable && typeof status.value === "string"
|
|
1345
|
-
?
|
|
1396
|
+
? (status.value)
|
|
1346
1397
|
: undefined;
|
|
1347
1398
|
if (statusBase === "refused" && diagnostic.readable) {
|
|
1348
1399
|
facts.diagnosticPresent = typeof diagnostic.value === "string" && diagnostic.value.trim().length > 0;
|
|
@@ -1550,35 +1601,6 @@ export function assertCollectorReceiptMatchesAdmitted(
|
|
|
1550
1601
|
}
|
|
1551
1602
|
}
|
|
1552
1603
|
|
|
1553
|
-
/**
|
|
1554
|
-
* Shared audit-incomplete extraction for the four roles with Soul auditors.
|
|
1555
|
-
* The role submission and retained auditor response are separate evidence faces;
|
|
1556
|
-
* neither is converted into an accepted Receipt.
|
|
1557
|
-
*/
|
|
1558
|
-
function isComplianceAuditIncomplete(value: unknown): value is ComplianceAuditIncomplete {
|
|
1559
|
-
if (!isRecord(value) || value.status !== "audit-incomplete") return false;
|
|
1560
|
-
const observation = value.observation;
|
|
1561
|
-
if (!isRecord(observation)) return false;
|
|
1562
|
-
if (observation.kind === "missing-dossier") return true;
|
|
1563
|
-
if (observation.kind === "missing-subject") {
|
|
1564
|
-
return typeof observation.subject === "string" && observation.subject.length > 0;
|
|
1565
|
-
}
|
|
1566
|
-
if (observation.kind === "object-status-unreadable") {
|
|
1567
|
-
return observation.status === "missing" || observation.status === "unknown";
|
|
1568
|
-
}
|
|
1569
|
-
return observation.kind === "non-object-arguments" && [
|
|
1570
|
-
"null",
|
|
1571
|
-
"array",
|
|
1572
|
-
"undefined",
|
|
1573
|
-
"string",
|
|
1574
|
-
"number",
|
|
1575
|
-
"boolean",
|
|
1576
|
-
"bigint",
|
|
1577
|
-
"symbol",
|
|
1578
|
-
"function",
|
|
1579
|
-
].includes(observation.type as string);
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
1604
|
function auditToolNameForRole(
|
|
1583
1605
|
role: (typeof AUDITOR_SOUL_ROLES)[number],
|
|
1584
1606
|
): string {
|
|
@@ -1592,19 +1614,6 @@ function auditToolNameForRole(
|
|
|
1592
1614
|
}
|
|
1593
1615
|
}
|
|
1594
1616
|
|
|
1595
|
-
function outputToolNameForAuditedRole(
|
|
1596
|
-
role: (typeof AUDITOR_SOUL_ROLES)[number],
|
|
1597
|
-
): string {
|
|
1598
|
-
switch (role) {
|
|
1599
|
-
case "judge":
|
|
1600
|
-
return JUDGE_OUTPUT_TOOL_NAME;
|
|
1601
|
-
case "reviewer":
|
|
1602
|
-
return REVIEWER_OUTPUT_TOOL_NAME;
|
|
1603
|
-
case "doctor":
|
|
1604
|
-
return DOCTOR_OUTPUT_TOOL_NAME;
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
1617
|
type BoundRoleToolCall = {
|
|
1609
1618
|
callIndex: number;
|
|
1610
1619
|
candidate: unknown;
|
|
@@ -1757,13 +1766,6 @@ function isUnboundAuditEscalationFace(details: unknown): boolean {
|
|
|
1757
1766
|
return kind.readable && kind.value === "audit_escalation";
|
|
1758
1767
|
}
|
|
1759
1768
|
|
|
1760
|
-
function auditIncompleteFromCandidate(
|
|
1761
|
-
candidate: unknown,
|
|
1762
|
-
): ComplianceAuditIncomplete | undefined {
|
|
1763
|
-
const decision = readComplianceCandidate(candidate);
|
|
1764
|
-
return decision.status === "audit-incomplete" ? decision : undefined;
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
1769
|
function boundRetainedAuditResponse(
|
|
1768
1770
|
entries: readonly SessionEntry[],
|
|
1769
1771
|
callIndex: number,
|
|
@@ -1791,113 +1793,6 @@ function boundRetainedAuditResponse(
|
|
|
1791
1793
|
return matches.length === 1 ? matches[0] : undefined;
|
|
1792
1794
|
}
|
|
1793
1795
|
|
|
1794
|
-
export function extractComplianceAuditIncompleteRoleOutcome(
|
|
1795
|
-
entries: readonly SessionEntry[],
|
|
1796
|
-
role: (typeof AUDITOR_SOUL_ROLES)[number],
|
|
1797
|
-
outputToolName: string,
|
|
1798
|
-
): { outcome: ReturnType<typeof buildAuditIncompleteTerminalOutcome> } | undefined {
|
|
1799
|
-
if (outputToolName !== outputToolNameForAuditedRole(role)) return undefined;
|
|
1800
|
-
const auditToolName = auditToolNameForRole(role);
|
|
1801
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
1802
|
-
const message = entries[index]?.message;
|
|
1803
|
-
if (
|
|
1804
|
-
entries[index]?.type !== "message" ||
|
|
1805
|
-
message?.role !== "toolResult" ||
|
|
1806
|
-
message.toolName !== outputToolName ||
|
|
1807
|
-
message.isError === true ||
|
|
1808
|
-
!isComplianceAuditIncomplete(message.details)
|
|
1809
|
-
) {
|
|
1810
|
-
continue;
|
|
1811
|
-
}
|
|
1812
|
-
const roleCall = boundRoleToolCallForResult(
|
|
1813
|
-
entries,
|
|
1814
|
-
index,
|
|
1815
|
-
message,
|
|
1816
|
-
outputToolName,
|
|
1817
|
-
);
|
|
1818
|
-
if (roleCall === undefined) continue;
|
|
1819
|
-
// Preflight missing-dossier / missing-subject never contacts the provider, so
|
|
1820
|
-
// there is no retained auditor response — the role tool details are the audit.
|
|
1821
|
-
// details already narrowed by isComplianceAuditIncomplete at the loop gate.
|
|
1822
|
-
const details = message.details;
|
|
1823
|
-
if (
|
|
1824
|
-
details.observation.kind === "missing-dossier"
|
|
1825
|
-
|| details.observation.kind === "missing-subject"
|
|
1826
|
-
) {
|
|
1827
|
-
return {
|
|
1828
|
-
outcome: buildAuditIncompleteTerminalOutcome({
|
|
1829
|
-
role,
|
|
1830
|
-
roleCandidate: roleCall.candidate,
|
|
1831
|
-
audit: details,
|
|
1832
|
-
}),
|
|
1833
|
-
};
|
|
1834
|
-
}
|
|
1835
|
-
const retained = boundRetainedAuditResponse(
|
|
1836
|
-
entries,
|
|
1837
|
-
roleCall.callIndex,
|
|
1838
|
-
index,
|
|
1839
|
-
auditToolName,
|
|
1840
|
-
);
|
|
1841
|
-
if (retained === undefined) continue;
|
|
1842
|
-
const audit = auditIncompleteFromCandidate(retained.candidate);
|
|
1843
|
-
if (audit === undefined) continue;
|
|
1844
|
-
return {
|
|
1845
|
-
outcome: buildAuditIncompleteTerminalOutcome({
|
|
1846
|
-
role,
|
|
1847
|
-
roleCandidate: roleCall.candidate,
|
|
1848
|
-
audit,
|
|
1849
|
-
}),
|
|
1850
|
-
};
|
|
1851
|
-
}
|
|
1852
|
-
return undefined;
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
function auditArtifactPublicationError(message: string, code: string): Error & {
|
|
1856
|
-
code: string;
|
|
1857
|
-
} {
|
|
1858
|
-
const error = new Error(message) as Error & { code: string };
|
|
1859
|
-
error.name = "ArtifactPublicationError";
|
|
1860
|
-
error.code = code;
|
|
1861
|
-
return error;
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
async function ensureAuditEvidenceDirectory(runDirectory: string): Promise<string> {
|
|
1865
|
-
const artifactsDir = join(runDirectory, "artifacts");
|
|
1866
|
-
const runStat = await lstat(runDirectory);
|
|
1867
|
-
if (runStat.isSymbolicLink() || !runStat.isDirectory()) {
|
|
1868
|
-
throw auditArtifactPublicationError(
|
|
1869
|
-
"audit evidence run directory is not a real directory",
|
|
1870
|
-
"ELOOP",
|
|
1871
|
-
);
|
|
1872
|
-
}
|
|
1873
|
-
try {
|
|
1874
|
-
const existing = await lstat(artifactsDir);
|
|
1875
|
-
if (existing.isSymbolicLink()) {
|
|
1876
|
-
throw auditArtifactPublicationError(
|
|
1877
|
-
"audit evidence artifacts directory is a symlink",
|
|
1878
|
-
"ELOOP",
|
|
1879
|
-
);
|
|
1880
|
-
}
|
|
1881
|
-
if (!existing.isDirectory()) {
|
|
1882
|
-
throw auditArtifactPublicationError(
|
|
1883
|
-
"audit evidence artifacts path is not a directory",
|
|
1884
|
-
"EEXIST",
|
|
1885
|
-
);
|
|
1886
|
-
}
|
|
1887
|
-
} catch (error) {
|
|
1888
|
-
if (!isMissingPathError(error)) throw error;
|
|
1889
|
-
await mkdir(artifactsDir, { recursive: true });
|
|
1890
|
-
const created = await lstat(artifactsDir);
|
|
1891
|
-
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
1892
|
-
throw auditArtifactPublicationError(
|
|
1893
|
-
"audit evidence artifacts directory is not a real directory",
|
|
1894
|
-
"ELOOP",
|
|
1895
|
-
);
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
return artifactsDir;
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
1796
|
/**
|
|
1902
1797
|
* #419 per-attempt process history. 史必追加,指针可覆盖;指针可以覆盖的前提是史已落。
|
|
1903
1798
|
* Reuses the run session principal's append-only JSONL custom-entry shape
|
|
@@ -1957,167 +1852,6 @@ export async function appendRunAttemptHistory(
|
|
|
1957
1852
|
await appendFile(admitted.sessionFile, line, "utf8");
|
|
1958
1853
|
}
|
|
1959
1854
|
|
|
1960
|
-
/**
|
|
1961
|
-
* Publish the retained residual with complete-write semantics (#419: the
|
|
1962
|
-
* previous attempt's pointer file is a rebuildable view once this attempt's
|
|
1963
|
-
* complete result is in the appended history; planted symlinks/directories
|
|
1964
|
-
* still fail loudly with their #182-A identities).
|
|
1965
|
-
*/
|
|
1966
|
-
export async function publishComplianceAuditIncompleteEvidence(
|
|
1967
|
-
admitted: AdmittedRoleInvocation,
|
|
1968
|
-
outcome: ReturnType<typeof buildAuditIncompleteTerminalOutcome>,
|
|
1969
|
-
): Promise<TerminalArtifactRef> {
|
|
1970
|
-
await appendRunAttemptHistory(admitted, outcome);
|
|
1971
|
-
if (
|
|
1972
|
-
typeof fsConstants.O_NOFOLLOW !== "number" ||
|
|
1973
|
-
typeof fsConstants.O_NONBLOCK !== "number"
|
|
1974
|
-
) {
|
|
1975
|
-
// #418 fail-closed: on platforms without O_NOFOLLOW (e.g. Windows,
|
|
1976
|
-
// nodejs/node#41590) the JS bitwise-or below would silently drop the flag
|
|
1977
|
-
// and the #182-A anti-symlink protection would vanish. Refuse loudly via
|
|
1978
|
-
// the existing publication-failure channel instead of publishing
|
|
1979
|
-
// unprotected; the complete attempt result above stays in appended history.
|
|
1980
|
-
throw auditArtifactPublicationError(
|
|
1981
|
-
"audit evidence publication requires O_NOFOLLOW|O_NONBLOCK open-flag support (anti-symlink/anti-planted protection must not be silently dropped); refusing to publish",
|
|
1982
|
-
"ENOSYS",
|
|
1983
|
-
);
|
|
1984
|
-
}
|
|
1985
|
-
const artifactsDir = await ensureAuditEvidenceDirectory(admitted.runDirectory);
|
|
1986
|
-
const evidencePath = join(artifactsDir, "audit-incomplete.json");
|
|
1987
|
-
let existing: Awaited<ReturnType<typeof lstat>> | undefined;
|
|
1988
|
-
try {
|
|
1989
|
-
existing = await lstat(evidencePath);
|
|
1990
|
-
} catch (error) {
|
|
1991
|
-
if (!isMissingPathError(error)) throw error;
|
|
1992
|
-
}
|
|
1993
|
-
if (existing?.isSymbolicLink()) {
|
|
1994
|
-
throw auditArtifactPublicationError(
|
|
1995
|
-
"audit evidence destination is a symlink",
|
|
1996
|
-
"ELOOP",
|
|
1997
|
-
);
|
|
1998
|
-
}
|
|
1999
|
-
if (existing && !existing.isFile()) {
|
|
2000
|
-
throw auditArtifactPublicationError(
|
|
2001
|
-
"audit evidence destination is not a regular file",
|
|
2002
|
-
"EEXIST",
|
|
2003
|
-
);
|
|
2004
|
-
}
|
|
2005
|
-
// #182-A protection restored atomically at this open seam: O_NOFOLLOW makes
|
|
2006
|
-
// the open itself refuse symlinks (ELOOP), so one swapped in after the lstat
|
|
2007
|
-
// above can never be followed or written through; O_NONBLOCK keeps a
|
|
2008
|
-
// race-planted FIFO from blocking this open forever. The fstat below
|
|
2009
|
-
// backstops any non-regular object that wins the window.
|
|
2010
|
-
const handle = await open(
|
|
2011
|
-
evidencePath,
|
|
2012
|
-
fsConstants.O_WRONLY |
|
|
2013
|
-
fsConstants.O_CREAT |
|
|
2014
|
-
fsConstants.O_TRUNC |
|
|
2015
|
-
fsConstants.O_NOFOLLOW |
|
|
2016
|
-
fsConstants.O_NONBLOCK,
|
|
2017
|
-
0o600,
|
|
2018
|
-
);
|
|
2019
|
-
try {
|
|
2020
|
-
if (!(await handle.stat()).isFile()) {
|
|
2021
|
-
throw auditArtifactPublicationError(
|
|
2022
|
-
"audit evidence destination is not a regular file",
|
|
2023
|
-
"EEXIST",
|
|
2024
|
-
);
|
|
2025
|
-
}
|
|
2026
|
-
await handle.writeFile(`${JSON.stringify(outcome, null, 2)}\n`, "utf8");
|
|
2027
|
-
await handle.sync();
|
|
2028
|
-
} finally {
|
|
2029
|
-
await handle.close();
|
|
2030
|
-
}
|
|
2031
|
-
return { kind: "evidence", path: evidencePath };
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
function auditPublicationFailureTerminal(
|
|
2035
|
-
admitted: AdmittedRoleInvocation,
|
|
2036
|
-
entries: readonly SessionEntry[],
|
|
2037
|
-
outcome: ReturnType<typeof buildAuditIncompleteTerminalOutcome>,
|
|
2038
|
-
error: unknown,
|
|
2039
|
-
): TerminalResult {
|
|
2040
|
-
const attempt = publicationAttemptFromError(
|
|
2041
|
-
join(admitted.runDirectory, "artifacts", "audit-incomplete.json"),
|
|
2042
|
-
error,
|
|
2043
|
-
);
|
|
2044
|
-
const diagnostic = `audit-incomplete evidence publication failed: ${attempt.diagnostic}`;
|
|
2045
|
-
const decisiveFacts: Record<string, unknown> = {
|
|
2046
|
-
...outcome.decisiveFacts,
|
|
2047
|
-
cause: "unrecognized",
|
|
2048
|
-
diagnostic,
|
|
2049
|
-
publicationFailure: attempt,
|
|
2050
|
-
};
|
|
2051
|
-
if (attempt.identity?.name !== undefined) decisiveFacts.errorName = attempt.identity.name;
|
|
2052
|
-
if (attempt.identity?.code !== undefined) decisiveFacts.errorCode = attempt.identity.code;
|
|
2053
|
-
const auditResidual: AuditIncompleteResidual = {
|
|
2054
|
-
roleCandidate: outcome.roleCandidate,
|
|
2055
|
-
audit: outcome.audit,
|
|
2056
|
-
acceptedReceipt: false,
|
|
2057
|
-
};
|
|
2058
|
-
return {
|
|
2059
|
-
roleOutcome: {
|
|
2060
|
-
kind: "failure",
|
|
2061
|
-
role: admitted.role,
|
|
2062
|
-
cause: "unrecognized",
|
|
2063
|
-
diagnostic,
|
|
2064
|
-
decisiveFacts,
|
|
2065
|
-
auditResidual,
|
|
2066
|
-
},
|
|
2067
|
-
navigator: extractNavigatorFact(entries),
|
|
2068
|
-
artifacts: [],
|
|
2069
|
-
runId: admitted.runId,
|
|
2070
|
-
};
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
/**
|
|
2074
|
-
* Settle the shared audit-incomplete Terminal for Judge/Fixer/Reviewer/Doctor.
|
|
2075
|
-
* Callers invoke this only after their ordinary lawful extractor found no result,
|
|
2076
|
-
* which preserves the no-other-lawful-result invariant without a second validator.
|
|
2077
|
-
*/
|
|
2078
|
-
export async function trySettleComplianceAuditIncompleteTerminalResult(
|
|
2079
|
-
admitted: AdmittedRoleInvocation,
|
|
2080
|
-
): Promise<TerminalResult | undefined> {
|
|
2081
|
-
if (!(AUDITOR_SOUL_ROLES as readonly string[]).includes(admitted.role)) {
|
|
2082
|
-
return undefined;
|
|
2083
|
-
}
|
|
2084
|
-
const outputToolName =
|
|
2085
|
-
admitted.role === "judge"
|
|
2086
|
-
? JUDGE_OUTPUT_TOOL_NAME
|
|
2087
|
-
: admitted.role === "reviewer"
|
|
2088
|
-
? REVIEWER_OUTPUT_TOOL_NAME
|
|
2089
|
-
: DOCTOR_OUTPUT_TOOL_NAME;
|
|
2090
|
-
const entries = await readLawfulSettlementEntries(admitted);
|
|
2091
|
-
if (entries === undefined) return undefined;
|
|
2092
|
-
const extracted = extractComplianceAuditIncompleteRoleOutcome(
|
|
2093
|
-
entries,
|
|
2094
|
-
admitted.role as (typeof AUDITOR_SOUL_ROLES)[number],
|
|
2095
|
-
outputToolName,
|
|
2096
|
-
);
|
|
2097
|
-
if (extracted === undefined) return undefined;
|
|
2098
|
-
// Catch covers publication only — menxia read errors keep their session/JSONL identity
|
|
2099
|
-
// and must not be washed into the publication-failure label.
|
|
2100
|
-
let evidence: TerminalArtifactRef;
|
|
2101
|
-
try {
|
|
2102
|
-
evidence = await publishComplianceAuditIncompleteEvidence(
|
|
2103
|
-
admitted,
|
|
2104
|
-
extracted.outcome,
|
|
2105
|
-
);
|
|
2106
|
-
} catch (error) {
|
|
2107
|
-
// Publication failure is a non-lawful terminal, never an accepted audit result.
|
|
2108
|
-
return auditPublicationFailureTerminal(admitted, entries, extracted.outcome, error);
|
|
2109
|
-
}
|
|
2110
|
-
return withOptionalMenxiaProjection(
|
|
2111
|
-
{
|
|
2112
|
-
roleOutcome: extracted.outcome,
|
|
2113
|
-
navigator: extractNavigatorFact(entries),
|
|
2114
|
-
artifacts: [evidence],
|
|
2115
|
-
runId: admitted.runId,
|
|
2116
|
-
},
|
|
2117
|
-
admitted.sessionDirectory,
|
|
2118
|
-
);
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
1855
|
/** Lawful Judge outcomes extracted from session (never a fabricated failure Receipt). */
|
|
2122
1856
|
export type LawfulJudgeRoleOutcome = Extract<
|
|
2123
1857
|
TerminalRoleOutcome,
|
|
@@ -2160,10 +1894,8 @@ export function extractJudgeRoleOutcome(
|
|
|
2160
1894
|
const statusRead = safelyRead(details, "judgeStatus");
|
|
2161
1895
|
if (!statusRead.readable || typeof statusRead.value !== "string") continue;
|
|
2162
1896
|
const judgeStatus = statusRead.value;
|
|
2163
|
-
const statusBase =
|
|
1897
|
+
const statusBase = judgeStatus;
|
|
2164
1898
|
if (statusBase !== "converged" && statusBase !== "continue" && statusBase !== "escalate") continue;
|
|
2165
|
-
// ADR 0071: `-by-fallback` without latch-shaped evidence is not a lawful terminal.
|
|
2166
|
-
if (!seatFallbackStatusHasLawfulEvidence(judgeStatus, details)) continue;
|
|
2167
1899
|
return {
|
|
2168
1900
|
kind: "accepted",
|
|
2169
1901
|
role: "judge",
|
|
@@ -2327,6 +2059,21 @@ async function withOptionalMenxiaProjection<
|
|
|
2327
2059
|
artifacts: readonly TerminalArtifactRef[];
|
|
2328
2060
|
},
|
|
2329
2061
|
>(base: T, sessionDirectory: string): Promise<T & { menxia?: TerminalMenxiaFact }> {
|
|
2062
|
+
// A gate transport failure is already represented by typed evidence and has no
|
|
2063
|
+
// accepted gate cycle to project. Re-reading that rejected receipt as an
|
|
2064
|
+
// accepted cycle would replace the original failure with a projection error.
|
|
2065
|
+
const secondaryEvidence = base.roleOutcome.kind === "failure"
|
|
2066
|
+
? base.roleOutcome.decisiveFacts.secondaryEvidence
|
|
2067
|
+
: undefined;
|
|
2068
|
+
if (
|
|
2069
|
+
isRecord(secondaryEvidence)
|
|
2070
|
+
&& secondaryEvidence.kind === "role_infrastructure_failure"
|
|
2071
|
+
&& (
|
|
2072
|
+
secondaryEvidence.stage === "gatekeeper"
|
|
2073
|
+
|| secondaryEvidence.stage === "inspector"
|
|
2074
|
+
|| secondaryEvidence.stage === "notary"
|
|
2075
|
+
)
|
|
2076
|
+
) return base;
|
|
2330
2077
|
const menxia = await extractMenxiaFactFromSessionDirectory(sessionDirectory);
|
|
2331
2078
|
return menxia === undefined ? base : { ...base, menxia };
|
|
2332
2079
|
}
|
|
@@ -3254,10 +3001,9 @@ async function settleLawfulDoctorTerminalResult(
|
|
|
3254
3001
|
const extracted = extractDoctorRoleOutcome(entries);
|
|
3255
3002
|
if (extracted === undefined) return undefined;
|
|
3256
3003
|
// Bind completed receipt case identity to the admitted Issue evidence case.
|
|
3257
|
-
// Seat-fallback may taint status; base semantics still require case binding.
|
|
3258
3004
|
if (
|
|
3259
3005
|
extracted.output !== undefined &&
|
|
3260
|
-
|
|
3006
|
+
(String(extracted.output.status)) === "completed"
|
|
3261
3007
|
) {
|
|
3262
3008
|
const completedCase = (
|
|
3263
3009
|
extracted.output as {
|
|
@@ -3314,7 +3060,7 @@ export async function trySettleDoctorTerminalResult(
|
|
|
3314
3060
|
return settleLawfulDoctorTerminalResult(admitted);
|
|
3315
3061
|
}
|
|
3316
3062
|
|
|
3317
|
-
/** Lawful Notary accepted outcome (pass/bounce
|
|
3063
|
+
/** Lawful Notary accepted outcome (pass/bounce). */
|
|
3318
3064
|
export type LawfulNotaryRoleOutcome = {
|
|
3319
3065
|
kind: "accepted";
|
|
3320
3066
|
role: "notary";
|
|
@@ -3356,6 +3102,7 @@ async function settleLawfulNotaryTerminalResult(
|
|
|
3356
3102
|
if (entries === undefined) return undefined;
|
|
3357
3103
|
const extracted = extractNotaryRoleOutcome(entries);
|
|
3358
3104
|
if (extracted === undefined) {
|
|
3105
|
+
// No usable Notary release → existing non-zero failure channel with candidate (#475).
|
|
3359
3106
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
3360
3107
|
const message = entries[index]?.message;
|
|
3361
3108
|
if (message?.role !== "toolResult") continue;
|
|
@@ -3366,16 +3113,11 @@ async function settleLawfulNotaryTerminalResult(
|
|
|
3366
3113
|
NOTARY_OUTPUT_TOOL_NAME,
|
|
3367
3114
|
);
|
|
3368
3115
|
if (residual === undefined) continue;
|
|
3369
|
-
return {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
}),
|
|
3375
|
-
navigator: extractNavigatorFact(entries),
|
|
3376
|
-
artifacts: [],
|
|
3377
|
-
runId: admitted.runId,
|
|
3378
|
-
};
|
|
3116
|
+
return settleFailureTerminalResult(admitted, {
|
|
3117
|
+
cause: "output",
|
|
3118
|
+
diagnostic: residual.diagnostic,
|
|
3119
|
+
details: { candidate: residual.candidate, acceptedReceipt: false },
|
|
3120
|
+
});
|
|
3379
3121
|
}
|
|
3380
3122
|
return undefined;
|
|
3381
3123
|
}
|
|
@@ -3712,7 +3454,7 @@ function mergerDecisiveFacts(output: MergerOutput): Record<string, unknown> {
|
|
|
3712
3454
|
if (attemptId.readable && attemptId.value !== undefined) facts.attemptId = attemptId.value;
|
|
3713
3455
|
const statusBase =
|
|
3714
3456
|
status.readable && typeof status.value === "string"
|
|
3715
|
-
?
|
|
3457
|
+
? (status.value)
|
|
3716
3458
|
: undefined;
|
|
3717
3459
|
const decisiveKey = statusBase === "completed" ? "mergeCommitId" : "diagnosis";
|
|
3718
3460
|
const decisive = safelyRead(candidate, decisiveKey);
|