@akagilnc/pi-workflow-roles 0.1.5478 → 0.1.5504
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 +4945 -4980
- package/dist/analyst-gate-cycles-read.js +11 -29
- package/dist/analyst-ledger.js +10 -12
- package/dist/archivist-record-entry.js +4 -3
- package/dist/archivist-record-pointer.js +59 -32
- package/dist/atomic-write.js +2 -2
- package/dist/auditor-dossier-tool.js +8 -4
- package/dist/auditor-soul.js +2 -1
- package/dist/collector-config.js +6 -5
- package/dist/doctor-evidence.js +5 -1
- package/dist/engine-detour-usage.js +4 -10
- package/dist/headless-host/description.js +251 -0
- package/dist/headless-host/production-host.js +2787 -2658
- package/dist/ledger-session-read.js +5 -0
- package/dist/migrate-book-topology.js +129 -100
- package/dist/navigator-public-session.js +13 -17
- package/dist/notary-source-run.js +7 -15
- package/dist/packaged-role-registry.js +0 -12
- package/dist/pi/durable-principal.js +2 -3
- package/dist/pi/role-turn-host.js +27 -13
- package/dist/public-cli/instruction-seat-run.js +4 -4
- package/dist/public-cli/invocation.js +17 -45
- package/dist/public-cli/main.js +1074 -1085
- package/dist/public-cli/run-lifecycle.js +17 -47
- package/dist/public-cli/settlement.js +28 -128
- package/dist/public-cli/terminal.js +0 -12
- package/dist/role-run-placement.js +52 -5
- package/dist/role-run-relocation.js +39 -2
- package/dist/run-terminal-artifacts.js +2 -6
- package/dist/run-ticket-number.js +33 -4
- package/dist/session-assistant-usage.js +9 -18
- package/dist/session-identity.js +3 -4
- package/dist/sitian-appender.js +20 -6
- package/dist/submission-ledger.js +4 -4
- package/dist/ticket-provenance-contracts.js +2 -8
- package/dist/ticket-provenance.js +5 -4
- package/package.json +1 -1
- package/src/acp-host/role-turn-host.ts +2 -2
- package/src/analyst-gate-cycles-read.ts +11 -34
- package/src/analyst-ledger.ts +14 -14
- package/src/archivist-record-entry.ts +4 -3
- package/src/archivist-record-pointer.ts +70 -34
- package/src/atomic-write.ts +3 -1
- package/src/auditor-dossier-tool.ts +8 -4
- package/src/auditor-soul.ts +2 -1
- package/src/collector-config.ts +5 -3
- package/src/doctor-evidence.ts +5 -1
- package/src/engine-detour-usage.ts +10 -13
- package/src/factory-board.ts +16 -81
- package/src/headless-host/description.ts +248 -1
- package/src/headless-host/production-host.ts +1 -2
- package/src/headless-host/role-turn-host.ts +10 -9
- 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 +7 -17
- package/src/packaged-role-registry.ts +0 -15
- package/src/pi/durable-principal.ts +2 -4
- package/src/pi/role-turn-host.ts +35 -12
- package/src/public-cli/instruction-seat-run.ts +4 -4
- package/src/public-cli/invocation.ts +29 -57
- package/src/public-cli/run-lifecycle.ts +20 -43
- package/src/public-cli/settlement.ts +30 -173
- package/src/public-cli/terminal.ts +0 -60
- package/src/role-envelope.ts +4 -2
- package/src/role-run-placement.ts +63 -4
- package/src/role-run-relocation.ts +39 -2
- package/src/role-runtime.ts +40 -59
- package/src/run-terminal-artifacts.ts +3 -5
- package/src/run-ticket-number.ts +41 -3
- package/src/session-assistant-usage.ts +8 -20
- package/src/session-identity.ts +3 -4
- package/src/sitian-appender.ts +20 -12
- package/src/submission-ledger.ts +4 -4
- package/src/ticket-provenance-contracts.ts +3 -6
- package/src/ticket-provenance.ts +5 -6
- package/src/ticket-snapshot.ts +3 -2
- package/src/ticket-trajectory.ts +85 -99
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ROLE_RUN_SESSION_FILENAME, sessionFileOf } from "./role-run-placement.js";
|
|
2
|
+
import { readStrictPiSessionJsonl } from "./ledger-session-read.js";
|
|
2
3
|
import { errorText } from "./unknown-value.js";
|
|
3
4
|
export async function readAssistantUsageFromSessionFile(sessionFile) {
|
|
4
|
-
|
|
5
|
-
let text;
|
|
5
|
+
let rows;
|
|
6
6
|
try {
|
|
7
|
-
|
|
7
|
+
rows = await readStrictPiSessionJsonl(sessionFile);
|
|
8
8
|
}
|
|
9
9
|
catch (error) {
|
|
10
10
|
if (error?.code === "ENOENT")
|
|
11
11
|
return undefined;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let rows;
|
|
15
|
-
try {
|
|
16
|
-
rows = text
|
|
17
|
-
.trim()
|
|
18
|
-
.split("\n")
|
|
19
|
-
.filter(Boolean)
|
|
20
|
-
.map((line) => JSON.parse(line));
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
12
|
+
if (!(error instanceof SyntaxError))
|
|
13
|
+
throw error;
|
|
23
14
|
throw new Error(`session usage parse failed (${sessionFile}): ${errorText(error)}`, { cause: error });
|
|
24
15
|
}
|
|
25
16
|
let total = 0;
|
|
@@ -82,11 +73,11 @@ export async function readAssistantUsageFromSessionFile(sessionFile) {
|
|
|
82
73
|
*/
|
|
83
74
|
export function sessionFileFromPublicSummon(summoned) {
|
|
84
75
|
if (typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== "") {
|
|
85
|
-
return
|
|
76
|
+
return sessionFileOf(summoned.runDirectory);
|
|
86
77
|
}
|
|
87
78
|
const fromArtifacts = summoned.terminal?.artifacts
|
|
88
79
|
?.map((a) => a.path)
|
|
89
|
-
.find((p) => typeof p === "string" && p.endsWith(
|
|
80
|
+
.find((p) => typeof p === "string" && p.endsWith(ROLE_RUN_SESSION_FILENAME));
|
|
90
81
|
if (fromArtifacts !== undefined)
|
|
91
82
|
return fromArtifacts;
|
|
92
83
|
const outcome = summoned.terminal?.roleOutcome;
|
|
@@ -95,7 +86,7 @@ export function sessionFileFromPublicSummon(summoned) {
|
|
|
95
86
|
const facts = outcome.decisiveFacts;
|
|
96
87
|
const pointer = facts?.runPointer;
|
|
97
88
|
if (typeof pointer === "string" && pointer.trim() !== "") {
|
|
98
|
-
return
|
|
89
|
+
return sessionFileOf(pointer);
|
|
99
90
|
}
|
|
100
91
|
return undefined;
|
|
101
92
|
}
|
package/dist/session-identity.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { mkdir, readFile
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
+
import { writeFileAtomically } from "./atomic-write.js";
|
|
3
4
|
import { DEFAULT_ROLE_TURN_HOST, lookupHeadlessHostDescription, lookupHostDescription, } from "./host-descriptions.js";
|
|
4
5
|
/** Durable session binding stored beside the host-owned session principal. */
|
|
5
6
|
export function createSessionIdentityAuthority(authority, sessionBindingFile) {
|
|
@@ -25,9 +26,7 @@ export function createSessionIdentityAuthority(authority, sessionBindingFile) {
|
|
|
25
26
|
async bind(principal, sessionId) {
|
|
26
27
|
const target = bindingPath(principal);
|
|
27
28
|
await mkdir(dirname(target), { recursive: true });
|
|
28
|
-
|
|
29
|
-
await writeFile(temporary, `${JSON.stringify({ sessionId })}\n`, { encoding: "utf8", mode: 0o600 });
|
|
30
|
-
await rename(temporary, target);
|
|
29
|
+
await writeFileAtomically(target, `${JSON.stringify({ sessionId })}\n`, { mode: 0o600 });
|
|
31
30
|
},
|
|
32
31
|
};
|
|
33
32
|
}
|
package/dist/sitian-appender.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { appendFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { sessionDirectoryOf } from "./role-run-placement.js";
|
|
5
|
+
import { ticketNumberFromSitianSubject } from "./run-ticket-number.js";
|
|
4
6
|
import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
|
|
5
7
|
import {
|
|
6
8
|
activationBookDirectory,
|
|
@@ -41,13 +43,22 @@ function resolveSitianVolumeCategory(kind) {
|
|
|
41
43
|
return kind;
|
|
42
44
|
}
|
|
43
45
|
const SITIAN_RECORDS_LEAF = "records.jsonl";
|
|
46
|
+
function sitianVolumeDirectory(sessionDirectory, category) {
|
|
47
|
+
return join(sessionDirectory, category);
|
|
48
|
+
}
|
|
49
|
+
function sitianRunVolumeDirectory(runDirectory, category) {
|
|
50
|
+
return sitianVolumeDirectory(sessionDirectoryOf(runDirectory), category);
|
|
51
|
+
}
|
|
52
|
+
function sitianVolumeRecordsFile(volumeDirectory) {
|
|
53
|
+
return join(volumeDirectory, SITIAN_RECORDS_LEAF);
|
|
54
|
+
}
|
|
44
55
|
function ticketProvenanceUnderBookPaths(ledgerHome, bookKey, ticketId) {
|
|
45
56
|
const sessionDir = join(activationBookDirectory(ledgerHome, bookKey), ticketId);
|
|
46
|
-
return { sessionDir, recordFile:
|
|
57
|
+
return { sessionDir, recordFile: sitianVolumeRecordsFile(sessionDir) };
|
|
47
58
|
}
|
|
48
59
|
function resolveSitianRecordPathInLedger(input, ledgerHome) {
|
|
49
60
|
const category = resolveSitianVolumeCategory(input.kind);
|
|
50
|
-
const ticketNumber = category === "ticket-provenance" ?
|
|
61
|
+
const ticketNumber = category === "ticket-provenance" ? ticketNumberFromSitianSubject(input.subject) : void 0;
|
|
51
62
|
let sessionDir;
|
|
52
63
|
let recordFile;
|
|
53
64
|
if (ticketNumber !== void 0) {
|
|
@@ -60,13 +71,13 @@ function resolveSitianRecordPathInLedger(input, ledgerHome) {
|
|
|
60
71
|
recordFile = paths.recordFile;
|
|
61
72
|
} else if (category === "ticket-provenance" && input.runDirectory !== void 0) {
|
|
62
73
|
sessionDir = input.runDirectory;
|
|
63
|
-
recordFile =
|
|
74
|
+
recordFile = sitianVolumeRecordsFile(sessionDir);
|
|
64
75
|
} else {
|
|
65
76
|
if (input.sessionParent === void 0 || input.sessionParent.length === 0 || !physicallyContainedIn(ledgerHome, input.sessionParent)) {
|
|
66
77
|
throw new Error("Sitian record ownership requires a parent session inside the ledger home");
|
|
67
78
|
}
|
|
68
|
-
sessionDir =
|
|
69
|
-
recordFile =
|
|
79
|
+
sessionDir = sitianVolumeDirectory(dirname(input.sessionParent), category);
|
|
80
|
+
recordFile = sitianVolumeRecordsFile(sessionDir);
|
|
70
81
|
}
|
|
71
82
|
if (!physicallyContainedIn(ledgerHome, sessionDir)) {
|
|
72
83
|
throw new Error("Sitian record ownership requires a directory inside the ledger home");
|
|
@@ -120,5 +131,8 @@ export {
|
|
|
120
131
|
appendSitianRecordBlock,
|
|
121
132
|
resolveSitianRecordPath,
|
|
122
133
|
resolveSitianRecordPathInLedger,
|
|
123
|
-
resolveSitianVolumeCategory
|
|
134
|
+
resolveSitianVolumeCategory,
|
|
135
|
+
sitianRunVolumeDirectory,
|
|
136
|
+
sitianVolumeDirectory,
|
|
137
|
+
sitianVolumeRecordsFile
|
|
124
138
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sessionFileIn, sessionFileOf } from "./role-run-placement.js";
|
|
2
2
|
import { resolveActivationLedgerHome, tryHomeFromAkRolesPath, } from "./activation-ledger-topology.js";
|
|
3
3
|
import { courtAttemptIdFromHostContext, runDirectoryFromHostContext, } from "./host-contracts.js";
|
|
4
4
|
import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
|
|
@@ -49,7 +49,7 @@ async function submissionRecordFile(cwd, runId, scope) {
|
|
|
49
49
|
const discoveredRun = await findRunDirectoryById(scope.home, runId);
|
|
50
50
|
if (discoveredRun === undefined)
|
|
51
51
|
return undefined;
|
|
52
|
-
sessionParent =
|
|
52
|
+
sessionParent = sessionFileOf(discoveredRun);
|
|
53
53
|
}
|
|
54
54
|
return resolveSitianRecordPathInLedger({
|
|
55
55
|
level: "event",
|
|
@@ -265,13 +265,13 @@ async function restoreState(cwd, runId, scope) {
|
|
|
265
265
|
function sessionParentFromHostContext(context) {
|
|
266
266
|
const runDirectory = runDirectoryFromHostContext(context);
|
|
267
267
|
if (runDirectory !== undefined)
|
|
268
|
-
return
|
|
268
|
+
return sessionFileOf(runDirectory);
|
|
269
269
|
const sessionFile = context.sessionManager.getSessionFile?.();
|
|
270
270
|
if (typeof sessionFile === "string" && sessionFile.length > 0)
|
|
271
271
|
return sessionFile;
|
|
272
272
|
const sessionDir = context.sessionManager.getSessionDir?.();
|
|
273
273
|
if (typeof sessionDir === "string" && sessionDir.length > 0) {
|
|
274
|
-
return
|
|
274
|
+
return sessionFileIn(sessionDir);
|
|
275
275
|
}
|
|
276
276
|
return undefined;
|
|
277
277
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseTicketNumber } from "./run-ticket-number.js";
|
|
1
2
|
import { isRecord } from "./unknown-value.js";
|
|
2
3
|
/**
|
|
3
4
|
* 起居录(ticket-provenance)typed 形状 —— ADR 0075 / ADR 0081 / #1090。
|
|
@@ -9,14 +10,7 @@ import { isRecord } from "./unknown-value.js";
|
|
|
9
10
|
/** Sitian kind for per-ticket court diary volumes. */
|
|
10
11
|
export const TICKET_PROVENANCE_KIND = "ticket-provenance";
|
|
11
12
|
function positiveInteger(value) {
|
|
12
|
-
|
|
13
|
-
return value;
|
|
14
|
-
if (typeof value === "string" && /^[1-9][0-9]*$/.test(value)) {
|
|
15
|
-
const parsed = Number(value);
|
|
16
|
-
if (Number.isSafeInteger(parsed))
|
|
17
|
-
return parsed;
|
|
18
|
-
}
|
|
19
|
-
return undefined;
|
|
13
|
+
return parseTicketNumber(value);
|
|
20
14
|
}
|
|
21
15
|
function projectBound(value) {
|
|
22
16
|
if (!isRecord(value))
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* 纯追加、不回读历史去重、不折叠。
|
|
5
5
|
*/
|
|
6
6
|
import { readFile, unlink } from "node:fs/promises";
|
|
7
|
-
import {
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
8
|
import { errnoCode, packageMachineHome, physicallyContainedIn, resolveActivationLedgerHome, } from "./activation-ledger-topology.js";
|
|
9
9
|
import { readLedgerSessionJsonlLines, } from "./ledger-session-read.js";
|
|
10
10
|
import { isSafePositiveTicketNumber } from "./run-ticket-number.js";
|
|
11
|
+
import { runDirectoryOfSessionFile, sessionFileOf } from "./role-run-placement.js";
|
|
12
|
+
import { sitianVolumeRecordsFile } from "./sitian-appender.js";
|
|
11
13
|
import { adaptSessionDialogue, nativeEventId } from "./session-dialogue.js";
|
|
12
14
|
import { appendSitianRecordBlock, appendSitianRecord, resolveSitianRecordPath, } from "./sitian-facade.js";
|
|
13
15
|
import { TICKET_PROVENANCE_KIND, } from "./ticket-provenance-contracts.js";
|
|
@@ -48,8 +50,7 @@ function isLedgerRoleSessionFile(absolute, home) {
|
|
|
48
50
|
const ledgerHome = resolveActivationLedgerHome(machineHome);
|
|
49
51
|
if (!physicallyContainedIn(ledgerHome, absolute))
|
|
50
52
|
return false;
|
|
51
|
-
return (
|
|
52
|
-
basename(dirname(absolute)) === "session");
|
|
53
|
+
return sessionFileOf(runDirectoryOfSessionFile(absolute)) === absolute;
|
|
53
54
|
}
|
|
54
55
|
/** Real I/O seam gate: only host session stores or sitian role-run session.jsonl. */
|
|
55
56
|
function assertDialogueSessionSourcePath(path, home) {
|
|
@@ -87,7 +88,7 @@ export function resolveTicketProvenanceVolume(ticketNumber, cwd, home) {
|
|
|
87
88
|
}
|
|
88
89
|
/** Assign a run-owned unbound diary as its original block through Sitian. */
|
|
89
90
|
export async function rehomeUnboundTicketProvenance(runDirectory, ticketNumber, cwd, home) {
|
|
90
|
-
const source =
|
|
91
|
+
const source = sitianVolumeRecordsFile(runDirectory);
|
|
91
92
|
let content;
|
|
92
93
|
try {
|
|
93
94
|
content = await readFile(source, "utf8");
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
1
|
import { spawn } from "node:child_process";
|
|
3
2
|
import { createInterface } from "node:readline";
|
|
4
3
|
|
|
5
4
|
import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
|
|
5
|
+
import { sessionDirectoryOf } from "../role-run-placement.ts";
|
|
6
6
|
import {
|
|
7
7
|
createSerializedRoleTurnHost,
|
|
8
8
|
driveExternalRoleTurnRounds,
|
|
@@ -496,7 +496,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
496
496
|
host: config.hostName,
|
|
497
497
|
sessionId,
|
|
498
498
|
cwd: request.cwd,
|
|
499
|
-
sessionDirectory:
|
|
499
|
+
sessionDirectory: sessionDirectoryOf(request.runDirectory),
|
|
500
500
|
sessionParent,
|
|
501
501
|
continuation: request.continuation,
|
|
502
502
|
...(request.model !== undefined ? { model: request.model } : {}),
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sole nested-volume reader for gate-cycle facts under session/auditor-roles/.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* consume retained facts. Terminal settlement reuses this same pairing seam
|
|
7
|
-
* (no second auditor-roles scanner).
|
|
4
|
+
* Consumer: Analyst sole ledger scan (classifyScopedRun). Metric families must
|
|
5
|
+
* not open a second disk scan — they consume retained facts.
|
|
8
6
|
*
|
|
9
7
|
* Naming: records may carry pre-#440 menxia/jishizhong/fubaolang tool faces or
|
|
10
8
|
* the current gatekeeper/inspector/notary English face. Projection always uses
|
|
@@ -38,7 +36,10 @@ import {
|
|
|
38
36
|
type LedgerSessionRow,
|
|
39
37
|
} from "./ledger-session-read.ts";
|
|
40
38
|
|
|
41
|
-
import { isRecord,
|
|
39
|
+
import { isRecord, isEnoent } from "./unknown-value.ts";
|
|
40
|
+
import { retainedRunPathsMatch } from "./role-run-relocation.ts";
|
|
41
|
+
import { runDirectoryOfSessionFile } from "./role-run-placement.ts";
|
|
42
|
+
import { resolveOfficerSessionFromPointerFile } from "./archivist-record-pointer.ts";
|
|
42
43
|
|
|
43
44
|
/** One completed gate round: direct officer receipt or historical province/officer pair. */
|
|
44
45
|
/** Honest origin discriminant: direct summons vs historical province dispatch. */
|
|
@@ -416,32 +417,6 @@ function pairGateRounds(
|
|
|
416
417
|
.map((round, index) => ({ ...round, roundIndex: index + 1 }));
|
|
417
418
|
}
|
|
418
419
|
|
|
419
|
-
/** Resolve a direct-officer-run-pointer file to the officer session 正本 path. */
|
|
420
|
-
async function resolveOfficerSessionFromPointerFile(
|
|
421
|
-
pointerPath: string,
|
|
422
|
-
): Promise<{ sessionFile: string; officer?: "inspector" | "notary" }> {
|
|
423
|
-
const { readFile } = await import("node:fs/promises");
|
|
424
|
-
let raw: unknown;
|
|
425
|
-
try {
|
|
426
|
-
raw = JSON.parse(await readFile(pointerPath, "utf8"));
|
|
427
|
-
} catch (error) {
|
|
428
|
-
throw new Error(
|
|
429
|
-
`direct officer run pointer unreadable in ${pointerPath}: ${errorText(error)}`,
|
|
430
|
-
{ cause: error },
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
if (!isRecord(raw) || raw.kind !== "direct-officer-run-pointer" || raw.version !== 1) {
|
|
434
|
-
throw new Error(`direct officer run pointer has unknown shape in ${pointerPath}`);
|
|
435
|
-
}
|
|
436
|
-
const sessionFile = raw.sessionFile;
|
|
437
|
-
if (typeof sessionFile !== "string" || sessionFile.trim() === "") {
|
|
438
|
-
throw new Error(`direct officer run pointer missing sessionFile in ${pointerPath}`);
|
|
439
|
-
}
|
|
440
|
-
const officer = raw.officer === "inspector" || raw.officer === "notary"
|
|
441
|
-
? raw.officer : undefined;
|
|
442
|
-
return { sessionFile, ...(officer === undefined ? {} : { officer }) };
|
|
443
|
-
}
|
|
444
|
-
|
|
445
420
|
/**
|
|
446
421
|
* Read and pair gate-cycle rounds from one or more auditor-roles directories.
|
|
447
422
|
* Accepts historical nested JSONL volumes and #675 direct-officer-run-pointer
|
|
@@ -463,7 +438,7 @@ export async function readAnalystGateCyclesFromAuditorRoles(
|
|
|
463
438
|
const volumes: ClassifiedVolume[] = [];
|
|
464
439
|
// Same officer session pointed at N times (historical multi-mint pointers, or
|
|
465
440
|
// one parent booking many leaves) must classify once — else each accepted
|
|
466
|
-
// seal is counted ×N in
|
|
441
|
+
// seal is counted ×N in gate-cycle statistics (#753 acceptance A).
|
|
467
442
|
const classifiedOfficerSessions = new Set<string>();
|
|
468
443
|
for (const directory of directories) {
|
|
469
444
|
let names: string[];
|
|
@@ -492,12 +467,14 @@ export async function readAnalystGateCyclesFromAuditorRoles(
|
|
|
492
467
|
if (classifiedOfficerSessions.has(sessionPath)) continue;
|
|
493
468
|
classifiedOfficerSessions.add(sessionPath);
|
|
494
469
|
}
|
|
495
|
-
const
|
|
470
|
+
const officer = pointer?.officer === "inspector" || pointer?.officer === "notary"
|
|
471
|
+
? pointer.officer : undefined;
|
|
472
|
+
const classified = await classifyAuditorVolume(sessionPath, officer);
|
|
496
473
|
for (const volume of classified) {
|
|
497
474
|
if (
|
|
498
475
|
options.parentSessionFile !== undefined &&
|
|
499
476
|
volume.parentSessionFile !== undefined &&
|
|
500
|
-
volume.parentSessionFile
|
|
477
|
+
!await retainedRunPathsMatch(volume.parentSessionFile, options.parentSessionFile, runDirectoryOfSessionFile(options.parentSessionFile))
|
|
501
478
|
) {
|
|
502
479
|
continue;
|
|
503
480
|
}
|
package/src/analyst-ledger.ts
CHANGED
|
@@ -21,9 +21,15 @@ import {
|
|
|
21
21
|
physicalPathIdentity,
|
|
22
22
|
resolveActivationLedgerHome,
|
|
23
23
|
} from "./activation-ledger-topology.ts";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
listBookRunDirectories,
|
|
26
|
+
parseRunLeaf,
|
|
27
|
+
sessionFileOf,
|
|
28
|
+
} from "./role-run-placement.ts";
|
|
29
|
+
import { sitianRunVolumeDirectory } from "./sitian-appender.ts";
|
|
25
30
|
import { autopsyWriterLock, readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
|
|
26
31
|
import { readRunTicketNumber } from "./run-ticket-number.ts";
|
|
32
|
+
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.ts";
|
|
27
33
|
import {
|
|
28
34
|
extractSessionModelSequence,
|
|
29
35
|
extractSessionTimestampSpan,
|
|
@@ -124,14 +130,6 @@ async function classifyGhostCandidate(input: {
|
|
|
124
130
|
};
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
function parseRunDirectoryName(
|
|
128
|
-
name: string,
|
|
129
|
-
): { runId: string; role: string } | undefined {
|
|
130
|
-
const at = name.lastIndexOf("@");
|
|
131
|
-
if (at <= 0 || at === name.length - 1) return undefined;
|
|
132
|
-
return { runId: name.slice(0, at), role: name.slice(at + 1) };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
133
|
/**
|
|
136
134
|
* Invocation scope faces used for issue 圈定 (C4 / #399).
|
|
137
135
|
* projectRoot is retained for narrow path match and conflict facts;
|
|
@@ -222,12 +220,14 @@ async function resolveSessionFile(
|
|
|
222
220
|
&& typeof parsed.sessionFile === "string"
|
|
223
221
|
&& parsed.sessionFile.trim() !== ""
|
|
224
222
|
) {
|
|
225
|
-
return parsed.
|
|
223
|
+
return typeof parsed.runDirectory === "string"
|
|
224
|
+
? rewriteRunDirectoryPathValue(parsed.sessionFile, parsed.runDirectory, runDirectory) as string
|
|
225
|
+
: parsed.sessionFile;
|
|
226
226
|
}
|
|
227
227
|
} catch (error) {
|
|
228
228
|
if (!isMissingPathError(error)) throw error;
|
|
229
229
|
}
|
|
230
|
-
return
|
|
230
|
+
return sessionFileOf(runDirectory);
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
@@ -579,9 +579,9 @@ async function classifyScopedRun(input: {
|
|
|
579
579
|
// nested JSONL is page-local unreadable — never silently under-count rounds.
|
|
580
580
|
let gateCycles: readonly AnalystGateCycleRound[];
|
|
581
581
|
try {
|
|
582
|
-
const parentSessionFile =
|
|
582
|
+
const parentSessionFile = sessionFileOf(input.runDirectory);
|
|
583
583
|
gateCycles = await readAnalystGateCyclesFromAuditorRoles(
|
|
584
|
-
|
|
584
|
+
sitianRunVolumeDirectory(input.runDirectory, "auditor-roles"),
|
|
585
585
|
{ parentSessionFile },
|
|
586
586
|
);
|
|
587
587
|
} catch (error) {
|
|
@@ -697,7 +697,7 @@ export async function scanAnalystIssueRuns(input: {
|
|
|
697
697
|
|
|
698
698
|
for (const runDirectory of runDirectories) {
|
|
699
699
|
const runName = basename(runDirectory);
|
|
700
|
-
const parsed =
|
|
700
|
+
const parsed = parseRunLeaf(runName);
|
|
701
701
|
if (parsed === undefined) continue;
|
|
702
702
|
|
|
703
703
|
let scopeFields: InvocationScopeFields | undefined;
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "./archivist-record-topology.ts";
|
|
32
32
|
import type { HostRecordSession, RecordSessionHost } from "./host-contracts.ts";
|
|
33
33
|
import { piRecordSessionHost } from "./pi/record-session-host.ts";
|
|
34
|
+
import { sitianVolumeDirectory } from "./sitian-appender.ts";
|
|
34
35
|
export {
|
|
35
36
|
DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
36
37
|
bookDirectOfficerRunPointer,
|
|
@@ -47,7 +48,7 @@ const CURRENT_SESSION_LEDGER = "current-session.json";
|
|
|
47
48
|
type CurrentSessionRecord = { readonly sessionFile: string };
|
|
48
49
|
|
|
49
50
|
function readCurrentSession(sessionDir: string): string {
|
|
50
|
-
const ledger =
|
|
51
|
+
const ledger = currentSessionLedgerPath(sessionDir);
|
|
51
52
|
try {
|
|
52
53
|
const value: unknown = JSON.parse(readFileSync(ledger, "utf8"));
|
|
53
54
|
if (
|
|
@@ -68,7 +69,7 @@ function readCurrentSession(sessionDir: string): string {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
function writeCurrentSession(sessionDir: string, sessionFile: string): void {
|
|
71
|
-
const ledger =
|
|
72
|
+
const ledger = currentSessionLedgerPath(sessionDir);
|
|
72
73
|
try {
|
|
73
74
|
writeFileSync(ledger, `${JSON.stringify({ sessionFile })}\n`, { flag: "wx" });
|
|
74
75
|
} catch (error) {
|
|
@@ -420,7 +421,7 @@ export function createRecordSessionOpen(
|
|
|
420
421
|
// Ordinary kinds: durable parent's file is the sole nesting authority. A divergent
|
|
421
422
|
// SessionManager directory must not create a second placement route.
|
|
422
423
|
// Do not restore generic bookDir/<kind> fallback for foreign/unrooted parents.
|
|
423
|
-
sessionDir =
|
|
424
|
+
sessionDir = sitianVolumeDirectory(dirname(parentResolved), options.kind);
|
|
424
425
|
parentSession = parentFile;
|
|
425
426
|
mayResumeSameNest = options.kind === WORKER_SUBMISSION_GATE_KIND;
|
|
426
427
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { isRecord } from "./unknown-value.ts";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { isRecord, errorText } from "./unknown-value.ts";
|
|
5
|
+
|
|
6
|
+
import { homeFromRunDirectory } from "./activation-ledger-topology.ts";
|
|
7
|
+
import { findRunDirectoryById, readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
|
|
8
|
+
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.ts";
|
|
9
|
+
import { parseRunLeaf, runDirectoryOfSessionFile, runDirectoryFromSessionDirectory } from "./role-run-placement.ts";
|
|
10
|
+
import { sitianVolumeDirectory } from "./sitian-appender.ts";
|
|
5
11
|
|
|
6
12
|
/** Typed parent-side pointer to an independent officer run 正本 (ADR 0079 / #675). */
|
|
7
13
|
export const DIRECT_OFFICER_RUN_POINTER_KIND = "direct-officer-run-pointer" as const;
|
|
@@ -21,6 +27,13 @@ export type DirectOfficerRunPointer = {
|
|
|
21
27
|
readonly submissionToolCallId?: string;
|
|
22
28
|
};
|
|
23
29
|
|
|
30
|
+
function directOfficerRunPointerFile(
|
|
31
|
+
parentSessionFile: string,
|
|
32
|
+
officer: DirectOfficerRunPointer["officer"],
|
|
33
|
+
): string {
|
|
34
|
+
return join(sitianVolumeDirectory(dirname(parentSessionFile), "auditor-roles"), `${officer}.pointer.json`);
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
/**
|
|
25
38
|
* Book a typed pointer under parent session/auditor-roles (same nest owner as
|
|
26
39
|
* createRecordSession). Never fabricates user/assistant/toolResult rows (#675).
|
|
@@ -28,7 +41,7 @@ export type DirectOfficerRunPointer = {
|
|
|
28
41
|
*
|
|
29
42
|
* Stable leaf per officer under one parent (#753 gate-round accounting):
|
|
30
43
|
* same-parent re-summons upsert the same pointer instead of minting N files that
|
|
31
|
-
* each re-scan the full officer session and multiply
|
|
44
|
+
* each re-scan the full officer session and multiply gate-cycle counts.
|
|
32
45
|
*/
|
|
33
46
|
export function bookDirectOfficerRunPointer(options: {
|
|
34
47
|
readonly parentSessionFile: string;
|
|
@@ -37,8 +50,8 @@ export function bookDirectOfficerRunPointer(options: {
|
|
|
37
50
|
readonly runDirectory?: string;
|
|
38
51
|
readonly submissionToolCallId?: string;
|
|
39
52
|
}): DirectOfficerRunPointer {
|
|
40
|
-
const
|
|
41
|
-
mkdirSync(
|
|
53
|
+
const pointerFile = directOfficerRunPointerFile(options.parentSessionFile, options.officer);
|
|
54
|
+
mkdirSync(dirname(pointerFile), { recursive: true });
|
|
42
55
|
const submissionToolCallId = options.submissionToolCallId?.trim() ?? "";
|
|
43
56
|
const pointer: DirectOfficerRunPointer = {
|
|
44
57
|
version: 1,
|
|
@@ -51,46 +64,69 @@ export function bookDirectOfficerRunPointer(options: {
|
|
|
51
64
|
...(submissionToolCallId === "" ? {} : { submissionToolCallId }),
|
|
52
65
|
};
|
|
53
66
|
writeFileSync(
|
|
54
|
-
|
|
67
|
+
pointerFile,
|
|
55
68
|
`${JSON.stringify(pointer)}\n`,
|
|
56
69
|
"utf8",
|
|
57
70
|
);
|
|
58
71
|
return pointer;
|
|
59
72
|
}
|
|
60
73
|
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
/** Shared strict pointer IO; internal audit wraps unreadability as a re-summons. */
|
|
75
|
+
export async function resolveOfficerSessionFromPointerFile(pointerPath: string): Promise<{
|
|
76
|
+
sessionFile: string;
|
|
77
|
+
officer?: DirectOfficerRunPointer["officer"];
|
|
78
|
+
runDirectory?: string;
|
|
79
|
+
submissionToolCallId?: string;
|
|
80
|
+
}> {
|
|
81
|
+
let raw: unknown;
|
|
82
|
+
try {
|
|
83
|
+
raw = JSON.parse(await readFile(pointerPath, "utf8"));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new Error(`direct officer run pointer unreadable in ${pointerPath}: ${errorText(error)}`, { cause: error });
|
|
86
|
+
}
|
|
87
|
+
if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
|
|
88
|
+
throw new Error(`direct officer run pointer has unknown shape in ${pointerPath}`);
|
|
89
|
+
}
|
|
90
|
+
if (typeof raw.sessionFile !== "string" || raw.sessionFile.trim() === "") {
|
|
91
|
+
throw new Error(`direct officer run pointer missing sessionFile in ${pointerPath}`);
|
|
92
|
+
}
|
|
93
|
+
const officer = raw.officer === "inspector" || raw.officer === "notary"
|
|
94
|
+
|| raw.officer === "auditor" || raw.officer === "countersign" ? raw.officer : undefined;
|
|
95
|
+
let runDirectory = typeof raw.runDirectory === "string" && raw.runDirectory.trim() !== ""
|
|
96
|
+
? raw.runDirectory : undefined;
|
|
97
|
+
let sessionFile = raw.sessionFile;
|
|
98
|
+
const recordedRunDirectory = runDirectory ?? runDirectoryOfSessionFile(sessionFile);
|
|
99
|
+
const leaf = parseRunLeaf(basename(recordedRunDirectory));
|
|
100
|
+
const parentRunDirectory = runDirectoryFromSessionDirectory(dirname(dirname(pointerPath)));
|
|
101
|
+
const parentIdentity = await readRoleRunIdentity(parentRunDirectory);
|
|
102
|
+
if (leaf !== undefined && parentIdentity !== undefined) {
|
|
103
|
+
const currentRunDirectory = await findRunDirectoryById(
|
|
104
|
+
homeFromRunDirectory(parentRunDirectory), leaf.runId, parentIdentity.bookKey, leaf.role,
|
|
105
|
+
);
|
|
106
|
+
if (currentRunDirectory !== undefined) {
|
|
107
|
+
sessionFile = rewriteRunDirectoryPathValue(sessionFile, recordedRunDirectory, currentRunDirectory) as string;
|
|
108
|
+
if (runDirectory !== undefined) runDirectory = currentRunDirectory;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const submissionToolCallId = typeof raw.submissionToolCallId === "string"
|
|
112
|
+
&& raw.submissionToolCallId.trim() !== "" ? raw.submissionToolCallId : undefined;
|
|
113
|
+
return {
|
|
114
|
+
sessionFile,
|
|
115
|
+
...(officer === undefined ? {} : { officer }),
|
|
116
|
+
...(runDirectory === undefined ? {} : { runDirectory }),
|
|
117
|
+
...(submissionToolCallId === undefined ? {} : { submissionToolCallId }),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Missing or unreadable is not a pass: the internal audit caller re-runs the gate. */
|
|
65
122
|
export async function readDirectOfficerRunPointer(
|
|
66
123
|
parentSessionFile: string,
|
|
67
124
|
officer: DirectOfficerRunPointer["officer"],
|
|
68
125
|
): Promise<DirectOfficerRunPointer | undefined> {
|
|
69
126
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
));
|
|
74
|
-
if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
|
-
if (raw.officer !== officer) return undefined;
|
|
78
|
-
if (typeof raw.sessionFile !== "string" || raw.sessionFile.trim() === "") return undefined;
|
|
79
|
-
const runDirectory = typeof raw.runDirectory === "string" && raw.runDirectory.trim() !== ""
|
|
80
|
-
? raw.runDirectory
|
|
81
|
-
: undefined;
|
|
82
|
-
const submissionToolCallId = typeof raw.submissionToolCallId === "string"
|
|
83
|
-
&& raw.submissionToolCallId.trim() !== ""
|
|
84
|
-
? raw.submissionToolCallId
|
|
85
|
-
: undefined;
|
|
86
|
-
return {
|
|
87
|
-
version: 1,
|
|
88
|
-
kind: DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
89
|
-
officer,
|
|
90
|
-
sessionFile: raw.sessionFile,
|
|
91
|
-
...(runDirectory === undefined ? {} : { runDirectory }),
|
|
92
|
-
...(submissionToolCallId === undefined ? {} : { submissionToolCallId }),
|
|
93
|
-
};
|
|
127
|
+
const pointer = await resolveOfficerSessionFromPointerFile(directOfficerRunPointerFile(parentSessionFile, officer));
|
|
128
|
+
if (pointer.officer !== officer) return undefined;
|
|
129
|
+
return { ...pointer, officer, version: 1, kind: DIRECT_OFFICER_RUN_POINTER_KIND };
|
|
94
130
|
} catch {
|
|
95
131
|
return undefined;
|
|
96
132
|
}
|
package/src/atomic-write.ts
CHANGED
|
@@ -7,17 +7,19 @@
|
|
|
7
7
|
* ledger home own confinement via ensureRealDirectoryTree (ADR 0038).
|
|
8
8
|
*/
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
|
+
import type { Mode } from "node:fs";
|
|
10
11
|
import { rename, rm, writeFile } from "node:fs/promises";
|
|
11
12
|
import { dirname, join } from "node:path";
|
|
12
13
|
|
|
13
14
|
export async function writeFileAtomically(
|
|
14
15
|
destination: string,
|
|
15
16
|
contents: string | Uint8Array,
|
|
17
|
+
options?: { readonly mode?: Mode },
|
|
16
18
|
): Promise<void> {
|
|
17
19
|
const parent = dirname(destination);
|
|
18
20
|
const temporary = join(parent, `.atomic-write-${randomUUID()}.tmp`);
|
|
19
21
|
try {
|
|
20
|
-
await writeFile(temporary, contents);
|
|
22
|
+
await writeFile(temporary, contents, options);
|
|
21
23
|
await rename(temporary, destination);
|
|
22
24
|
} catch (error) {
|
|
23
25
|
await rm(temporary, { force: true }).catch(() => undefined);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
2
|
|
|
3
3
|
import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import type { HostContext } from "./host-contracts.ts";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
roleRunArtifactsDirectory,
|
|
7
|
+
runDirectoryOfSessionFile,
|
|
8
|
+
sessionFileOf,
|
|
9
|
+
} from "./role-run-placement.ts";
|
|
6
10
|
import { Type } from "typebox";
|
|
7
11
|
|
|
8
12
|
export const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier" as const;
|
|
@@ -20,7 +24,7 @@ export function auditorRunDirectory(context: ExtensionContext | HostContext): st
|
|
|
20
24
|
const sessionFile = context.sessionManager?.getSessionFile?.();
|
|
21
25
|
if (sessionFile === undefined) return undefined;
|
|
22
26
|
// Sole layout: <run>/session/session.jsonl → climb two levels to the run directory.
|
|
23
|
-
return resolve(
|
|
27
|
+
return resolve(runDirectoryOfSessionFile(sessionFile));
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
/** The one shared, run-bound dossier locator exposed to every auditor seat. */
|
|
@@ -44,7 +48,7 @@ export function createAuditorDossierTool(
|
|
|
44
48
|
const details: AuditorDossierLocation = {
|
|
45
49
|
runDirectory,
|
|
46
50
|
admittedRequest: join(runDirectory, "admitted-request.json"),
|
|
47
|
-
parentSessionCandidate:
|
|
51
|
+
parentSessionCandidate: sessionFileOf(runDirectory),
|
|
48
52
|
attachments: join(runDirectory, "attachments"),
|
|
49
53
|
artifacts: roleRunArtifactsDirectory(runDirectory),
|
|
50
54
|
};
|
package/src/auditor-soul.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { basename, join } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { parseRunLeaf } from "./role-run-placement.ts";
|
|
4
5
|
import { isRecord } from "./unknown-value.ts";
|
|
5
6
|
|
|
6
7
|
import {
|
|
@@ -109,7 +110,7 @@ export function loadAuditorReferenceMaterialsFromSubjectInput(raw?: string): Pro
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
function subjectFromSourceDirectory(sourceRunDirectory: string): AuditorSoulRole | undefined {
|
|
112
|
-
const role = basename(sourceRunDirectory)
|
|
113
|
+
const role = parseRunLeaf(basename(sourceRunDirectory))?.role;
|
|
113
114
|
return isAuditorSoulRole(role) ? role : undefined;
|
|
114
115
|
}
|
|
115
116
|
|