@akagilnc/pi-workflow-roles 0.1.5504 → 0.1.5571
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/acp-host/production-host.js +5965 -5994
- package/dist/analyst-gate-cycles-read.js +23 -55
- package/dist/analyst-ledger.js +21 -35
- package/dist/analyst-metric-families/acceptance-success-rework.js +25 -1
- package/dist/analyst-metric-families/round-timeline.js +2 -5
- package/dist/archivist-record-pointer.js +38 -30
- package/dist/auditor-dossier-tool.js +4 -3
- package/dist/auditor-soul.js +3 -11
- package/dist/doctor-evidence.js +3 -3
- package/dist/engine-detour-usage.js +6 -14
- package/dist/headless-host/production-host.js +5316 -5324
- package/dist/host-descriptions.js +0 -4
- package/dist/host-session-record.js +266 -267
- package/dist/migrate-book-topology.js +1314 -696
- package/dist/notary-source-run.js +1 -1
- package/dist/packaged-role-registry.js +5 -44
- package/dist/pi/role-turn-host.js +53 -3
- package/dist/public-cli/auto-resume.js +8 -47
- package/dist/public-cli/instruction-seat-run.js +5 -3
- package/dist/public-cli/invocation.js +51 -56
- package/dist/public-cli/main.js +1916 -1748
- package/dist/public-cli/post-admission.js +96 -55
- package/dist/public-cli/run-lifecycle.js +27 -53
- package/dist/public-cli/settlement.js +157 -302
- package/dist/role-run-placement.js +1 -11
- package/dist/role-run-relocation.js +72 -50
- package/dist/run-dossier-files.js +30 -0
- package/dist/run-dossier.js +322 -0
- package/dist/run-terminal-artifacts.js +68 -197
- package/dist/run-ticket-number.js +16 -3
- package/dist/session-identity.js +49 -18
- package/dist/sitian-appender.js +13 -16
- package/dist/sitian-facade.js +11 -0
- package/dist/sitian-reader.js +44 -29
- package/dist/submission-gate.js +1 -1
- package/dist/ticket-provenance.js +22 -7
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -0
- package/src/acp-host/production-host.ts +1 -1
- package/src/acp-host/role-turn-host.ts +9 -2
- package/src/analyst-gate-cycles-read.ts +26 -60
- package/src/analyst-ledger.ts +27 -42
- package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
- package/src/analyst-metric-families/gate-cycles.ts +1 -1
- package/src/analyst-metric-families/round-timeline.ts +2 -5
- package/src/analyst-page.ts +3 -3
- package/src/archivist-record-entry.ts +2 -2
- package/src/archivist-record-pointer.ts +50 -36
- package/src/auditor-dossier-tool.ts +7 -5
- package/src/auditor-soul.ts +3 -9
- package/src/book-topology-migration-placement.ts +16 -3
- package/src/book-topology-record-class-migrators.ts +4 -2
- package/src/book-topology-runs-migrator.ts +6 -3
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-evidence.ts +2 -2
- package/src/engine-detour-tool.ts +1 -1
- package/src/engine-detour-usage.ts +6 -16
- package/src/external-host-turn-loop.ts +25 -0
- package/src/headless-host/production-host.ts +1 -4
- package/src/headless-host/role-turn-host.ts +54 -20
- package/src/host-descriptions.ts +0 -6
- package/src/host-session-record.ts +224 -140
- package/src/notary-source-run.ts +1 -1
- package/src/packaged-role-registry.ts +1 -50
- package/src/pi/adapter.ts +50 -7
- package/src/pi/role-turn-host.ts +51 -3
- package/src/public-cli/auto-resume.ts +7 -43
- package/src/public-cli/instruction-seat-run.ts +9 -2
- package/src/public-cli/invocation.ts +61 -87
- package/src/public-cli/post-admission.ts +111 -72
- package/src/public-cli/run-lifecycle.ts +27 -72
- package/src/public-cli/settlement.ts +193 -418
- package/src/public-cli/terminal.ts +1 -1
- package/src/role-run-placement.ts +2 -13
- package/src/role-run-relocation.ts +71 -68
- package/src/role-runtime.ts +6 -10
- package/src/run-dossier-files.ts +33 -0
- package/src/run-dossier.ts +357 -0
- package/src/run-terminal-artifacts.ts +83 -237
- package/src/run-ticket-number.ts +41 -11
- package/src/session-identity.ts +52 -20
- package/src/sitian-appender.ts +17 -18
- package/src/sitian-facade.ts +22 -0
- package/src/sitian-reader.ts +62 -32
- package/src/submission-gate.ts +1 -1
- package/src/ticket-provenance.ts +20 -7
- package/src/ticket-trajectory.ts +10 -10
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
* 一册=一个追加式 records.jsonl。每轮新投影经司天台 appender 追加为不可变提交;
|
|
4
4
|
* 纯追加、不回读历史去重、不折叠。
|
|
5
5
|
*/
|
|
6
|
-
import { readFile
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
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
11
|
import { runDirectoryOfSessionFile, sessionFileOf } from "./role-run-placement.js";
|
|
12
|
-
import {
|
|
12
|
+
import { RUN_HISTORY_FILE } from "./run-dossier-files.js";
|
|
13
13
|
import { adaptSessionDialogue, nativeEventId } from "./session-dialogue.js";
|
|
14
14
|
import { appendSitianRecordBlock, appendSitianRecord, resolveSitianRecordPath, } from "./sitian-facade.js";
|
|
15
|
+
import { decodeSitianRecordLine } from "./sitian-reader.js";
|
|
15
16
|
import { TICKET_PROVENANCE_KIND, } from "./ticket-provenance-contracts.js";
|
|
16
17
|
import { errorText } from "./unknown-value.js";
|
|
17
18
|
/**
|
|
@@ -88,18 +89,32 @@ export function resolveTicketProvenanceVolume(ticketNumber, cwd, home) {
|
|
|
88
89
|
}
|
|
89
90
|
/** Assign a run-owned unbound diary as its original block through Sitian. */
|
|
90
91
|
export async function rehomeUnboundTicketProvenance(runDirectory, ticketNumber, cwd, home) {
|
|
91
|
-
const
|
|
92
|
-
|
|
92
|
+
const ticketInput = ticketProvenanceRecordInput(ticketNumber, cwd, home);
|
|
93
|
+
// Live staging: ticket-provenance rows in the run's history.jsonl. Append-only
|
|
94
|
+
// history keeps the staging rows; the ticket-root diary is the home.
|
|
95
|
+
// Old run/records.jsonl is not a runtime input (#1161); only migration tools read it.
|
|
96
|
+
let history;
|
|
93
97
|
try {
|
|
94
|
-
|
|
98
|
+
history = await readFile(join(runDirectory, RUN_HISTORY_FILE), "utf8");
|
|
95
99
|
}
|
|
96
100
|
catch (error) {
|
|
97
101
|
if (errnoCode(error) === "ENOENT")
|
|
98
102
|
return;
|
|
99
103
|
throw error;
|
|
100
104
|
}
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
const block = history
|
|
106
|
+
.split("\n")
|
|
107
|
+
.flatMap((line, index) => {
|
|
108
|
+
const decoded = decodeSitianRecordLine(line, index + 1);
|
|
109
|
+
if (decoded === undefined || !decoded.ok)
|
|
110
|
+
return [];
|
|
111
|
+
if (decoded.record.kind !== TICKET_PROVENANCE_KIND)
|
|
112
|
+
return [];
|
|
113
|
+
return [`${line}\n`];
|
|
114
|
+
})
|
|
115
|
+
.join("");
|
|
116
|
+
if (block !== "")
|
|
117
|
+
appendSitianRecordBlock(ticketInput, block);
|
|
103
118
|
}
|
|
104
119
|
/**
|
|
105
120
|
* Resolve a bound endpoint against this round's session lines.
|
package/package.json
CHANGED
|
@@ -14,6 +14,8 @@ const entries = [
|
|
|
14
14
|
// navigator-session-contracts is a static import of the published attendance root
|
|
15
15
|
// (#590: published non-bundle graph must stay closed under its own relative edges).
|
|
16
16
|
"navigator-public-session",
|
|
17
|
+
// Static import of navigator-public-session (sitianReportSafe).
|
|
18
|
+
"host-session-record",
|
|
17
19
|
"navigator-session-contracts",
|
|
18
20
|
// Static import of navigator-attendance and navigator-public-session.
|
|
19
21
|
"navigator-work-base",
|
|
@@ -61,7 +61,7 @@ export function createProductionAcpRoleTurnHost(options: ProductionAcpHostOption
|
|
|
61
61
|
|
|
62
62
|
return createComposedAcpRoleTurnHost({
|
|
63
63
|
hostName,
|
|
64
|
-
sessionIdentity: createSessionIdentityAuthority(principalAuthority,
|
|
64
|
+
sessionIdentity: createSessionIdentityAuthority(principalAuthority, hostName),
|
|
65
65
|
modelPassing: description.modelPassing,
|
|
66
66
|
...(description.setModelId === undefined ? {} : { setModelId: description.setModelId }),
|
|
67
67
|
roleRuntimeDependencies: createRoleRuntimeDependencies(packageRoot),
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
disposeExternalRoleTurn,
|
|
10
10
|
externalHostFailure as failure,
|
|
11
11
|
raceAgainstHostAbort,
|
|
12
|
+
recordTurnDelivery,
|
|
12
13
|
} from "../external-host-turn-loop.ts";
|
|
13
14
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
14
15
|
import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.ts";
|
|
@@ -372,6 +373,14 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
372
373
|
roundLimitName: "AcpRoundLimit",
|
|
373
374
|
currentSessionId: () => sessionId,
|
|
374
375
|
async runRound({ prompt, abortSignal }) {
|
|
376
|
+
// One history row per actual session/prompt start (催交回合各算一次).
|
|
377
|
+
// MCP advertises tool.parameters; prepared.jsonSchema is the headless
|
|
378
|
+
// draft-07 stamp of that clone — record the delivered parameters face.
|
|
379
|
+
const { $schema: _headlessStamp, ...deliveredSchema } = prepared.jsonSchema;
|
|
380
|
+
await recordTurnDelivery(request.runDirectory, {
|
|
381
|
+
systemPrompt: systemPromptOverride,
|
|
382
|
+
outputSchema: deliveredSchema,
|
|
383
|
+
}, "acp-host");
|
|
375
384
|
let result: Readonly<Record<string, unknown>>;
|
|
376
385
|
// Open the prose gate only for this prompt round; clear any stale chunks first.
|
|
377
386
|
agentProseChunks.length = 0;
|
|
@@ -498,8 +507,6 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
498
507
|
cwd: request.cwd,
|
|
499
508
|
sessionDirectory: sessionDirectoryOf(request.runDirectory),
|
|
500
509
|
sessionParent,
|
|
501
|
-
continuation: request.continuation,
|
|
502
|
-
...(request.model !== undefined ? { model: request.model } : {}),
|
|
503
510
|
...(request.home !== undefined ? { home: request.home } : {}),
|
|
504
511
|
});
|
|
505
512
|
} catch (error) {
|
|
@@ -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,9 +22,6 @@
|
|
|
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
|
-
|
|
29
25
|
import {
|
|
30
26
|
AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE,
|
|
31
27
|
} from "./compliance-transport.ts";
|
|
@@ -39,7 +35,7 @@ import {
|
|
|
39
35
|
import { isRecord, isEnoent } from "./unknown-value.ts";
|
|
40
36
|
import { retainedRunPathsMatch } from "./role-run-relocation.ts";
|
|
41
37
|
import { runDirectoryOfSessionFile } from "./role-run-placement.ts";
|
|
42
|
-
import {
|
|
38
|
+
import { readOfficerPointers } from "./archivist-record-pointer.ts";
|
|
43
39
|
|
|
44
40
|
/** One completed gate round: direct officer receipt or historical province/officer pair. */
|
|
45
41
|
/** Honest origin discriminant: direct summons vs historical province dispatch. */
|
|
@@ -418,68 +414,38 @@ function pairGateRounds(
|
|
|
418
414
|
}
|
|
419
415
|
|
|
420
416
|
/**
|
|
421
|
-
* Read and pair gate-cycle rounds from
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* (failure honesty — damaged topology must not wash to zero rounds).
|
|
417
|
+
* Read and pair gate-cycle rounds from the officer pointers booked in one
|
|
418
|
+
* run's current.json `officers` section; each pointer names the independent
|
|
419
|
+
* officer session 正本. Absent section → [] (lawful zero). A damaged pointer or
|
|
420
|
+
* officer session propagates — damaged topology must not wash to zero rounds.
|
|
426
421
|
*
|
|
427
422
|
* Pass `parentSessionFile` to keep only rounds whose attempt binding names that parent.
|
|
428
423
|
*/
|
|
429
|
-
export async function
|
|
430
|
-
|
|
424
|
+
export async function readAnalystGateCyclesFromOfficers(
|
|
425
|
+
parentRunDirectory: string,
|
|
431
426
|
options: {
|
|
432
427
|
readonly parentSessionFile?: string;
|
|
433
428
|
} = {},
|
|
434
429
|
): Promise<readonly AnalystGateCycleRound[]> {
|
|
435
|
-
const directories = typeof auditorRolesDirectory === "string"
|
|
436
|
-
? [auditorRolesDirectory]
|
|
437
|
-
: auditorRolesDirectory;
|
|
438
430
|
const volumes: ClassifiedVolume[] = [];
|
|
439
|
-
// Same officer session pointed at N times
|
|
440
|
-
//
|
|
441
|
-
// seal is counted ×N in gate-cycle statistics (#753 acceptance A).
|
|
431
|
+
// Same officer session pointed at N times must classify once — else each
|
|
432
|
+
// accepted seal is counted ×N in gate-cycle statistics (#753 acceptance A).
|
|
442
433
|
const classifiedOfficerSessions = new Set<string>();
|
|
443
|
-
for (const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
.
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (isEnoent(error)) continue;
|
|
457
|
-
throw error;
|
|
458
|
-
}
|
|
459
|
-
for (const name of names) {
|
|
460
|
-
const path = join(directory, name);
|
|
461
|
-
const fromPointer = name.endsWith(".pointer.json");
|
|
462
|
-
const pointer = fromPointer
|
|
463
|
-
? await resolveOfficerSessionFromPointerFile(path)
|
|
464
|
-
: undefined;
|
|
465
|
-
const sessionPath = pointer?.sessionFile ?? path;
|
|
466
|
-
if (fromPointer) {
|
|
467
|
-
if (classifiedOfficerSessions.has(sessionPath)) continue;
|
|
468
|
-
classifiedOfficerSessions.add(sessionPath);
|
|
469
|
-
}
|
|
470
|
-
const officer = pointer?.officer === "inspector" || pointer?.officer === "notary"
|
|
471
|
-
? pointer.officer : undefined;
|
|
472
|
-
const classified = await classifyAuditorVolume(sessionPath, officer);
|
|
473
|
-
for (const volume of classified) {
|
|
474
|
-
if (
|
|
475
|
-
options.parentSessionFile !== undefined &&
|
|
476
|
-
volume.parentSessionFile !== undefined &&
|
|
477
|
-
!await retainedRunPathsMatch(volume.parentSessionFile, options.parentSessionFile, runDirectoryOfSessionFile(options.parentSessionFile))
|
|
478
|
-
) {
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
volumes.push(volume);
|
|
434
|
+
for (const pointer of await readOfficerPointers(parentRunDirectory)) {
|
|
435
|
+
if (classifiedOfficerSessions.has(pointer.sessionFile)) continue;
|
|
436
|
+
classifiedOfficerSessions.add(pointer.sessionFile);
|
|
437
|
+
const officer = pointer.officer === "inspector" || pointer.officer === "notary"
|
|
438
|
+
? pointer.officer : undefined;
|
|
439
|
+
const classified = await classifyAuditorVolume(pointer.sessionFile, officer);
|
|
440
|
+
for (const volume of classified) {
|
|
441
|
+
if (
|
|
442
|
+
options.parentSessionFile !== undefined &&
|
|
443
|
+
volume.parentSessionFile !== undefined &&
|
|
444
|
+
!await retainedRunPathsMatch(volume.parentSessionFile, options.parentSessionFile, runDirectoryOfSessionFile(options.parentSessionFile))
|
|
445
|
+
) {
|
|
446
|
+
continue;
|
|
482
447
|
}
|
|
448
|
+
volumes.push(volume);
|
|
483
449
|
}
|
|
484
450
|
}
|
|
485
451
|
return pairGateRounds(volumes);
|
package/src/analyst-ledger.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
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.ts";
|
|
15
16
|
import { readdir, readFile } from "node:fs/promises";
|
|
16
17
|
import { basename, join } from "node:path";
|
|
17
18
|
|
|
@@ -26,7 +27,6 @@ import {
|
|
|
26
27
|
parseRunLeaf,
|
|
27
28
|
sessionFileOf,
|
|
28
29
|
} from "./role-run-placement.ts";
|
|
29
|
-
import { sitianRunVolumeDirectory } from "./sitian-appender.ts";
|
|
30
30
|
import { autopsyWriterLock, readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
|
|
31
31
|
import { readRunTicketNumber } from "./run-ticket-number.ts";
|
|
32
32
|
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.ts";
|
|
@@ -40,8 +40,8 @@ import {
|
|
|
40
40
|
type SessionToolInterval,
|
|
41
41
|
} from "./ledger-session-read.ts";
|
|
42
42
|
import {
|
|
43
|
-
|
|
44
|
-
type
|
|
43
|
+
readRunTerminal,
|
|
44
|
+
type RunTerminalFace,
|
|
45
45
|
} from "./run-terminal-artifacts.ts";
|
|
46
46
|
import type {
|
|
47
47
|
AnalystFirstFrameAt,
|
|
@@ -51,7 +51,7 @@ import type {
|
|
|
51
51
|
AnalystUnreadableRun,
|
|
52
52
|
} from "./analyst-page.ts";
|
|
53
53
|
import {
|
|
54
|
-
|
|
54
|
+
readAnalystGateCyclesFromOfficers,
|
|
55
55
|
type AnalystGateCycleRound,
|
|
56
56
|
} from "./analyst-gate-cycles-read.ts";
|
|
57
57
|
|
|
@@ -63,7 +63,7 @@ export type { AnalystGateCycleRound } from "./analyst-gate-cycles-read.ts";
|
|
|
63
63
|
const LIVE_RUN_STATES = new Set(["admitted", "running", "resumable"]);
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Read lifecycle state from the
|
|
66
|
+
* Read lifecycle state from the state.jsonl run-state fact row.
|
|
67
67
|
* Used only to distinguish live in-flight runs from terminal no-receipt.
|
|
68
68
|
*/
|
|
69
69
|
async function readExistingRunLifecycleState(
|
|
@@ -134,7 +134,7 @@ async function classifyGhostCandidate(input: {
|
|
|
134
134
|
* Invocation scope faces used for issue 圈定 (C4 / #399).
|
|
135
135
|
* projectRoot is retained for narrow path match and conflict facts;
|
|
136
136
|
* ticketNumber is the #176 typed face when present (integer ≥ 1).
|
|
137
|
-
* Single read of
|
|
137
|
+
* Single read of the current.json invocation section — no second parse kernel.
|
|
138
138
|
*/
|
|
139
139
|
type InvocationScopeFields = {
|
|
140
140
|
readonly projectRoot: string;
|
|
@@ -154,15 +154,8 @@ async function listLedgerBookNames(booksRoot: string): Promise<string[]> {
|
|
|
154
154
|
async function readInvocationScopeFields(
|
|
155
155
|
runDirectory: string,
|
|
156
156
|
): Promise<InvocationScopeFields | undefined> {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
raw = await readFile(join(runDirectory, "invocation.json"), "utf8");
|
|
160
|
-
} catch (error) {
|
|
161
|
-
if (isMissingPathError(error)) return undefined;
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
164
|
-
const parsed: unknown = JSON.parse(raw);
|
|
165
|
-
if (!isRecord(parsed)) return undefined;
|
|
157
|
+
const parsed = readSectionSync(runDirectory, "invocation");
|
|
158
|
+
if (parsed === undefined) return undefined;
|
|
166
159
|
if (typeof parsed.projectRoot !== "string" || parsed.projectRoot.trim() === "") {
|
|
167
160
|
return undefined;
|
|
168
161
|
}
|
|
@@ -212,20 +205,11 @@ async function resolveSessionFile(
|
|
|
212
205
|
runDirectory: string,
|
|
213
206
|
): Promise<string> {
|
|
214
207
|
// Prefer invocation.sessionFile when present; fall back to S-family principal.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
&& typeof parsed.sessionFile === "string"
|
|
221
|
-
&& parsed.sessionFile.trim() !== ""
|
|
222
|
-
) {
|
|
223
|
-
return typeof parsed.runDirectory === "string"
|
|
224
|
-
? rewriteRunDirectoryPathValue(parsed.sessionFile, parsed.runDirectory, runDirectory) as string
|
|
225
|
-
: parsed.sessionFile;
|
|
226
|
-
}
|
|
227
|
-
} catch (error) {
|
|
228
|
-
if (!isMissingPathError(error)) throw error;
|
|
208
|
+
const parsed = readSectionSync(runDirectory, "invocation");
|
|
209
|
+
if (parsed !== undefined && typeof parsed.sessionFile === "string" && parsed.sessionFile.trim() !== "") {
|
|
210
|
+
return typeof parsed.runDirectory === "string"
|
|
211
|
+
? rewriteRunDirectoryPathValue(parsed.sessionFile, parsed.runDirectory, runDirectory) as string
|
|
212
|
+
: parsed.sessionFile;
|
|
229
213
|
}
|
|
230
214
|
return sessionFileOf(runDirectory);
|
|
231
215
|
}
|
|
@@ -327,7 +311,7 @@ export type AnalystRunTerminalFace =
|
|
|
327
311
|
| { readonly status: "absent" }
|
|
328
312
|
| {
|
|
329
313
|
readonly status: "present";
|
|
330
|
-
readonly
|
|
314
|
+
readonly face: RunTerminalFace;
|
|
331
315
|
readonly body: Record<string, unknown>;
|
|
332
316
|
/** Nonblank producer role — sole owner is readRunTerminalArtifact. */
|
|
333
317
|
readonly role: string;
|
|
@@ -352,9 +336,9 @@ export type AnalystReadableRunFacts = {
|
|
|
352
336
|
*/
|
|
353
337
|
readonly models: readonly string[];
|
|
354
338
|
/**
|
|
355
|
-
* Paired gate-cycle rounds
|
|
356
|
-
*
|
|
357
|
-
*
|
|
339
|
+
* Paired gate-cycle rounds of the officers booked in current.json (#446).
|
|
340
|
+
* No officers booked → empty (lawful zero rounds).
|
|
341
|
+
* A damaged officer pointer or session → leg unreadable (`auditor-roles` source).
|
|
358
342
|
*/
|
|
359
343
|
readonly gateCycles: readonly AnalystGateCycleRound[];
|
|
360
344
|
};
|
|
@@ -515,10 +499,14 @@ async function classifyScopedRun(input: {
|
|
|
515
499
|
}
|
|
516
500
|
|
|
517
501
|
try {
|
|
518
|
-
const
|
|
502
|
+
const read = readRunTerminal(input.runDirectory);
|
|
503
|
+
// A no_receipt terminal is the explicit form of "no accepted receipt".
|
|
504
|
+
const artifact = read.status === "present" && read.face === "no_receipt"
|
|
505
|
+
? { status: "absent" as const }
|
|
506
|
+
: read;
|
|
519
507
|
if (artifact.status === "unreadable") {
|
|
520
508
|
missingSources.push("terminal-artifact");
|
|
521
|
-
reasons.push(
|
|
509
|
+
reasons.push(`terminal: ${artifact.reason}`);
|
|
522
510
|
} else if (artifact.status === "absent") {
|
|
523
511
|
if (lifecycle === "admitted" || lifecycle === "running") {
|
|
524
512
|
// No terminal yet: ghost report only, or live in-flight omit.
|
|
@@ -530,10 +518,10 @@ async function classifyScopedRun(input: {
|
|
|
530
518
|
}
|
|
531
519
|
terminal = { status: "absent" };
|
|
532
520
|
} else {
|
|
533
|
-
// role already required nonblank by
|
|
521
|
+
// role already required nonblank by readRunTerminal (single owner).
|
|
534
522
|
terminal = {
|
|
535
523
|
status: "present",
|
|
536
|
-
|
|
524
|
+
face: artifact.face,
|
|
537
525
|
body: artifact.body,
|
|
538
526
|
role: artifact.body.role as string,
|
|
539
527
|
};
|
|
@@ -574,16 +562,13 @@ async function classifyScopedRun(input: {
|
|
|
574
562
|
);
|
|
575
563
|
}
|
|
576
564
|
|
|
577
|
-
//
|
|
565
|
+
// Officer gate pairs stay inside the sole scan (families must
|
|
578
566
|
// not readdir this tree again). Missing directory → []. Damaged discovered
|
|
579
567
|
// nested JSONL is page-local unreadable — never silently under-count rounds.
|
|
580
568
|
let gateCycles: readonly AnalystGateCycleRound[];
|
|
581
569
|
try {
|
|
582
570
|
const parentSessionFile = sessionFileOf(input.runDirectory);
|
|
583
|
-
gateCycles = await
|
|
584
|
-
sitianRunVolumeDirectory(input.runDirectory, "auditor-roles"),
|
|
585
|
-
{ parentSessionFile },
|
|
586
|
-
);
|
|
571
|
+
gateCycles = await readAnalystGateCyclesFromOfficers(input.runDirectory, { parentSessionFile });
|
|
587
572
|
} catch (error) {
|
|
588
573
|
return {
|
|
589
574
|
kind: "unreadable",
|
|
@@ -97,7 +97,10 @@ export type AnalystAcceptanceSuccessReworkSection = {
|
|
|
97
97
|
readonly rework: AnalystReworkLens;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* Locate collector groups array from the canonical typed receipt faces.
|
|
102
|
+
* Same leaf order as extractStatus: body / receipt / decisiveFacts / payloads.
|
|
103
|
+
*/
|
|
101
104
|
function findCollectorGroups(body: Record<string, unknown>): unknown {
|
|
102
105
|
if (Array.isArray(body.groups)) return body.groups;
|
|
103
106
|
const receipt = body.receipt;
|
|
@@ -106,6 +109,15 @@ function findCollectorGroups(body: Record<string, unknown>): unknown {
|
|
|
106
109
|
if (isRecord(outcome)) {
|
|
107
110
|
const facts = outcome.decisiveFacts;
|
|
108
111
|
if (isRecord(facts) && Array.isArray(facts.groups)) return facts.groups;
|
|
112
|
+
// Typed sealed receipt: settlement keeps payloads on attempt-history; the
|
|
113
|
+
// terminal reader rehydrates them via attemptHistoryIdentity. Groups are the
|
|
114
|
+
// collector leaf on that receipt — never invent a second discriminator.
|
|
115
|
+
if (Array.isArray(outcome.payloads)) {
|
|
116
|
+
for (let index = outcome.payloads.length - 1; index >= 0; index -= 1) {
|
|
117
|
+
const payload = outcome.payloads[index];
|
|
118
|
+
if (isRecord(payload) && Array.isArray(payload.groups)) return payload.groups;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
109
121
|
}
|
|
110
122
|
return undefined;
|
|
111
123
|
}
|
|
@@ -116,6 +128,17 @@ export function extractStatus(body: Record<string, unknown>): string | undefined
|
|
|
116
128
|
if (isRecord(outcome) && typeof outcome.status === "string" && outcome.status.trim() !== "") {
|
|
117
129
|
return outcome.status;
|
|
118
130
|
}
|
|
131
|
+
// Typed sealed receipt: settlement keeps payloads on attempt-history; the
|
|
132
|
+
// terminal reader rehydrates them via attemptHistoryIdentity. Status is the role's
|
|
133
|
+
// own leaf on that receipt — never invent a second mapping.
|
|
134
|
+
if (isRecord(outcome) && Array.isArray(outcome.payloads)) {
|
|
135
|
+
for (let index = outcome.payloads.length - 1; index >= 0; index -= 1) {
|
|
136
|
+
const payload = outcome.payloads[index];
|
|
137
|
+
if (isRecord(payload) && typeof payload.status === "string" && payload.status.trim() !== "") {
|
|
138
|
+
return payload.status;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
119
142
|
const receipt = body.receipt;
|
|
120
143
|
if (isRecord(receipt) && typeof receipt.status === "string" && receipt.status.trim() !== "") {
|
|
121
144
|
return receipt.status;
|
|
@@ -26,7 +26,7 @@ export type AnalystGateCyclesLeg = {
|
|
|
26
26
|
readonly runId: string;
|
|
27
27
|
readonly book: string;
|
|
28
28
|
readonly role: string;
|
|
29
|
-
/** Paired dispatch↔officer volume count (0 when no
|
|
29
|
+
/** Paired dispatch↔officer volume count (0 when no officers booked). */
|
|
30
30
|
readonly roundCount: number;
|
|
31
31
|
readonly rounds: readonly AnalystGateCyclesRoundRow[];
|
|
32
32
|
};
|
|
@@ -82,13 +82,10 @@ function projectTerminal(facts: AnalystReadableRunFacts): AnalystRoundTimelineTe
|
|
|
82
82
|
if (facts.terminal.status === "absent") {
|
|
83
83
|
return { kind: "death", channel: "no-receipt" };
|
|
84
84
|
}
|
|
85
|
-
if (facts.terminal.
|
|
85
|
+
if (facts.terminal.face === "error") {
|
|
86
86
|
return { kind: "death", channel: "error" };
|
|
87
87
|
}
|
|
88
|
-
|
|
89
|
-
return { kind: "death", channel: "audit-incomplete" };
|
|
90
|
-
}
|
|
91
|
-
// report.json — receipt face; classCount only when producer wrote a number.
|
|
88
|
+
// report face — receipt; classCount only when producer wrote a number.
|
|
92
89
|
const status = extractStatus(facts.terminal.body);
|
|
93
90
|
const classCount = readClassCount(facts.terminal.body);
|
|
94
91
|
const receiptStatus = status ?? "unparsed";
|
package/src/analyst-page.ts
CHANGED
|
@@ -28,9 +28,9 @@ export type AnalystMissingSource =
|
|
|
28
28
|
/** Model-groups mode: leg has no usable session model identity. */
|
|
29
29
|
| "session-model"
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* A booked officer pointer or its session failed canonical read/parse (#446).
|
|
32
|
+
* No officers booked is lawful zero rounds, not this face.
|
|
33
|
+
* Public label stays `auditor-roles` (终局来源标签以外不得改名).
|
|
34
34
|
*/
|
|
35
35
|
| "auditor-roles";
|
|
36
36
|
|
|
@@ -293,7 +293,7 @@ export type RecordSessionParent = {
|
|
|
293
293
|
export type CreateRecordSessionOptions = {
|
|
294
294
|
/** Role working directory passed to the record session; not a placement input. */
|
|
295
295
|
readonly cwd: string;
|
|
296
|
-
/** What kind of record this is (e.g. "
|
|
296
|
+
/** What kind of record this is (e.g. "attendance"). Single path segment; not a destination path. */
|
|
297
297
|
readonly kind: string;
|
|
298
298
|
/** Parent session — nesting authority for ordinary kinds; optional parentSession link for navigator. */
|
|
299
299
|
readonly parent?: RecordSessionParent;
|
|
@@ -373,7 +373,7 @@ export type RecordSessionOpen = {
|
|
|
373
373
|
* symlink). New principals mint under the already-validated sessionDir.
|
|
374
374
|
* Navigator work-subject nests use their AK ledger; worker-submission-gate asks the
|
|
375
375
|
* host to continue its recent native session without selecting a stored file path.
|
|
376
|
-
* Other ordinary no-subject children (
|
|
376
|
+
* Other ordinary no-subject children (attendance, …) always mint fresh.
|
|
377
377
|
* New persisted principals materialize their deferred session header before return so
|
|
378
378
|
* custom-entry-only writers do not need a parallel delayed-header helper.
|
|
379
379
|
*
|