@akagilnc/pi-workflow-roles 0.1.5714 → 0.1.5753
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-host/production-host.js +517 -516
- package/dist/analyst-gate-cycles-read.js +7 -6
- package/dist/archivist-record-pointer.js +9 -45
- package/dist/auditor-soul.js +7 -5
- package/dist/external-host-turn-loop.js +2 -16
- package/dist/gatekeeper-role.js +14 -1
- package/dist/headless-host/production-host.js +517 -516
- package/dist/judge-role.js +1 -1
- package/dist/migrate-book-topology.js +61 -51
- package/dist/navigator-public-session.js +17 -8
- package/dist/notary-contracts.js +2 -2
- package/dist/notary-source-run.js +2 -2
- package/dist/packaged-role-registry.js +0 -10
- package/dist/public-cli/cli.js +13 -6
- package/dist/public-cli/instruction-seat-run.js +157 -138
- package/dist/public-cli/invocation.js +35 -0
- package/dist/public-cli/main.js +497 -505
- package/dist/public-cli/post-admission.js +11 -12
- package/dist/public-cli/run-lifecycle.js +56 -34
- package/dist/public-cli/seat-ticket-binding.js +8 -7
- package/dist/public-cli/settlement.js +4 -126
- package/dist/public-cli/terminal.js +3 -3
- package/dist/public-role-summons.js +14 -6
- package/dist/review-submission.js +47 -14
- package/dist/role-run-relocation.js +4 -3
- package/dist/role-submission-declarations.js +5 -5
- package/dist/run-dossier.js +6 -21
- package/dist/run-terminal-artifacts.js +11 -0
- package/dist/secretariat-contracts.js +0 -4
- package/dist/submission-gate.js +58 -42
- package/dist/submission-ledger.js +3 -2
- package/package.json +1 -1
- package/souls/notary.md +6 -5
- package/src/analyst-gate-cycles-read.ts +7 -6
- package/src/analyst-ledger.ts +2 -2
- package/src/archivist-record-pointer.ts +10 -64
- package/src/auditor-soul.ts +7 -5
- package/src/countersign-role.ts +5 -5
- package/src/external-host-turn-loop.ts +2 -23
- package/src/gatekeeper-role.ts +24 -3
- package/src/judge-role.ts +1 -1
- package/src/navigator-public-session.ts +22 -10
- package/src/notary-contracts.ts +3 -2
- package/src/notary-source-run.ts +2 -7
- package/src/packaged-role-registry.ts +0 -13
- package/src/public-cli/cli.ts +18 -4
- package/src/public-cli/instruction-seat-run.ts +213 -155
- package/src/public-cli/invocation.ts +39 -0
- package/src/public-cli/post-admission.ts +11 -9
- package/src/public-cli/run-lifecycle.ts +80 -33
- package/src/public-cli/seat-ticket-binding.ts +9 -7
- package/src/public-cli/settlement.ts +3 -167
- package/src/public-cli/terminal.ts +3 -3
- package/src/public-role-summons.ts +40 -12
- package/src/review-submission.ts +72 -21
- package/src/role-run-relocation.ts +4 -3
- package/src/role-runtime.ts +3 -3
- package/src/role-submission-declarations.ts +5 -5
- package/src/run-dossier.ts +7 -19
- package/src/run-terminal-artifacts.ts +13 -0
- package/src/secretariat-contracts.ts +0 -19
- package/src/submission-gate.ts +75 -62
- package/src/submission-ledger.ts +3 -6
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* Role runners supply only turn request projection and narrow settlement adapters.
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
9
|
import { writeFile } from "node:fs/promises";
|
|
11
|
-
import { buildAutoResumeContinuationPrompt, findRunDirectoryById, readRoleRunState, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
10
|
+
import { buildAutoResumeContinuationPrompt, findRunDirectoryById, resolveLiveRunDirectoryPath, readRoleRunState, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
12
11
|
import { CliUsageError } from "./cli-errors.js";
|
|
13
12
|
import { projectPublicTurnAxes } from "./turn-request.js";
|
|
14
13
|
import { appendCallerFileFlagPaths, bindAdmittedTicketNumber, relocateAdmittedRunToTicket, } from "./invocation.js";
|
|
@@ -65,7 +64,7 @@ function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
/**
|
|
68
|
-
* #1171: after the host returns,
|
|
67
|
+
* #1171: after the host returns, follow the leg's placement (report-ticket may
|
|
69
68
|
* have moved it mid-turn in the tool process). Settlement and render use the
|
|
70
69
|
* live path; lease cleanup follows when the held path changed.
|
|
71
70
|
* Failure is not best-effort: a missing former path with no replacement must not
|
|
@@ -73,14 +72,14 @@ function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
|
|
|
73
72
|
*/
|
|
74
73
|
export async function refreshAdmittedPlacementAfterHostTurn(admitted, authority, heldLease) {
|
|
75
74
|
const home = homeFromRunDirectory(admitted.runDirectory);
|
|
76
|
-
const found = await
|
|
77
|
-
if (found === undefined
|
|
75
|
+
const found = await resolveLiveRunDirectoryPath(admitted.runDirectory, home);
|
|
76
|
+
if (found === undefined) {
|
|
78
77
|
throw new Error(`admitted run ${admitted.runId} missing after host turn; not found under ${home}`);
|
|
79
78
|
}
|
|
80
79
|
// Live identity first: later board-ticket read must not leave settlement on the
|
|
81
80
|
// pre-relocate path when facts are damaged (#1171 F4-R1).
|
|
82
81
|
let relocation;
|
|
83
|
-
if (found !==
|
|
82
|
+
if (found !== admitted.runDirectory) {
|
|
84
83
|
const oldRunDirectory = admitted.runDirectory;
|
|
85
84
|
const coords = { ...authority.decode(admitted.principal) };
|
|
86
85
|
rewritePrincipalSessionPaths(coords, oldRunDirectory, found);
|
|
@@ -92,11 +91,11 @@ export async function refreshAdmittedPlacementAfterHostTurn(admitted, authority,
|
|
|
92
91
|
}
|
|
93
92
|
// Board re-read discovers a ticket written mid-turn while still unbound
|
|
94
93
|
// (report-ticket board page). Already-bound identity needs no discovery —
|
|
95
|
-
//
|
|
94
|
+
// placement refresh already followed the retained path. This is not damage
|
|
96
95
|
// recovery: when unbound, readBoardTicketNumber damage/non-ENOENT IO still
|
|
97
96
|
// propagates (失败诚实; notary: no damaged-control-plane → no-ticket wash).
|
|
98
97
|
if (admitted.ticketNumber === undefined) {
|
|
99
|
-
const boardTicket = await readBoardTicketNumber(found
|
|
98
|
+
const boardTicket = await readBoardTicketNumber(found);
|
|
100
99
|
if (boardTicket !== undefined) {
|
|
101
100
|
admitted.ticketNumber = boardTicket;
|
|
102
101
|
}
|
|
@@ -1384,10 +1383,10 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
1384
1383
|
return { exitCode: error instanceof CliUsageError ? 2 : 1 };
|
|
1385
1384
|
}
|
|
1386
1385
|
}
|
|
1387
|
-
async function pointExistingRunFailure(home, runId, authority, io, thrown) {
|
|
1386
|
+
async function pointExistingRunFailure(home, runId, authority, io, thrown, knownRunDirectory) {
|
|
1388
1387
|
let foundRunDirectory;
|
|
1389
1388
|
try {
|
|
1390
|
-
foundRunDirectory = await findRunDirectoryById(home, runId);
|
|
1389
|
+
foundRunDirectory = await findRunDirectoryById(home, runId, undefined, undefined, knownRunDirectory);
|
|
1391
1390
|
}
|
|
1392
1391
|
catch {
|
|
1393
1392
|
return false;
|
|
@@ -1408,8 +1407,8 @@ async function pointExistingRunFailure(home, runId, authority, io, thrown) {
|
|
|
1408
1407
|
await presentResumeFailurePointer(io, thrown, (failure) => writeResumeDiagnostic(runDirectory, runId, failure, record.role));
|
|
1409
1408
|
return true;
|
|
1410
1409
|
}
|
|
1411
|
-
export async function presentLocatedResumeFailure(home, runId, authority, io, thrown) {
|
|
1412
|
-
return await pointExistingRunFailure(home, runId, authority, io, thrown);
|
|
1410
|
+
export async function presentLocatedResumeFailure(home, runId, authority, io, thrown, knownRunDirectory) {
|
|
1411
|
+
return await pointExistingRunFailure(home, runId, authority, io, thrown, knownRunDirectory);
|
|
1413
1412
|
}
|
|
1414
1413
|
function writeResumeFailurePointer(io, errorPath) {
|
|
1415
1414
|
io.stderr(formatCliDiagnostic(`续跑失败,当次错误记录:${errorPath}`));
|
|
@@ -5,16 +5,17 @@
|
|
|
5
5
|
* any existing run with an available Pi session principal may be resumed; caller decides.
|
|
6
6
|
* Prose is never regex-classified as quota evidence.
|
|
7
7
|
*/
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
8
9
|
import { chmod, lstat, open, readdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
9
10
|
import { basename, isAbsolute, join } from "node:path";
|
|
10
|
-
import { activationBookDirectory, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
|
|
11
|
+
import { activationBookDirectory, resolveActivationLedgerHome, tryBookKeyFromAkRolesPath, } from "../activation-ledger-topology.js";
|
|
11
12
|
import { findRoleRunDirectory, listBookRunDirectories, parseRunLeaf, sessionFileIn } from "../role-run-placement.js";
|
|
12
13
|
import { isSafePositiveTicketNumber } from "../run-ticket-number.js";
|
|
13
14
|
import { CliUsageError } from "./cli-errors.js";
|
|
14
15
|
import { parseCollectorRepository } from "../collector-config.js";
|
|
15
16
|
import { retainedRunPathsMatch, rewriteAdmittedRoleRunPage, rewriteRunDirectoryPathValue, } from "../role-run-relocation.js";
|
|
16
17
|
import { packagedResumeSourcePath, packagedRoleMetadata, } from "../packaged-role-registry.js";
|
|
17
|
-
import { admittedReviewerBaseFault, recordEffectiveInvocationModel, requireAuthorityRef, isReviewerLens, normalizeAdmittedAttachment, } from "./invocation.js";
|
|
18
|
+
import { admittedReviewerBaseFault, admittedSourceRunPath, recordEffectiveInvocationModel, requireAuthorityRef, isReviewerLens, normalizeAdmittedAttachment, } from "./invocation.js";
|
|
18
19
|
import { readPageSync, writeSectionSync } from "../run-dossier.js";
|
|
19
20
|
import { isRecord, errorText, isEnoent } from "../unknown-value.js";
|
|
20
21
|
/** Default for the #422 configurable single-call auto-resume ceiling
|
|
@@ -53,9 +54,7 @@ function parseSameTicketSummonsMaterials(raw) {
|
|
|
53
54
|
const attachmentPaths = Array.isArray(record.attachmentPaths)
|
|
54
55
|
? record.attachmentPaths.filter((p) => typeof p === "string")
|
|
55
56
|
: undefined;
|
|
56
|
-
const sourceRunPath =
|
|
57
|
-
? record.sourceRunPath
|
|
58
|
-
: undefined;
|
|
57
|
+
const sourceRunPath = admittedSourceRunPath(record);
|
|
59
58
|
let sourceRun;
|
|
60
59
|
if (isRecord(record.sourceRun)) {
|
|
61
60
|
const sr = record.sourceRun;
|
|
@@ -607,12 +606,20 @@ export async function acquireRunWriterLease(runDirectory, onCleanupFailure) {
|
|
|
607
606
|
}
|
|
608
607
|
/**
|
|
609
608
|
* Locate a Role run directory by run ID under the ledger books home.
|
|
610
|
-
*
|
|
609
|
+
* A supplied directory is checked against the ID and retained without a book walk.
|
|
610
|
+
* Returns undefined when the bare ID is unknown.
|
|
611
611
|
* Walk surface = listBookRunDirectories (flat legacy + subject-tree).
|
|
612
612
|
* Collects every match under the supplied book/role filters: zero → undefined,
|
|
613
613
|
* one → path, many → loud ambiguity (never readdir-first).
|
|
614
614
|
*/
|
|
615
|
-
export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole) {
|
|
615
|
+
export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole, knownRunDirectory) {
|
|
616
|
+
if (typeof knownRunDirectory === "string" && knownRunDirectory.trim() !== "") {
|
|
617
|
+
const parsed = parseRunLeaf(basename(knownRunDirectory));
|
|
618
|
+
if (parsed === undefined || parsed.runId !== runId) {
|
|
619
|
+
throw new CliUsageError(`run directory does not match run id ${runId}: ${knownRunDirectory}`);
|
|
620
|
+
}
|
|
621
|
+
return knownRunDirectory;
|
|
622
|
+
}
|
|
616
623
|
if (runId.trim() === "")
|
|
617
624
|
return undefined;
|
|
618
625
|
const ledgerHome = resolveActivationLedgerHome(home);
|
|
@@ -631,6 +638,16 @@ export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole) {
|
|
|
631
638
|
.filter((bookKey) => onlyBookKey === undefined || bookKey === onlyBookKey)
|
|
632
639
|
.map((bookKey) => activationBookDirectory(ledgerHome, bookKey)), runId, onlyRole);
|
|
633
640
|
}
|
|
641
|
+
/** Keep an existing placement; rediscover only after its recorded path disappeared. */
|
|
642
|
+
export async function resolveLiveRunDirectoryPath(recordedPath, home, onlyBookKey) {
|
|
643
|
+
if (existsSync(recordedPath))
|
|
644
|
+
return recordedPath;
|
|
645
|
+
const parsed = parseRunLeaf(basename(recordedPath));
|
|
646
|
+
const bookKey = onlyBookKey ?? tryBookKeyFromAkRolesPath(recordedPath);
|
|
647
|
+
if (parsed === undefined || bookKey === undefined)
|
|
648
|
+
return undefined;
|
|
649
|
+
return findRunDirectoryById(home, parsed.runId, bookKey, parsed.role);
|
|
650
|
+
}
|
|
634
651
|
/**
|
|
635
652
|
* Parent-run binding on a retained officer run (#747 / #1166).
|
|
636
653
|
* Typed sourceRunPath only — instruction bytes are never a path protocol.
|
|
@@ -638,12 +655,7 @@ export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole) {
|
|
|
638
655
|
*/
|
|
639
656
|
export async function readRunParentPath(runDirectory) {
|
|
640
657
|
const record = readPageSync(runDirectory, "admitted");
|
|
641
|
-
|
|
642
|
-
return undefined;
|
|
643
|
-
if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
|
|
644
|
-
return record.sourceRunPath;
|
|
645
|
-
}
|
|
646
|
-
return undefined;
|
|
658
|
+
return admittedSourceRunPath(record);
|
|
647
659
|
}
|
|
648
660
|
/**
|
|
649
661
|
* True when durable run-state names a session principal that has actually formed
|
|
@@ -681,6 +693,15 @@ async function runHasFormedSessionPrincipal(runDirectory) {
|
|
|
681
693
|
* Only a truly missing book directory means no history; damage/permission errors propagate.
|
|
682
694
|
*/
|
|
683
695
|
export async function findLatestRunIdForSeatTicket(input) {
|
|
696
|
+
const found = await findLatestRunForSeatTicket(input);
|
|
697
|
+
return found?.runId;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Latest retained seat run under one book/parent (and optional ticket), with its
|
|
701
|
+
* directory — callers that already scoped the search must not drop the path and
|
|
702
|
+
* re-resolve runId across every book (#1195).
|
|
703
|
+
*/
|
|
704
|
+
export async function findLatestRunForSeatTicket(input) {
|
|
684
705
|
if (input.parentRunPath.trim() === "") {
|
|
685
706
|
return undefined;
|
|
686
707
|
}
|
|
@@ -708,8 +729,8 @@ export async function findLatestRunIdForSeatTicket(input) {
|
|
|
708
729
|
// Durable fact: never resume-select a provisional that never formed principal.
|
|
709
730
|
if (!(await runHasFormedSessionPrincipal(runDirectory)))
|
|
710
731
|
continue;
|
|
711
|
-
if (best === undefined || runId > best)
|
|
712
|
-
best = runId;
|
|
732
|
+
if (best === undefined || runId > best.runId)
|
|
733
|
+
best = { runId, runDirectory };
|
|
713
734
|
}
|
|
714
735
|
return best;
|
|
715
736
|
}
|
|
@@ -732,8 +753,8 @@ function restoredTicketFields(fields) {
|
|
|
732
753
|
...(fields.ticketNumber === undefined ? {} : { ticketNumber: fields.ticketNumber }),
|
|
733
754
|
};
|
|
734
755
|
}
|
|
735
|
-
async function loadResumableRunRecord(home, runId, authority) {
|
|
736
|
-
const runDirectory = await findRunDirectoryById(home, runId);
|
|
756
|
+
async function loadResumableRunRecord(home, runId, authority, knownRunDirectory) {
|
|
757
|
+
const runDirectory = await findRunDirectoryById(home, runId, undefined, undefined, knownRunDirectory);
|
|
737
758
|
if (runDirectory === undefined) {
|
|
738
759
|
throw new CliUsageError(`unknown role run id: ${runId}`);
|
|
739
760
|
}
|
|
@@ -842,9 +863,7 @@ async function loadResumableRunRecord(home, runId, authority) {
|
|
|
842
863
|
}
|
|
843
864
|
}
|
|
844
865
|
// Notary — admitted source-run locator identity (#633 resume).
|
|
845
|
-
|
|
846
|
-
sourceRunPath = record.sourceRunPath;
|
|
847
|
-
}
|
|
866
|
+
sourceRunPath = admittedSourceRunPath(record);
|
|
848
867
|
if (isRecord(record.sourceRun)) {
|
|
849
868
|
const sr = record.sourceRun;
|
|
850
869
|
if (typeof sr.runId === "string" &&
|
|
@@ -930,11 +949,9 @@ async function loadResumableRunRecord(home, runId, authority) {
|
|
|
930
949
|
throw new CliUsageError(`role run invocation identity is unreadable: ${runId}`, { cause: error });
|
|
931
950
|
}
|
|
932
951
|
}
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (referencedRunId !== undefined && referencedRunId !== "") {
|
|
937
|
-
const currentSourceRunDirectory = await findRunDirectoryById(home, referencedRunId, run.bookKey, referencedRole);
|
|
952
|
+
const recordedSourceDirectory = sourceRunPath ?? sourceRun?.runDirectory;
|
|
953
|
+
if (recordedSourceDirectory !== undefined) {
|
|
954
|
+
const currentSourceRunDirectory = await resolveLiveRunDirectoryPath(recordedSourceDirectory, home, run.bookKey);
|
|
938
955
|
if (currentSourceRunDirectory !== undefined) {
|
|
939
956
|
sourceRunPath = currentSourceRunDirectory;
|
|
940
957
|
if (sourceRun !== undefined) {
|
|
@@ -997,8 +1014,8 @@ function seatLoadedResult(loaded, admitted) {
|
|
|
997
1014
|
run: loaded.run,
|
|
998
1015
|
};
|
|
999
1016
|
}
|
|
1000
|
-
export async function peekRoleRunRole(home, runId) {
|
|
1001
|
-
const runDirectory = await findRunDirectoryById(home, runId);
|
|
1017
|
+
export async function peekRoleRunRole(home, runId, options) {
|
|
1018
|
+
const runDirectory = await findRunDirectoryById(home, runId, undefined, undefined, options?.runDirectory);
|
|
1002
1019
|
if (runDirectory === undefined)
|
|
1003
1020
|
return undefined;
|
|
1004
1021
|
const run = await readRoleRunIdentity(runDirectory);
|
|
@@ -1009,8 +1026,8 @@ export async function peekRoleRunRole(home, runId) {
|
|
|
1009
1026
|
* selects the existing restore checks. The stored native host session id is
|
|
1010
1027
|
* read on the public explicit resume seam, not from this load.
|
|
1011
1028
|
*/
|
|
1012
|
-
export async function loadResumablePublicRole(home, runId, authority) {
|
|
1013
|
-
const loaded = await loadResumableRunRecord(home, runId, authority);
|
|
1029
|
+
export async function loadResumablePublicRole(home, runId, authority, options) {
|
|
1030
|
+
const loaded = await loadResumableRunRecord(home, runId, authority, options?.runDirectory);
|
|
1014
1031
|
return seatLoadedResult(loaded, admitResumedRole(loaded));
|
|
1015
1032
|
}
|
|
1016
1033
|
/**
|
|
@@ -1028,25 +1045,30 @@ function admitResumedRole(loaded) {
|
|
|
1028
1045
|
const base = resumedBaseAdmitted(loaded);
|
|
1029
1046
|
switch (record.admission) {
|
|
1030
1047
|
case "instruction": {
|
|
1048
|
+
// Restore sourceRunPath whenever the admitted page has it (auditor/inspector/…).
|
|
1049
|
+
// Downstream continueParent / seal-id belonging needs this fact (#1195).
|
|
1050
|
+
const withSource = fields.sourceRunPath === undefined
|
|
1051
|
+
? base
|
|
1052
|
+
: { ...base, sourceRunPath: fields.sourceRunPath };
|
|
1031
1053
|
if (packagedResumeSourcePath(role)) {
|
|
1032
1054
|
const admitted = {
|
|
1033
1055
|
role: "inspector",
|
|
1034
|
-
...
|
|
1035
|
-
...(fields.sourceRunPath === undefined
|
|
1036
|
-
? {}
|
|
1037
|
-
: { sourceRunPath: fields.sourceRunPath }),
|
|
1056
|
+
...withSource,
|
|
1038
1057
|
};
|
|
1039
1058
|
return admitted;
|
|
1040
1059
|
}
|
|
1041
1060
|
return {
|
|
1042
1061
|
role,
|
|
1043
|
-
...
|
|
1062
|
+
...withSource,
|
|
1044
1063
|
};
|
|
1045
1064
|
}
|
|
1046
1065
|
case "court-materials": {
|
|
1047
1066
|
const admitted = {
|
|
1048
1067
|
role: "countersign",
|
|
1049
1068
|
...base,
|
|
1069
|
+
...(fields.sourceRunPath === undefined
|
|
1070
|
+
? {}
|
|
1071
|
+
: { sourceRunPath: fields.sourceRunPath }),
|
|
1050
1072
|
};
|
|
1051
1073
|
return admitted;
|
|
1052
1074
|
}
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
* freshSummons is required so no seat can drift back into its own skip branch.
|
|
10
10
|
*/
|
|
11
11
|
import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
|
|
12
|
-
import {
|
|
12
|
+
import { findLatestRunForSeatTicket, } from "./run-lifecycle.js";
|
|
13
13
|
/**
|
|
14
14
|
* Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987).
|
|
15
15
|
* A supplied typed ticket only narrows the prior-run search. When a prior run is
|
|
16
|
-
* found, resume carries this summons' materials
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* found, resume carries this summons' materials and the located runDirectory so
|
|
17
|
+
* load does not re-walk every book for the bare runId (#1195). Lookup/resume
|
|
18
|
+
* failures propagate (失败诚实) — never wash into a fresh mint. Returns undefined
|
|
19
|
+
* when the caller declared an explicit fresh summons (`ak-role new`), the parent
|
|
19
20
|
* path is blank, or no prior run exists; those mint new. freshSummons is
|
|
20
21
|
* required so no seat can drift back into its own skip branch.
|
|
21
22
|
*/
|
|
@@ -24,14 +25,14 @@ export async function tryResumeSameTicketSeatRun(input) {
|
|
|
24
25
|
return undefined;
|
|
25
26
|
if (input.parentRunPath.trim() === "")
|
|
26
27
|
return undefined;
|
|
27
|
-
const
|
|
28
|
+
const previous = await findLatestRunForSeatTicket({
|
|
28
29
|
home: input.home,
|
|
29
30
|
bookKey: resolveBookKeyFromGit(input.projectRoot),
|
|
30
31
|
role: input.role,
|
|
31
32
|
parentRunPath: input.parentRunPath,
|
|
32
33
|
...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
|
|
33
34
|
});
|
|
34
|
-
if (
|
|
35
|
+
if (previous === undefined)
|
|
35
36
|
return undefined;
|
|
36
|
-
return await input.resume(
|
|
37
|
+
return await input.resume(previous.runId, input.summons, previous.runDirectory);
|
|
37
38
|
}
|
|
@@ -18,9 +18,8 @@ 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
20
|
import { homeFromRunDirectory, } from "./invocation.js";
|
|
21
|
-
import { SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, } from "../secretariat-contracts.js";
|
|
22
21
|
import { findLatestDurablePackagedRoleTerminal, NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, } from "../navigator-invocation-identity.js";
|
|
23
|
-
import {
|
|
22
|
+
import { packagedRoleAcceptedOutputTool, packagedRoleMetadata, } from "../packaged-role-registry.js";
|
|
24
23
|
import { NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, noReceiptLifecycleFacts, parseNoReceiptLifecycleFacts, priorReceiptContinuation, receiptAttemptPointer, } from "../receipt-delivery-policy.js";
|
|
25
24
|
import { readStateRowsSync, runCurrentPath } from "../run-dossier.js";
|
|
26
25
|
import { reportRunRecord } from "../sitian-facade.js";
|
|
@@ -135,13 +134,12 @@ function roleOutcomeFromRows(role, rows) {
|
|
|
135
134
|
return { outcome: { kind: "accepted", role, payloads }, ...belonging };
|
|
136
135
|
}
|
|
137
136
|
async function sealedLedgerOutcome(admitted, role, scope) {
|
|
138
|
-
const home = sealedLedgerHome(admitted);
|
|
139
137
|
// #879: when court scope is present, roleOutcome is this-court original only —
|
|
140
138
|
// never last-wins over an undivided historical array, and never falls back to
|
|
141
139
|
// full-run history when this court sealed zero rows. #836 presentation of full
|
|
142
140
|
// history stays on attachRecordedSubmissions / terminal.submissions (run-scoped).
|
|
143
141
|
if (scope?.courtAttemptId !== undefined && scope.courtAttemptId.length > 0) {
|
|
144
|
-
const thisCourt = await readAttemptScopedSubmissionRows(admitted.projectRoot, admitted.runId, scope.courtAttemptId,
|
|
142
|
+
const thisCourt = await readAttemptScopedSubmissionRows(admitted.projectRoot, admitted.runId, scope.courtAttemptId, ledgerReadScope(admitted, scope));
|
|
145
143
|
// Scope present + zero this-court rows → no this-court outcome (not run history).
|
|
146
144
|
return roleOutcomeFromRows(role, thisCourt);
|
|
147
145
|
}
|
|
@@ -1084,138 +1082,18 @@ function currentAttemptStartIndex(entries) {
|
|
|
1084
1082
|
}
|
|
1085
1083
|
return 0;
|
|
1086
1084
|
}
|
|
1087
|
-
/** Accepted-tool seats
|
|
1085
|
+
/** Accepted-tool seats: declaration checks only. Other-seat finals stay on their own runs. */
|
|
1088
1086
|
async function trySettleAcceptedSeatTerminalResult(admitted, authority, scope) {
|
|
1089
1087
|
const toolName = packagedRoleAcceptedOutputTool(admitted.role);
|
|
1090
1088
|
if (toolName === undefined) {
|
|
1091
1089
|
throw new Error(`accepted-seat settlement is not declared for ${admitted.role}`);
|
|
1092
1090
|
}
|
|
1093
|
-
|
|
1094
|
-
const record = packagedRoleMetadata(admitted.role);
|
|
1095
|
-
if (settled === undefined
|
|
1096
|
-
|| record === undefined
|
|
1097
|
-
|| !("projectCountersignTerminal" in record)
|
|
1098
|
-
|| record.projectCountersignTerminal !== true) {
|
|
1099
|
-
return settled;
|
|
1100
|
-
}
|
|
1101
|
-
return applySecretariatCountersignTerminal(admitted, authority, settled, scope);
|
|
1091
|
+
return settleSealedSeat(admitted, authority, scope, { acceptedOnly: false });
|
|
1102
1092
|
}
|
|
1103
1093
|
/** One settlement dispatch. The registry `settlement` leaf selects the path. */
|
|
1104
1094
|
export async function trySettlePublicSeat(admitted, authority, scope) {
|
|
1105
1095
|
return settleSeat(admitted, authority, scope);
|
|
1106
1096
|
}
|
|
1107
|
-
/**
|
|
1108
|
-
* #969 seat projection sole authority: 给事中 terminal (署|上呈) booked as a
|
|
1109
|
-
* durable custom entry (envelope-safe) — ledger accepted stays LLM params (#836).
|
|
1110
|
-
* Never scan toolResult rows: those are memory-only on headless/ACP (#617/#959).
|
|
1111
|
-
* Receipt bytes stay original; nested runId rides beside them.
|
|
1112
|
-
* Caller must only apply this when the terminal-defining submission actually
|
|
1113
|
-
* produced the officer entry (gate-bound converged, or 给事中 audit_escalation).
|
|
1114
|
-
*/
|
|
1115
|
-
function countersignTerminalFromEntries(entries) {
|
|
1116
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
1117
|
-
const entry = entries[index];
|
|
1118
|
-
if (entry?.type !== "custom")
|
|
1119
|
-
continue;
|
|
1120
|
-
if (entry.customType !== SECRETARIAT_GATE_OFFICER_ENTRY_TYPE)
|
|
1121
|
-
continue;
|
|
1122
|
-
const data = isRecord(entry.data)
|
|
1123
|
-
? entry.data
|
|
1124
|
-
: undefined;
|
|
1125
|
-
if (data === undefined || !packagedDurableOfficerEntry(data.officer))
|
|
1126
|
-
continue;
|
|
1127
|
-
if (data.receipt === undefined)
|
|
1128
|
-
continue;
|
|
1129
|
-
const runId = typeof data.runId === "string" && data.runId.trim() !== ""
|
|
1130
|
-
? data.runId
|
|
1131
|
-
: undefined;
|
|
1132
|
-
return {
|
|
1133
|
-
receipt: data.receipt,
|
|
1134
|
-
...(runId === undefined ? {} : { runId }),
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
return undefined;
|
|
1138
|
-
}
|
|
1139
|
-
function withCountersignTerminalFact(roleOutcome, officer) {
|
|
1140
|
-
const prior = roleOutcome.decisiveFacts !== undefined && isRecord(roleOutcome.decisiveFacts)
|
|
1141
|
-
? roleOutcome.decisiveFacts
|
|
1142
|
-
: {};
|
|
1143
|
-
return {
|
|
1144
|
-
...roleOutcome,
|
|
1145
|
-
decisiveFacts: {
|
|
1146
|
-
...prior,
|
|
1147
|
-
[SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY]: officer,
|
|
1148
|
-
},
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
/**
|
|
1152
|
-
* Terminal-defining secretariat submission owns officer projection.
|
|
1153
|
-
* Last readable secretariatStatus on accepted payloads decides: only gate-bound
|
|
1154
|
-
* converged projects a prior durable officer entry. Parent escalate bypasses the
|
|
1155
|
-
* gate and must not inherit a stale pass entry from an earlier turn (#969).
|
|
1156
|
-
*/
|
|
1157
|
-
function acceptedSecretariatDefinesOfficerProjection(roleOutcome) {
|
|
1158
|
-
const payloads = roleOutcome.payloads ?? [];
|
|
1159
|
-
for (let index = payloads.length - 1; index >= 0; index -= 1) {
|
|
1160
|
-
const payload = payloads[index];
|
|
1161
|
-
if (!isRecord(payload))
|
|
1162
|
-
continue;
|
|
1163
|
-
const status = payload.secretariatStatus;
|
|
1164
|
-
if (typeof status !== "string")
|
|
1165
|
-
continue;
|
|
1166
|
-
return status === "converged";
|
|
1167
|
-
}
|
|
1168
|
-
return false;
|
|
1169
|
-
}
|
|
1170
|
-
async function applySecretariatCountersignTerminal(admitted, authority, settled, scope) {
|
|
1171
|
-
const coordinates = coordinatesFromAdmitted(authority, admitted);
|
|
1172
|
-
let entries = [];
|
|
1173
|
-
try {
|
|
1174
|
-
entries = await readBoundSessionEntries(coordinates.sessionFile);
|
|
1175
|
-
}
|
|
1176
|
-
catch (error) {
|
|
1177
|
-
if (!isEnoent(error)) {
|
|
1178
|
-
await noteSettlementFault(admitted.runDirectory, scope, `secretariat officer read failed beside lawful terminal: ${describeErrorIdentity(error)}`);
|
|
1179
|
-
}
|
|
1180
|
-
return settled;
|
|
1181
|
-
}
|
|
1182
|
-
const officer = countersignTerminalFromEntries(entries);
|
|
1183
|
-
if (officer === undefined)
|
|
1184
|
-
return settled;
|
|
1185
|
-
// 给事中上呈: public payloads = officer receipt; runId + receipt fact beside.
|
|
1186
|
-
// This kind is produced only when beforeAccept booked the officer entry.
|
|
1187
|
-
if (settled.roleOutcome.kind === "audit_escalation") {
|
|
1188
|
-
return {
|
|
1189
|
-
...settled,
|
|
1190
|
-
roleOutcome: withCountersignTerminalFact({
|
|
1191
|
-
...settled.roleOutcome,
|
|
1192
|
-
payloads: [officer.receipt],
|
|
1193
|
-
}, officer),
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
// Pass (署): keep 中书省 payloads; present 给事中 判词 + runId via decisiveFacts.
|
|
1197
|
-
// Parent escalate accepted terminal must not project a stale prior pass entry.
|
|
1198
|
-
if (settled.roleOutcome.kind === "accepted") {
|
|
1199
|
-
if (!acceptedSecretariatDefinesOfficerProjection(settled.roleOutcome)) {
|
|
1200
|
-
return settled;
|
|
1201
|
-
}
|
|
1202
|
-
return {
|
|
1203
|
-
...settled,
|
|
1204
|
-
roleOutcome: withCountersignTerminalFact(settled.roleOutcome, officer),
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
return settled;
|
|
1208
|
-
}
|
|
1209
|
-
/**
|
|
1210
|
-
* After the audit gate returns, attach the secretariat officer fact onto the
|
|
1211
|
-
* terminal this turn already settled. Does not publish again — a second publish
|
|
1212
|
-
* would settle the same attempt twice.
|
|
1213
|
-
*/
|
|
1214
|
-
export async function attachPostAuditCountersignFact(admitted, authority, terminal, scope) {
|
|
1215
|
-
if (admitted.role !== "secretariat")
|
|
1216
|
-
return terminal;
|
|
1217
|
-
return applySecretariatCountersignTerminal(admitted, authority, terminal, scope);
|
|
1218
|
-
}
|
|
1219
1097
|
/**
|
|
1220
1098
|
* Replace the leg's terminal with the controlled failure. A write failure
|
|
1221
1099
|
* propagates: the caller notes it beside the host terminal.
|
|
@@ -49,9 +49,9 @@ export function formatTerminalResult(result) {
|
|
|
49
49
|
if (result.roleOutcome.kind === "failure") {
|
|
50
50
|
lines.push(`diagnostic\t${encodeTerminalField(result.roleOutcome.diagnostic)}`);
|
|
51
51
|
}
|
|
52
|
-
// Accepted volumes do not dump decisiveFacts unless
|
|
53
|
-
//
|
|
54
|
-
// audit_escalation already dumps every decisiveFact below.
|
|
52
|
+
// Accepted volumes do not dump internal decisiveFacts unless recorded.
|
|
53
|
+
// Other-seat finals stay on those runs (#1195); only unsettled-direction is
|
|
54
|
+
// visible here. audit_escalation already dumps every decisiveFact below.
|
|
55
55
|
if (result.roleOutcome.kind === "accepted"
|
|
56
56
|
&& result.roleOutcome.decisiveFacts?.directionUnsettled === true) {
|
|
57
57
|
lines.push("fact\tdirectionUnsettled\ttrue");
|
|
@@ -248,6 +248,7 @@ async function summonPublicRole(options) {
|
|
|
248
248
|
const stepped = await runPrepared(parse, (env) => runPublicInstructionSeatResume(
|
|
249
249
|
{
|
|
250
250
|
runId: resumeRunId,
|
|
251
|
+
...options.resumeRunDirectory === void 0 ? {} : { runDirectory: options.resumeRunDirectory },
|
|
251
252
|
summons: {
|
|
252
253
|
instruction,
|
|
253
254
|
instructionEmpty: instruction.length === 0
|
|
@@ -264,10 +265,13 @@ async function summonPublicRole(options) {
|
|
|
264
265
|
result = stepped.ok;
|
|
265
266
|
}
|
|
266
267
|
const stderr = captured?.stderrText();
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
let runDirectory = result.admitted?.runDirectory;
|
|
269
|
+
if (result.terminal !== void 0 && result.terminal.roleOutcome.role !== options.role && typeof result.terminal.runId === "string" && result.admitted?.runId !== result.terminal.runId) {
|
|
270
|
+
runDirectory = await (await import("./public-cli/run-lifecycle.js")).findRunDirectoryById(
|
|
271
|
+
home,
|
|
272
|
+
result.terminal.runId
|
|
273
|
+
) ?? runDirectory;
|
|
274
|
+
}
|
|
271
275
|
return {
|
|
272
276
|
exitCode: result.exitCode,
|
|
273
277
|
...result.terminal === void 0 ? {} : { terminal: result.terminal },
|
|
@@ -348,8 +352,12 @@ async function summonGateOfficer(options) {
|
|
|
348
352
|
}
|
|
349
353
|
let gateReviewInstruction;
|
|
350
354
|
if (options.reask === void 0 && options.submission !== void 0) {
|
|
351
|
-
const {
|
|
352
|
-
|
|
355
|
+
const { isTicketCourtCountersignSource } = await import("./run-terminal-artifacts.js");
|
|
356
|
+
const blindTicketCourt = options.officer === "notary" && isTicketCourtCountersignSource(options.sourceRunDirectory);
|
|
357
|
+
if (!blindTicketCourt) {
|
|
358
|
+
const { readableGateItem } = await import("./readable-gate-item.js");
|
|
359
|
+
gateReviewInstruction = readableGateItem(options.submission);
|
|
360
|
+
}
|
|
353
361
|
}
|
|
354
362
|
const { runIdFromRunDirectory } = await import("./run-terminal-artifacts.js");
|
|
355
363
|
const parentRunId = runIdFromRunDirectory(options.sourceRunDirectory);
|
|
@@ -5,20 +5,15 @@ export const REVIEW_SUBMISSION_OUTPUT_TOOL_NAME = "ak_submission_output";
|
|
|
5
5
|
/** Single schema copy of the review queue words. Runtime membership reads this set. */
|
|
6
6
|
export const REVIEW_QUEUE_WORDS = ["converged", "continue", "escalate"];
|
|
7
7
|
export const REVIEW_QUEUE_STATUSES = new Set(REVIEW_QUEUE_WORDS);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Type.Literal(REVIEW_QUEUE_WORDS[1]),
|
|
18
|
-
Type.Literal(REVIEW_QUEUE_WORDS[2]),
|
|
19
|
-
], {
|
|
20
|
-
description: "审核席本轮裁决的判别状态。",
|
|
21
|
-
}),
|
|
8
|
+
const reviewStatusField = Type.Union([
|
|
9
|
+
Type.Literal(REVIEW_QUEUE_WORDS[0]),
|
|
10
|
+
Type.Literal(REVIEW_QUEUE_WORDS[1]),
|
|
11
|
+
Type.Literal(REVIEW_QUEUE_WORDS[2]),
|
|
12
|
+
], {
|
|
13
|
+
description: "审核席本轮裁决的判别状态。",
|
|
14
|
+
});
|
|
15
|
+
/** Shared optional review narrative fields (generation guidance only). */
|
|
16
|
+
const reviewNarrativeFields = {
|
|
22
17
|
findings: Type.Optional(Type.Unknown({ description: "审核发现,原样留存" })),
|
|
23
18
|
reason: Type.Optional(Type.Unknown({ description: "上呈理由" })),
|
|
24
19
|
violations: Type.Optional(Type.Unknown({ description: "违规条目" })),
|
|
@@ -28,4 +23,42 @@ export const reviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Obj
|
|
|
28
23
|
note: Type.Optional(Type.Unknown({ description: "裁决附注" })),
|
|
29
24
|
evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
|
|
30
25
|
decisionGate: Type.Optional(Type.Unknown({ description: "需人权威处置的问题与选项;可说明 question、options,原样留存" })),
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* One object root. Codex structured output rejects a root anyOf, and status
|
|
29
|
+
* stays a required three-state enum with no omitted-status failure branch.
|
|
30
|
+
* External-dependency failure while the seat can still submit:
|
|
31
|
+
* docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md
|
|
32
|
+
*/
|
|
33
|
+
export const reviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Object({
|
|
34
|
+
status: reviewStatusField,
|
|
35
|
+
...reviewNarrativeFields,
|
|
36
|
+
}, { additionalProperties: true }));
|
|
37
|
+
/**
|
|
38
|
+
* #1195 ticket-court clause row: original ticket text, owner uuid (or null),
|
|
39
|
+
* and one-line derivation. Dual readings of runner context live in derivation.
|
|
40
|
+
* Generation-required on countersign/notary only; package still records as-is.
|
|
41
|
+
*/
|
|
42
|
+
export const courtClauseItemSchema = Type.Object({
|
|
43
|
+
clause: Type.String({
|
|
44
|
+
description: "票面条款原文(改变实际行为的条款;不合并、不改写)",
|
|
45
|
+
}),
|
|
46
|
+
ownerUuid: Type.Union([Type.String(), Type.Null()], {
|
|
47
|
+
description: "推出该条的陛下原话 uuid;无原话则 null",
|
|
48
|
+
}),
|
|
49
|
+
derivation: Type.String({
|
|
50
|
+
description: "一句推导:从原话哪个意思到该条款。用作上下文的 runner 句先写至少两种读法,再写票面取哪种;写不出第二种才记无歧义",
|
|
51
|
+
}),
|
|
52
|
+
}, { additionalProperties: true });
|
|
53
|
+
export const courtClausesField = Type.Array(courtClauseItemSchema, {
|
|
54
|
+
description: "票面逐条核旨表:每条改变实际行为的条款的原文、陛下原话 uuid、推导。无原话则 ownerUuid 为 null。",
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Countersign + Notary generation schema (#1195): shared review face plus
|
|
58
|
+
* required top-level clauses. Package execute does not reject a missing table.
|
|
59
|
+
*/
|
|
60
|
+
export const courtReviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Object({
|
|
61
|
+
status: reviewStatusField,
|
|
62
|
+
...reviewNarrativeFields,
|
|
63
|
+
clauses: courtClausesField,
|
|
31
64
|
}, { additionalProperties: true }));
|
|
@@ -12,7 +12,7 @@ import { dirname, join } from "node:path";
|
|
|
12
12
|
import { activationBookDirectory, physicalPathIdentity } from "./activation-ledger-topology.js";
|
|
13
13
|
import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE } from "./compliance-transport.js";
|
|
14
14
|
import { readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
|
|
15
|
-
import {
|
|
15
|
+
import { roleRunPlacement } from "./role-run-placement.js";
|
|
16
16
|
import { INVOCATION_PAGE_PATH_FIELDS, PRINCIPAL_SESSION_PATH_FIELDS, RUN_STATE_PAGE_PATH_FIELDS, projectTurnRequestLiveRunDirectory, rewriteAdmittedRoleRunPage, rewritePrincipalSessionPaths, rewriteRunDirectoryPathFields, rewriteRunDirectoryPathFieldsAgainstRewrites, rewriteRunDirectoryPathValue, rewriteRunDirectoryPathValueAgainstRewrites, rewriteSummonsMaterials, } from "./role-run-path-rewrite.js";
|
|
17
17
|
import { readPageSync, updateSectionSync } from "./run-dossier.js";
|
|
18
18
|
import { isEnoent, isRecord } from "./unknown-value.js";
|
|
@@ -51,8 +51,9 @@ export async function retainedRunPathsMatch(recordedPath, currentPath, currentRu
|
|
|
51
51
|
if (rewriteRunDirectoryPathValue(recordedPathIdentity, unbound.runDirectory, runDirectory) !== currentPathIdentity) {
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// The current volume's identity and the unbound→ticket rewrite already
|
|
55
|
+
// establish this relationship; do not select that volume again by bare ID.
|
|
56
|
+
return true;
|
|
56
57
|
}
|
|
57
58
|
return false;
|
|
58
59
|
}
|