@akagilnc/pi-workflow-roles 0.1.5504 → 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 +5965 -5994
- package/dist/analyst-gate-cycles-read.js +23 -55
- package/dist/analyst-ledger.js +21 -35
- 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-pointer.js +38 -30
- package/dist/auditor-dossier-tool.js +4 -3
- package/dist/auditor-soul.js +3 -11
- package/dist/doctor-evidence.js +3 -3
- package/dist/engine-detour-usage.js +6 -14
- package/dist/headless-host/production-host.js +5316 -5324
- package/dist/host-descriptions.js +0 -4
- package/dist/host-session-record.js +266 -267
- package/dist/migrate-book-topology.js +1314 -696
- package/dist/notary-source-run.js +1 -1
- package/dist/packaged-role-registry.js +5 -44
- package/dist/pi/role-turn-host.js +53 -3
- package/dist/public-cli/auto-resume.js +8 -47
- package/dist/public-cli/instruction-seat-run.js +5 -3
- package/dist/public-cli/invocation.js +51 -56
- package/dist/public-cli/main.js +1916 -1748
- package/dist/public-cli/post-admission.js +96 -55
- package/dist/public-cli/run-lifecycle.js +27 -53
- package/dist/public-cli/settlement.js +157 -302
- package/dist/role-run-placement.js +1 -11
- package/dist/role-run-relocation.js +72 -50
- package/dist/run-dossier-files.js +30 -0
- package/dist/run-dossier.js +322 -0
- package/dist/run-terminal-artifacts.js +68 -197
- package/dist/run-ticket-number.js +16 -3
- package/dist/session-identity.js +49 -18
- package/dist/sitian-appender.js +13 -16
- package/dist/sitian-facade.js +11 -0
- package/dist/sitian-reader.js +44 -29
- package/dist/submission-gate.js +1 -1
- package/dist/ticket-provenance.js +22 -7
- 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 +9 -2
- package/src/analyst-gate-cycles-read.ts +26 -60
- package/src/analyst-ledger.ts +27 -42
- 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 +2 -2
- package/src/archivist-record-pointer.ts +50 -36
- package/src/auditor-dossier-tool.ts +7 -5
- package/src/auditor-soul.ts +3 -9
- 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/doctor-contracts.ts +1 -1
- package/src/doctor-evidence.ts +2 -2
- package/src/engine-detour-tool.ts +1 -1
- package/src/engine-detour-usage.ts +6 -16
- package/src/external-host-turn-loop.ts +25 -0
- package/src/headless-host/production-host.ts +1 -4
- package/src/headless-host/role-turn-host.ts +54 -20
- package/src/host-descriptions.ts +0 -6
- package/src/host-session-record.ts +224 -140
- package/src/notary-source-run.ts +1 -1
- package/src/packaged-role-registry.ts +1 -50
- package/src/pi/adapter.ts +50 -7
- package/src/pi/role-turn-host.ts +51 -3
- package/src/public-cli/auto-resume.ts +7 -43
- package/src/public-cli/instruction-seat-run.ts +9 -2
- package/src/public-cli/invocation.ts +61 -87
- package/src/public-cli/post-admission.ts +111 -72
- package/src/public-cli/run-lifecycle.ts +27 -72
- package/src/public-cli/settlement.ts +193 -418
- package/src/public-cli/terminal.ts +1 -1
- package/src/role-run-placement.ts +2 -13
- package/src/role-run-relocation.ts +71 -68
- package/src/role-runtime.ts +6 -10
- package/src/run-dossier-files.ts +33 -0
- package/src/run-dossier.ts +357 -0
- package/src/run-terminal-artifacts.ts +83 -237
- package/src/run-ticket-number.ts +41 -11
- package/src/session-identity.ts +52 -20
- package/src/sitian-appender.ts +17 -18
- package/src/sitian-facade.ts +22 -0
- package/src/sitian-reader.ts +62 -32
- package/src/submission-gate.ts +1 -1
- package/src/ticket-provenance.ts +20 -7
- package/src/ticket-trajectory.ts +10 -10
|
@@ -7,8 +7,8 @@ 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
|
import { sessionFileOf } from "../role-run-placement.js";
|
|
10
|
-
import { writeHardenedArtifactFile } from "./auto-resume.js";
|
|
11
10
|
import { sitianReport } from "../sitian-facade.js";
|
|
11
|
+
import { ATTEMPT_HISTORY_IDENTITY_FIELD, TERMINAL_COURT_ATTEMPT_FIELD, writeRunTerminal, } from "../run-terminal-artifacts.js";
|
|
12
12
|
import { readAttemptScopedSubmissionRows, readRecordedSubmissionRows, readRecordedSubmissions, } from "../submission-ledger.js";
|
|
13
13
|
import { CONTROLLED_FAILURE_CAUSES } from "../host-contracts.js";
|
|
14
14
|
import { childSignalDeathDiagnostic, processCancelDiagnostic, } from "./process-cancel.js";
|
|
@@ -17,44 +17,17 @@ import { serializeThrownValue } from "../serialize-thrown-value.js";
|
|
|
17
17
|
import { describeErrorIdentity } from "./run-lifecycle.js";
|
|
18
18
|
import { readEngineDetourToolUsage, readInvocationEngineMounted, runDirectoryFromSessionDirectory, sessionFileFromSessionDirectory, withEngineDetourToolUsageFact, } from "../engine-detour-usage.js";
|
|
19
19
|
import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "../dossier-resolution.js";
|
|
20
|
-
import {
|
|
20
|
+
import { homeFromRunDirectory, } from "./invocation.js";
|
|
21
21
|
import { SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, } from "../secretariat-contracts.js";
|
|
22
22
|
import { findLatestDurablePackagedRoleTerminal, NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, } from "../navigator-invocation-identity.js";
|
|
23
23
|
import { packagedDurableOfficerEntry, packagedRoleAcceptedOutputTool, packagedRoleMetadata, } from "../packaged-role-registry.js";
|
|
24
24
|
import { NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, noReceiptLifecycleFacts, parseNoReceiptLifecycleFacts, priorReceiptContinuation, receiptAttemptPointer, } from "../receipt-delivery-policy.js";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
25
|
+
import { readStateRowsSync, runCurrentPath } from "../run-dossier.js";
|
|
26
|
+
import { reportRunRecord } from "../sitian-facade.js";
|
|
27
27
|
/** Ledger reads use the run's machine home — not ambient process HOME (child write vs parent settle). */
|
|
28
28
|
function sealedLedgerHome(admitted) {
|
|
29
29
|
return homeFromRunDirectory(admitted.runDirectory);
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Hardened artifacts directory. A planted symlink at the run directory or the
|
|
33
|
-
* artifacts path must not receive a durable run artifact.
|
|
34
|
-
*/
|
|
35
|
-
export async function ensureRealArtifactsDirectory(runDirectory) {
|
|
36
|
-
const runStat = await lstat(runDirectory);
|
|
37
|
-
if (runStat.isSymbolicLink() || !runStat.isDirectory()) {
|
|
38
|
-
throw new Error("run artifact retention: run directory is not a real directory");
|
|
39
|
-
}
|
|
40
|
-
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
41
|
-
try {
|
|
42
|
-
const existing = await lstat(artifactsDir);
|
|
43
|
-
if (existing.isSymbolicLink() || !existing.isDirectory()) {
|
|
44
|
-
throw new Error("run artifact retention: artifacts path is not a real directory");
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
if (!isEnoent(error))
|
|
49
|
-
throw error;
|
|
50
|
-
await mkdir(artifactsDir, { recursive: true });
|
|
51
|
-
const created = await lstat(artifactsDir);
|
|
52
|
-
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
53
|
-
throw new Error("run artifact retention: artifacts directory is not a real directory");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return artifactsDir;
|
|
57
|
-
}
|
|
58
31
|
/**
|
|
59
32
|
* One retention path for a package fault beside an already chosen terminal.
|
|
60
33
|
* Session append is first when the caller has that channel. The artifact file
|
|
@@ -68,13 +41,14 @@ export async function retainPackageFault(input) {
|
|
|
68
41
|
...(Object.hasOwn(input, "error") ? { failure: projectThrownFailureLeaf(input.error) } : {}),
|
|
69
42
|
};
|
|
70
43
|
let retentionFailure;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
44
|
+
// The run's log is the fallback after the session append throws, and the only
|
|
45
|
+
// channel when the caller has none.
|
|
46
|
+
const writeLog = (appendFailure) => {
|
|
47
|
+
reportRunRecord(input.runDirectory, "post-admission-diagnostic", {
|
|
74
48
|
version: 1,
|
|
75
49
|
...payload,
|
|
76
50
|
...(appendFailure === undefined ? {} : { retentionFailure: appendFailure }),
|
|
77
|
-
});
|
|
51
|
+
}, "settlement");
|
|
78
52
|
};
|
|
79
53
|
if (input.appendSession !== undefined) {
|
|
80
54
|
try {
|
|
@@ -84,21 +58,21 @@ export async function retainPackageFault(input) {
|
|
|
84
58
|
retentionFailure =
|
|
85
59
|
`post-dispatch diagnostic session append failed (best-effort continue): dossier=${describeErrorIdentity(appendError)}`;
|
|
86
60
|
try {
|
|
87
|
-
|
|
61
|
+
writeLog(projectThrownFailureLeaf(appendError));
|
|
88
62
|
}
|
|
89
|
-
catch (
|
|
63
|
+
catch (logError) {
|
|
90
64
|
retentionFailure =
|
|
91
|
-
`post-dispatch diagnostic durable retention failed on both channels (best-effort continue): dossier=${describeErrorIdentity(appendError)};
|
|
65
|
+
`post-dispatch diagnostic durable retention failed on both channels (best-effort continue): dossier=${describeErrorIdentity(appendError)}; log=${describeErrorIdentity(logError)}`;
|
|
92
66
|
}
|
|
93
67
|
}
|
|
94
68
|
}
|
|
95
69
|
else {
|
|
96
70
|
try {
|
|
97
|
-
|
|
71
|
+
writeLog();
|
|
98
72
|
}
|
|
99
|
-
catch (
|
|
73
|
+
catch (logError) {
|
|
100
74
|
retentionFailure =
|
|
101
|
-
`post-dispatch diagnostic durable retention failed (best-effort continue):
|
|
75
|
+
`post-dispatch diagnostic durable retention failed (best-effort continue): log=${describeErrorIdentity(logError)}`;
|
|
102
76
|
}
|
|
103
77
|
}
|
|
104
78
|
const stderr = input.stderr ?? ((text) => {
|
|
@@ -293,11 +267,24 @@ async function settleNoReceiptTerminal(admitted, authority, scope, facts) {
|
|
|
293
267
|
kind: "no_receipt", role: admitted.role, status: "no-accepted-receipt",
|
|
294
268
|
...facts, decisiveFacts: facts,
|
|
295
269
|
};
|
|
270
|
+
let attemptHistoryIdentity;
|
|
296
271
|
if (scope?.recordAttemptHistory === true) {
|
|
297
|
-
await appendRunAttemptHistory(
|
|
272
|
+
const pointer = await appendRunAttemptHistory(attemptHistorySource(admitted, coordinates), roleOutcome);
|
|
273
|
+
attemptHistoryIdentity = pointer.identity;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
attemptHistoryIdentity = await belongingAttemptHistoryIdentity(admitted.runDirectory, scope);
|
|
277
|
+
}
|
|
278
|
+
if (scope?.previewOnly !== true) {
|
|
279
|
+
// Belonging settlement only (#1161 甲 / R2): never inherit another court's
|
|
280
|
+
// latest terminal pointer, and never invent a history row on re-projection (N1).
|
|
281
|
+
writeRunTerminal(admitted.runDirectory, "no_receipt", {
|
|
282
|
+
role: admitted.role,
|
|
283
|
+
runId: admitted.runId,
|
|
284
|
+
outcome: roleOutcome,
|
|
285
|
+
...terminalBelongingFields(attemptHistoryIdentity, scope),
|
|
286
|
+
});
|
|
298
287
|
}
|
|
299
|
-
if (scope?.previewOnly !== true)
|
|
300
|
-
await clearOppositeTerminalArtifactFace(admitted.runDirectory);
|
|
301
288
|
return attachEngineDetourToolUsage({
|
|
302
289
|
roleOutcome,
|
|
303
290
|
navigator: await extractNavigatorFactFromAdmittedSession(coordinates.sessionFile, admitted.runDirectory, scope),
|
|
@@ -648,9 +635,16 @@ export async function readBoundSessionEntries(sessionFile) {
|
|
|
648
635
|
* second ledger mechanism is introduced.
|
|
649
636
|
*/
|
|
650
637
|
export const ATTEMPT_HISTORY_ENTRY_TYPE = "ak_run_attempt_history";
|
|
638
|
+
function attemptHistorySource(admitted, coordinates) {
|
|
639
|
+
return {
|
|
640
|
+
role: admitted.role,
|
|
641
|
+
runId: admitted.runId,
|
|
642
|
+
sessionFile: coordinates.sessionFile,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
651
645
|
/** Append the complete attempt to the package ledger before overwriting pointer artifacts (#419). */
|
|
652
646
|
export async function appendRunAttemptHistory(source, outcome) {
|
|
653
|
-
sitianReport({
|
|
647
|
+
return sitianReport({
|
|
654
648
|
level: "event",
|
|
655
649
|
kind: "attempt-history",
|
|
656
650
|
subject: { runId: source.runId },
|
|
@@ -868,85 +862,92 @@ async function extractNavigatorFactFromAdmittedSession(sessionFile, runDirectory
|
|
|
868
862
|
}
|
|
869
863
|
}
|
|
870
864
|
/**
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
*
|
|
865
|
+
* Sole success-terminal publisher (#953): replace the leg's terminal in
|
|
866
|
+
* current.json. The report carries the outcome's verdict facts; the role's
|
|
867
|
+
* submitted payloads live in history.jsonl. The terminal names the
|
|
868
|
+
* attempt-history row that belongs to this settlement — from this settle's
|
|
869
|
+
* append when recording, otherwise that court's prior terminal row in
|
|
870
|
+
* state.jsonl (#1161 甲 / R2). Never content-match payloads, never inherit the
|
|
871
|
+
* leg's latest terminal, never invent a history row on re-projection (N1).
|
|
874
872
|
*/
|
|
875
|
-
async function
|
|
876
|
-
|
|
877
|
-
|
|
873
|
+
async function publishAcceptedTerminal(admitted, roleOutcome, coordinates, scope, report) {
|
|
874
|
+
// #419: a dispatched turn appends before rewriting last-write-wins views;
|
|
875
|
+
// a later projection may refresh views but must not invent another attempt.
|
|
876
|
+
let attemptHistoryIdentity;
|
|
877
|
+
if (scope?.recordAttemptHistory === true) {
|
|
878
|
+
const pointer = await appendRunAttemptHistory(attemptHistorySource(admitted, coordinates), roleOutcome);
|
|
879
|
+
attemptHistoryIdentity = pointer.identity;
|
|
878
880
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
// (artifacts-as-file mid-path) — same absent-face semantics as the reader.
|
|
882
|
-
// Other errno stay loud (失败诚实).
|
|
883
|
-
if (isMissingPathError(error))
|
|
884
|
-
return;
|
|
885
|
-
throw error;
|
|
881
|
+
else {
|
|
882
|
+
attemptHistoryIdentity = await belongingAttemptHistoryIdentity(admitted.runDirectory, scope);
|
|
886
883
|
}
|
|
884
|
+
writeRunTerminal(admitted.runDirectory, "report", {
|
|
885
|
+
...report,
|
|
886
|
+
...terminalBelongingFields(attemptHistoryIdentity, scope),
|
|
887
|
+
});
|
|
888
|
+
return [{ kind: "report", path: runCurrentPath(admitted.runDirectory) }];
|
|
887
889
|
}
|
|
888
|
-
/**
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
* remove recursively. Unique ownership is listSeamOwnedUniqueErrorFacePaths:
|
|
900
|
-
* parent runs/ faces clear only when body.runId binds; unparseable runId → none.
|
|
901
|
-
* Path absence (ENOENT/ENOTDIR) is silent; other delete failures stay loud.
|
|
902
|
-
*/
|
|
903
|
-
async function clearOppositeTerminalArtifactFace(runDirectory) {
|
|
904
|
-
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
905
|
-
for (const file of RUN_TERMINAL_ARTIFACT_FILES) {
|
|
906
|
-
await removeFaceIfPresent(join(artifactsDir, file));
|
|
907
|
-
}
|
|
908
|
-
// Fixed fallbacks + unique clear too; subsequent publish writes the new
|
|
909
|
-
// durable path after this clear (conventional first when writable).
|
|
910
|
-
for (const relative of RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS) {
|
|
911
|
-
await removeFaceIfPresent(join(runDirectory, relative));
|
|
912
|
-
}
|
|
913
|
-
for (const path of await listSeamOwnedUniqueErrorFacePaths(runDirectory)) {
|
|
914
|
-
await removeFaceIfPresent(path);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
async function ensureTerminalArtifactFace(runDirectory) {
|
|
918
|
-
const artifactsDir = await ensureRunArtifactsDir(runDirectory);
|
|
919
|
-
await clearOppositeTerminalArtifactFace(runDirectory);
|
|
920
|
-
return artifactsDir;
|
|
921
|
-
}
|
|
922
|
-
function acceptedArtifactAttachmentRefs(attachments) {
|
|
923
|
-
return attachments.map((a) => ({
|
|
924
|
-
provenancePath: a.provenancePath,
|
|
925
|
-
frozenPath: a.frozenPath,
|
|
926
|
-
sha256: a.sha256,
|
|
927
|
-
byteLength: a.byteLength,
|
|
928
|
-
}));
|
|
890
|
+
/** 甲 pointer + court tag carried on the terminal body for later re-projection. */
|
|
891
|
+
function terminalBelongingFields(attemptHistoryIdentity, scope) {
|
|
892
|
+
const courtAttemptId = scope?.courtAttemptId;
|
|
893
|
+
return {
|
|
894
|
+
...(attemptHistoryIdentity === undefined
|
|
895
|
+
? {}
|
|
896
|
+
: { [ATTEMPT_HISTORY_IDENTITY_FIELD]: attemptHistoryIdentity }),
|
|
897
|
+
...(courtAttemptId === undefined || courtAttemptId.length === 0
|
|
898
|
+
? {}
|
|
899
|
+
: { [TERMINAL_COURT_ATTEMPT_FIELD]: courtAttemptId }),
|
|
900
|
+
};
|
|
929
901
|
}
|
|
930
902
|
/**
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
903
|
+
* Belonging attempt-history identity for a non-recording re-projection (#1161 甲 / R2).
|
|
904
|
+
* Court-tagged settle recovers from that court's own prior terminal row in
|
|
905
|
+
* state.jsonl. Untagged normal rewrite (bare resume after a sealed face, no open
|
|
906
|
+
* court) keeps only the face being rewritten — the last terminal row's own
|
|
907
|
+
* identity. If that face has none (approved gap), return undefined: never walk
|
|
908
|
+
* older terminals for a non-empty pointer, never invent-append (N1), never
|
|
909
|
+
* subject.attemptId on attempt-history. Bare resume therefore cannot restore
|
|
910
|
+
* court B's pointer while the current face is A (or A's gap). Court-tagged
|
|
911
|
+
* lookup that finds no row still returns undefined — the approved gap.
|
|
934
912
|
*/
|
|
935
|
-
async function
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
913
|
+
async function belongingAttemptHistoryIdentity(runDirectory, scope) {
|
|
914
|
+
const courtAttemptId = scope?.courtAttemptId;
|
|
915
|
+
let rows;
|
|
916
|
+
try {
|
|
917
|
+
rows = readStateRowsSync(runDirectory);
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
// Documented: history/state failure domain does not block the terminal face
|
|
921
|
+
// (#1161 / main history disposition). True cause must leave a trace.
|
|
922
|
+
await noteSettlementFault(runDirectory, scope, `terminal state read failed beside terminal projection: ${describeErrorIdentity(error)}`);
|
|
923
|
+
return undefined;
|
|
924
|
+
}
|
|
925
|
+
if (courtAttemptId !== undefined && courtAttemptId.length > 0) {
|
|
926
|
+
let found;
|
|
927
|
+
for (const row of rows) {
|
|
928
|
+
if (row.kind !== "terminal" || !isRecord(row.payload))
|
|
929
|
+
continue;
|
|
930
|
+
const body = row.payload.body;
|
|
931
|
+
if (!isRecord(body) || body[TERMINAL_COURT_ATTEMPT_FIELD] !== courtAttemptId)
|
|
932
|
+
continue;
|
|
933
|
+
const identity = body[ATTEMPT_HISTORY_IDENTITY_FIELD];
|
|
934
|
+
if (typeof identity === "string" && identity.length > 0)
|
|
935
|
+
found = identity;
|
|
936
|
+
}
|
|
937
|
+
return found;
|
|
938
|
+
}
|
|
939
|
+
// Untagged: only the current face (last terminal). Gap face stays a gap.
|
|
940
|
+
for (let index = rows.length - 1; index >= 0; index -= 1) {
|
|
941
|
+
const row = rows[index];
|
|
942
|
+
if (row.kind !== "terminal" || !isRecord(row.payload))
|
|
943
|
+
continue;
|
|
944
|
+
const body = row.payload.body;
|
|
945
|
+
if (!isRecord(body))
|
|
946
|
+
return undefined;
|
|
947
|
+
const identity = body[ATTEMPT_HISTORY_IDENTITY_FIELD];
|
|
948
|
+
return typeof identity === "string" && identity.length > 0 ? identity : undefined;
|
|
949
|
+
}
|
|
950
|
+
return undefined;
|
|
950
951
|
}
|
|
951
952
|
/**
|
|
952
953
|
* Read session entries for lawful settlement.
|
|
@@ -982,7 +983,7 @@ async function settleSealedSeat(admitted, authority, scope, options) {
|
|
|
982
983
|
async function finishLawfulSeat(admitted, coordinates, entries, roleOutcome, scope) {
|
|
983
984
|
const artifacts = scope?.previewOnly === true
|
|
984
985
|
? []
|
|
985
|
-
: await
|
|
986
|
+
: await publishDeclaredSeatTerminal(admitted, roleOutcome, coordinates, entries, scope);
|
|
986
987
|
const terminal = await attachEngineDetourToolUsage({
|
|
987
988
|
roleOutcome,
|
|
988
989
|
navigator: extractNavigatorFact(entries),
|
|
@@ -1030,8 +1031,8 @@ function extractDoctorCandidateFacts(entries) {
|
|
|
1030
1031
|
}
|
|
1031
1032
|
return {};
|
|
1032
1033
|
}
|
|
1033
|
-
const EMPTY_ARTIFACT_FACE = {
|
|
1034
|
-
/**
|
|
1034
|
+
const EMPTY_ARTIFACT_FACE = {};
|
|
1035
|
+
/** Terminal face for one seat. Absent means the report carries only the shared facts. */
|
|
1035
1036
|
function seatArtifactFace(role) {
|
|
1036
1037
|
const record = packagedRoleMetadata(role);
|
|
1037
1038
|
if (record !== undefined && "artifactFace" in record && record.artifactFace !== undefined) {
|
|
@@ -1039,30 +1040,6 @@ function seatArtifactFace(role) {
|
|
|
1039
1040
|
}
|
|
1040
1041
|
return EMPTY_ARTIFACT_FACE;
|
|
1041
1042
|
}
|
|
1042
|
-
function readAdmittedPath(source, path) {
|
|
1043
|
-
let current = source;
|
|
1044
|
-
for (const part of path.split(".")) {
|
|
1045
|
-
if (typeof current !== "object" || current === null)
|
|
1046
|
-
return undefined;
|
|
1047
|
-
current = current[part];
|
|
1048
|
-
}
|
|
1049
|
-
return current;
|
|
1050
|
-
}
|
|
1051
|
-
function evidenceLeaves(admitted, leaves) {
|
|
1052
|
-
const evidence = {};
|
|
1053
|
-
for (const leaf of leaves) {
|
|
1054
|
-
if (leaf.callerProvenance === true) {
|
|
1055
|
-
if (!admitted.instructionEmpty)
|
|
1056
|
-
evidence[leaf.key] = admitted.instruction;
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
const value = readAdmittedPath(admitted, leaf.from ?? leaf.key);
|
|
1060
|
-
if (value === undefined && leaf.omitUndefined === true)
|
|
1061
|
-
continue;
|
|
1062
|
-
evidence[leaf.key] = leaf.copyArray === true && Array.isArray(value) ? [...value] : value;
|
|
1063
|
-
}
|
|
1064
|
-
return evidence;
|
|
1065
|
-
}
|
|
1066
1043
|
function doctorReportFacts(face, roleOutcome, entries) {
|
|
1067
1044
|
if (face.doctorReportFacts !== true || roleOutcome.kind === "audit_escalation")
|
|
1068
1045
|
return {};
|
|
@@ -1072,28 +1049,19 @@ function doctorReportFacts(face, roleOutcome, entries) {
|
|
|
1072
1049
|
...(facts.auditNoReceipt === undefined ? {} : { auditNoReceipt: facts.auditNoReceipt }),
|
|
1073
1050
|
};
|
|
1074
1051
|
}
|
|
1075
|
-
async function
|
|
1052
|
+
async function publishDeclaredSeatTerminal(admitted, roleOutcome, coordinates, entries, scope) {
|
|
1076
1053
|
const face = seatArtifactFace(admitted.role);
|
|
1077
1054
|
const phase = face.reportPhase === true
|
|
1078
|
-
? { phase:
|
|
1055
|
+
? { phase: admitted.phase }
|
|
1079
1056
|
: {};
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
evidence: {
|
|
1089
|
-
runId: admitted.runId,
|
|
1090
|
-
...(face.evidenceRole === true ? { role: admitted.role } : {}),
|
|
1091
|
-
...evidenceLeaves(admitted, face.leaves),
|
|
1092
|
-
sessionDirectory: coordinates.sessionDirectory,
|
|
1093
|
-
sessionFile: coordinates.sessionFile,
|
|
1094
|
-
admittedRequestPath: admitted.admittedRequestPath,
|
|
1095
|
-
attachments: acceptedArtifactAttachmentRefs(admitted.attachments),
|
|
1096
|
-
},
|
|
1057
|
+
// Payloads are the role's submitted words: they live in history.jsonl, not here.
|
|
1058
|
+
const { payloads: _payloads, ...verdict } = roleOutcome;
|
|
1059
|
+
return publishAcceptedTerminal(admitted, roleOutcome, coordinates, scope, {
|
|
1060
|
+
role: admitted.role,
|
|
1061
|
+
runId: admitted.runId,
|
|
1062
|
+
...phase,
|
|
1063
|
+
outcome: verdict,
|
|
1064
|
+
...doctorReportFacts(face, roleOutcome, entries),
|
|
1097
1065
|
});
|
|
1098
1066
|
}
|
|
1099
1067
|
/** Latest top-level user message index; 0 when the session has none (initial attempt). */
|
|
@@ -1231,140 +1199,38 @@ async function applySecretariatCountersignTerminal(admitted, authority, settled,
|
|
|
1231
1199
|
/**
|
|
1232
1200
|
* After the audit gate returns, attach the secretariat officer fact onto the
|
|
1233
1201
|
* terminal this turn already settled. Does not publish again — a second publish
|
|
1234
|
-
* would
|
|
1202
|
+
* would settle the same attempt twice.
|
|
1235
1203
|
*/
|
|
1236
1204
|
export async function attachPostAuditCountersignFact(admitted, authority, terminal, scope) {
|
|
1237
1205
|
if (admitted.role !== "secretariat")
|
|
1238
1206
|
return terminal;
|
|
1239
1207
|
return applySecretariatCountersignTerminal(admitted, authority, terminal, scope);
|
|
1240
1208
|
}
|
|
1241
|
-
function publicationAttemptFromError(path, error) {
|
|
1242
|
-
if (error instanceof Error) {
|
|
1243
|
-
return {
|
|
1244
|
-
path,
|
|
1245
|
-
diagnostic: error.message || error.name || "write failed",
|
|
1246
|
-
identity: thrownIdentity(error),
|
|
1247
|
-
};
|
|
1248
|
-
}
|
|
1249
|
-
return { path, diagnostic: String(error) };
|
|
1250
|
-
}
|
|
1251
1209
|
/**
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1254
|
-
* unwritable run tree cannot strand the original controlled failure.
|
|
1210
|
+
* Replace the leg's terminal with the controlled failure. A write failure
|
|
1211
|
+
* propagates: the caller notes it beside the host terminal.
|
|
1255
1212
|
*/
|
|
1256
|
-
function
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
/**
|
|
1265
|
-
* Resolve a writable artifacts base directory. If `artifacts/` cannot be created
|
|
1266
|
-
* (e.g. a file occupies that name), fall back to the run directory itself.
|
|
1267
|
-
*/
|
|
1268
|
-
async function resolveFailureArtifactsBase(runDirectory) {
|
|
1269
|
-
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
1270
|
-
try {
|
|
1271
|
-
await ensureRunArtifactsDir(runDirectory);
|
|
1272
|
-
return { baseDir: artifactsDir };
|
|
1273
|
-
}
|
|
1274
|
-
catch (error) {
|
|
1275
|
-
return {
|
|
1276
|
-
baseDir: runDirectory,
|
|
1277
|
-
attempt: publicationAttemptFromError(artifactsDir, error),
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
/**
|
|
1282
|
-
* Write JSON across preferred paths, then unique open-ended fallbacks.
|
|
1283
|
-
* Finite fixed names must not be able to exhaust durability and strand the
|
|
1284
|
-
* original controlled failure outside settlement.
|
|
1285
|
-
*/
|
|
1286
|
-
async function writeFailureJsonRetainingCause(preferredCandidates, uniqueFallbackDirs, stem, basePayload, priorIssues) {
|
|
1287
|
-
const issues = [...priorIssues];
|
|
1288
|
-
const candidates = [
|
|
1289
|
-
...preferredCandidates,
|
|
1290
|
-
// One unique name per fallback dir — collisions on fixed names cannot exhaust this.
|
|
1291
|
-
...uniqueFallbackDirs.map((dir) => join(dir, stem === UNIQUE_ERROR_FALLBACK_STEM ? uniqueErrorFallbackName() : `${stem}.${randomUUID()}.json`)),
|
|
1292
|
-
];
|
|
1293
|
-
for (let i = 0; i < candidates.length; i += 1) {
|
|
1294
|
-
const path = candidates[i];
|
|
1295
|
-
const payload = issues.length === 0
|
|
1296
|
-
? basePayload
|
|
1297
|
-
: { ...basePayload, publicationIssues: issues };
|
|
1298
|
-
try {
|
|
1299
|
-
await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
1300
|
-
return { path, issues };
|
|
1301
|
-
}
|
|
1302
|
-
catch (error) {
|
|
1303
|
-
issues.push(publicationAttemptFromError(path, error));
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
const last = issues.at(-1);
|
|
1307
|
-
const error = new Error(last?.diagnostic ?? "unable to write durable failure artifact");
|
|
1308
|
-
if (last?.identity?.name !== undefined && last.identity.name !== "") {
|
|
1309
|
-
error.name = last.identity.name;
|
|
1310
|
-
}
|
|
1311
|
-
if (last?.identity?.code !== undefined) {
|
|
1312
|
-
error.code = last.identity.code;
|
|
1313
|
-
}
|
|
1314
|
-
error.publicationAttempts = issues;
|
|
1315
|
-
throw error;
|
|
1316
|
-
}
|
|
1317
|
-
export async function publishFailureArtifacts(admitted, failure, authority, onErrorPublished, recordAttemptHistory = false) {
|
|
1318
|
-
const { sessionDirectory, sessionFile } = coordinatesFromAdmitted(authority, admitted);
|
|
1319
|
-
const { baseDir, attempt: baseAttempt } = await resolveFailureArtifactsBase(admitted.runDirectory);
|
|
1320
|
-
const priorIssues = baseAttempt === undefined ? [] : [baseAttempt];
|
|
1321
|
-
// #419: a dispatched failure joins history before fixed-name views change.
|
|
1322
|
-
// Re-projecting an existing failure does not append. History write failure
|
|
1323
|
-
// rides publicationIssues rather than stranding the controlled failure.
|
|
1324
|
-
if (recordAttemptHistory) {
|
|
1213
|
+
export async function publishFailureTerminal(admitted, failure, coordinates, onErrorPublished, scope) {
|
|
1214
|
+
// #419: a dispatched failure joins history before the terminal changes. A history
|
|
1215
|
+
// write failure never strands the failure terminal; it is rethrown after it.
|
|
1216
|
+
let historyFailure;
|
|
1217
|
+
let historyFailed = false;
|
|
1218
|
+
const failureOutcome = { kind: "failure", role: admitted.role, ...failure };
|
|
1219
|
+
let attemptHistoryIdentity;
|
|
1220
|
+
if (scope?.recordAttemptHistory === true) {
|
|
1325
1221
|
try {
|
|
1326
|
-
await appendRunAttemptHistory(
|
|
1327
|
-
|
|
1328
|
-
});
|
|
1222
|
+
const pointer = await appendRunAttemptHistory(attemptHistorySource(admitted, coordinates), failureOutcome);
|
|
1223
|
+
attemptHistoryIdentity = pointer.identity;
|
|
1329
1224
|
}
|
|
1330
1225
|
catch (error) {
|
|
1331
|
-
|
|
1226
|
+
historyFailure = error;
|
|
1227
|
+
historyFailed = true;
|
|
1332
1228
|
}
|
|
1333
1229
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
try {
|
|
1337
|
-
await clearOppositeTerminalArtifactFace(admitted.runDirectory);
|
|
1230
|
+
else {
|
|
1231
|
+
attemptHistoryIdentity = await belongingAttemptHistoryIdentity(admitted.runDirectory, scope);
|
|
1338
1232
|
}
|
|
1339
|
-
|
|
1340
|
-
priorIssues.push(publicationAttemptFromError(roleRunArtifactsDirectory(admitted.runDirectory), error));
|
|
1341
|
-
}
|
|
1342
|
-
// Prefer conventional names; unique fallback dirs keep colliding fixed paths
|
|
1343
|
-
// from stranding the original failure outside settlement. Include the ledger
|
|
1344
|
-
// runs/ parent so a locked run directory (EACCES) cannot exhaust durability.
|
|
1345
|
-
const underArtifacts = baseDir === roleRunArtifactsDirectory(admitted.runDirectory);
|
|
1346
|
-
const uniqueFallbackDirs = uniqueFailureFallbackDirs(admitted.runDirectory, baseDir);
|
|
1347
|
-
const errorCandidates = underArtifacts
|
|
1348
|
-
? [
|
|
1349
|
-
join(baseDir, RUN_TERMINAL_ERROR_FILE),
|
|
1350
|
-
join(baseDir, RUN_TERMINAL_ERROR_SETTLEMENT_FILE),
|
|
1351
|
-
join(admitted.runDirectory, RUN_TERMINAL_ERROR_SETTLEMENT_FILE),
|
|
1352
|
-
]
|
|
1353
|
-
: [
|
|
1354
|
-
join(baseDir, RUN_TERMINAL_ERROR_SETTLEMENT_FILE),
|
|
1355
|
-
join(baseDir, RUN_TERMINAL_ERROR_FILE),
|
|
1356
|
-
];
|
|
1357
|
-
const evidenceCandidates = underArtifacts
|
|
1358
|
-
? [
|
|
1359
|
-
join(baseDir, RUN_TERMINAL_EVIDENCE_FILE),
|
|
1360
|
-
join(baseDir, "evidence.settlement.json"),
|
|
1361
|
-
join(admitted.runDirectory, "evidence.settlement.json"),
|
|
1362
|
-
]
|
|
1363
|
-
: [
|
|
1364
|
-
join(baseDir, "evidence.settlement.json"),
|
|
1365
|
-
join(baseDir, RUN_TERMINAL_EVIDENCE_FILE),
|
|
1366
|
-
];
|
|
1367
|
-
const errorPayloadBase = {
|
|
1233
|
+
writeRunTerminal(admitted.runDirectory, "error", {
|
|
1368
1234
|
kind: "error",
|
|
1369
1235
|
role: admitted.role,
|
|
1370
1236
|
runId: admitted.runId,
|
|
@@ -1375,24 +1241,13 @@ export async function publishFailureArtifacts(admitted, failure, authority, onEr
|
|
|
1375
1241
|
// This package's own facts, kept beside the host's report.
|
|
1376
1242
|
...(failure.packageFact === undefined ? {} : { packageFact: failure.packageFact }),
|
|
1377
1243
|
...(failure.stderr === undefined ? {} : { stderr: failure.stderr }),
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
admittedRequestPath: admitted.admittedRequestPath,
|
|
1386
|
-
attachments: acceptedArtifactAttachmentRefs(admitted.attachments),
|
|
1387
|
-
...(failure.cause === undefined ? {} : { failureCause: failure.cause }),
|
|
1388
|
-
};
|
|
1389
|
-
const evidenceWrite = await writeFailureJsonRetainingCause(evidenceCandidates, uniqueFallbackDirs, "evidence", evidencePayload,
|
|
1390
|
-
// Evidence records the same publication collisions observed placing the error body.
|
|
1391
|
-
errorWrite.issues);
|
|
1392
|
-
return [
|
|
1393
|
-
{ kind: "error", path: errorWrite.path },
|
|
1394
|
-
{ kind: "evidence", path: evidenceWrite.path },
|
|
1395
|
-
];
|
|
1244
|
+
...terminalBelongingFields(attemptHistoryIdentity, scope),
|
|
1245
|
+
});
|
|
1246
|
+
const path = runCurrentPath(admitted.runDirectory);
|
|
1247
|
+
onErrorPublished?.(path);
|
|
1248
|
+
if (historyFailed)
|
|
1249
|
+
throw historyFailure;
|
|
1250
|
+
return [{ kind: "error", path }];
|
|
1396
1251
|
}
|
|
1397
1252
|
/**
|
|
1398
1253
|
* Shared controlled-failure Terminal settlement (#107 ownership).
|
|
@@ -1406,7 +1261,7 @@ export async function settleFailureTerminalResult(admitted, failure, authority,
|
|
|
1406
1261
|
let artifacts = [];
|
|
1407
1262
|
if (options.previewOnly !== true) {
|
|
1408
1263
|
try {
|
|
1409
|
-
artifacts = await
|
|
1264
|
+
artifacts = await publishFailureTerminal(admitted, failure, coordinates, options.onErrorPublished, options);
|
|
1410
1265
|
}
|
|
1411
1266
|
catch (error) {
|
|
1412
1267
|
await noteSettlementFault(admitted.runDirectory, options, `failure artifact publication failed beside host terminal: ${describeErrorIdentity(error)}`);
|
|
@@ -93,13 +93,9 @@ function roleRunPlacement(ledgerHome, input) {
|
|
|
93
93
|
runDirectory,
|
|
94
94
|
sessionDirectory,
|
|
95
95
|
sessionFile: sessionFileIn(sessionDirectory),
|
|
96
|
-
artifactsDirectory: roleRunArtifactsDirectory(runDirectory),
|
|
97
96
|
attachmentsDirectory: join(runDirectory, "attachments")
|
|
98
97
|
};
|
|
99
98
|
}
|
|
100
|
-
function roleRunArtifactsDirectory(runDirectory) {
|
|
101
|
-
return join(runDirectory, "artifacts");
|
|
102
|
-
}
|
|
103
99
|
function isUnboundRunDirectory(runDirectory) {
|
|
104
100
|
return runDirectory.replaceAll("\\", "/").includes("/unbound/runs/");
|
|
105
101
|
}
|
|
@@ -107,12 +103,7 @@ function ensureRoleRunDirectory(ledgerHome, directory) {
|
|
|
107
103
|
return ensureRealDirectoryTree(ledgerHome, directory);
|
|
108
104
|
}
|
|
109
105
|
function ensureRoleRunPlacement(ledgerHome, placement) {
|
|
110
|
-
|
|
111
|
-
placement.sessionDirectory,
|
|
112
|
-
placement.attachmentsDirectory
|
|
113
|
-
]) {
|
|
114
|
-
ensureRoleRunDirectory(ledgerHome, directory);
|
|
115
|
-
}
|
|
106
|
+
ensureRoleRunDirectory(ledgerHome, placement.sessionDirectory);
|
|
116
107
|
}
|
|
117
108
|
export {
|
|
118
109
|
ROLE_RUN_SESSION_FILENAME,
|
|
@@ -123,7 +114,6 @@ export {
|
|
|
123
114
|
isUnboundRunDirectory,
|
|
124
115
|
listBookRunDirectories,
|
|
125
116
|
parseRunLeaf,
|
|
126
|
-
roleRunArtifactsDirectory,
|
|
127
117
|
roleRunPlacement,
|
|
128
118
|
runDirectoryFromSessionDirectory,
|
|
129
119
|
runDirectoryOfSessionFile,
|