@akagilnc/pi-workflow-roles 0.1.2383 → 0.1.2397
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/audit-escalation.js +0 -13
- package/dist/compliance-transport.js +28 -5
- package/dist/dossier-resolution.js +21 -2
- package/dist/evidence-child-executor.js +1 -1
- package/dist/navigator-invocation-identity.js +16 -4
- package/dist/notary-contracts.js +4 -17
- package/dist/package-contracts/terminating-tools.js +1 -1
- package/dist/public-cli/main.js +101 -374
- package/package.json +1 -1
- package/src/analyst-gate-cycles-read.ts +5 -8
- package/src/audit-escalation.ts +0 -25
- package/src/compliance-transport.ts +38 -6
- package/src/doctor-auditor.ts +3 -3
- package/src/doctor-role.ts +1 -1
- package/src/dossier-resolution.ts +25 -4
- package/src/evidence-child-executor.ts +1 -1
- package/src/gatekeeper-role.ts +33 -43
- package/src/judge-auditor.ts +3 -3
- package/src/judge-role.ts +0 -1
- package/src/navigator-invocation-identity.ts +34 -13
- package/src/notary-contracts.ts +5 -19
- package/src/notary-role.ts +2 -2
- package/src/package-contracts/terminating-tools.ts +1 -1
- 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 +102 -348
- package/src/public-cli/terminal.ts +3 -71
- package/src/reviewer-auditor.ts +3 -3
- package/src/reviewer-role.ts +0 -1
- package/src/role-runtime.ts +42 -9
|
@@ -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 {
|
|
@@ -118,10 +116,8 @@ import {
|
|
|
118
116
|
formatTerminalResult,
|
|
119
117
|
isLawfulTypedTerminalOutcome,
|
|
120
118
|
recommendationNavigatorFact,
|
|
121
|
-
buildAuditIncompleteTerminalOutcome,
|
|
122
119
|
buildResidualIncompleteTerminalOutcome,
|
|
123
120
|
redactExactRunId,
|
|
124
|
-
type AuditIncompleteResidual,
|
|
125
121
|
type ControlledFailureCause,
|
|
126
122
|
type TerminalArtifactRef,
|
|
127
123
|
type TerminalMenxiaFact,
|
|
@@ -728,10 +724,16 @@ export async function readBoundEvidenceChildKnownFailure(
|
|
|
728
724
|
return undefined;
|
|
729
725
|
}
|
|
730
726
|
|
|
731
|
-
|
|
732
|
-
|
|
727
|
+
type BoundAuditorVolume = {
|
|
728
|
+
readonly entries: SessionEntry[];
|
|
729
|
+
readonly attemptEntryId?: string;
|
|
730
|
+
readonly parentId: string;
|
|
731
|
+
readonly sessionFile: string;
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
async function loadBoundAuditorVolumes(
|
|
733
735
|
sessionFile: string,
|
|
734
|
-
): Promise<
|
|
736
|
+
): Promise<readonly BoundAuditorVolume[] | undefined> {
|
|
735
737
|
let parentEntries: SessionEntry[];
|
|
736
738
|
try {
|
|
737
739
|
parentEntries = await readBoundSessionEntries(sessionFile);
|
|
@@ -773,7 +775,7 @@ export async function readBoundAuditorKnownFailure(
|
|
|
773
775
|
// first attempt's child after resume advanced latest, losing retentionFailure
|
|
774
776
|
// when retry had no compliance entry. Fix: ignore envelope for staleness and
|
|
775
777
|
// prefer any valid compliance failure before falling back to primary.
|
|
776
|
-
const
|
|
778
|
+
const valid: BoundAuditorVolume[] = [];
|
|
777
779
|
for (const file of names.filter((name) => name.endsWith(".jsonl")).sort().reverse()) {
|
|
778
780
|
let entries: SessionEntry[];
|
|
779
781
|
try {
|
|
@@ -788,10 +790,20 @@ export async function readBoundAuditorKnownFailure(
|
|
|
788
790
|
const attemptEntryId = typeof bindingParent?.attemptEntryId === "string" ? bindingParent.attemptEntryId : undefined;
|
|
789
791
|
const attemptEntryIndex = attemptEntryId === undefined ? -1 : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
|
|
790
792
|
if (bindingParent?.sessionId !== parentId || bindingParent.sessionFile !== sessionFile || attemptEntryIndex < latestParentUserIndex) continue;
|
|
791
|
-
|
|
793
|
+
valid.push({
|
|
794
|
+
entries,
|
|
795
|
+
parentId,
|
|
796
|
+
sessionFile,
|
|
797
|
+
...(attemptEntryId === undefined ? {} : { attemptEntryId }),
|
|
798
|
+
});
|
|
792
799
|
}
|
|
793
|
-
|
|
794
|
-
|
|
800
|
+
return valid;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function complianceFailureFromAuditorVolumes(
|
|
804
|
+
volumes: readonly BoundAuditorVolume[],
|
|
805
|
+
): ExplicitInternalKnownFailure | undefined {
|
|
806
|
+
for (const { entries, attemptEntryId, parentId, sessionFile } of volumes) {
|
|
795
807
|
const stop = extractSessionProviderStop(entries);
|
|
796
808
|
if (stop === undefined) continue;
|
|
797
809
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
@@ -812,8 +824,13 @@ export async function readBoundAuditorKnownFailure(
|
|
|
812
824
|
};
|
|
813
825
|
}
|
|
814
826
|
}
|
|
815
|
-
|
|
816
|
-
|
|
827
|
+
return undefined;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function providerStopFallbackFromAuditorVolumes(
|
|
831
|
+
volumes: readonly BoundAuditorVolume[],
|
|
832
|
+
): ExplicitInternalKnownFailure | undefined {
|
|
833
|
+
for (const { entries } of volumes) {
|
|
817
834
|
const stop = extractSessionProviderStop(entries);
|
|
818
835
|
if (stop === undefined) continue;
|
|
819
836
|
const primary = knownFailureFromProviderStop(stop)!;
|
|
@@ -828,6 +845,34 @@ export async function readBoundAuditorKnownFailure(
|
|
|
828
845
|
return undefined;
|
|
829
846
|
}
|
|
830
847
|
|
|
848
|
+
/** Recover a provider stop from the auditor child bound to the current parent attempt. */
|
|
849
|
+
export async function readBoundAuditorKnownFailure(
|
|
850
|
+
sessionFile: string,
|
|
851
|
+
): Promise<ExplicitInternalKnownFailure | undefined> {
|
|
852
|
+
const volumes = await loadBoundAuditorVolumes(sessionFile);
|
|
853
|
+
if (volumes === undefined) return undefined;
|
|
854
|
+
return complianceFailureFromAuditorVolumes(volumes)
|
|
855
|
+
?? providerStopFallbackFromAuditorVolumes(volumes);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** Strong auditor tier only — retained compliance-failure entries, no provider-stop fallback. */
|
|
859
|
+
async function readBoundAuditorComplianceFailure(
|
|
860
|
+
sessionFile: string,
|
|
861
|
+
): Promise<ExplicitInternalKnownFailure | undefined> {
|
|
862
|
+
const volumes = await loadBoundAuditorVolumes(sessionFile);
|
|
863
|
+
if (volumes === undefined) return undefined;
|
|
864
|
+
return complianceFailureFromAuditorVolumes(volumes);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/** Weaker auditor tier: provider stop without a retained compliance-failure entry. */
|
|
868
|
+
async function readBoundAuditorProviderStopFallback(
|
|
869
|
+
sessionFile: string,
|
|
870
|
+
): Promise<ExplicitInternalKnownFailure | undefined> {
|
|
871
|
+
const volumes = await loadBoundAuditorVolumes(sessionFile);
|
|
872
|
+
if (volumes === undefined) return undefined;
|
|
873
|
+
return providerStopFallbackFromAuditorVolumes(volumes);
|
|
874
|
+
}
|
|
875
|
+
|
|
831
876
|
function typedFailedTerminatingToolKnownFailure(
|
|
832
877
|
entries: readonly SessionEntry[],
|
|
833
878
|
): ExplicitInternalKnownFailure | undefined {
|
|
@@ -850,11 +895,14 @@ function typedFailedTerminatingToolKnownFailure(
|
|
|
850
895
|
? message.content.find((part) => isRecord(part) && part.type === "text" && typeof part.text === "string")
|
|
851
896
|
: undefined;
|
|
852
897
|
const diagnostic = isRecord(textPart) ? textPart.text : undefined;
|
|
898
|
+
// Durable details already carry fact + typed evidence from envelope one-shot projection (#475).
|
|
899
|
+
// Do not re-parse retained compliance responses here.
|
|
900
|
+
const details = isRecord(message.details) ? message.details : classification.fact;
|
|
853
901
|
return {
|
|
854
902
|
cause: "output",
|
|
855
903
|
identity: { name: message.toolName, code: message.toolCallId },
|
|
856
904
|
...(typeof diagnostic === "string" && diagnostic.trim() !== "" ? { diagnostic } : {}),
|
|
857
|
-
details
|
|
905
|
+
details,
|
|
858
906
|
};
|
|
859
907
|
}
|
|
860
908
|
return undefined;
|
|
@@ -913,12 +961,13 @@ export async function resolveAuditedRunnerFailureResolution(input: {
|
|
|
913
961
|
});
|
|
914
962
|
}
|
|
915
963
|
}
|
|
916
|
-
// Bound auditor
|
|
917
|
-
// caused (retention EISDIR race). A typed terminating-tool
|
|
918
|
-
//
|
|
964
|
+
// Bound auditor compliance-failure retention outranks a parent failure that the
|
|
965
|
+
// auditor path itself caused (retention EISDIR race). A typed terminating-tool
|
|
966
|
+
// host failure is next — it outranks weaker auditor provider-stop fallback so
|
|
967
|
+
// parent failInfrastructure abort pollution cannot wash a real diagnostic (#475).
|
|
919
968
|
try {
|
|
920
|
-
const
|
|
921
|
-
if (
|
|
969
|
+
const auditorCompliance = await readBoundAuditorComplianceFailure(input.sessionFile);
|
|
970
|
+
if (auditorCompliance !== undefined) return resolutionOf(auditorCompliance);
|
|
922
971
|
} catch (error) {
|
|
923
972
|
const failure = sessionReadFailure(error, "failed to recover bound auditor failure");
|
|
924
973
|
return resolutionOf({
|
|
@@ -942,6 +991,17 @@ export async function resolveAuditedRunnerFailureResolution(input: {
|
|
|
942
991
|
});
|
|
943
992
|
}
|
|
944
993
|
}
|
|
994
|
+
try {
|
|
995
|
+
const auditorStop = await readBoundAuditorProviderStopFallback(input.sessionFile);
|
|
996
|
+
if (auditorStop !== undefined) return resolutionOf(auditorStop);
|
|
997
|
+
} catch (error) {
|
|
998
|
+
const failure = sessionReadFailure(error, "failed to recover bound auditor provider stop");
|
|
999
|
+
return resolutionOf({
|
|
1000
|
+
cause: "session",
|
|
1001
|
+
identity: thrownIdentity(failure),
|
|
1002
|
+
diagnostic: failure.message || failure.name,
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
945
1005
|
// Reviewer axis evidence-children are next: fixed two-axis dispatch fails
|
|
946
1006
|
// during activation with only child stops durable. Parent stop remains the
|
|
947
1007
|
// fallback; credential is last.
|
|
@@ -1550,35 +1610,6 @@ export function assertCollectorReceiptMatchesAdmitted(
|
|
|
1550
1610
|
}
|
|
1551
1611
|
}
|
|
1552
1612
|
|
|
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
1613
|
function auditToolNameForRole(
|
|
1583
1614
|
role: (typeof AUDITOR_SOUL_ROLES)[number],
|
|
1584
1615
|
): string {
|
|
@@ -1592,19 +1623,6 @@ function auditToolNameForRole(
|
|
|
1592
1623
|
}
|
|
1593
1624
|
}
|
|
1594
1625
|
|
|
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
1626
|
type BoundRoleToolCall = {
|
|
1609
1627
|
callIndex: number;
|
|
1610
1628
|
candidate: unknown;
|
|
@@ -1757,13 +1775,6 @@ function isUnboundAuditEscalationFace(details: unknown): boolean {
|
|
|
1757
1775
|
return kind.readable && kind.value === "audit_escalation";
|
|
1758
1776
|
}
|
|
1759
1777
|
|
|
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
1778
|
function boundRetainedAuditResponse(
|
|
1768
1779
|
entries: readonly SessionEntry[],
|
|
1769
1780
|
callIndex: number,
|
|
@@ -1791,113 +1802,6 @@ function boundRetainedAuditResponse(
|
|
|
1791
1802
|
return matches.length === 1 ? matches[0] : undefined;
|
|
1792
1803
|
}
|
|
1793
1804
|
|
|
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
1805
|
/**
|
|
1902
1806
|
* #419 per-attempt process history. 史必追加,指针可覆盖;指针可以覆盖的前提是史已落。
|
|
1903
1807
|
* Reuses the run session principal's append-only JSONL custom-entry shape
|
|
@@ -1957,167 +1861,6 @@ export async function appendRunAttemptHistory(
|
|
|
1957
1861
|
await appendFile(admitted.sessionFile, line, "utf8");
|
|
1958
1862
|
}
|
|
1959
1863
|
|
|
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
1864
|
/** Lawful Judge outcomes extracted from session (never a fabricated failure Receipt). */
|
|
2122
1865
|
export type LawfulJudgeRoleOutcome = Extract<
|
|
2123
1866
|
TerminalRoleOutcome,
|
|
@@ -2327,6 +2070,21 @@ async function withOptionalMenxiaProjection<
|
|
|
2327
2070
|
artifacts: readonly TerminalArtifactRef[];
|
|
2328
2071
|
},
|
|
2329
2072
|
>(base: T, sessionDirectory: string): Promise<T & { menxia?: TerminalMenxiaFact }> {
|
|
2073
|
+
// A gate transport failure is already represented by typed evidence and has no
|
|
2074
|
+
// accepted gate cycle to project. Re-reading that rejected receipt as an
|
|
2075
|
+
// accepted cycle would replace the original failure with a projection error.
|
|
2076
|
+
const secondaryEvidence = base.roleOutcome.kind === "failure"
|
|
2077
|
+
? base.roleOutcome.decisiveFacts.secondaryEvidence
|
|
2078
|
+
: undefined;
|
|
2079
|
+
if (
|
|
2080
|
+
isRecord(secondaryEvidence)
|
|
2081
|
+
&& secondaryEvidence.kind === "role_infrastructure_failure"
|
|
2082
|
+
&& (
|
|
2083
|
+
secondaryEvidence.stage === "gatekeeper"
|
|
2084
|
+
|| secondaryEvidence.stage === "inspector"
|
|
2085
|
+
|| secondaryEvidence.stage === "notary"
|
|
2086
|
+
)
|
|
2087
|
+
) return base;
|
|
2330
2088
|
const menxia = await extractMenxiaFactFromSessionDirectory(sessionDirectory);
|
|
2331
2089
|
return menxia === undefined ? base : { ...base, menxia };
|
|
2332
2090
|
}
|
|
@@ -3314,7 +3072,7 @@ export async function trySettleDoctorTerminalResult(
|
|
|
3314
3072
|
return settleLawfulDoctorTerminalResult(admitted);
|
|
3315
3073
|
}
|
|
3316
3074
|
|
|
3317
|
-
/** Lawful Notary accepted outcome (pass/bounce
|
|
3075
|
+
/** Lawful Notary accepted outcome (pass/bounce). */
|
|
3318
3076
|
export type LawfulNotaryRoleOutcome = {
|
|
3319
3077
|
kind: "accepted";
|
|
3320
3078
|
role: "notary";
|
|
@@ -3356,6 +3114,7 @@ async function settleLawfulNotaryTerminalResult(
|
|
|
3356
3114
|
if (entries === undefined) return undefined;
|
|
3357
3115
|
const extracted = extractNotaryRoleOutcome(entries);
|
|
3358
3116
|
if (extracted === undefined) {
|
|
3117
|
+
// No usable Notary release → existing non-zero failure channel with candidate (#475).
|
|
3359
3118
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
3360
3119
|
const message = entries[index]?.message;
|
|
3361
3120
|
if (message?.role !== "toolResult") continue;
|
|
@@ -3366,16 +3125,11 @@ async function settleLawfulNotaryTerminalResult(
|
|
|
3366
3125
|
NOTARY_OUTPUT_TOOL_NAME,
|
|
3367
3126
|
);
|
|
3368
3127
|
if (residual === undefined) continue;
|
|
3369
|
-
return {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
}),
|
|
3375
|
-
navigator: extractNavigatorFact(entries),
|
|
3376
|
-
artifacts: [],
|
|
3377
|
-
runId: admitted.runId,
|
|
3378
|
-
};
|
|
3128
|
+
return settleFailureTerminalResult(admitted, {
|
|
3129
|
+
cause: "output",
|
|
3130
|
+
diagnostic: residual.diagnostic,
|
|
3131
|
+
details: { candidate: residual.candidate, acceptedReceipt: false },
|
|
3132
|
+
});
|
|
3379
3133
|
}
|
|
3380
3134
|
return undefined;
|
|
3381
3135
|
}
|