@akagilnc/pi-workflow-roles 0.1.5483 → 0.1.5571
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/acp-host/production-host.js +6312 -6376
- package/dist/analyst-gate-cycles-read.js +28 -78
- package/dist/analyst-ledger.js +26 -42
- package/dist/analyst-metric-families/acceptance-success-rework.js +25 -1
- package/dist/analyst-metric-families/round-timeline.js +2 -5
- package/dist/archivist-record-entry.js +4 -3
- package/dist/archivist-record-pointer.js +71 -36
- package/dist/atomic-write.js +2 -2
- package/dist/auditor-dossier-tool.js +11 -6
- package/dist/auditor-soul.js +5 -12
- package/dist/collector-config.js +6 -5
- package/dist/doctor-evidence.js +8 -4
- package/dist/engine-detour-usage.js +8 -22
- package/dist/headless-host/production-host.js +5969 -6011
- package/dist/host-descriptions.js +0 -4
- package/dist/host-session-record.js +266 -267
- package/dist/ledger-session-read.js +5 -0
- package/dist/migrate-book-topology.js +1369 -722
- package/dist/navigator-public-session.js +13 -17
- package/dist/notary-source-run.js +8 -16
- package/dist/packaged-role-registry.js +5 -56
- package/dist/pi/durable-principal.js +2 -3
- package/dist/pi/role-turn-host.js +79 -15
- package/dist/public-cli/auto-resume.js +8 -47
- package/dist/public-cli/instruction-seat-run.js +9 -7
- package/dist/public-cli/invocation.js +67 -100
- package/dist/public-cli/main.js +2689 -2532
- package/dist/public-cli/post-admission.js +96 -55
- package/dist/public-cli/run-lifecycle.js +44 -100
- package/dist/public-cli/settlement.js +184 -429
- package/dist/public-cli/terminal.js +0 -12
- package/dist/role-run-placement.js +53 -16
- package/dist/role-run-relocation.js +111 -52
- package/dist/run-dossier-files.js +30 -0
- package/dist/run-dossier.js +322 -0
- package/dist/run-terminal-artifacts.js +69 -202
- package/dist/run-ticket-number.js +49 -7
- package/dist/session-assistant-usage.js +9 -18
- package/dist/session-identity.js +49 -19
- package/dist/sitian-appender.js +29 -18
- package/dist/sitian-facade.js +11 -0
- package/dist/sitian-reader.js +44 -29
- package/dist/submission-gate.js +1 -1
- package/dist/submission-ledger.js +4 -4
- package/dist/ticket-provenance-contracts.js +2 -8
- package/dist/ticket-provenance.js +25 -9
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -0
- package/src/acp-host/production-host.ts +1 -1
- package/src/acp-host/role-turn-host.ts +11 -4
- package/src/analyst-gate-cycles-read.ts +31 -88
- package/src/analyst-ledger.ts +36 -51
- package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
- package/src/analyst-metric-families/gate-cycles.ts +1 -1
- package/src/analyst-metric-families/round-timeline.ts +2 -5
- package/src/analyst-page.ts +3 -3
- package/src/archivist-record-entry.ts +6 -5
- package/src/archivist-record-pointer.ts +92 -42
- package/src/atomic-write.ts +3 -1
- package/src/auditor-dossier-tool.ts +14 -8
- package/src/auditor-soul.ts +5 -10
- package/src/book-topology-migration-placement.ts +16 -3
- package/src/book-topology-record-class-migrators.ts +4 -2
- package/src/book-topology-runs-migrator.ts +6 -3
- package/src/collector-config.ts +5 -3
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-evidence.ts +7 -3
- package/src/engine-detour-tool.ts +1 -1
- package/src/engine-detour-usage.ts +12 -25
- package/src/external-host-turn-loop.ts +25 -0
- package/src/factory-board.ts +16 -81
- package/src/headless-host/production-host.ts +1 -4
- package/src/headless-host/role-turn-host.ts +56 -21
- package/src/host-descriptions.ts +0 -6
- package/src/host-session-record.ts +224 -140
- package/src/ledger-session-read.ts +6 -0
- package/src/navigator-public-session.ts +13 -18
- package/src/navigator-work-context.ts +3 -1
- package/src/notary-role.ts +2 -1
- package/src/notary-source-run.ts +8 -18
- package/src/packaged-role-registry.ts +1 -65
- package/src/pi/adapter.ts +50 -7
- package/src/pi/durable-principal.ts +2 -4
- package/src/pi/role-turn-host.ts +85 -14
- package/src/public-cli/auto-resume.ts +7 -43
- package/src/public-cli/instruction-seat-run.ts +13 -6
- package/src/public-cli/invocation.ts +90 -144
- package/src/public-cli/post-admission.ts +111 -72
- package/src/public-cli/run-lifecycle.ts +47 -115
- package/src/public-cli/settlement.ts +221 -589
- package/src/public-cli/terminal.ts +1 -61
- package/src/role-envelope.ts +4 -2
- package/src/role-run-placement.ts +65 -17
- package/src/role-run-relocation.ts +110 -70
- package/src/role-runtime.ts +46 -69
- package/src/run-dossier-files.ts +33 -0
- package/src/run-dossier.ts +357 -0
- package/src/run-terminal-artifacts.ts +84 -240
- package/src/run-ticket-number.ts +82 -14
- package/src/session-assistant-usage.ts +8 -20
- package/src/session-identity.ts +52 -21
- package/src/sitian-appender.ts +33 -26
- package/src/sitian-facade.ts +22 -0
- package/src/sitian-reader.ts +62 -32
- package/src/submission-gate.ts +1 -1
- package/src/submission-ledger.ts +4 -4
- package/src/ticket-provenance-contracts.ts +3 -6
- package/src/ticket-provenance.ts +23 -11
- package/src/ticket-snapshot.ts +3 -2
- package/src/ticket-trajectory.ts +95 -109
|
@@ -7,12 +7,14 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { lstat, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { sessionFileOf } from "../role-run-placement.ts";
|
|
11
|
+
|
|
12
|
+
import { sitianReport, type RecordPointer } from "../sitian-facade.ts";
|
|
11
13
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
ATTEMPT_HISTORY_IDENTITY_FIELD,
|
|
15
|
+
TERMINAL_COURT_ATTEMPT_FIELD,
|
|
16
|
+
writeRunTerminal,
|
|
17
|
+
} from "../run-terminal-artifacts.ts";
|
|
16
18
|
|
|
17
19
|
import {
|
|
18
20
|
readAttemptScopedSubmissionRows,
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
processCancelDiagnostic,
|
|
27
29
|
type CatchableProcessSignal,
|
|
28
30
|
} from "./process-cancel.ts";
|
|
31
|
+
import { readStrictPiSessionJsonl } from "../ledger-session-read.ts";
|
|
29
32
|
import { serializeThrownValue } from "../serialize-thrown-value.ts";
|
|
30
33
|
import { describeErrorIdentity } from "./run-lifecycle.ts";
|
|
31
34
|
import {
|
|
@@ -39,7 +42,6 @@ import {
|
|
|
39
42
|
import type { DoctorCaseCost } from "../doctor-contracts.ts";
|
|
40
43
|
import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "../dossier-resolution.ts";
|
|
41
44
|
import {
|
|
42
|
-
ensureRunArtifactsDir,
|
|
43
45
|
homeFromRunDirectory,
|
|
44
46
|
type AdmittedRoleInvocation,
|
|
45
47
|
} from "./invocation.ts";
|
|
@@ -55,9 +57,7 @@ import {
|
|
|
55
57
|
packagedDurableOfficerEntry,
|
|
56
58
|
packagedRoleAcceptedOutputTool,
|
|
57
59
|
packagedRoleMetadata,
|
|
58
|
-
packagedSkipsGateOnInfrastructureStage,
|
|
59
60
|
type PackagedArtifactFace,
|
|
60
|
-
type PackagedArtifactLeaf,
|
|
61
61
|
} from "../packaged-role-registry.ts";
|
|
62
62
|
import {
|
|
63
63
|
NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
|
|
@@ -72,18 +72,8 @@ import type {
|
|
|
72
72
|
DurablePrincipalAuthority,
|
|
73
73
|
DurablePrincipalCoordinates,
|
|
74
74
|
} from "../host-contracts.ts";
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
77
|
-
listSeamOwnedUniqueErrorFacePaths,
|
|
78
|
-
uniqueErrorFallbackName,
|
|
79
|
-
UNIQUE_ERROR_FALLBACK_STEM,
|
|
80
|
-
RUN_TERMINAL_ARTIFACT_FILES,
|
|
81
|
-
RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS,
|
|
82
|
-
RUN_TERMINAL_REPORT_FILE,
|
|
83
|
-
RUN_TERMINAL_ERROR_FILE,
|
|
84
|
-
RUN_TERMINAL_EVIDENCE_FILE,
|
|
85
|
-
RUN_TERMINAL_ERROR_SETTLEMENT_FILE,
|
|
86
|
-
} from "../run-terminal-artifacts.ts";
|
|
75
|
+
import { readStateRowsSync, runCurrentPath } from "../run-dossier.ts";
|
|
76
|
+
import { reportRunRecord } from "../sitian-facade.ts";
|
|
87
77
|
|
|
88
78
|
/** Ledger reads use the run's machine home — not ambient process HOME (child write vs parent settle). */
|
|
89
79
|
function sealedLedgerHome(admitted: Pick<AdmittedRoleInvocation, "runDirectory">): string {
|
|
@@ -109,32 +99,6 @@ export type SettlementCourtScope = {
|
|
|
109
99
|
readonly notePackageFault?: (diagnostic: string) => void | Promise<void>;
|
|
110
100
|
};
|
|
111
101
|
|
|
112
|
-
/**
|
|
113
|
-
* Hardened artifacts directory. A planted symlink at the run directory or the
|
|
114
|
-
* artifacts path must not receive a durable run artifact.
|
|
115
|
-
*/
|
|
116
|
-
export async function ensureRealArtifactsDirectory(runDirectory: string): Promise<string> {
|
|
117
|
-
const runStat = await lstat(runDirectory);
|
|
118
|
-
if (runStat.isSymbolicLink() || !runStat.isDirectory()) {
|
|
119
|
-
throw new Error("run artifact retention: run directory is not a real directory");
|
|
120
|
-
}
|
|
121
|
-
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
122
|
-
try {
|
|
123
|
-
const existing = await lstat(artifactsDir);
|
|
124
|
-
if (existing.isSymbolicLink() || !existing.isDirectory()) {
|
|
125
|
-
throw new Error("run artifact retention: artifacts path is not a real directory");
|
|
126
|
-
}
|
|
127
|
-
} catch (error) {
|
|
128
|
-
if (!isEnoent(error)) throw error;
|
|
129
|
-
await mkdir(artifactsDir, { recursive: true });
|
|
130
|
-
const created = await lstat(artifactsDir);
|
|
131
|
-
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
132
|
-
throw new Error("run artifact retention: artifacts directory is not a real directory");
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return artifactsDir;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
102
|
/**
|
|
139
103
|
* One retention path for a package fault beside an already chosen terminal.
|
|
140
104
|
* Session append is first when the caller has that channel. The artifact file
|
|
@@ -158,13 +122,14 @@ export async function retainPackageFault(input: {
|
|
|
158
122
|
...(Object.hasOwn(input, "error") ? { failure: projectThrownFailureLeaf(input.error) } : {}),
|
|
159
123
|
};
|
|
160
124
|
let retentionFailure: string | undefined;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
125
|
+
// The run's log is the fallback after the session append throws, and the only
|
|
126
|
+
// channel when the caller has none.
|
|
127
|
+
const writeLog = (appendFailure?: ControlledFailure): void => {
|
|
128
|
+
reportRunRecord(input.runDirectory, "post-admission-diagnostic", {
|
|
164
129
|
version: 1,
|
|
165
130
|
...payload,
|
|
166
131
|
...(appendFailure === undefined ? {} : { retentionFailure: appendFailure }),
|
|
167
|
-
});
|
|
132
|
+
}, "settlement");
|
|
168
133
|
};
|
|
169
134
|
if (input.appendSession !== undefined) {
|
|
170
135
|
try {
|
|
@@ -173,18 +138,18 @@ export async function retainPackageFault(input: {
|
|
|
173
138
|
retentionFailure =
|
|
174
139
|
`post-dispatch diagnostic session append failed (best-effort continue): dossier=${describeErrorIdentity(appendError)}`;
|
|
175
140
|
try {
|
|
176
|
-
|
|
177
|
-
} catch (
|
|
141
|
+
writeLog(projectThrownFailureLeaf(appendError));
|
|
142
|
+
} catch (logError) {
|
|
178
143
|
retentionFailure =
|
|
179
|
-
`post-dispatch diagnostic durable retention failed on both channels (best-effort continue): dossier=${describeErrorIdentity(appendError)};
|
|
144
|
+
`post-dispatch diagnostic durable retention failed on both channels (best-effort continue): dossier=${describeErrorIdentity(appendError)}; log=${describeErrorIdentity(logError)}`;
|
|
180
145
|
}
|
|
181
146
|
}
|
|
182
147
|
} else {
|
|
183
148
|
try {
|
|
184
|
-
|
|
185
|
-
} catch (
|
|
149
|
+
writeLog();
|
|
150
|
+
} catch (logError) {
|
|
186
151
|
retentionFailure =
|
|
187
|
-
`post-dispatch diagnostic durable retention failed (best-effort continue):
|
|
152
|
+
`post-dispatch diagnostic durable retention failed (best-effort continue): log=${describeErrorIdentity(logError)}`;
|
|
188
153
|
}
|
|
189
154
|
}
|
|
190
155
|
const stderr = input.stderr ?? ((text: string) => {
|
|
@@ -217,7 +182,7 @@ export function ledgerReadScope(
|
|
|
217
182
|
): { home: string; sessionParent: string; attemptId?: string } {
|
|
218
183
|
return {
|
|
219
184
|
home: sealedLedgerHome(admitted),
|
|
220
|
-
sessionParent:
|
|
185
|
+
sessionParent: sessionFileOf(admitted.runDirectory),
|
|
221
186
|
...(scope?.courtAttemptId === undefined || scope.courtAttemptId.length === 0
|
|
222
187
|
? {}
|
|
223
188
|
: { attemptId: scope.courtAttemptId }),
|
|
@@ -462,14 +427,30 @@ async function settleNoReceiptTerminal(
|
|
|
462
427
|
kind: "no_receipt", role: admitted.role, status: "no-accepted-receipt",
|
|
463
428
|
...facts, decisiveFacts: facts,
|
|
464
429
|
};
|
|
430
|
+
let attemptHistoryIdentity: string | undefined;
|
|
465
431
|
if (scope?.recordAttemptHistory === true) {
|
|
466
|
-
await appendRunAttemptHistory(
|
|
467
|
-
|
|
432
|
+
const pointer = await appendRunAttemptHistory(
|
|
433
|
+
attemptHistorySource(admitted, coordinates),
|
|
468
434
|
roleOutcome,
|
|
469
435
|
);
|
|
436
|
+
attemptHistoryIdentity = pointer.identity;
|
|
437
|
+
} else {
|
|
438
|
+
attemptHistoryIdentity = await belongingAttemptHistoryIdentity(
|
|
439
|
+
admitted.runDirectory,
|
|
440
|
+
scope,
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
if (scope?.previewOnly !== true) {
|
|
444
|
+
// Belonging settlement only (#1161 甲 / R2): never inherit another court's
|
|
445
|
+
// latest terminal pointer, and never invent a history row on re-projection (N1).
|
|
446
|
+
writeRunTerminal(admitted.runDirectory, "no_receipt", {
|
|
447
|
+
role: admitted.role,
|
|
448
|
+
runId: admitted.runId,
|
|
449
|
+
outcome: roleOutcome,
|
|
450
|
+
...terminalBelongingFields(attemptHistoryIdentity, scope),
|
|
451
|
+
});
|
|
470
452
|
}
|
|
471
|
-
|
|
472
|
-
return withOptionalGateProjection({
|
|
453
|
+
return attachEngineDetourToolUsage({
|
|
473
454
|
roleOutcome,
|
|
474
455
|
navigator: await extractNavigatorFactFromAdmittedSession(
|
|
475
456
|
coordinates.sessionFile,
|
|
@@ -478,7 +459,7 @@ async function settleNoReceiptTerminal(
|
|
|
478
459
|
),
|
|
479
460
|
artifacts: [],
|
|
480
461
|
runId: admitted.runId,
|
|
481
|
-
}, coordinates.sessionDirectory,
|
|
462
|
+
}, coordinates.sessionDirectory, detourUsageContext(admitted, scope));
|
|
482
463
|
}
|
|
483
464
|
|
|
484
465
|
/** Transitional host-session reads remain only for non-sealed failure and audit evidence. */
|
|
@@ -495,8 +476,6 @@ import {
|
|
|
495
476
|
adviceNavigatorFact,
|
|
496
477
|
type ControlledFailureCause,
|
|
497
478
|
type TerminalArtifactRef,
|
|
498
|
-
type TerminalGateFact,
|
|
499
|
-
type TerminalGateSeat,
|
|
500
479
|
type TerminalNavigatorFact,
|
|
501
480
|
type TerminalResult,
|
|
502
481
|
type TerminalRoleName,
|
|
@@ -530,7 +509,7 @@ export type ControlledFailure = RoleTurnKnownFailure & {
|
|
|
530
509
|
*/
|
|
531
510
|
readonly packageFact?: PackageSideFact;
|
|
532
511
|
/**
|
|
533
|
-
* Host stderr that is not already this failure's diagnostic. A stderr
|
|
512
|
+
* Host stderr that is not already this failure's diagnostic. A stderr log
|
|
534
513
|
* or artifact write can fail; the original bytes stay on this object.
|
|
535
514
|
*/
|
|
536
515
|
readonly stderr?: string;
|
|
@@ -559,7 +538,7 @@ export type PackageSideFact = {
|
|
|
559
538
|
readonly cancelName?: string;
|
|
560
539
|
/** An exception caught after the host already reported this call. */
|
|
561
540
|
readonly thrown?: RoleTurnKnownFailure & { readonly diagnostic: string };
|
|
562
|
-
/** A durable stderr
|
|
541
|
+
/** A durable stderr log-line write that failed while handling this call. */
|
|
563
542
|
readonly stderrLogWriteFailure?: unknown;
|
|
564
543
|
};
|
|
565
544
|
|
|
@@ -1034,12 +1013,7 @@ type SessionEntry = {
|
|
|
1034
1013
|
export async function readBoundSessionEntries(
|
|
1035
1014
|
sessionFile: string,
|
|
1036
1015
|
): Promise<SessionEntry[]> {
|
|
1037
|
-
|
|
1038
|
-
const entries: SessionEntry[] = [];
|
|
1039
|
-
for (const line of text.trim().split("\n").filter(Boolean)) {
|
|
1040
|
-
entries.push(JSON.parse(line) as SessionEntry);
|
|
1041
|
-
}
|
|
1042
|
-
return entries;
|
|
1016
|
+
return await readStrictPiSessionJsonl(sessionFile) as SessionEntry[];
|
|
1043
1017
|
}
|
|
1044
1018
|
|
|
1045
1019
|
/**
|
|
@@ -1061,12 +1035,23 @@ type AttemptHistorySource = {
|
|
|
1061
1035
|
readonly sessionFile: string;
|
|
1062
1036
|
};
|
|
1063
1037
|
|
|
1038
|
+
function attemptHistorySource(
|
|
1039
|
+
admitted: Pick<AdmittedRoleInvocation, "role" | "runId">,
|
|
1040
|
+
coordinates: Pick<DurablePrincipalCoordinates, "sessionFile">,
|
|
1041
|
+
): AttemptHistorySource {
|
|
1042
|
+
return {
|
|
1043
|
+
role: admitted.role,
|
|
1044
|
+
runId: admitted.runId,
|
|
1045
|
+
sessionFile: coordinates.sessionFile,
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1064
1049
|
/** Append the complete attempt to the package ledger before overwriting pointer artifacts (#419). */
|
|
1065
1050
|
export async function appendRunAttemptHistory(
|
|
1066
1051
|
source: AttemptHistorySource,
|
|
1067
1052
|
outcome: AttemptHistoryOutcome,
|
|
1068
|
-
): Promise<
|
|
1069
|
-
sitianReport({
|
|
1053
|
+
): Promise<RecordPointer> {
|
|
1054
|
+
return sitianReport({
|
|
1070
1055
|
level: "event",
|
|
1071
1056
|
kind: "attempt-history",
|
|
1072
1057
|
subject: { runId: source.runId },
|
|
@@ -1151,73 +1136,6 @@ function parseNavigatorAttendanceDetails(
|
|
|
1151
1136
|
};
|
|
1152
1137
|
}
|
|
1153
1138
|
|
|
1154
|
-
/**
|
|
1155
|
-
* Project direct and historical paired gate rounds onto the public Terminal.
|
|
1156
|
-
* actualSeats derive only from accepted receipts, never expected/missing seats.
|
|
1157
|
-
*/
|
|
1158
|
-
export function projectTerminalGateFact(
|
|
1159
|
-
rounds: readonly AnalystGateCycleRound[],
|
|
1160
|
-
): TerminalGateFact | undefined {
|
|
1161
|
-
if (rounds.length === 0) return undefined;
|
|
1162
|
-
const seen = new Set<TerminalGateSeat>();
|
|
1163
|
-
for (const round of rounds) {
|
|
1164
|
-
if (round.origin.kind === "historical_dispatch") seen.add("gatekeeper");
|
|
1165
|
-
seen.add(round.officer);
|
|
1166
|
-
}
|
|
1167
|
-
const actualSeats = (["gatekeeper", "inspector", "notary"] as const).filter(
|
|
1168
|
-
(seat) => seen.has(seat),
|
|
1169
|
-
);
|
|
1170
|
-
return {
|
|
1171
|
-
actualSeats,
|
|
1172
|
-
rounds: rounds.map((round) => ({
|
|
1173
|
-
roundIndex: round.roundIndex,
|
|
1174
|
-
dispatch:
|
|
1175
|
-
round.origin.kind === "direct"
|
|
1176
|
-
? { kind: "direct" as const, officer: round.officer }
|
|
1177
|
-
: {
|
|
1178
|
-
kind: "historical_dispatch" as const,
|
|
1179
|
-
officer: round.officer,
|
|
1180
|
-
...(round.origin.reason === undefined
|
|
1181
|
-
? {}
|
|
1182
|
-
: { reason: round.origin.reason }),
|
|
1183
|
-
},
|
|
1184
|
-
officer: {
|
|
1185
|
-
seat: round.officer,
|
|
1186
|
-
status: round.status,
|
|
1187
|
-
findings: round.findings,
|
|
1188
|
-
},
|
|
1189
|
-
})),
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
/**
|
|
1194
|
-
* Read gate facts from the run's session/auditor-roles nest (#446/#478).
|
|
1195
|
-
* Missing directories → undefined (no-gate zero change).
|
|
1196
|
-
* Damaged discovered volumes propagate — never wash to "no gate".
|
|
1197
|
-
*/
|
|
1198
|
-
export async function extractGateFactFromSessionDirectory(
|
|
1199
|
-
sessionDirectory: string,
|
|
1200
|
-
options: {
|
|
1201
|
-
readonly runDirectory?: string;
|
|
1202
|
-
readonly parentSessionFile?: string;
|
|
1203
|
-
} = {},
|
|
1204
|
-
): Promise<TerminalGateFact | undefined> {
|
|
1205
|
-
const directories = [join(sessionDirectory, "auditor-roles")];
|
|
1206
|
-
const parentSessionFile =
|
|
1207
|
-
options.parentSessionFile ?? join(sessionDirectory, "session.jsonl");
|
|
1208
|
-
const rounds = await readAnalystGateCyclesFromAuditorRoles(directories, {
|
|
1209
|
-
parentSessionFile,
|
|
1210
|
-
});
|
|
1211
|
-
return projectTerminalGateFact(rounds);
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
/**
|
|
1215
|
-
* Attach optional gate projection onto a settled Terminal base.
|
|
1216
|
-
* Shared by every settle path so auditor-roles is scanned once here only.
|
|
1217
|
-
* `runId` is optional only for a batch projection that has no parent run.
|
|
1218
|
-
* Gate read damage is a package note beside the host terminal. It does not
|
|
1219
|
-
* replace that terminal and it does not omit the gate without a trace.
|
|
1220
|
-
*/
|
|
1221
1139
|
/**
|
|
1222
1140
|
* #537: project this-invocation ak_engine_detour usage onto decisiveFacts.
|
|
1223
1141
|
* Absent when engine is not mounted; callCount 0 when mounted with zero calls.
|
|
@@ -1231,16 +1149,15 @@ async function attachEngineDetourToolUsage<
|
|
|
1231
1149
|
>(
|
|
1232
1150
|
base: T,
|
|
1233
1151
|
sessionDirectory: string,
|
|
1234
|
-
|
|
1152
|
+
detourContext: {
|
|
1235
1153
|
readonly runDirectory?: string;
|
|
1236
|
-
readonly courtAttemptId?: string;
|
|
1237
1154
|
readonly invocationScopeId?: string;
|
|
1238
1155
|
readonly notePackageFault?: SettlementCourtScope["notePackageFault"];
|
|
1239
1156
|
} = {},
|
|
1240
1157
|
): Promise<T> {
|
|
1241
1158
|
const runDirectory =
|
|
1242
|
-
typeof
|
|
1243
|
-
?
|
|
1159
|
+
typeof detourContext.runDirectory === "string" && detourContext.runDirectory.length > 0
|
|
1160
|
+
? detourContext.runDirectory
|
|
1244
1161
|
: runDirectoryFromSessionDirectory(sessionDirectory);
|
|
1245
1162
|
let engineMounted = false;
|
|
1246
1163
|
try {
|
|
@@ -1248,9 +1165,9 @@ async function attachEngineDetourToolUsage<
|
|
|
1248
1165
|
} catch (error) {
|
|
1249
1166
|
await noteSettlementFault(
|
|
1250
1167
|
runDirectory,
|
|
1251
|
-
|
|
1168
|
+
detourContext.notePackageFault === undefined
|
|
1252
1169
|
? undefined
|
|
1253
|
-
: { notePackageFault:
|
|
1170
|
+
: { notePackageFault: detourContext.notePackageFault },
|
|
1254
1171
|
`engine mount read failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
1255
1172
|
);
|
|
1256
1173
|
return base;
|
|
@@ -1260,9 +1177,9 @@ async function attachEngineDetourToolUsage<
|
|
|
1260
1177
|
// Public-invocation scope from the shared Host envelope (settlement scope), never
|
|
1261
1178
|
// courtAttemptId and never a detour sidecar file.
|
|
1262
1179
|
const invocationScopeId =
|
|
1263
|
-
typeof
|
|
1264
|
-
|
|
1265
|
-
?
|
|
1180
|
+
typeof detourContext.invocationScopeId === "string" &&
|
|
1181
|
+
detourContext.invocationScopeId.length > 0
|
|
1182
|
+
? detourContext.invocationScopeId
|
|
1266
1183
|
: undefined;
|
|
1267
1184
|
|
|
1268
1185
|
const sessionFile = sessionFileFromSessionDirectory(sessionDirectory);
|
|
@@ -1277,9 +1194,9 @@ async function attachEngineDetourToolUsage<
|
|
|
1277
1194
|
} catch (error) {
|
|
1278
1195
|
await noteSettlementFault(
|
|
1279
1196
|
runDirectory,
|
|
1280
|
-
|
|
1197
|
+
detourContext.notePackageFault === undefined
|
|
1281
1198
|
? undefined
|
|
1282
|
-
: { notePackageFault:
|
|
1199
|
+
: { notePackageFault: detourContext.notePackageFault },
|
|
1283
1200
|
`engine detour read failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
1284
1201
|
);
|
|
1285
1202
|
return base;
|
|
@@ -1290,20 +1207,17 @@ async function attachEngineDetourToolUsage<
|
|
|
1290
1207
|
};
|
|
1291
1208
|
}
|
|
1292
1209
|
|
|
1293
|
-
/**
|
|
1294
|
-
function
|
|
1210
|
+
/** Engine-detour usage context from admitted run + settlement scope. */
|
|
1211
|
+
function detourUsageContext(
|
|
1295
1212
|
admitted: { readonly runDirectory: string },
|
|
1296
1213
|
scope?: SettlementCourtScope,
|
|
1297
1214
|
): {
|
|
1298
1215
|
readonly runDirectory: string;
|
|
1299
|
-
readonly courtAttemptId?: string;
|
|
1300
1216
|
readonly invocationScopeId?: string;
|
|
1217
|
+
readonly notePackageFault?: SettlementCourtScope["notePackageFault"];
|
|
1301
1218
|
} {
|
|
1302
1219
|
return {
|
|
1303
1220
|
runDirectory: admitted.runDirectory,
|
|
1304
|
-
...(scope?.courtAttemptId === undefined || scope.courtAttemptId.length === 0
|
|
1305
|
-
? {}
|
|
1306
|
-
: { courtAttemptId: scope.courtAttemptId }),
|
|
1307
1221
|
...(scope?.invocationScopeId === undefined || scope.invocationScopeId.length === 0
|
|
1308
1222
|
? {}
|
|
1309
1223
|
: { invocationScopeId: scope.invocationScopeId }),
|
|
@@ -1311,54 +1225,6 @@ function detourGateContext(
|
|
|
1311
1225
|
};
|
|
1312
1226
|
}
|
|
1313
1227
|
|
|
1314
|
-
async function withOptionalGateProjection<
|
|
1315
|
-
T extends {
|
|
1316
|
-
roleOutcome: TerminalRoleOutcome;
|
|
1317
|
-
navigator: TerminalNavigatorFact;
|
|
1318
|
-
artifacts: readonly TerminalArtifactRef[];
|
|
1319
|
-
},
|
|
1320
|
-
>(
|
|
1321
|
-
base: T,
|
|
1322
|
-
sessionDirectory: string,
|
|
1323
|
-
gateContext: {
|
|
1324
|
-
readonly runDirectory?: string;
|
|
1325
|
-
readonly parentSessionFile?: string;
|
|
1326
|
-
readonly courtAttemptId?: string;
|
|
1327
|
-
readonly invocationScopeId?: string;
|
|
1328
|
-
readonly notePackageFault?: SettlementCourtScope["notePackageFault"];
|
|
1329
|
-
} = {},
|
|
1330
|
-
): Promise<T & { gate?: TerminalGateFact }> {
|
|
1331
|
-
// A gate transport failure is already represented by typed evidence and has no
|
|
1332
|
-
// accepted gate cycle to project. Re-reading that rejected receipt as an
|
|
1333
|
-
// accepted cycle would replace the original failure with a projection error.
|
|
1334
|
-
const secondaryEvidence = base.roleOutcome.kind === "failure"
|
|
1335
|
-
? base.roleOutcome.decisiveFacts.secondaryEvidence
|
|
1336
|
-
: undefined;
|
|
1337
|
-
const skipGate =
|
|
1338
|
-
isRecord(secondaryEvidence)
|
|
1339
|
-
&& secondaryEvidence.kind === "role_infrastructure_failure"
|
|
1340
|
-
&& packagedSkipsGateOnInfrastructureStage(secondaryEvidence.stage);
|
|
1341
|
-
|
|
1342
|
-
let next: T & { gate?: TerminalGateFact } = base;
|
|
1343
|
-
if (!skipGate) {
|
|
1344
|
-
try {
|
|
1345
|
-
const gate = await extractGateFactFromSessionDirectory(sessionDirectory, gateContext);
|
|
1346
|
-
if (gate !== undefined) next = { ...base, gate };
|
|
1347
|
-
} catch (error) {
|
|
1348
|
-
const runDirectory = gateContext.runDirectory ?? runDirectoryFromSessionDirectory(sessionDirectory);
|
|
1349
|
-
await noteSettlementFault(
|
|
1350
|
-
runDirectory,
|
|
1351
|
-
gateContext.notePackageFault === undefined
|
|
1352
|
-
? undefined
|
|
1353
|
-
: { notePackageFault: gateContext.notePackageFault },
|
|
1354
|
-
`gate read failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
1355
|
-
);
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
return attachEngineDetourToolUsage(next, sessionDirectory, gateContext);
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
1228
|
function routePlaybookFailureMessage(entries: readonly SessionEntry[]): string | undefined {
|
|
1363
1229
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1364
1230
|
const entry = entries[i];
|
|
@@ -1444,120 +1310,109 @@ async function extractNavigatorFactFromAdmittedSession(
|
|
|
1444
1310
|
}
|
|
1445
1311
|
|
|
1446
1312
|
/**
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
} catch (error) {
|
|
1455
|
-
// force:true already ignores ENOENT; ENOTDIR = face path not enterable
|
|
1456
|
-
// (artifacts-as-file mid-path) — same absent-face semantics as the reader.
|
|
1457
|
-
// Other errno stay loud (失败诚实).
|
|
1458
|
-
if (isMissingPathError(error)) return;
|
|
1459
|
-
throw error;
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
/**
|
|
1464
|
-
* #953: artifact face reflects the current terminal only.
|
|
1465
|
-
* Reader contract owns the full adoptable set (conventional trio + fixed
|
|
1466
|
-
* fallbacks + seam-owned unique). Clear every seam-owned face before the
|
|
1467
|
-
* new current publishes — including the conventional name about to be
|
|
1468
|
-
* rewritten. Retaining that name left directory collision plants in place
|
|
1469
|
-
* (supported publish input); writer then fell back while the reader stopped
|
|
1470
|
-
* at EISDIR on the stale conventional path and never adopted the fallback.
|
|
1471
|
-
* report / error / empty share one method (no per-branch face list).
|
|
1472
|
-
* empty publishes nothing; does not create a no_receipt-shaped public artifact.
|
|
1473
|
-
* Non-terminal materials stay. Face names may be directory collision plants —
|
|
1474
|
-
* remove recursively. Unique ownership is listSeamOwnedUniqueErrorFacePaths:
|
|
1475
|
-
* parent runs/ faces clear only when body.runId binds; unparseable runId → none.
|
|
1476
|
-
* Path absence (ENOENT/ENOTDIR) is silent; other delete failures stay loud.
|
|
1477
|
-
*/
|
|
1478
|
-
async function clearOppositeTerminalArtifactFace(
|
|
1479
|
-
runDirectory: string,
|
|
1480
|
-
): Promise<void> {
|
|
1481
|
-
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
1482
|
-
for (const file of RUN_TERMINAL_ARTIFACT_FILES) {
|
|
1483
|
-
await removeFaceIfPresent(join(artifactsDir, file));
|
|
1484
|
-
}
|
|
1485
|
-
// Fixed fallbacks + unique clear too; subsequent publish writes the new
|
|
1486
|
-
// durable path after this clear (conventional first when writable).
|
|
1487
|
-
for (const relative of RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS) {
|
|
1488
|
-
await removeFaceIfPresent(join(runDirectory, relative));
|
|
1489
|
-
}
|
|
1490
|
-
for (const path of await listSeamOwnedUniqueErrorFacePaths(runDirectory)) {
|
|
1491
|
-
await removeFaceIfPresent(path);
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
async function ensureTerminalArtifactFace(
|
|
1496
|
-
runDirectory: string,
|
|
1497
|
-
): Promise<string> {
|
|
1498
|
-
const artifactsDir = await ensureRunArtifactsDir(runDirectory);
|
|
1499
|
-
await clearOppositeTerminalArtifactFace(runDirectory);
|
|
1500
|
-
return artifactsDir;
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
type AcceptedArtifactAttachment = {
|
|
1504
|
-
readonly provenancePath: string;
|
|
1505
|
-
readonly frozenPath: string;
|
|
1506
|
-
readonly sha256: string;
|
|
1507
|
-
readonly byteLength: number;
|
|
1508
|
-
};
|
|
1509
|
-
|
|
1510
|
-
function acceptedArtifactAttachmentRefs(
|
|
1511
|
-
attachments: readonly AcceptedArtifactAttachment[],
|
|
1512
|
-
): AcceptedArtifactAttachment[] {
|
|
1513
|
-
return attachments.map((a) => ({
|
|
1514
|
-
provenancePath: a.provenancePath,
|
|
1515
|
-
frozenPath: a.frozenPath,
|
|
1516
|
-
sha256: a.sha256,
|
|
1517
|
-
byteLength: a.byteLength,
|
|
1518
|
-
}));
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
/**
|
|
1522
|
-
* Sole success-terminal artifact publisher (#953): append attempt history,
|
|
1523
|
-
* refresh the public terminal face, write report/evidence, return refs.
|
|
1524
|
-
* Seat-specific structured fields stay in callers; do not fork this flow.
|
|
1313
|
+
* Sole success-terminal publisher (#953): replace the leg's terminal in
|
|
1314
|
+
* current.json. The report carries the outcome's verdict facts; the role's
|
|
1315
|
+
* submitted payloads live in history.jsonl. The terminal names the
|
|
1316
|
+
* attempt-history row that belongs to this settlement — from this settle's
|
|
1317
|
+
* append when recording, otherwise that court's prior terminal row in
|
|
1318
|
+
* state.jsonl (#1161 甲 / R2). Never content-match payloads, never inherit the
|
|
1319
|
+
* leg's latest terminal, never invent a history row on re-projection (N1).
|
|
1525
1320
|
*/
|
|
1526
|
-
async function
|
|
1321
|
+
async function publishAcceptedTerminal(
|
|
1527
1322
|
admitted: AdmittedRoleInvocation,
|
|
1528
1323
|
roleOutcome: TerminalRoleOutcome,
|
|
1529
1324
|
coordinates: DurablePrincipalCoordinates,
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
readonly report: Record<string, unknown>;
|
|
1533
|
-
readonly evidence: Record<string, unknown>;
|
|
1534
|
-
},
|
|
1325
|
+
scope: SettlementCourtScope | undefined,
|
|
1326
|
+
report: Record<string, unknown>,
|
|
1535
1327
|
): Promise<TerminalArtifactRef[]> {
|
|
1536
1328
|
// #419: a dispatched turn appends before rewriting last-write-wins views;
|
|
1537
1329
|
// a later projection may refresh views but must not invent another attempt.
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1330
|
+
let attemptHistoryIdentity: string | undefined;
|
|
1331
|
+
if (scope?.recordAttemptHistory === true) {
|
|
1332
|
+
const pointer = await appendRunAttemptHistory(
|
|
1333
|
+
attemptHistorySource(admitted, coordinates),
|
|
1541
1334
|
roleOutcome,
|
|
1542
1335
|
);
|
|
1336
|
+
attemptHistoryIdentity = pointer.identity;
|
|
1337
|
+
} else {
|
|
1338
|
+
attemptHistoryIdentity = await belongingAttemptHistoryIdentity(
|
|
1339
|
+
admitted.runDirectory,
|
|
1340
|
+
scope,
|
|
1341
|
+
);
|
|
1543
1342
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
return
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1343
|
+
writeRunTerminal(admitted.runDirectory, "report", {
|
|
1344
|
+
...report,
|
|
1345
|
+
...terminalBelongingFields(attemptHistoryIdentity, scope),
|
|
1346
|
+
});
|
|
1347
|
+
return [{ kind: "report", path: runCurrentPath(admitted.runDirectory) }];
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/** 甲 pointer + court tag carried on the terminal body for later re-projection. */
|
|
1351
|
+
function terminalBelongingFields(
|
|
1352
|
+
attemptHistoryIdentity: string | undefined,
|
|
1353
|
+
scope: SettlementCourtScope | undefined,
|
|
1354
|
+
): Record<string, string> {
|
|
1355
|
+
const courtAttemptId = scope?.courtAttemptId;
|
|
1356
|
+
return {
|
|
1357
|
+
...(attemptHistoryIdentity === undefined
|
|
1358
|
+
? {}
|
|
1359
|
+
: { [ATTEMPT_HISTORY_IDENTITY_FIELD]: attemptHistoryIdentity }),
|
|
1360
|
+
...(courtAttemptId === undefined || courtAttemptId.length === 0
|
|
1361
|
+
? {}
|
|
1362
|
+
: { [TERMINAL_COURT_ATTEMPT_FIELD]: courtAttemptId }),
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Belonging attempt-history identity for a non-recording re-projection (#1161 甲 / R2).
|
|
1368
|
+
* Court-tagged settle recovers from that court's own prior terminal row in
|
|
1369
|
+
* state.jsonl. Untagged normal rewrite (bare resume after a sealed face, no open
|
|
1370
|
+
* court) keeps only the face being rewritten — the last terminal row's own
|
|
1371
|
+
* identity. If that face has none (approved gap), return undefined: never walk
|
|
1372
|
+
* older terminals for a non-empty pointer, never invent-append (N1), never
|
|
1373
|
+
* subject.attemptId on attempt-history. Bare resume therefore cannot restore
|
|
1374
|
+
* court B's pointer while the current face is A (or A's gap). Court-tagged
|
|
1375
|
+
* lookup that finds no row still returns undefined — the approved gap.
|
|
1376
|
+
*/
|
|
1377
|
+
async function belongingAttemptHistoryIdentity(
|
|
1378
|
+
runDirectory: string,
|
|
1379
|
+
scope: SettlementCourtScope | undefined,
|
|
1380
|
+
): Promise<string | undefined> {
|
|
1381
|
+
const courtAttemptId = scope?.courtAttemptId;
|
|
1382
|
+
let rows: readonly Record<string, unknown>[];
|
|
1383
|
+
try {
|
|
1384
|
+
rows = readStateRowsSync(runDirectory);
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
// Documented: history/state failure domain does not block the terminal face
|
|
1387
|
+
// (#1161 / main history disposition). True cause must leave a trace.
|
|
1388
|
+
await noteSettlementFault(
|
|
1389
|
+
runDirectory,
|
|
1390
|
+
scope,
|
|
1391
|
+
`terminal state read failed beside terminal projection: ${describeErrorIdentity(error)}`,
|
|
1392
|
+
);
|
|
1393
|
+
return undefined;
|
|
1394
|
+
}
|
|
1395
|
+
if (courtAttemptId !== undefined && courtAttemptId.length > 0) {
|
|
1396
|
+
let found: string | undefined;
|
|
1397
|
+
for (const row of rows) {
|
|
1398
|
+
if (row.kind !== "terminal" || !isRecord(row.payload)) continue;
|
|
1399
|
+
const body = row.payload.body;
|
|
1400
|
+
if (!isRecord(body) || body[TERMINAL_COURT_ATTEMPT_FIELD] !== courtAttemptId) continue;
|
|
1401
|
+
const identity = body[ATTEMPT_HISTORY_IDENTITY_FIELD];
|
|
1402
|
+
if (typeof identity === "string" && identity.length > 0) found = identity;
|
|
1403
|
+
}
|
|
1404
|
+
return found;
|
|
1405
|
+
}
|
|
1406
|
+
// Untagged: only the current face (last terminal). Gap face stays a gap.
|
|
1407
|
+
for (let index = rows.length - 1; index >= 0; index -= 1) {
|
|
1408
|
+
const row = rows[index]!;
|
|
1409
|
+
if (row.kind !== "terminal" || !isRecord(row.payload)) continue;
|
|
1410
|
+
const body = row.payload.body;
|
|
1411
|
+
if (!isRecord(body)) return undefined;
|
|
1412
|
+
const identity = body[ATTEMPT_HISTORY_IDENTITY_FIELD];
|
|
1413
|
+
return typeof identity === "string" && identity.length > 0 ? identity : undefined;
|
|
1414
|
+
}
|
|
1415
|
+
return undefined;
|
|
1561
1416
|
}
|
|
1562
1417
|
|
|
1563
1418
|
type LawfulSessionRead =
|
|
@@ -1619,13 +1474,13 @@ async function finishLawfulSeat(
|
|
|
1619
1474
|
): Promise<TerminalResult> {
|
|
1620
1475
|
const artifacts = scope?.previewOnly === true
|
|
1621
1476
|
? []
|
|
1622
|
-
: await
|
|
1623
|
-
const terminal = await
|
|
1477
|
+
: await publishDeclaredSeatTerminal(admitted, roleOutcome, coordinates, entries, scope);
|
|
1478
|
+
const terminal = await attachEngineDetourToolUsage({
|
|
1624
1479
|
roleOutcome,
|
|
1625
1480
|
navigator: extractNavigatorFact(entries),
|
|
1626
1481
|
artifacts,
|
|
1627
1482
|
runId: admitted.runId,
|
|
1628
|
-
}, coordinates.sessionDirectory,
|
|
1483
|
+
}, coordinates.sessionDirectory, detourUsageContext(admitted, scope));
|
|
1629
1484
|
return attachRecordedSubmissions(admitted, terminal, scope);
|
|
1630
1485
|
}
|
|
1631
1486
|
|
|
@@ -1675,9 +1530,9 @@ function extractDoctorCandidateFacts(
|
|
|
1675
1530
|
return {};
|
|
1676
1531
|
}
|
|
1677
1532
|
|
|
1678
|
-
const EMPTY_ARTIFACT_FACE: PackagedArtifactFace = {
|
|
1533
|
+
const EMPTY_ARTIFACT_FACE: PackagedArtifactFace = {};
|
|
1679
1534
|
|
|
1680
|
-
/**
|
|
1535
|
+
/** Terminal face for one seat. Absent means the report carries only the shared facts. */
|
|
1681
1536
|
function seatArtifactFace(role: string): PackagedArtifactFace {
|
|
1682
1537
|
const record = packagedRoleMetadata(role);
|
|
1683
1538
|
if (record !== undefined && "artifactFace" in record && record.artifactFace !== undefined) {
|
|
@@ -1686,32 +1541,6 @@ function seatArtifactFace(role: string): PackagedArtifactFace {
|
|
|
1686
1541
|
return EMPTY_ARTIFACT_FACE;
|
|
1687
1542
|
}
|
|
1688
1543
|
|
|
1689
|
-
function readAdmittedPath(source: object, path: string): unknown {
|
|
1690
|
-
let current: unknown = source;
|
|
1691
|
-
for (const part of path.split(".")) {
|
|
1692
|
-
if (typeof current !== "object" || current === null) return undefined;
|
|
1693
|
-
current = (current as Record<string, unknown>)[part];
|
|
1694
|
-
}
|
|
1695
|
-
return current;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
function evidenceLeaves(
|
|
1699
|
-
admitted: AdmittedRoleInvocation,
|
|
1700
|
-
leaves: readonly PackagedArtifactLeaf[],
|
|
1701
|
-
): Record<string, unknown> {
|
|
1702
|
-
const evidence: Record<string, unknown> = {};
|
|
1703
|
-
for (const leaf of leaves) {
|
|
1704
|
-
if (leaf.callerProvenance === true) {
|
|
1705
|
-
if (!admitted.instructionEmpty) evidence[leaf.key] = admitted.instruction;
|
|
1706
|
-
continue;
|
|
1707
|
-
}
|
|
1708
|
-
const value = readAdmittedPath(admitted, leaf.from ?? leaf.key);
|
|
1709
|
-
if (value === undefined && leaf.omitUndefined === true) continue;
|
|
1710
|
-
evidence[leaf.key] = leaf.copyArray === true && Array.isArray(value) ? [...value] : value;
|
|
1711
|
-
}
|
|
1712
|
-
return evidence;
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
1544
|
function doctorReportFacts(
|
|
1716
1545
|
face: PackagedArtifactFace,
|
|
1717
1546
|
roleOutcome: TerminalRoleOutcome,
|
|
@@ -1725,34 +1554,25 @@ function doctorReportFacts(
|
|
|
1725
1554
|
};
|
|
1726
1555
|
}
|
|
1727
1556
|
|
|
1728
|
-
async function
|
|
1557
|
+
async function publishDeclaredSeatTerminal(
|
|
1729
1558
|
admitted: AdmittedRoleInvocation,
|
|
1730
1559
|
roleOutcome: TerminalRoleOutcome,
|
|
1731
1560
|
coordinates: DurablePrincipalCoordinates,
|
|
1732
1561
|
entries: readonly SessionEntry[],
|
|
1733
|
-
|
|
1562
|
+
scope: SettlementCourtScope | undefined,
|
|
1734
1563
|
): Promise<TerminalArtifactRef[]> {
|
|
1735
1564
|
const face = seatArtifactFace(admitted.role);
|
|
1736
1565
|
const phase = face.reportPhase === true
|
|
1737
|
-
? { phase:
|
|
1566
|
+
? { phase: (admitted as { phase?: unknown }).phase }
|
|
1738
1567
|
: {};
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
evidence: {
|
|
1748
|
-
runId: admitted.runId,
|
|
1749
|
-
...(face.evidenceRole === true ? { role: admitted.role } : {}),
|
|
1750
|
-
...evidenceLeaves(admitted, face.leaves),
|
|
1751
|
-
sessionDirectory: coordinates.sessionDirectory,
|
|
1752
|
-
sessionFile: coordinates.sessionFile,
|
|
1753
|
-
admittedRequestPath: admitted.admittedRequestPath,
|
|
1754
|
-
attachments: acceptedArtifactAttachmentRefs(admitted.attachments),
|
|
1755
|
-
},
|
|
1568
|
+
// Payloads are the role's submitted words: they live in history.jsonl, not here.
|
|
1569
|
+
const { payloads: _payloads, ...verdict } = roleOutcome as TerminalRoleOutcome & { payloads?: unknown };
|
|
1570
|
+
return publishAcceptedTerminal(admitted, roleOutcome, coordinates, scope, {
|
|
1571
|
+
role: admitted.role,
|
|
1572
|
+
runId: admitted.runId,
|
|
1573
|
+
...phase,
|
|
1574
|
+
outcome: verdict,
|
|
1575
|
+
...doctorReportFacts(face, roleOutcome, entries),
|
|
1756
1576
|
});
|
|
1757
1577
|
}
|
|
1758
1578
|
|
|
@@ -1933,211 +1753,55 @@ async function applySecretariatCountersignTerminal(
|
|
|
1933
1753
|
}
|
|
1934
1754
|
|
|
1935
1755
|
/**
|
|
1936
|
-
* After the audit gate returns, attach
|
|
1937
|
-
*
|
|
1938
|
-
*
|
|
1756
|
+
* After the audit gate returns, attach the secretariat officer fact onto the
|
|
1757
|
+
* terminal this turn already settled. Does not publish again — a second publish
|
|
1758
|
+
* would settle the same attempt twice.
|
|
1939
1759
|
*/
|
|
1940
|
-
export async function
|
|
1760
|
+
export async function attachPostAuditCountersignFact(
|
|
1941
1761
|
admitted: AdmittedRoleInvocation,
|
|
1942
1762
|
authority: DurablePrincipalAuthority,
|
|
1943
1763
|
terminal: TerminalResult,
|
|
1944
1764
|
scope?: SettlementCourtScope,
|
|
1945
1765
|
): Promise<TerminalResult> {
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
try {
|
|
1949
|
-
gate = await extractGateFactFromSessionDirectory(sessionDirectory, {
|
|
1950
|
-
runDirectory: admitted.runDirectory,
|
|
1951
|
-
parentSessionFile: sessionFile,
|
|
1952
|
-
});
|
|
1953
|
-
} catch (error) {
|
|
1954
|
-
await noteSettlementFault(
|
|
1955
|
-
admitted.runDirectory,
|
|
1956
|
-
scope,
|
|
1957
|
-
`gate read failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
1958
|
-
);
|
|
1959
|
-
}
|
|
1960
|
-
const withGate = gate === undefined ? terminal : { ...terminal, gate };
|
|
1961
|
-
if (admitted.role !== "secretariat") return withGate;
|
|
1962
|
-
return applySecretariatCountersignTerminal(admitted, authority, withGate, scope);
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
/** One failed attempt to place a durable failure artifact (path is private layout). */
|
|
1966
|
-
type PublicationAttempt = {
|
|
1967
|
-
readonly path: string;
|
|
1968
|
-
readonly diagnostic: string;
|
|
1969
|
-
readonly identity?: {
|
|
1970
|
-
readonly name?: string;
|
|
1971
|
-
readonly code?: string | number;
|
|
1972
|
-
};
|
|
1973
|
-
};
|
|
1974
|
-
|
|
1975
|
-
function publicationAttemptFromError(
|
|
1976
|
-
path: string,
|
|
1977
|
-
error: unknown,
|
|
1978
|
-
): PublicationAttempt {
|
|
1979
|
-
if (error instanceof Error) {
|
|
1980
|
-
return {
|
|
1981
|
-
path,
|
|
1982
|
-
diagnostic: error.message || error.name || "write failed",
|
|
1983
|
-
identity: thrownIdentity(error),
|
|
1984
|
-
};
|
|
1985
|
-
}
|
|
1986
|
-
return { path, diagnostic: String(error) };
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
/**
|
|
1990
|
-
* Directories eligible for open-ended unique failure-artifact placement.
|
|
1991
|
-
* Always includes the ledger runs/ parent of the run directory so an
|
|
1992
|
-
* unwritable run tree cannot strand the original controlled failure.
|
|
1993
|
-
*/
|
|
1994
|
-
function uniqueFailureFallbackDirs(
|
|
1995
|
-
runDirectory: string,
|
|
1996
|
-
baseDir: string,
|
|
1997
|
-
): string[] {
|
|
1998
|
-
const dirs: string[] = [];
|
|
1999
|
-
for (const dir of [baseDir, runDirectory, dirname(runDirectory)]) {
|
|
2000
|
-
if (!dirs.includes(dir)) dirs.push(dir);
|
|
2001
|
-
}
|
|
2002
|
-
return dirs;
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
/**
|
|
2006
|
-
* Resolve a writable artifacts base directory. If `artifacts/` cannot be created
|
|
2007
|
-
* (e.g. a file occupies that name), fall back to the run directory itself.
|
|
2008
|
-
*/
|
|
2009
|
-
async function resolveFailureArtifactsBase(
|
|
2010
|
-
runDirectory: string,
|
|
2011
|
-
): Promise<{ baseDir: string; attempt?: PublicationAttempt }> {
|
|
2012
|
-
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
2013
|
-
try {
|
|
2014
|
-
await ensureRunArtifactsDir(runDirectory);
|
|
2015
|
-
return { baseDir: artifactsDir };
|
|
2016
|
-
} catch (error) {
|
|
2017
|
-
return {
|
|
2018
|
-
baseDir: runDirectory,
|
|
2019
|
-
attempt: publicationAttemptFromError(artifactsDir, error),
|
|
2020
|
-
};
|
|
2021
|
-
}
|
|
1766
|
+
if (admitted.role !== "secretariat") return terminal;
|
|
1767
|
+
return applySecretariatCountersignTerminal(admitted, authority, terminal, scope);
|
|
2022
1768
|
}
|
|
2023
1769
|
|
|
2024
1770
|
/**
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
2027
|
-
* original controlled failure outside settlement.
|
|
1771
|
+
* Replace the leg's terminal with the controlled failure. A write failure
|
|
1772
|
+
* propagates: the caller notes it beside the host terminal.
|
|
2028
1773
|
*/
|
|
2029
|
-
async function
|
|
2030
|
-
preferredCandidates: readonly string[],
|
|
2031
|
-
uniqueFallbackDirs: readonly string[],
|
|
2032
|
-
stem: string,
|
|
2033
|
-
basePayload: Readonly<Record<string, unknown>>,
|
|
2034
|
-
priorIssues: readonly PublicationAttempt[],
|
|
2035
|
-
): Promise<{ path: string; issues: PublicationAttempt[] }> {
|
|
2036
|
-
const issues: PublicationAttempt[] = [...priorIssues];
|
|
2037
|
-
const candidates: string[] = [
|
|
2038
|
-
...preferredCandidates,
|
|
2039
|
-
// One unique name per fallback dir — collisions on fixed names cannot exhaust this.
|
|
2040
|
-
...uniqueFallbackDirs.map((dir) => join(dir, stem === UNIQUE_ERROR_FALLBACK_STEM ? uniqueErrorFallbackName() : `${stem}.${randomUUID()}.json`)),
|
|
2041
|
-
];
|
|
2042
|
-
for (let i = 0; i < candidates.length; i += 1) {
|
|
2043
|
-
const path = candidates[i]!;
|
|
2044
|
-
const payload =
|
|
2045
|
-
issues.length === 0
|
|
2046
|
-
? basePayload
|
|
2047
|
-
: { ...basePayload, publicationIssues: issues };
|
|
2048
|
-
try {
|
|
2049
|
-
await writeFile(
|
|
2050
|
-
path,
|
|
2051
|
-
`${JSON.stringify(payload, null, 2)}\n`,
|
|
2052
|
-
"utf8",
|
|
2053
|
-
);
|
|
2054
|
-
return { path, issues };
|
|
2055
|
-
} catch (error) {
|
|
2056
|
-
issues.push(publicationAttemptFromError(path, error));
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
const last = issues.at(-1);
|
|
2060
|
-
const error = new Error(
|
|
2061
|
-
last?.diagnostic ?? "unable to write durable failure artifact",
|
|
2062
|
-
) as Error & {
|
|
2063
|
-
code?: string | number;
|
|
2064
|
-
publicationAttempts?: PublicationAttempt[];
|
|
2065
|
-
};
|
|
2066
|
-
if (last?.identity?.name !== undefined && last.identity.name !== "") {
|
|
2067
|
-
error.name = last.identity.name;
|
|
2068
|
-
}
|
|
2069
|
-
if (last?.identity?.code !== undefined) {
|
|
2070
|
-
error.code = last.identity.code;
|
|
2071
|
-
}
|
|
2072
|
-
error.publicationAttempts = issues;
|
|
2073
|
-
throw error;
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
export async function publishFailureArtifacts(
|
|
1774
|
+
export async function publishFailureTerminal(
|
|
2077
1775
|
admitted: AdmittedRoleInvocation,
|
|
2078
1776
|
failure: ControlledFailure,
|
|
2079
|
-
|
|
1777
|
+
coordinates: DurablePrincipalCoordinates,
|
|
2080
1778
|
onErrorPublished?: (path: string) => void,
|
|
2081
|
-
|
|
1779
|
+
scope?: SettlementCourtScope,
|
|
2082
1780
|
): Promise<TerminalArtifactRef[]> {
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
const
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
// Re-projecting an existing failure does not append. History write failure
|
|
2091
|
-
// rides publicationIssues rather than stranding the controlled failure.
|
|
2092
|
-
if (recordAttemptHistory) {
|
|
1781
|
+
// #419: a dispatched failure joins history before the terminal changes. A history
|
|
1782
|
+
// write failure never strands the failure terminal; it is rethrown after it.
|
|
1783
|
+
let historyFailure: unknown;
|
|
1784
|
+
let historyFailed = false;
|
|
1785
|
+
const failureOutcome = { kind: "failure" as const, role: admitted.role, ...failure };
|
|
1786
|
+
let attemptHistoryIdentity: string | undefined;
|
|
1787
|
+
if (scope?.recordAttemptHistory === true) {
|
|
2093
1788
|
try {
|
|
2094
|
-
await appendRunAttemptHistory(
|
|
2095
|
-
|
|
2096
|
-
|
|
1789
|
+
const pointer = await appendRunAttemptHistory(
|
|
1790
|
+
attemptHistorySource(admitted, coordinates),
|
|
1791
|
+
failureOutcome,
|
|
1792
|
+
);
|
|
1793
|
+
attemptHistoryIdentity = pointer.identity;
|
|
2097
1794
|
} catch (error) {
|
|
2098
|
-
|
|
1795
|
+
historyFailure = error;
|
|
1796
|
+
historyFailed = true;
|
|
2099
1797
|
}
|
|
1798
|
+
} else {
|
|
1799
|
+
attemptHistoryIdentity = await belongingAttemptHistoryIdentity(
|
|
1800
|
+
admitted.runDirectory,
|
|
1801
|
+
scope,
|
|
1802
|
+
);
|
|
2100
1803
|
}
|
|
2101
|
-
|
|
2102
|
-
// the original controlled cause; preserve the issue beside the fallback.
|
|
2103
|
-
try {
|
|
2104
|
-
await clearOppositeTerminalArtifactFace(admitted.runDirectory);
|
|
2105
|
-
} catch (error) {
|
|
2106
|
-
priorIssues.push(publicationAttemptFromError(
|
|
2107
|
-
roleRunArtifactsDirectory(admitted.runDirectory), error,
|
|
2108
|
-
));
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
// Prefer conventional names; unique fallback dirs keep colliding fixed paths
|
|
2112
|
-
// from stranding the original failure outside settlement. Include the ledger
|
|
2113
|
-
// runs/ parent so a locked run directory (EACCES) cannot exhaust durability.
|
|
2114
|
-
const underArtifacts = baseDir === roleRunArtifactsDirectory(admitted.runDirectory);
|
|
2115
|
-
const uniqueFallbackDirs = uniqueFailureFallbackDirs(
|
|
2116
|
-
admitted.runDirectory,
|
|
2117
|
-
baseDir,
|
|
2118
|
-
);
|
|
2119
|
-
const errorCandidates = underArtifacts
|
|
2120
|
-
? [
|
|
2121
|
-
join(baseDir, RUN_TERMINAL_ERROR_FILE),
|
|
2122
|
-
join(baseDir, RUN_TERMINAL_ERROR_SETTLEMENT_FILE),
|
|
2123
|
-
join(admitted.runDirectory, RUN_TERMINAL_ERROR_SETTLEMENT_FILE),
|
|
2124
|
-
]
|
|
2125
|
-
: [
|
|
2126
|
-
join(baseDir, RUN_TERMINAL_ERROR_SETTLEMENT_FILE),
|
|
2127
|
-
join(baseDir, RUN_TERMINAL_ERROR_FILE),
|
|
2128
|
-
];
|
|
2129
|
-
const evidenceCandidates = underArtifacts
|
|
2130
|
-
? [
|
|
2131
|
-
join(baseDir, RUN_TERMINAL_EVIDENCE_FILE),
|
|
2132
|
-
join(baseDir, "evidence.settlement.json"),
|
|
2133
|
-
join(admitted.runDirectory, "evidence.settlement.json"),
|
|
2134
|
-
]
|
|
2135
|
-
: [
|
|
2136
|
-
join(baseDir, "evidence.settlement.json"),
|
|
2137
|
-
join(baseDir, RUN_TERMINAL_EVIDENCE_FILE),
|
|
2138
|
-
];
|
|
2139
|
-
|
|
2140
|
-
const errorPayloadBase: Record<string, unknown> = {
|
|
1804
|
+
writeRunTerminal(admitted.runDirectory, "error", {
|
|
2141
1805
|
kind: "error",
|
|
2142
1806
|
role: admitted.role,
|
|
2143
1807
|
runId: admitted.runId,
|
|
@@ -2148,38 +1812,12 @@ export async function publishFailureArtifacts(
|
|
|
2148
1812
|
// This package's own facts, kept beside the host's report.
|
|
2149
1813
|
...(failure.packageFact === undefined ? {} : { packageFact: failure.packageFact }),
|
|
2150
1814
|
...(failure.stderr === undefined ? {} : { stderr: failure.stderr }),
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
const
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
errorPayloadBase,
|
|
2158
|
-
priorIssues,
|
|
2159
|
-
);
|
|
2160
|
-
onErrorPublished?.(errorWrite.path);
|
|
2161
|
-
|
|
2162
|
-
const evidencePayload: Record<string, unknown> = {
|
|
2163
|
-
runId: admitted.runId,
|
|
2164
|
-
sessionDirectory: sessionDirectory,
|
|
2165
|
-
sessionFile: sessionFile,
|
|
2166
|
-
admittedRequestPath: admitted.admittedRequestPath,
|
|
2167
|
-
attachments: acceptedArtifactAttachmentRefs(admitted.attachments),
|
|
2168
|
-
...(failure.cause === undefined ? {} : { failureCause: failure.cause }),
|
|
2169
|
-
};
|
|
2170
|
-
const evidenceWrite = await writeFailureJsonRetainingCause(
|
|
2171
|
-
evidenceCandidates,
|
|
2172
|
-
uniqueFallbackDirs,
|
|
2173
|
-
"evidence",
|
|
2174
|
-
evidencePayload,
|
|
2175
|
-
// Evidence records the same publication collisions observed placing the error body.
|
|
2176
|
-
errorWrite.issues,
|
|
2177
|
-
);
|
|
2178
|
-
|
|
2179
|
-
return [
|
|
2180
|
-
{ kind: "error", path: errorWrite.path },
|
|
2181
|
-
{ kind: "evidence", path: evidenceWrite.path },
|
|
2182
|
-
];
|
|
1815
|
+
...terminalBelongingFields(attemptHistoryIdentity, scope),
|
|
1816
|
+
});
|
|
1817
|
+
const path = runCurrentPath(admitted.runDirectory);
|
|
1818
|
+
onErrorPublished?.(path);
|
|
1819
|
+
if (historyFailed) throw historyFailure;
|
|
1820
|
+
return [{ kind: "error", path }];
|
|
2183
1821
|
}
|
|
2184
1822
|
|
|
2185
1823
|
/**
|
|
@@ -2205,13 +1843,7 @@ export async function settleFailureTerminalResult(
|
|
|
2205
1843
|
let artifacts: TerminalArtifactRef[] = [];
|
|
2206
1844
|
if (options.previewOnly !== true) {
|
|
2207
1845
|
try {
|
|
2208
|
-
artifacts = await
|
|
2209
|
-
admitted,
|
|
2210
|
-
failure,
|
|
2211
|
-
authority,
|
|
2212
|
-
options.onErrorPublished,
|
|
2213
|
-
options.recordAttemptHistory === true,
|
|
2214
|
-
);
|
|
1846
|
+
artifacts = await publishFailureTerminal(admitted, failure, coordinates, options.onErrorPublished, options);
|
|
2215
1847
|
} catch (error) {
|
|
2216
1848
|
await noteSettlementFault(
|
|
2217
1849
|
admitted.runDirectory,
|
|
@@ -2246,10 +1878,10 @@ export async function settleFailureTerminalResult(
|
|
|
2246
1878
|
diagnostic: failure.diagnostic,
|
|
2247
1879
|
decisiveFacts,
|
|
2248
1880
|
};
|
|
2249
|
-
return
|
|
1881
|
+
return attachEngineDetourToolUsage(
|
|
2250
1882
|
{ roleOutcome, navigator, artifacts, runId: admitted.runId },
|
|
2251
1883
|
sessionDirectory,
|
|
2252
|
-
|
|
1884
|
+
detourUsageContext(admitted, options),
|
|
2253
1885
|
);
|
|
2254
1886
|
}
|
|
2255
1887
|
|