@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,9 +22,6 @@
|
|
|
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
|
-
|
|
31
25
|
import {
|
|
32
26
|
AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE,
|
|
33
27
|
} from "./compliance-transport.ts";
|
|
@@ -38,7 +32,10 @@ import {
|
|
|
38
32
|
type LedgerSessionRow,
|
|
39
33
|
} from "./ledger-session-read.ts";
|
|
40
34
|
|
|
41
|
-
import { isRecord,
|
|
35
|
+
import { isRecord, isEnoent } from "./unknown-value.ts";
|
|
36
|
+
import { retainedRunPathsMatch } from "./role-run-relocation.ts";
|
|
37
|
+
import { runDirectoryOfSessionFile } from "./role-run-placement.ts";
|
|
38
|
+
import { readOfficerPointers } from "./archivist-record-pointer.ts";
|
|
42
39
|
|
|
43
40
|
/** One completed gate round: direct officer receipt or historical province/officer pair. */
|
|
44
41
|
/** Honest origin discriminant: direct summons vs historical province dispatch. */
|
|
@@ -416,93 +413,39 @@ function pairGateRounds(
|
|
|
416
413
|
.map((round, index) => ({ ...round, roundIndex: index + 1 }));
|
|
417
414
|
}
|
|
418
415
|
|
|
419
|
-
/** Resolve a direct-officer-run-pointer file to the officer session 正本 path. */
|
|
420
|
-
async function resolveOfficerSessionFromPointerFile(
|
|
421
|
-
pointerPath: string,
|
|
422
|
-
): Promise<{ sessionFile: string; officer?: "inspector" | "notary" }> {
|
|
423
|
-
const { readFile } = await import("node:fs/promises");
|
|
424
|
-
let raw: unknown;
|
|
425
|
-
try {
|
|
426
|
-
raw = JSON.parse(await readFile(pointerPath, "utf8"));
|
|
427
|
-
} catch (error) {
|
|
428
|
-
throw new Error(
|
|
429
|
-
`direct officer run pointer unreadable in ${pointerPath}: ${errorText(error)}`,
|
|
430
|
-
{ cause: error },
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
if (!isRecord(raw) || raw.kind !== "direct-officer-run-pointer" || raw.version !== 1) {
|
|
434
|
-
throw new Error(`direct officer run pointer has unknown shape in ${pointerPath}`);
|
|
435
|
-
}
|
|
436
|
-
const sessionFile = raw.sessionFile;
|
|
437
|
-
if (typeof sessionFile !== "string" || sessionFile.trim() === "") {
|
|
438
|
-
throw new Error(`direct officer run pointer missing sessionFile in ${pointerPath}`);
|
|
439
|
-
}
|
|
440
|
-
const officer = raw.officer === "inspector" || raw.officer === "notary"
|
|
441
|
-
? raw.officer : undefined;
|
|
442
|
-
return { sessionFile, ...(officer === undefined ? {} : { officer }) };
|
|
443
|
-
}
|
|
444
|
-
|
|
445
416
|
/**
|
|
446
|
-
* Read and pair gate-cycle rounds from
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
* (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.
|
|
451
421
|
*
|
|
452
422
|
* Pass `parentSessionFile` to keep only rounds whose attempt binding names that parent.
|
|
453
423
|
*/
|
|
454
|
-
export async function
|
|
455
|
-
|
|
424
|
+
export async function readAnalystGateCyclesFromOfficers(
|
|
425
|
+
parentRunDirectory: string,
|
|
456
426
|
options: {
|
|
457
427
|
readonly parentSessionFile?: string;
|
|
458
428
|
} = {},
|
|
459
429
|
): Promise<readonly AnalystGateCycleRound[]> {
|
|
460
|
-
const directories = typeof auditorRolesDirectory === "string"
|
|
461
|
-
? [auditorRolesDirectory]
|
|
462
|
-
: auditorRolesDirectory;
|
|
463
430
|
const volumes: ClassifiedVolume[] = [];
|
|
464
|
-
// Same officer session pointed at N times
|
|
465
|
-
//
|
|
466
|
-
// seal is counted ×N in terminal gate-round (#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).
|
|
467
433
|
const classifiedOfficerSessions = new Set<string>();
|
|
468
|
-
for (const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (isEnoent(error)) continue;
|
|
482
|
-
throw error;
|
|
483
|
-
}
|
|
484
|
-
for (const name of names) {
|
|
485
|
-
const path = join(directory, name);
|
|
486
|
-
const fromPointer = name.endsWith(".pointer.json");
|
|
487
|
-
const pointer = fromPointer
|
|
488
|
-
? await resolveOfficerSessionFromPointerFile(path)
|
|
489
|
-
: undefined;
|
|
490
|
-
const sessionPath = pointer?.sessionFile ?? path;
|
|
491
|
-
if (fromPointer) {
|
|
492
|
-
if (classifiedOfficerSessions.has(sessionPath)) continue;
|
|
493
|
-
classifiedOfficerSessions.add(sessionPath);
|
|
494
|
-
}
|
|
495
|
-
const classified = await classifyAuditorVolume(sessionPath, pointer?.officer);
|
|
496
|
-
for (const volume of classified) {
|
|
497
|
-
if (
|
|
498
|
-
options.parentSessionFile !== undefined &&
|
|
499
|
-
volume.parentSessionFile !== undefined &&
|
|
500
|
-
volume.parentSessionFile !== options.parentSessionFile
|
|
501
|
-
) {
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
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;
|
|
505
447
|
}
|
|
448
|
+
volumes.push(volume);
|
|
506
449
|
}
|
|
507
450
|
}
|
|
508
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
|
|
|
@@ -21,9 +22,14 @@ import {
|
|
|
21
22
|
physicalPathIdentity,
|
|
22
23
|
resolveActivationLedgerHome,
|
|
23
24
|
} from "./activation-ledger-topology.ts";
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
listBookRunDirectories,
|
|
27
|
+
parseRunLeaf,
|
|
28
|
+
sessionFileOf,
|
|
29
|
+
} from "./role-run-placement.ts";
|
|
25
30
|
import { autopsyWriterLock, readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
|
|
26
31
|
import { readRunTicketNumber } from "./run-ticket-number.ts";
|
|
32
|
+
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.ts";
|
|
27
33
|
import {
|
|
28
34
|
extractSessionModelSequence,
|
|
29
35
|
extractSessionTimestampSpan,
|
|
@@ -34,8 +40,8 @@ import {
|
|
|
34
40
|
type SessionToolInterval,
|
|
35
41
|
} from "./ledger-session-read.ts";
|
|
36
42
|
import {
|
|
37
|
-
|
|
38
|
-
type
|
|
43
|
+
readRunTerminal,
|
|
44
|
+
type RunTerminalFace,
|
|
39
45
|
} from "./run-terminal-artifacts.ts";
|
|
40
46
|
import type {
|
|
41
47
|
AnalystFirstFrameAt,
|
|
@@ -45,7 +51,7 @@ import type {
|
|
|
45
51
|
AnalystUnreadableRun,
|
|
46
52
|
} from "./analyst-page.ts";
|
|
47
53
|
import {
|
|
48
|
-
|
|
54
|
+
readAnalystGateCyclesFromOfficers,
|
|
49
55
|
type AnalystGateCycleRound,
|
|
50
56
|
} from "./analyst-gate-cycles-read.ts";
|
|
51
57
|
|
|
@@ -57,7 +63,7 @@ export type { AnalystGateCycleRound } from "./analyst-gate-cycles-read.ts";
|
|
|
57
63
|
const LIVE_RUN_STATES = new Set(["admitted", "running", "resumable"]);
|
|
58
64
|
|
|
59
65
|
/**
|
|
60
|
-
* Read lifecycle state from the
|
|
66
|
+
* Read lifecycle state from the state.jsonl run-state fact row.
|
|
61
67
|
* Used only to distinguish live in-flight runs from terminal no-receipt.
|
|
62
68
|
*/
|
|
63
69
|
async function readExistingRunLifecycleState(
|
|
@@ -124,19 +130,11 @@ async function classifyGhostCandidate(input: {
|
|
|
124
130
|
};
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
function parseRunDirectoryName(
|
|
128
|
-
name: string,
|
|
129
|
-
): { runId: string; role: string } | undefined {
|
|
130
|
-
const at = name.lastIndexOf("@");
|
|
131
|
-
if (at <= 0 || at === name.length - 1) return undefined;
|
|
132
|
-
return { runId: name.slice(0, at), role: name.slice(at + 1) };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
133
|
/**
|
|
136
134
|
* Invocation scope faces used for issue 圈定 (C4 / #399).
|
|
137
135
|
* projectRoot is retained for narrow path match and conflict facts;
|
|
138
136
|
* ticketNumber is the #176 typed face when present (integer ≥ 1).
|
|
139
|
-
* Single read of
|
|
137
|
+
* Single read of the current.json invocation section — no second parse kernel.
|
|
140
138
|
*/
|
|
141
139
|
type InvocationScopeFields = {
|
|
142
140
|
readonly projectRoot: string;
|
|
@@ -156,15 +154,8 @@ async function listLedgerBookNames(booksRoot: string): Promise<string[]> {
|
|
|
156
154
|
async function readInvocationScopeFields(
|
|
157
155
|
runDirectory: string,
|
|
158
156
|
): Promise<InvocationScopeFields | undefined> {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
raw = await readFile(join(runDirectory, "invocation.json"), "utf8");
|
|
162
|
-
} catch (error) {
|
|
163
|
-
if (isMissingPathError(error)) return undefined;
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
166
|
-
const parsed: unknown = JSON.parse(raw);
|
|
167
|
-
if (!isRecord(parsed)) return undefined;
|
|
157
|
+
const parsed = readSectionSync(runDirectory, "invocation");
|
|
158
|
+
if (parsed === undefined) return undefined;
|
|
168
159
|
if (typeof parsed.projectRoot !== "string" || parsed.projectRoot.trim() === "") {
|
|
169
160
|
return undefined;
|
|
170
161
|
}
|
|
@@ -214,20 +205,13 @@ async function resolveSessionFile(
|
|
|
214
205
|
runDirectory: string,
|
|
215
206
|
): Promise<string> {
|
|
216
207
|
// Prefer invocation.sessionFile when present; fall back to S-family principal.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
&& typeof parsed.sessionFile === "string"
|
|
223
|
-
&& parsed.sessionFile.trim() !== ""
|
|
224
|
-
) {
|
|
225
|
-
return 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
|
-
return
|
|
214
|
+
return sessionFileOf(runDirectory);
|
|
231
215
|
}
|
|
232
216
|
|
|
233
217
|
/**
|
|
@@ -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
|
-
const parentSessionFile =
|
|
583
|
-
gateCycles = await
|
|
584
|
-
join(input.runDirectory, "session", "auditor-roles"),
|
|
585
|
-
{ parentSessionFile },
|
|
586
|
-
);
|
|
570
|
+
const parentSessionFile = sessionFileOf(input.runDirectory);
|
|
571
|
+
gateCycles = await readAnalystGateCyclesFromOfficers(input.runDirectory, { parentSessionFile });
|
|
587
572
|
} catch (error) {
|
|
588
573
|
return {
|
|
589
574
|
kind: "unreadable",
|
|
@@ -697,7 +682,7 @@ export async function scanAnalystIssueRuns(input: {
|
|
|
697
682
|
|
|
698
683
|
for (const runDirectory of runDirectories) {
|
|
699
684
|
const runName = basename(runDirectory);
|
|
700
|
-
const parsed =
|
|
685
|
+
const parsed = parseRunLeaf(runName);
|
|
701
686
|
if (parsed === undefined) continue;
|
|
702
687
|
|
|
703
688
|
let scopeFields: InvocationScopeFields | undefined;
|
|
@@ -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
|
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "./archivist-record-topology.ts";
|
|
32
32
|
import type { HostRecordSession, RecordSessionHost } from "./host-contracts.ts";
|
|
33
33
|
import { piRecordSessionHost } from "./pi/record-session-host.ts";
|
|
34
|
+
import { sitianVolumeDirectory } from "./sitian-appender.ts";
|
|
34
35
|
export {
|
|
35
36
|
DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
36
37
|
bookDirectOfficerRunPointer,
|
|
@@ -47,7 +48,7 @@ const CURRENT_SESSION_LEDGER = "current-session.json";
|
|
|
47
48
|
type CurrentSessionRecord = { readonly sessionFile: string };
|
|
48
49
|
|
|
49
50
|
function readCurrentSession(sessionDir: string): string {
|
|
50
|
-
const ledger =
|
|
51
|
+
const ledger = currentSessionLedgerPath(sessionDir);
|
|
51
52
|
try {
|
|
52
53
|
const value: unknown = JSON.parse(readFileSync(ledger, "utf8"));
|
|
53
54
|
if (
|
|
@@ -68,7 +69,7 @@ function readCurrentSession(sessionDir: string): string {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
function writeCurrentSession(sessionDir: string, sessionFile: string): void {
|
|
71
|
-
const ledger =
|
|
72
|
+
const ledger = currentSessionLedgerPath(sessionDir);
|
|
72
73
|
try {
|
|
73
74
|
writeFileSync(ledger, `${JSON.stringify({ sessionFile })}\n`, { flag: "wx" });
|
|
74
75
|
} catch (error) {
|
|
@@ -292,7 +293,7 @@ export type RecordSessionParent = {
|
|
|
292
293
|
export type CreateRecordSessionOptions = {
|
|
293
294
|
/** Role working directory passed to the record session; not a placement input. */
|
|
294
295
|
readonly cwd: string;
|
|
295
|
-
/** 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. */
|
|
296
297
|
readonly kind: string;
|
|
297
298
|
/** Parent session — nesting authority for ordinary kinds; optional parentSession link for navigator. */
|
|
298
299
|
readonly parent?: RecordSessionParent;
|
|
@@ -372,7 +373,7 @@ export type RecordSessionOpen = {
|
|
|
372
373
|
* symlink). New principals mint under the already-validated sessionDir.
|
|
373
374
|
* Navigator work-subject nests use their AK ledger; worker-submission-gate asks the
|
|
374
375
|
* host to continue its recent native session without selecting a stored file path.
|
|
375
|
-
* Other ordinary no-subject children (
|
|
376
|
+
* Other ordinary no-subject children (attendance, …) always mint fresh.
|
|
376
377
|
* New persisted principals materialize their deferred session header before return so
|
|
377
378
|
* custom-entry-only writers do not need a parallel delayed-header helper.
|
|
378
379
|
*
|
|
@@ -420,7 +421,7 @@ export function createRecordSessionOpen(
|
|
|
420
421
|
// Ordinary kinds: durable parent's file is the sole nesting authority. A divergent
|
|
421
422
|
// SessionManager directory must not create a second placement route.
|
|
422
423
|
// Do not restore generic bookDir/<kind> fallback for foreign/unrooted parents.
|
|
423
|
-
sessionDir =
|
|
424
|
+
sessionDir = sitianVolumeDirectory(dirname(parentResolved), options.kind);
|
|
424
425
|
parentSession = parentFile;
|
|
425
426
|
mayResumeSameNest = options.kind === WORKER_SUBMISSION_GATE_KIND;
|
|
426
427
|
}
|