@akagilnc/pi-workflow-roles 0.1.5504 → 0.1.5575
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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sole
|
|
2
|
+
* Sole reader for gate-cycle facts of the officers pointed at from current.json `officers`.
|
|
3
3
|
*
|
|
4
4
|
* Consumer: Analyst sole ledger scan (classifyScopedRun). Metric families must
|
|
5
5
|
* not open a second disk scan — they consume retained facts.
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
* the current gatekeeper/inspector/notary English face. Projection always uses
|
|
9
9
|
* the current English officer identity (inspector | notary).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* fails canonical read/parse must fail loudly (never silently under-count).
|
|
11
|
+
* Absent `officers` section → empty rounds (lawful zero). A pointer whose
|
|
12
|
+
* officer session fails canonical read/parse must fail loudly (never silently under-count).
|
|
14
13
|
* An accepted gate terminating receipt (isError:false pair on dispatch/officer
|
|
15
14
|
* tool) whose required typed facts are unusable — status, dispatch officer, or
|
|
16
15
|
* first/last span missing/unknown/unparseable/inverted — also fails loudly via
|
|
@@ -23,14 +22,12 @@
|
|
|
23
22
|
* `ak_auditor_parent_attempt_binding.parent.attemptEntryId` — never seat/time guessing.
|
|
24
23
|
* An orphan accepted dispatch must not consume a later same-seat direct officer.
|
|
25
24
|
*/
|
|
26
|
-
import { readdir } from "node:fs/promises";
|
|
27
|
-
import { join } from "node:path";
|
|
28
25
|
import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, } from "./compliance-transport.js";
|
|
29
26
|
import { extractSessionTimestampSpan, intervalRowsAroundAnchor, readLedgerSessionJsonl, } from "./ledger-session-read.js";
|
|
30
27
|
import { isRecord, isEnoent } from "./unknown-value.js";
|
|
31
28
|
import { retainedRunPathsMatch } from "./role-run-relocation.js";
|
|
32
29
|
import { runDirectoryOfSessionFile } from "./role-run-placement.js";
|
|
33
|
-
import {
|
|
30
|
+
import { readOfficerPointers } from "./archivist-record-pointer.js";
|
|
34
31
|
const DISPATCH_TOOLS = new Set(["ak_menxia_output", "ak_gatekeeper_output"]);
|
|
35
32
|
/** Officer terminating tool → current English officer identity. */
|
|
36
33
|
const OFFICER_TOOL_TO_FACE = {
|
|
@@ -311,61 +308,32 @@ function pairGateRounds(volumes) {
|
|
|
311
308
|
.map((round, index) => ({ ...round, roundIndex: index + 1 }));
|
|
312
309
|
}
|
|
313
310
|
/**
|
|
314
|
-
* Read and pair gate-cycle rounds from
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* (failure honesty — damaged topology must not wash to zero rounds).
|
|
311
|
+
* Read and pair gate-cycle rounds from the officer pointers booked in one
|
|
312
|
+
* run's current.json `officers` section; each pointer names the independent
|
|
313
|
+
* officer session 正本. Absent section → [] (lawful zero). A damaged pointer or
|
|
314
|
+
* officer session propagates — damaged topology must not wash to zero rounds.
|
|
319
315
|
*
|
|
320
316
|
* Pass `parentSessionFile` to keep only rounds whose attempt binding names that parent.
|
|
321
317
|
*/
|
|
322
|
-
export async function
|
|
323
|
-
const directories = typeof auditorRolesDirectory === "string"
|
|
324
|
-
? [auditorRolesDirectory]
|
|
325
|
-
: auditorRolesDirectory;
|
|
318
|
+
export async function readAnalystGateCyclesFromOfficers(parentRunDirectory, options = {}) {
|
|
326
319
|
const volumes = [];
|
|
327
|
-
// Same officer session pointed at N times
|
|
328
|
-
//
|
|
329
|
-
// seal is counted ×N in gate-cycle statistics (#753 acceptance A).
|
|
320
|
+
// Same officer session pointed at N times must classify once — else each
|
|
321
|
+
// accepted seal is counted ×N in gate-cycle statistics (#753 acceptance A).
|
|
330
322
|
const classifiedOfficerSessions = new Set();
|
|
331
|
-
for (const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (isEnoent(error))
|
|
323
|
+
for (const pointer of await readOfficerPointers(parentRunDirectory)) {
|
|
324
|
+
if (classifiedOfficerSessions.has(pointer.sessionFile))
|
|
325
|
+
continue;
|
|
326
|
+
classifiedOfficerSessions.add(pointer.sessionFile);
|
|
327
|
+
const officer = pointer.officer === "inspector" || pointer.officer === "notary"
|
|
328
|
+
? pointer.officer : undefined;
|
|
329
|
+
const classified = await classifyAuditorVolume(pointer.sessionFile, officer);
|
|
330
|
+
for (const volume of classified) {
|
|
331
|
+
if (options.parentSessionFile !== undefined &&
|
|
332
|
+
volume.parentSessionFile !== undefined &&
|
|
333
|
+
!await retainedRunPathsMatch(volume.parentSessionFile, options.parentSessionFile, runDirectoryOfSessionFile(options.parentSessionFile))) {
|
|
343
334
|
continue;
|
|
344
|
-
throw error;
|
|
345
|
-
}
|
|
346
|
-
for (const name of names) {
|
|
347
|
-
const path = join(directory, name);
|
|
348
|
-
const fromPointer = name.endsWith(".pointer.json");
|
|
349
|
-
const pointer = fromPointer
|
|
350
|
-
? await resolveOfficerSessionFromPointerFile(path)
|
|
351
|
-
: undefined;
|
|
352
|
-
const sessionPath = pointer?.sessionFile ?? path;
|
|
353
|
-
if (fromPointer) {
|
|
354
|
-
if (classifiedOfficerSessions.has(sessionPath))
|
|
355
|
-
continue;
|
|
356
|
-
classifiedOfficerSessions.add(sessionPath);
|
|
357
|
-
}
|
|
358
|
-
const officer = pointer?.officer === "inspector" || pointer?.officer === "notary"
|
|
359
|
-
? pointer.officer : undefined;
|
|
360
|
-
const classified = await classifyAuditorVolume(sessionPath, officer);
|
|
361
|
-
for (const volume of classified) {
|
|
362
|
-
if (options.parentSessionFile !== undefined &&
|
|
363
|
-
volume.parentSessionFile !== undefined &&
|
|
364
|
-
!await retainedRunPathsMatch(volume.parentSessionFile, options.parentSessionFile, runDirectoryOfSessionFile(options.parentSessionFile))) {
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
volumes.push(volume);
|
|
368
335
|
}
|
|
336
|
+
volumes.push(volume);
|
|
369
337
|
}
|
|
370
338
|
}
|
|
371
339
|
return pairGateRounds(volumes);
|
package/dist/analyst-ledger.js
CHANGED
|
@@ -12,23 +12,23 @@
|
|
|
12
12
|
* A2: classifyScopedRun retains typed per-run facts (frame span, tool intervals,
|
|
13
13
|
* terminal face) for metric-family modules — no longer discarded after checks.
|
|
14
14
|
*/
|
|
15
|
+
import { readSectionSync } from "./run-dossier.js";
|
|
15
16
|
import { readdir, readFile } from "node:fs/promises";
|
|
16
17
|
import { basename, join } from "node:path";
|
|
17
18
|
import { isSyntheticAnalystBookKey, resolveAnalystBookKey } from "./analyst-book-key.js";
|
|
18
19
|
import { pathContainedIn, physicalPathIdentity, resolveActivationLedgerHome, } from "./activation-ledger-topology.js";
|
|
19
20
|
import { listBookRunDirectories, parseRunLeaf, sessionFileOf, } from "./role-run-placement.js";
|
|
20
|
-
import { sitianRunVolumeDirectory } from "./sitian-appender.js";
|
|
21
21
|
import { autopsyWriterLock, readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
|
|
22
22
|
import { readRunTicketNumber } from "./run-ticket-number.js";
|
|
23
23
|
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.js";
|
|
24
24
|
import { extractSessionModelSequence, extractSessionTimestampSpan, extractSessionToolIntervals, LedgerSessionJsonlError, readLedgerSessionJsonl, } from "./ledger-session-read.js";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
25
|
+
import { readRunTerminal, } from "./run-terminal-artifacts.js";
|
|
26
|
+
import { readAnalystGateCyclesFromOfficers, } from "./analyst-gate-cycles-read.js";
|
|
27
27
|
import { isRecord, errorText, isMissingPathError } from "./unknown-value.js";
|
|
28
28
|
/** Existing public-cli run-state live phases — not a new state machine. */
|
|
29
29
|
const LIVE_RUN_STATES = new Set(["admitted", "running", "resumable"]);
|
|
30
30
|
/**
|
|
31
|
-
* Read lifecycle state from the
|
|
31
|
+
* Read lifecycle state from the state.jsonl run-state fact row.
|
|
32
32
|
* Used only to distinguish live in-flight runs from terminal no-receipt.
|
|
33
33
|
*/
|
|
34
34
|
async function readExistingRunLifecycleState(runDirectory) {
|
|
@@ -79,17 +79,8 @@ async function listLedgerBookNames(booksRoot) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
async function readInvocationScopeFields(runDirectory) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
raw = await readFile(join(runDirectory, "invocation.json"), "utf8");
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
if (isMissingPathError(error))
|
|
88
|
-
return undefined;
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
const parsed = JSON.parse(raw);
|
|
92
|
-
if (!isRecord(parsed))
|
|
82
|
+
const parsed = readSectionSync(runDirectory, "invocation");
|
|
83
|
+
if (parsed === undefined)
|
|
93
84
|
return undefined;
|
|
94
85
|
if (typeof parsed.projectRoot !== "string" || parsed.projectRoot.trim() === "") {
|
|
95
86
|
return undefined;
|
|
@@ -126,20 +117,11 @@ function decideIssueScope(input) {
|
|
|
126
117
|
}
|
|
127
118
|
async function resolveSessionFile(runDirectory) {
|
|
128
119
|
// Prefer invocation.sessionFile when present; fall back to S-family principal.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
&& parsed.sessionFile.trim() !== "") {
|
|
135
|
-
return typeof parsed.runDirectory === "string"
|
|
136
|
-
? rewriteRunDirectoryPathValue(parsed.sessionFile, parsed.runDirectory, runDirectory)
|
|
137
|
-
: parsed.sessionFile;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
if (!isMissingPathError(error))
|
|
142
|
-
throw error;
|
|
120
|
+
const parsed = readSectionSync(runDirectory, "invocation");
|
|
121
|
+
if (parsed !== undefined && typeof parsed.sessionFile === "string" && parsed.sessionFile.trim() !== "") {
|
|
122
|
+
return typeof parsed.runDirectory === "string"
|
|
123
|
+
? rewriteRunDirectoryPathValue(parsed.sessionFile, parsed.runDirectory, runDirectory)
|
|
124
|
+
: parsed.sessionFile;
|
|
143
125
|
}
|
|
144
126
|
return sessionFileOf(runDirectory);
|
|
145
127
|
}
|
|
@@ -331,10 +313,14 @@ async function classifyScopedRun(input) {
|
|
|
331
313
|
ghostLeg = ghost.leg;
|
|
332
314
|
}
|
|
333
315
|
try {
|
|
334
|
-
const
|
|
316
|
+
const read = readRunTerminal(input.runDirectory);
|
|
317
|
+
// A no_receipt terminal is the explicit form of "no accepted receipt".
|
|
318
|
+
const artifact = read.status === "present" && read.face === "no_receipt"
|
|
319
|
+
? { status: "absent" }
|
|
320
|
+
: read;
|
|
335
321
|
if (artifact.status === "unreadable") {
|
|
336
322
|
missingSources.push("terminal-artifact");
|
|
337
|
-
reasons.push(
|
|
323
|
+
reasons.push(`terminal: ${artifact.reason}`);
|
|
338
324
|
}
|
|
339
325
|
else if (artifact.status === "absent") {
|
|
340
326
|
if (lifecycle === "admitted" || lifecycle === "running") {
|
|
@@ -349,10 +335,10 @@ async function classifyScopedRun(input) {
|
|
|
349
335
|
terminal = { status: "absent" };
|
|
350
336
|
}
|
|
351
337
|
else {
|
|
352
|
-
// role already required nonblank by
|
|
338
|
+
// role already required nonblank by readRunTerminal (single owner).
|
|
353
339
|
terminal = {
|
|
354
340
|
status: "present",
|
|
355
|
-
|
|
341
|
+
face: artifact.face,
|
|
356
342
|
body: artifact.body,
|
|
357
343
|
role: artifact.body.role,
|
|
358
344
|
};
|
|
@@ -387,13 +373,13 @@ async function classifyScopedRun(input) {
|
|
|
387
373
|
if (frameSpan === undefined || toolIntervals === undefined || terminal === undefined) {
|
|
388
374
|
throw new Error(`classifyScopedRun internal invariant: missing retained facts for ${input.runId}`);
|
|
389
375
|
}
|
|
390
|
-
//
|
|
376
|
+
// Officer gate pairs stay inside the sole scan (families must
|
|
391
377
|
// not readdir this tree again). Missing directory → []. Damaged discovered
|
|
392
378
|
// nested JSONL is page-local unreadable — never silently under-count rounds.
|
|
393
379
|
let gateCycles;
|
|
394
380
|
try {
|
|
395
381
|
const parentSessionFile = sessionFileOf(input.runDirectory);
|
|
396
|
-
gateCycles = await
|
|
382
|
+
gateCycles = await readAnalystGateCyclesFromOfficers(input.runDirectory, { parentSessionFile });
|
|
397
383
|
}
|
|
398
384
|
catch (error) {
|
|
399
385
|
return {
|
|
@@ -33,7 +33,10 @@ const SUCCESS_STATUS = {
|
|
|
33
33
|
doctor: new Set(["completed"]),
|
|
34
34
|
merger: new Set(["completed"]),
|
|
35
35
|
};
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Locate collector groups array from the canonical typed receipt faces.
|
|
38
|
+
* Same leaf order as extractStatus: body / receipt / decisiveFacts / payloads.
|
|
39
|
+
*/
|
|
37
40
|
function findCollectorGroups(body) {
|
|
38
41
|
if (Array.isArray(body.groups))
|
|
39
42
|
return body.groups;
|
|
@@ -45,6 +48,16 @@ function findCollectorGroups(body) {
|
|
|
45
48
|
const facts = outcome.decisiveFacts;
|
|
46
49
|
if (isRecord(facts) && Array.isArray(facts.groups))
|
|
47
50
|
return facts.groups;
|
|
51
|
+
// Typed sealed receipt: settlement keeps payloads on attempt-history; the
|
|
52
|
+
// terminal reader rehydrates them via attemptHistoryIdentity. Groups are the
|
|
53
|
+
// collector leaf on that receipt — never invent a second discriminator.
|
|
54
|
+
if (Array.isArray(outcome.payloads)) {
|
|
55
|
+
for (let index = outcome.payloads.length - 1; index >= 0; index -= 1) {
|
|
56
|
+
const payload = outcome.payloads[index];
|
|
57
|
+
if (isRecord(payload) && Array.isArray(payload.groups))
|
|
58
|
+
return payload.groups;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
48
61
|
}
|
|
49
62
|
return undefined;
|
|
50
63
|
}
|
|
@@ -54,6 +67,17 @@ export function extractStatus(body) {
|
|
|
54
67
|
if (isRecord(outcome) && typeof outcome.status === "string" && outcome.status.trim() !== "") {
|
|
55
68
|
return outcome.status;
|
|
56
69
|
}
|
|
70
|
+
// Typed sealed receipt: settlement keeps payloads on attempt-history; the
|
|
71
|
+
// terminal reader rehydrates them via attemptHistoryIdentity. Status is the role's
|
|
72
|
+
// own leaf on that receipt — never invent a second mapping.
|
|
73
|
+
if (isRecord(outcome) && Array.isArray(outcome.payloads)) {
|
|
74
|
+
for (let index = outcome.payloads.length - 1; index >= 0; index -= 1) {
|
|
75
|
+
const payload = outcome.payloads[index];
|
|
76
|
+
if (isRecord(payload) && typeof payload.status === "string" && payload.status.trim() !== "") {
|
|
77
|
+
return payload.status;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
57
81
|
const receipt = body.receipt;
|
|
58
82
|
if (isRecord(receipt) && typeof receipt.status === "string" && receipt.status.trim() !== "") {
|
|
59
83
|
return receipt.status;
|
|
@@ -15,13 +15,10 @@ function projectTerminal(facts) {
|
|
|
15
15
|
if (facts.terminal.status === "absent") {
|
|
16
16
|
return { kind: "death", channel: "no-receipt" };
|
|
17
17
|
}
|
|
18
|
-
if (facts.terminal.
|
|
18
|
+
if (facts.terminal.face === "error") {
|
|
19
19
|
return { kind: "death", channel: "error" };
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
return { kind: "death", channel: "audit-incomplete" };
|
|
23
|
-
}
|
|
24
|
-
// report.json — receipt face; classCount only when producer wrote a number.
|
|
21
|
+
// report face — receipt; classCount only when producer wrote a number.
|
|
25
22
|
const status = extractStatus(facts.terminal.body);
|
|
26
23
|
const classCount = readClassCount(facts.terminal.body);
|
|
27
24
|
const receiptStatus = status ?? "unparsed";
|
|
@@ -1,29 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
|
+
import { isRecord } from "./unknown-value.js";
|
|
3
|
+
import { latestOfficerPointersFromRecords, RUN_HISTORY_FILE } from "./run-dossier.js";
|
|
4
|
+
import { OFFICER_POINTER_RECORD_KIND } from "./run-dossier-files.js";
|
|
5
|
+
import { readSitianRecords, reportRunRecord } from "./sitian-facade.js";
|
|
5
6
|
import { homeFromRunDirectory } from "./activation-ledger-topology.js";
|
|
6
7
|
import { findRunDirectoryById, readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
|
|
7
8
|
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.js";
|
|
8
|
-
import { parseRunLeaf, runDirectoryOfSessionFile
|
|
9
|
-
import { sitianVolumeDirectory } from "./sitian-appender.js";
|
|
9
|
+
import { parseRunLeaf, runDirectoryOfSessionFile } from "./role-run-placement.js";
|
|
10
10
|
/** Typed parent-side pointer to an independent officer run 正本 (ADR 0079 / #675). */
|
|
11
11
|
export const DIRECT_OFFICER_RUN_POINTER_KIND = "direct-officer-run-pointer";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
12
|
+
/** History record kind of a booked officer pointer (appender routes it to history.jsonl). */
|
|
13
|
+
export { OFFICER_POINTER_RECORD_KIND };
|
|
15
14
|
/**
|
|
16
|
-
* Book a typed pointer
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Book a typed pointer as one history record of the parent run; the settlement
|
|
16
|
+
* seam projects the latest per officer into current.json `officers`.
|
|
17
|
+
* Never fabricates user/assistant/toolResult rows (#675).
|
|
19
18
|
*
|
|
20
|
-
*
|
|
21
|
-
* same-parent re-summons
|
|
22
|
-
*
|
|
19
|
+
* Latest record wins per officer under one parent (#753 gate-round accounting):
|
|
20
|
+
* a same-parent re-summons supersedes the earlier pointer instead of adding a
|
|
21
|
+
* second one that re-scans the full officer session and multiplies gate-cycle counts.
|
|
23
22
|
*/
|
|
24
23
|
export function bookDirectOfficerRunPointer(options) {
|
|
25
|
-
const pointerFile = directOfficerRunPointerFile(options.parentSessionFile, options.officer);
|
|
26
|
-
mkdirSync(dirname(pointerFile), { recursive: true });
|
|
27
24
|
const submissionToolCallId = options.submissionToolCallId?.trim() ?? "";
|
|
28
25
|
const pointer = {
|
|
29
26
|
version: 1,
|
|
@@ -35,23 +32,17 @@ export function bookDirectOfficerRunPointer(options) {
|
|
|
35
32
|
: {}),
|
|
36
33
|
...(submissionToolCallId === "" ? {} : { submissionToolCallId }),
|
|
37
34
|
};
|
|
38
|
-
|
|
35
|
+
reportRunRecord(runDirectoryOfSessionFile(options.parentSessionFile), OFFICER_POINTER_RECORD_KIND, pointer, "submission-gate");
|
|
39
36
|
return pointer;
|
|
40
37
|
}
|
|
41
38
|
/** Shared strict pointer IO; internal audit wraps unreadability as a re-summons. */
|
|
42
|
-
export async function
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
raw = JSON.parse(await readFile(pointerPath, "utf8"));
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
throw new Error(`direct officer run pointer unreadable in ${pointerPath}: ${errorText(error)}`, { cause: error });
|
|
49
|
-
}
|
|
39
|
+
export async function resolveOfficerPointer(raw, parentRunDirectory) {
|
|
40
|
+
const where = `${parentRunDirectory} officers`;
|
|
50
41
|
if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
|
|
51
|
-
throw new Error(`direct officer run pointer has unknown shape in ${
|
|
42
|
+
throw new Error(`direct officer run pointer has unknown shape in ${where}`);
|
|
52
43
|
}
|
|
53
44
|
if (typeof raw.sessionFile !== "string" || raw.sessionFile.trim() === "") {
|
|
54
|
-
throw new Error(`direct officer run pointer missing sessionFile in ${
|
|
45
|
+
throw new Error(`direct officer run pointer missing sessionFile in ${where}`);
|
|
55
46
|
}
|
|
56
47
|
const officer = raw.officer === "inspector" || raw.officer === "notary"
|
|
57
48
|
|| raw.officer === "auditor" || raw.officer === "countersign" ? raw.officer : undefined;
|
|
@@ -60,7 +51,6 @@ export async function resolveOfficerSessionFromPointerFile(pointerPath) {
|
|
|
60
51
|
let sessionFile = raw.sessionFile;
|
|
61
52
|
const recordedRunDirectory = runDirectory ?? runDirectoryOfSessionFile(sessionFile);
|
|
62
53
|
const leaf = parseRunLeaf(basename(recordedRunDirectory));
|
|
63
|
-
const parentRunDirectory = runDirectoryFromSessionDirectory(dirname(dirname(pointerPath)));
|
|
64
54
|
const parentIdentity = await readRoleRunIdentity(parentRunDirectory);
|
|
65
55
|
if (leaf !== undefined && parentIdentity !== undefined) {
|
|
66
56
|
const currentRunDirectory = await findRunDirectoryById(homeFromRunDirectory(parentRunDirectory), leaf.runId, parentIdentity.bookKey, leaf.role);
|
|
@@ -79,10 +69,28 @@ export async function resolveOfficerSessionFromPointerFile(pointerPath) {
|
|
|
79
69
|
...(submissionToolCallId === undefined ? {} : { submissionToolCallId }),
|
|
80
70
|
};
|
|
81
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The latest pointer booked per officer on one run, as stored. History absent → empty.
|
|
74
|
+
* Canonical malformed diagnostics are not discarded: a damaged officer source must
|
|
75
|
+
* not wash into lawful zero rounds (#1161 O1).
|
|
76
|
+
*/
|
|
77
|
+
export async function readBookedOfficerPointers(parentRunDirectory) {
|
|
78
|
+
const { records, diagnostics } = await readSitianRecords(join(parentRunDirectory, RUN_HISTORY_FILE));
|
|
79
|
+
if (diagnostics.length > 0) {
|
|
80
|
+
throw new Error(`${RUN_HISTORY_FILE} has ${diagnostics.length} malformed row(s); officer pointers unreadable: ${parentRunDirectory}`);
|
|
81
|
+
}
|
|
82
|
+
return latestOfficerPointersFromRecords(records);
|
|
83
|
+
}
|
|
84
|
+
/** Every pointer booked on one run, in officer-name order. No pointers → []. */
|
|
85
|
+
export async function readOfficerPointers(parentRunDirectory) {
|
|
86
|
+
const officers = await readBookedOfficerPointers(parentRunDirectory);
|
|
87
|
+
return Promise.all(Object.keys(officers).sort().map((name) => resolveOfficerPointer(officers[name], parentRunDirectory)));
|
|
88
|
+
}
|
|
82
89
|
/** Missing or unreadable is not a pass: the internal audit caller re-runs the gate. */
|
|
83
90
|
export async function readDirectOfficerRunPointer(parentSessionFile, officer) {
|
|
84
91
|
try {
|
|
85
|
-
const
|
|
92
|
+
const parentRunDirectory = runDirectoryOfSessionFile(parentSessionFile);
|
|
93
|
+
const pointer = await resolveOfficerPointer((await readBookedOfficerPointers(parentRunDirectory))[officer], parentRunDirectory);
|
|
86
94
|
if (pointer.officer !== officer)
|
|
87
95
|
return undefined;
|
|
88
96
|
return { ...pointer, officer, version: 1, kind: DIRECT_OFFICER_RUN_POINTER_KIND };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
|
+
import { RUN_HISTORY_FILE, RUN_LOG_FILE, runCurrentPath } from "./run-dossier.js";
|
|
2
3
|
import {
|
|
3
|
-
roleRunArtifactsDirectory,
|
|
4
4
|
runDirectoryOfSessionFile,
|
|
5
5
|
sessionFileOf
|
|
6
6
|
} from "./role-run-placement.js";
|
|
@@ -27,10 +27,11 @@ function createAuditorDossierTool(runDirectory) {
|
|
|
27
27
|
}
|
|
28
28
|
const details = {
|
|
29
29
|
runDirectory,
|
|
30
|
-
|
|
30
|
+
current: runCurrentPath(runDirectory),
|
|
31
31
|
parentSessionCandidate: sessionFileOf(runDirectory),
|
|
32
32
|
attachments: join(runDirectory, "attachments"),
|
|
33
|
-
|
|
33
|
+
log: join(runDirectory, RUN_LOG_FILE),
|
|
34
|
+
history: join(runDirectory, RUN_HISTORY_FILE)
|
|
34
35
|
};
|
|
35
36
|
return {
|
|
36
37
|
content: [{ type: "text", text: JSON.stringify(details) }],
|
package/dist/auditor-soul.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { readPageSync } from "./run-dossier.js";
|
|
2
3
|
import { basename, join } from "node:path";
|
|
3
4
|
import { parseRunLeaf } from "./role-run-placement.js";
|
|
4
5
|
import { isRecord } from "./unknown-value.js";
|
|
@@ -93,18 +94,9 @@ function subjectFromSourceDirectory(sourceRunDirectory) {
|
|
|
93
94
|
* Explicit subject wins; otherwise the source-run role is the audited object.
|
|
94
95
|
*/
|
|
95
96
|
export async function readAuditorResumeBinding(runDirectory) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
raw = JSON.parse(await readFile(join(runDirectory, "admitted-request.json"), "utf8"));
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
if (error.code === "ENOENT")
|
|
102
|
-
return undefined;
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
if (!isRecord(raw))
|
|
97
|
+
const record = readPageSync(runDirectory, "admitted");
|
|
98
|
+
if (record === undefined)
|
|
106
99
|
return undefined;
|
|
107
|
-
const record = raw;
|
|
108
100
|
if (typeof record.sourceRunPath !== "string" || record.sourceRunPath.trim() === "")
|
|
109
101
|
return undefined;
|
|
110
102
|
const subject = isAuditorSoulRole(record.auditorSubject)
|
package/dist/doctor-evidence.js
CHANGED
|
@@ -9,7 +9,7 @@ async function discoverCaseFiles(root) { const found = []; async function walk(d
|
|
|
9
9
|
if (item.isDirectory()) {
|
|
10
10
|
await walk(path, depth + 1);
|
|
11
11
|
}
|
|
12
|
-
else if (item.isFile() && (item.name.endsWith(".jsonl") || (item.name === "
|
|
12
|
+
else if (item.isFile() && ((item.name.endsWith(".jsonl") && item.name !== "state.jsonl" && !(item.name === "log.jsonl" && depth === 1)) || (item.name === "log.jsonl" && depth === 1)))
|
|
13
13
|
found.push(path);
|
|
14
14
|
} } await walk(root, 0); return found.sort(); }
|
|
15
15
|
function sourceList(count, sources) { return { count, sources: [...new Set(sources)].sort() }; }
|
|
@@ -122,9 +122,9 @@ export async function loadDoctorCase(runsPath, projectRoot) {
|
|
|
122
122
|
const id = relative(root, path).split(sep).join("/");
|
|
123
123
|
const bytes = await readFile(path);
|
|
124
124
|
const content = bytes.toString("utf8");
|
|
125
|
-
const kind = id.endsWith(".jsonl") ? "
|
|
125
|
+
const kind = id.split("/").length === 2 && id.endsWith("/log.jsonl") ? "log" : "session";
|
|
126
126
|
evidence.push({ id, kind, byteLength: bytes.byteLength, contentLength: content.length, sha256: sha256Hex(bytes), content });
|
|
127
|
-
if (kind === "
|
|
127
|
+
if (kind === "log")
|
|
128
128
|
continue;
|
|
129
129
|
const result = deriveSession(content, id);
|
|
130
130
|
sessions.push(result.session);
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
* `ak-role resume`) mints a new one. Never courtAttemptId, never Pi session
|
|
16
16
|
* toolResult join keys, and never a detour-owned sidecar file.
|
|
17
17
|
*/
|
|
18
|
+
import { readPageSync, RUN_LOG_FILE } from "./run-dossier.js";
|
|
18
19
|
import { randomUUID } from "node:crypto";
|
|
19
20
|
import { readFileSync } from "node:fs";
|
|
20
21
|
import { join } from "node:path";
|
|
21
22
|
export { runDirectoryFromSessionDirectory, sessionFileIn as sessionFileFromSessionDirectory, } from "./role-run-placement.js";
|
|
22
23
|
import { ENGINE_DETOUR_TOOL_NAME } from "./engine-detour.js";
|
|
23
|
-
import { sitianRunVolumeDirectory, sitianVolumeRecordsFile } from "./sitian-appender.js";
|
|
24
24
|
import { readSitianRecords, resolveSitianRecordPath, sitianReport, } from "./sitian-facade.js";
|
|
25
25
|
import { isRecord } from "./unknown-value.js";
|
|
26
26
|
/** Sitian event kind for one detour-tool call (volume under session/). */
|
|
@@ -28,11 +28,11 @@ export const ENGINE_DETOUR_CALL_KIND = "engine-detour-call";
|
|
|
28
28
|
/** decisiveFacts key — name states detour-tool scope, not full engine usage. */
|
|
29
29
|
export const ENGINE_DETOUR_TOOL_USAGE_FACT_KEY = "engineDetourToolUsage";
|
|
30
30
|
/**
|
|
31
|
-
* Run-relative
|
|
31
|
+
* Run-relative path of the log that holds one detour call's record.
|
|
32
32
|
* Openable once the run directory is known from the terminal's run id.
|
|
33
33
|
* Contains no runId bytes itself (#108 + #537).
|
|
34
34
|
*/
|
|
35
|
-
export const ENGINE_DETOUR_CALL_RECORD_FILE_RELATIVE =
|
|
35
|
+
export const ENGINE_DETOUR_CALL_RECORD_FILE_RELATIVE = RUN_LOG_FILE;
|
|
36
36
|
/** stdout UTF-8 byte length (ticket-frozen metric; empty stdout is real 0). */
|
|
37
37
|
export function engineDetourStdoutByteLength(stdout) {
|
|
38
38
|
return Buffer.byteLength(stdout, "utf8");
|
|
@@ -182,19 +182,11 @@ export async function readEngineDetourToolUsage(input) {
|
|
|
182
182
|
const calls = Array.from(callsByToolCallId.values());
|
|
183
183
|
return { callCount: calls.length, calls };
|
|
184
184
|
}
|
|
185
|
-
/** One shared
|
|
185
|
+
/** One shared state.jsonl invocation-row reader (absent → undefined; other errors propagate). */
|
|
186
186
|
function readInvocationRecord(runDirectory) {
|
|
187
|
-
|
|
188
|
-
const raw = JSON.parse(readFileSync(join(runDirectory, "invocation.json"), "utf8"));
|
|
189
|
-
return isRecord(raw) ? raw : undefined;
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
if (error?.code === "ENOENT")
|
|
193
|
-
return undefined;
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
187
|
+
return readPageSync(runDirectory, "invocation");
|
|
196
188
|
}
|
|
197
|
-
/** True when invocation
|
|
189
|
+
/** True when the invocation section carries a non-empty engine axis. */
|
|
198
190
|
export async function readInvocationEngineMounted(runDirectory) {
|
|
199
191
|
const raw = readInvocationRecord(runDirectory);
|
|
200
192
|
if (raw === undefined)
|