@akagilnc/pi-workflow-roles 0.1.5483 → 0.1.5571
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/acp-host/production-host.js +6312 -6376
- package/dist/analyst-gate-cycles-read.js +28 -78
- package/dist/analyst-ledger.js +26 -42
- package/dist/analyst-metric-families/acceptance-success-rework.js +25 -1
- package/dist/analyst-metric-families/round-timeline.js +2 -5
- package/dist/archivist-record-entry.js +4 -3
- package/dist/archivist-record-pointer.js +71 -36
- package/dist/atomic-write.js +2 -2
- package/dist/auditor-dossier-tool.js +11 -6
- package/dist/auditor-soul.js +5 -12
- package/dist/collector-config.js +6 -5
- package/dist/doctor-evidence.js +8 -4
- package/dist/engine-detour-usage.js +8 -22
- package/dist/headless-host/production-host.js +5969 -6011
- package/dist/host-descriptions.js +0 -4
- package/dist/host-session-record.js +266 -267
- package/dist/ledger-session-read.js +5 -0
- package/dist/migrate-book-topology.js +1369 -722
- package/dist/navigator-public-session.js +13 -17
- package/dist/notary-source-run.js +8 -16
- package/dist/packaged-role-registry.js +5 -56
- package/dist/pi/durable-principal.js +2 -3
- package/dist/pi/role-turn-host.js +79 -15
- package/dist/public-cli/auto-resume.js +8 -47
- package/dist/public-cli/instruction-seat-run.js +9 -7
- package/dist/public-cli/invocation.js +67 -100
- package/dist/public-cli/main.js +2689 -2532
- package/dist/public-cli/post-admission.js +96 -55
- package/dist/public-cli/run-lifecycle.js +44 -100
- package/dist/public-cli/settlement.js +184 -429
- package/dist/public-cli/terminal.js +0 -12
- package/dist/role-run-placement.js +53 -16
- package/dist/role-run-relocation.js +111 -52
- package/dist/run-dossier-files.js +30 -0
- package/dist/run-dossier.js +322 -0
- package/dist/run-terminal-artifacts.js +69 -202
- package/dist/run-ticket-number.js +49 -7
- package/dist/session-assistant-usage.js +9 -18
- package/dist/session-identity.js +49 -19
- package/dist/sitian-appender.js +29 -18
- package/dist/sitian-facade.js +11 -0
- package/dist/sitian-reader.js +44 -29
- package/dist/submission-gate.js +1 -1
- package/dist/submission-ledger.js +4 -4
- package/dist/ticket-provenance-contracts.js +2 -8
- package/dist/ticket-provenance.js +25 -9
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -0
- package/src/acp-host/production-host.ts +1 -1
- package/src/acp-host/role-turn-host.ts +11 -4
- package/src/analyst-gate-cycles-read.ts +31 -88
- package/src/analyst-ledger.ts +36 -51
- package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
- package/src/analyst-metric-families/gate-cycles.ts +1 -1
- package/src/analyst-metric-families/round-timeline.ts +2 -5
- package/src/analyst-page.ts +3 -3
- package/src/archivist-record-entry.ts +6 -5
- package/src/archivist-record-pointer.ts +92 -42
- package/src/atomic-write.ts +3 -1
- package/src/auditor-dossier-tool.ts +14 -8
- package/src/auditor-soul.ts +5 -10
- package/src/book-topology-migration-placement.ts +16 -3
- package/src/book-topology-record-class-migrators.ts +4 -2
- package/src/book-topology-runs-migrator.ts +6 -3
- package/src/collector-config.ts +5 -3
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-evidence.ts +7 -3
- package/src/engine-detour-tool.ts +1 -1
- package/src/engine-detour-usage.ts +12 -25
- package/src/external-host-turn-loop.ts +25 -0
- package/src/factory-board.ts +16 -81
- package/src/headless-host/production-host.ts +1 -4
- package/src/headless-host/role-turn-host.ts +56 -21
- package/src/host-descriptions.ts +0 -6
- package/src/host-session-record.ts +224 -140
- package/src/ledger-session-read.ts +6 -0
- package/src/navigator-public-session.ts +13 -18
- package/src/navigator-work-context.ts +3 -1
- package/src/notary-role.ts +2 -1
- package/src/notary-source-run.ts +8 -18
- package/src/packaged-role-registry.ts +1 -65
- package/src/pi/adapter.ts +50 -7
- package/src/pi/durable-principal.ts +2 -4
- package/src/pi/role-turn-host.ts +85 -14
- package/src/public-cli/auto-resume.ts +7 -43
- package/src/public-cli/instruction-seat-run.ts +13 -6
- package/src/public-cli/invocation.ts +90 -144
- package/src/public-cli/post-admission.ts +111 -72
- package/src/public-cli/run-lifecycle.ts +47 -115
- package/src/public-cli/settlement.ts +221 -589
- package/src/public-cli/terminal.ts +1 -61
- package/src/role-envelope.ts +4 -2
- package/src/role-run-placement.ts +65 -17
- package/src/role-run-relocation.ts +110 -70
- package/src/role-runtime.ts +46 -69
- package/src/run-dossier-files.ts +33 -0
- package/src/run-dossier.ts +357 -0
- package/src/run-terminal-artifacts.ts +84 -240
- package/src/run-ticket-number.ts +82 -14
- package/src/session-assistant-usage.ts +8 -20
- package/src/session-identity.ts +52 -21
- package/src/sitian-appender.ts +33 -26
- package/src/sitian-facade.ts +22 -0
- package/src/sitian-reader.ts +62 -32
- package/src/submission-gate.ts +1 -1
- package/src/submission-ledger.ts +4 -4
- package/src/ticket-provenance-contracts.ts +3 -6
- package/src/ticket-provenance.ts +23 -11
- package/src/ticket-snapshot.ts +3 -2
- package/src/ticket-trajectory.ts +95 -109
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sole
|
|
2
|
+
* Sole reader for gate-cycle facts of the officers pointed at from current.json `officers`.
|
|
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
|
|
11
9
|
* the current English officer identity (inspector | notary).
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* 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).
|
|
16
13
|
* An accepted gate terminating receipt (isError:false pair on dispatch/officer
|
|
17
14
|
* tool) whose required typed facts are unusable — status, dispatch officer, or
|
|
18
15
|
* first/last span missing/unknown/unparseable/inverted — also fails loudly via
|
|
@@ -25,11 +22,12 @@
|
|
|
25
22
|
* `ak_auditor_parent_attempt_binding.parent.attemptEntryId` — never seat/time guessing.
|
|
26
23
|
* An orphan accepted dispatch must not consume a later same-seat direct officer.
|
|
27
24
|
*/
|
|
28
|
-
import { readdir } from "node:fs/promises";
|
|
29
|
-
import { join } from "node:path";
|
|
30
25
|
import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, } from "./compliance-transport.js";
|
|
31
26
|
import { extractSessionTimestampSpan, intervalRowsAroundAnchor, readLedgerSessionJsonl, } from "./ledger-session-read.js";
|
|
32
|
-
import { isRecord,
|
|
27
|
+
import { isRecord, isEnoent } from "./unknown-value.js";
|
|
28
|
+
import { retainedRunPathsMatch } from "./role-run-relocation.js";
|
|
29
|
+
import { runDirectoryOfSessionFile } from "./role-run-placement.js";
|
|
30
|
+
import { readOfficerPointers } from "./archivist-record-pointer.js";
|
|
33
31
|
const DISPATCH_TOOLS = new Set(["ak_menxia_output", "ak_gatekeeper_output"]);
|
|
34
32
|
/** Officer terminating tool → current English officer identity. */
|
|
35
33
|
const OFFICER_TOOL_TO_FACE = {
|
|
@@ -309,81 +307,33 @@ function pairGateRounds(volumes) {
|
|
|
309
307
|
.sort((a, b) => a.officerStartedAt.localeCompare(b.officerStartedAt))
|
|
310
308
|
.map((round, index) => ({ ...round, roundIndex: index + 1 }));
|
|
311
309
|
}
|
|
312
|
-
/** Resolve a direct-officer-run-pointer file to the officer session 正本 path. */
|
|
313
|
-
async function resolveOfficerSessionFromPointerFile(pointerPath) {
|
|
314
|
-
const { readFile } = await import("node:fs/promises");
|
|
315
|
-
let raw;
|
|
316
|
-
try {
|
|
317
|
-
raw = JSON.parse(await readFile(pointerPath, "utf8"));
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
throw new Error(`direct officer run pointer unreadable in ${pointerPath}: ${errorText(error)}`, { cause: error });
|
|
321
|
-
}
|
|
322
|
-
if (!isRecord(raw) || raw.kind !== "direct-officer-run-pointer" || raw.version !== 1) {
|
|
323
|
-
throw new Error(`direct officer run pointer has unknown shape in ${pointerPath}`);
|
|
324
|
-
}
|
|
325
|
-
const sessionFile = raw.sessionFile;
|
|
326
|
-
if (typeof sessionFile !== "string" || sessionFile.trim() === "") {
|
|
327
|
-
throw new Error(`direct officer run pointer missing sessionFile in ${pointerPath}`);
|
|
328
|
-
}
|
|
329
|
-
const officer = raw.officer === "inspector" || raw.officer === "notary"
|
|
330
|
-
? raw.officer : undefined;
|
|
331
|
-
return { sessionFile, ...(officer === undefined ? {} : { officer }) };
|
|
332
|
-
}
|
|
333
310
|
/**
|
|
334
|
-
* Read and pair gate-cycle rounds from
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
* (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.
|
|
339
315
|
*
|
|
340
316
|
* Pass `parentSessionFile` to keep only rounds whose attempt binding names that parent.
|
|
341
317
|
*/
|
|
342
|
-
export async function
|
|
343
|
-
const directories = typeof auditorRolesDirectory === "string"
|
|
344
|
-
? [auditorRolesDirectory]
|
|
345
|
-
: auditorRolesDirectory;
|
|
318
|
+
export async function readAnalystGateCyclesFromOfficers(parentRunDirectory, options = {}) {
|
|
346
319
|
const volumes = [];
|
|
347
|
-
// Same officer session pointed at N times
|
|
348
|
-
//
|
|
349
|
-
// seal is counted ×N in terminal gate-round (#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).
|
|
350
322
|
const classifiedOfficerSessions = new Set();
|
|
351
|
-
for (const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
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))) {
|
|
363
334
|
continue;
|
|
364
|
-
throw error;
|
|
365
|
-
}
|
|
366
|
-
for (const name of names) {
|
|
367
|
-
const path = join(directory, name);
|
|
368
|
-
const fromPointer = name.endsWith(".pointer.json");
|
|
369
|
-
const pointer = fromPointer
|
|
370
|
-
? await resolveOfficerSessionFromPointerFile(path)
|
|
371
|
-
: undefined;
|
|
372
|
-
const sessionPath = pointer?.sessionFile ?? path;
|
|
373
|
-
if (fromPointer) {
|
|
374
|
-
if (classifiedOfficerSessions.has(sessionPath))
|
|
375
|
-
continue;
|
|
376
|
-
classifiedOfficerSessions.add(sessionPath);
|
|
377
|
-
}
|
|
378
|
-
const classified = await classifyAuditorVolume(sessionPath, pointer?.officer);
|
|
379
|
-
for (const volume of classified) {
|
|
380
|
-
if (options.parentSessionFile !== undefined &&
|
|
381
|
-
volume.parentSessionFile !== undefined &&
|
|
382
|
-
volume.parentSessionFile !== options.parentSessionFile) {
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
volumes.push(volume);
|
|
386
335
|
}
|
|
336
|
+
volumes.push(volume);
|
|
387
337
|
}
|
|
388
338
|
}
|
|
389
339
|
return pairGateRounds(volumes);
|
package/dist/analyst-ledger.js
CHANGED
|
@@ -12,21 +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
|
-
import { listBookRunDirectories } from "./role-run-placement.js";
|
|
20
|
+
import { listBookRunDirectories, parseRunLeaf, sessionFileOf, } from "./role-run-placement.js";
|
|
20
21
|
import { autopsyWriterLock, readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
|
|
21
22
|
import { readRunTicketNumber } from "./run-ticket-number.js";
|
|
23
|
+
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.js";
|
|
22
24
|
import { extractSessionModelSequence, extractSessionTimestampSpan, extractSessionToolIntervals, LedgerSessionJsonlError, readLedgerSessionJsonl, } from "./ledger-session-read.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
+
import { readRunTerminal, } from "./run-terminal-artifacts.js";
|
|
26
|
+
import { readAnalystGateCyclesFromOfficers, } from "./analyst-gate-cycles-read.js";
|
|
25
27
|
import { isRecord, errorText, isMissingPathError } from "./unknown-value.js";
|
|
26
28
|
/** Existing public-cli run-state live phases — not a new state machine. */
|
|
27
29
|
const LIVE_RUN_STATES = new Set(["admitted", "running", "resumable"]);
|
|
28
30
|
/**
|
|
29
|
-
* Read lifecycle state from the
|
|
31
|
+
* Read lifecycle state from the state.jsonl run-state fact row.
|
|
30
32
|
* Used only to distinguish live in-flight runs from terminal no-receipt.
|
|
31
33
|
*/
|
|
32
34
|
async function readExistingRunLifecycleState(runDirectory) {
|
|
@@ -65,12 +67,6 @@ async function classifyGhostCandidate(input) {
|
|
|
65
67
|
},
|
|
66
68
|
};
|
|
67
69
|
}
|
|
68
|
-
function parseRunDirectoryName(name) {
|
|
69
|
-
const at = name.lastIndexOf("@");
|
|
70
|
-
if (at <= 0 || at === name.length - 1)
|
|
71
|
-
return undefined;
|
|
72
|
-
return { runId: name.slice(0, at), role: name.slice(at + 1) };
|
|
73
|
-
}
|
|
74
70
|
async function listLedgerBookNames(booksRoot) {
|
|
75
71
|
try {
|
|
76
72
|
const entries = await readdir(booksRoot, { withFileTypes: true });
|
|
@@ -83,17 +79,8 @@ async function listLedgerBookNames(booksRoot) {
|
|
|
83
79
|
}
|
|
84
80
|
}
|
|
85
81
|
async function readInvocationScopeFields(runDirectory) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
raw = await readFile(join(runDirectory, "invocation.json"), "utf8");
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
if (isMissingPathError(error))
|
|
92
|
-
return undefined;
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
const parsed = JSON.parse(raw);
|
|
96
|
-
if (!isRecord(parsed))
|
|
82
|
+
const parsed = readSectionSync(runDirectory, "invocation");
|
|
83
|
+
if (parsed === undefined)
|
|
97
84
|
return undefined;
|
|
98
85
|
if (typeof parsed.projectRoot !== "string" || parsed.projectRoot.trim() === "") {
|
|
99
86
|
return undefined;
|
|
@@ -130,20 +117,13 @@ function decideIssueScope(input) {
|
|
|
130
117
|
}
|
|
131
118
|
async function resolveSessionFile(runDirectory) {
|
|
132
119
|
// Prefer invocation.sessionFile when present; fall back to S-family principal.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
&& parsed.sessionFile.trim() !== "") {
|
|
139
|
-
return parsed.sessionFile;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
if (!isMissingPathError(error))
|
|
144
|
-
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;
|
|
145
125
|
}
|
|
146
|
-
return
|
|
126
|
+
return sessionFileOf(runDirectory);
|
|
147
127
|
}
|
|
148
128
|
/**
|
|
149
129
|
* Attribute session rows to one run.
|
|
@@ -333,10 +313,14 @@ async function classifyScopedRun(input) {
|
|
|
333
313
|
ghostLeg = ghost.leg;
|
|
334
314
|
}
|
|
335
315
|
try {
|
|
336
|
-
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;
|
|
337
321
|
if (artifact.status === "unreadable") {
|
|
338
322
|
missingSources.push("terminal-artifact");
|
|
339
|
-
reasons.push(
|
|
323
|
+
reasons.push(`terminal: ${artifact.reason}`);
|
|
340
324
|
}
|
|
341
325
|
else if (artifact.status === "absent") {
|
|
342
326
|
if (lifecycle === "admitted" || lifecycle === "running") {
|
|
@@ -351,10 +335,10 @@ async function classifyScopedRun(input) {
|
|
|
351
335
|
terminal = { status: "absent" };
|
|
352
336
|
}
|
|
353
337
|
else {
|
|
354
|
-
// role already required nonblank by
|
|
338
|
+
// role already required nonblank by readRunTerminal (single owner).
|
|
355
339
|
terminal = {
|
|
356
340
|
status: "present",
|
|
357
|
-
|
|
341
|
+
face: artifact.face,
|
|
358
342
|
body: artifact.body,
|
|
359
343
|
role: artifact.body.role,
|
|
360
344
|
};
|
|
@@ -389,13 +373,13 @@ async function classifyScopedRun(input) {
|
|
|
389
373
|
if (frameSpan === undefined || toolIntervals === undefined || terminal === undefined) {
|
|
390
374
|
throw new Error(`classifyScopedRun internal invariant: missing retained facts for ${input.runId}`);
|
|
391
375
|
}
|
|
392
|
-
//
|
|
376
|
+
// Officer gate pairs stay inside the sole scan (families must
|
|
393
377
|
// not readdir this tree again). Missing directory → []. Damaged discovered
|
|
394
378
|
// nested JSONL is page-local unreadable — never silently under-count rounds.
|
|
395
379
|
let gateCycles;
|
|
396
380
|
try {
|
|
397
|
-
const parentSessionFile =
|
|
398
|
-
gateCycles = await
|
|
381
|
+
const parentSessionFile = sessionFileOf(input.runDirectory);
|
|
382
|
+
gateCycles = await readAnalystGateCyclesFromOfficers(input.runDirectory, { parentSessionFile });
|
|
399
383
|
}
|
|
400
384
|
catch (error) {
|
|
401
385
|
return {
|
|
@@ -497,7 +481,7 @@ export async function scanAnalystIssueRuns(input) {
|
|
|
497
481
|
}
|
|
498
482
|
for (const runDirectory of runDirectories) {
|
|
499
483
|
const runName = basename(runDirectory);
|
|
500
|
-
const parsed =
|
|
484
|
+
const parsed = parseRunLeaf(runName);
|
|
501
485
|
if (parsed === undefined)
|
|
502
486
|
continue;
|
|
503
487
|
let scopeFields;
|
|
@@ -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";
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
resolveNavigatorWorkSubjectPlacement
|
|
26
26
|
} from "./archivist-record-topology.js";
|
|
27
27
|
import { piRecordSessionHost } from "./pi/record-session-host.js";
|
|
28
|
+
import { sitianVolumeDirectory } from "./sitian-appender.js";
|
|
28
29
|
import {
|
|
29
30
|
DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
30
31
|
bookDirectOfficerRunPointer
|
|
@@ -35,7 +36,7 @@ import {
|
|
|
35
36
|
} from "./archivist-record-topology.js";
|
|
36
37
|
const CURRENT_SESSION_LEDGER = "current-session.json";
|
|
37
38
|
function readCurrentSession(sessionDir) {
|
|
38
|
-
const ledger =
|
|
39
|
+
const ledger = currentSessionLedgerPath(sessionDir);
|
|
39
40
|
try {
|
|
40
41
|
const value = JSON.parse(readFileSync(ledger, "utf8"));
|
|
41
42
|
if (typeof value !== "object" || value === null || typeof value.sessionFile !== "string" || value.sessionFile.length === 0) {
|
|
@@ -50,7 +51,7 @@ function readCurrentSession(sessionDir) {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
function writeCurrentSession(sessionDir, sessionFile) {
|
|
53
|
-
const ledger =
|
|
54
|
+
const ledger = currentSessionLedgerPath(sessionDir);
|
|
54
55
|
try {
|
|
55
56
|
writeFileSync(ledger, `${JSON.stringify({ sessionFile })}
|
|
56
57
|
`, { flag: "wx" });
|
|
@@ -268,7 +269,7 @@ function createRecordSessionOpen(options, host = piRecordSessionHost) {
|
|
|
268
269
|
if (!physicallyContainedIn(ledgerHome, parentResolved)) {
|
|
269
270
|
throw new Error("Durable record ownership requires a parent session inside the ledger home");
|
|
270
271
|
}
|
|
271
|
-
sessionDir =
|
|
272
|
+
sessionDir = sitianVolumeDirectory(dirname(parentResolved), options.kind);
|
|
272
273
|
parentSession = parentFile;
|
|
273
274
|
mayResumeSameNest = options.kind === WORKER_SUBMISSION_GATE_KIND;
|
|
274
275
|
}
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
1
|
+
import { basename, join } from "node:path";
|
|
4
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";
|
|
6
|
+
import { homeFromRunDirectory } from "./activation-ledger-topology.js";
|
|
7
|
+
import { findRunDirectoryById, readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
|
|
8
|
+
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.js";
|
|
9
|
+
import { parseRunLeaf, runDirectoryOfSessionFile } from "./role-run-placement.js";
|
|
5
10
|
/** Typed parent-side pointer to an independent officer run 正本 (ADR 0079 / #675). */
|
|
6
11
|
export const DIRECT_OFFICER_RUN_POINTER_KIND = "direct-officer-run-pointer";
|
|
12
|
+
/** History record kind of a booked officer pointer (appender routes it to history.jsonl). */
|
|
13
|
+
export { OFFICER_POINTER_RECORD_KIND };
|
|
7
14
|
/**
|
|
8
|
-
* Book a typed pointer
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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).
|
|
11
18
|
*
|
|
12
|
-
*
|
|
13
|
-
* same-parent re-summons
|
|
14
|
-
*
|
|
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.
|
|
15
22
|
*/
|
|
16
23
|
export function bookDirectOfficerRunPointer(options) {
|
|
17
|
-
const nest = join(dirname(options.parentSessionFile), "auditor-roles");
|
|
18
|
-
mkdirSync(nest, { recursive: true });
|
|
19
24
|
const submissionToolCallId = options.submissionToolCallId?.trim() ?? "";
|
|
20
25
|
const pointer = {
|
|
21
26
|
version: 1,
|
|
@@ -27,38 +32,68 @@ export function bookDirectOfficerRunPointer(options) {
|
|
|
27
32
|
: {}),
|
|
28
33
|
...(submissionToolCallId === "" ? {} : { submissionToolCallId }),
|
|
29
34
|
};
|
|
30
|
-
|
|
35
|
+
reportRunRecord(runDirectoryOfSessionFile(options.parentSessionFile), OFFICER_POINTER_RECORD_KIND, pointer, "submission-gate");
|
|
31
36
|
return pointer;
|
|
32
37
|
}
|
|
38
|
+
/** Shared strict pointer IO; internal audit wraps unreadability as a re-summons. */
|
|
39
|
+
export async function resolveOfficerPointer(raw, parentRunDirectory) {
|
|
40
|
+
const where = `${parentRunDirectory} officers`;
|
|
41
|
+
if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
|
|
42
|
+
throw new Error(`direct officer run pointer has unknown shape in ${where}`);
|
|
43
|
+
}
|
|
44
|
+
if (typeof raw.sessionFile !== "string" || raw.sessionFile.trim() === "") {
|
|
45
|
+
throw new Error(`direct officer run pointer missing sessionFile in ${where}`);
|
|
46
|
+
}
|
|
47
|
+
const officer = raw.officer === "inspector" || raw.officer === "notary"
|
|
48
|
+
|| raw.officer === "auditor" || raw.officer === "countersign" ? raw.officer : undefined;
|
|
49
|
+
let runDirectory = typeof raw.runDirectory === "string" && raw.runDirectory.trim() !== ""
|
|
50
|
+
? raw.runDirectory : undefined;
|
|
51
|
+
let sessionFile = raw.sessionFile;
|
|
52
|
+
const recordedRunDirectory = runDirectory ?? runDirectoryOfSessionFile(sessionFile);
|
|
53
|
+
const leaf = parseRunLeaf(basename(recordedRunDirectory));
|
|
54
|
+
const parentIdentity = await readRoleRunIdentity(parentRunDirectory);
|
|
55
|
+
if (leaf !== undefined && parentIdentity !== undefined) {
|
|
56
|
+
const currentRunDirectory = await findRunDirectoryById(homeFromRunDirectory(parentRunDirectory), leaf.runId, parentIdentity.bookKey, leaf.role);
|
|
57
|
+
if (currentRunDirectory !== undefined) {
|
|
58
|
+
sessionFile = rewriteRunDirectoryPathValue(sessionFile, recordedRunDirectory, currentRunDirectory);
|
|
59
|
+
if (runDirectory !== undefined)
|
|
60
|
+
runDirectory = currentRunDirectory;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const submissionToolCallId = typeof raw.submissionToolCallId === "string"
|
|
64
|
+
&& raw.submissionToolCallId.trim() !== "" ? raw.submissionToolCallId : undefined;
|
|
65
|
+
return {
|
|
66
|
+
sessionFile,
|
|
67
|
+
...(officer === undefined ? {} : { officer }),
|
|
68
|
+
...(runDirectory === undefined ? {} : { runDirectory }),
|
|
69
|
+
...(submissionToolCallId === undefined ? {} : { submissionToolCallId }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
33
72
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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).
|
|
36
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
|
+
}
|
|
89
|
+
/** Missing or unreadable is not a pass: the internal audit caller re-runs the gate. */
|
|
37
90
|
export async function readDirectOfficerRunPointer(parentSessionFile, officer) {
|
|
38
91
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
if (raw.officer !== officer)
|
|
44
|
-
return undefined;
|
|
45
|
-
if (typeof raw.sessionFile !== "string" || raw.sessionFile.trim() === "")
|
|
92
|
+
const parentRunDirectory = runDirectoryOfSessionFile(parentSessionFile);
|
|
93
|
+
const pointer = await resolveOfficerPointer((await readBookedOfficerPointers(parentRunDirectory))[officer], parentRunDirectory);
|
|
94
|
+
if (pointer.officer !== officer)
|
|
46
95
|
return undefined;
|
|
47
|
-
|
|
48
|
-
? raw.runDirectory
|
|
49
|
-
: undefined;
|
|
50
|
-
const submissionToolCallId = typeof raw.submissionToolCallId === "string"
|
|
51
|
-
&& raw.submissionToolCallId.trim() !== ""
|
|
52
|
-
? raw.submissionToolCallId
|
|
53
|
-
: undefined;
|
|
54
|
-
return {
|
|
55
|
-
version: 1,
|
|
56
|
-
kind: DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
57
|
-
officer,
|
|
58
|
-
sessionFile: raw.sessionFile,
|
|
59
|
-
...(runDirectory === undefined ? {} : { runDirectory }),
|
|
60
|
-
...(submissionToolCallId === undefined ? {} : { submissionToolCallId }),
|
|
61
|
-
};
|
|
96
|
+
return { ...pointer, officer, version: 1, kind: DIRECT_OFFICER_RUN_POINTER_KIND };
|
|
62
97
|
}
|
|
63
98
|
catch {
|
|
64
99
|
return undefined;
|
package/dist/atomic-write.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
async function writeFileAtomically(destination, contents) {
|
|
4
|
+
async function writeFileAtomically(destination, contents, options) {
|
|
5
5
|
const parent = dirname(destination);
|
|
6
6
|
const temporary = join(parent, `.atomic-write-${randomUUID()}.tmp`);
|
|
7
7
|
try {
|
|
8
|
-
await writeFile(temporary, contents);
|
|
8
|
+
await writeFile(temporary, contents, options);
|
|
9
9
|
await rename(temporary, destination);
|
|
10
10
|
} catch (error) {
|
|
11
11
|
await rm(temporary, { force: true }).catch(() => void 0);
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { RUN_HISTORY_FILE, RUN_LOG_FILE, runCurrentPath } from "./run-dossier.js";
|
|
3
|
+
import {
|
|
4
|
+
runDirectoryOfSessionFile,
|
|
5
|
+
sessionFileOf
|
|
6
|
+
} from "./role-run-placement.js";
|
|
3
7
|
import { Type } from "typebox";
|
|
4
8
|
const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier";
|
|
5
9
|
function auditorRunDirectory(context) {
|
|
6
10
|
const sessionFile = context.sessionManager?.getSessionFile?.();
|
|
7
11
|
if (sessionFile === void 0) return void 0;
|
|
8
|
-
return resolve(
|
|
12
|
+
return resolve(runDirectoryOfSessionFile(sessionFile));
|
|
9
13
|
}
|
|
10
14
|
function createAuditorDossierTool(runDirectory) {
|
|
11
15
|
return {
|
|
@@ -23,10 +27,11 @@ function createAuditorDossierTool(runDirectory) {
|
|
|
23
27
|
}
|
|
24
28
|
const details = {
|
|
25
29
|
runDirectory,
|
|
26
|
-
|
|
27
|
-
parentSessionCandidate:
|
|
30
|
+
current: runCurrentPath(runDirectory),
|
|
31
|
+
parentSessionCandidate: sessionFileOf(runDirectory),
|
|
28
32
|
attachments: join(runDirectory, "attachments"),
|
|
29
|
-
|
|
33
|
+
log: join(runDirectory, RUN_LOG_FILE),
|
|
34
|
+
history: join(runDirectory, RUN_HISTORY_FILE)
|
|
30
35
|
};
|
|
31
36
|
return {
|
|
32
37
|
content: [{ type: "text", text: JSON.stringify(details) }],
|
package/dist/auditor-soul.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { readPageSync } from "./run-dossier.js";
|
|
2
3
|
import { basename, join } from "node:path";
|
|
4
|
+
import { parseRunLeaf } from "./role-run-placement.js";
|
|
3
5
|
import { isRecord } from "./unknown-value.js";
|
|
4
6
|
import { joinPackageMaterials, readPackageMaterial, } from "./session-opening-materials.js";
|
|
5
7
|
/**
|
|
@@ -84,7 +86,7 @@ export function loadAuditorReferenceMaterialsFromSubjectInput(raw) {
|
|
|
84
86
|
return loadAuditorReferenceMaterials(resolveAuditorSubject(raw));
|
|
85
87
|
}
|
|
86
88
|
function subjectFromSourceDirectory(sourceRunDirectory) {
|
|
87
|
-
const role = basename(sourceRunDirectory)
|
|
89
|
+
const role = parseRunLeaf(basename(sourceRunDirectory))?.role;
|
|
88
90
|
return isAuditorSoulRole(role) ? role : undefined;
|
|
89
91
|
}
|
|
90
92
|
/**
|
|
@@ -92,18 +94,9 @@ function subjectFromSourceDirectory(sourceRunDirectory) {
|
|
|
92
94
|
* Explicit subject wins; otherwise the source-run role is the audited object.
|
|
93
95
|
*/
|
|
94
96
|
export async function readAuditorResumeBinding(runDirectory) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
raw = JSON.parse(await readFile(join(runDirectory, "admitted-request.json"), "utf8"));
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
if (error.code === "ENOENT")
|
|
101
|
-
return undefined;
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
if (!isRecord(raw))
|
|
97
|
+
const record = readPageSync(runDirectory, "admitted");
|
|
98
|
+
if (record === undefined)
|
|
105
99
|
return undefined;
|
|
106
|
-
const record = raw;
|
|
107
100
|
if (typeof record.sourceRunPath !== "string" || record.sourceRunPath.trim() === "")
|
|
108
101
|
return undefined;
|
|
109
102
|
const subject = isAuditorSoulRole(record.auditorSubject)
|
package/dist/collector-config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { sha256Hex } from "./sha256.js";
|
|
3
3
|
import { isRecord } from "./unknown-value.js";
|
|
4
|
+
import { isTicketNumberString, parseTicketNumber } from "./run-ticket-number.js";
|
|
4
5
|
export const COLLECTOR_OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
|
|
5
6
|
export const COLLECTOR_REPO_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
|
|
6
7
|
function fail(message, cause) {
|
|
@@ -30,13 +31,13 @@ export function parseCollectorRepository(raw) {
|
|
|
30
31
|
return { display: raw, canonical: `${owner}/${repo}`, owner, repo };
|
|
31
32
|
}
|
|
32
33
|
export function parseCollectorPrNumber(raw) {
|
|
33
|
-
if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
|
|
34
|
-
fail("Collector pull request number must be a positive safe integer string");
|
|
35
34
|
if (typeof raw !== "string" && typeof raw !== "number")
|
|
36
35
|
fail("Collector pull request number is required");
|
|
37
|
-
const value =
|
|
38
|
-
if (
|
|
39
|
-
fail(
|
|
36
|
+
const value = parseTicketNumber(raw);
|
|
37
|
+
if (value === undefined)
|
|
38
|
+
fail(typeof raw === "string" && !isTicketNumberString(raw)
|
|
39
|
+
? "Collector pull request number must be a positive safe integer string"
|
|
40
|
+
: "Collector pull request number must be a positive safe integer");
|
|
40
41
|
return value;
|
|
41
42
|
}
|
|
42
43
|
function canonicalManifest(requests) {
|