@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
package/src/sitian-appender.ts
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { appendFileSync } from "node:fs";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
8
|
+
import { basename, dirname, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
import { parseRunLeaf, sessionDirectoryOf } from "./role-run-placement.ts";
|
|
11
|
+
import { RUN_HISTORY_FILE, RUN_HISTORY_KINDS, RUN_LOG_FILE, RUN_STATE_FILE, RUN_STATE_KINDS } from "./run-dossier-files.ts";
|
|
12
|
+
import { ticketNumberFromSitianSubject } from "./run-ticket-number.ts";
|
|
9
13
|
|
|
10
14
|
import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
|
|
11
15
|
import {
|
|
@@ -39,20 +43,8 @@ export function appendSitianRecordBlock(input: SitianRecordInput, block: string)
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
/**
|
|
43
|
-
export const S4_SUBMISSION_LEDGER_KINDS = new Set([
|
|
44
|
-
"candidate",
|
|
45
|
-
"roundContext",
|
|
46
|
-
"outcome",
|
|
47
|
-
"sealed",
|
|
48
|
-
"post-seal-anomaly"
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
/** Compute the volume partition key for directory placement. */
|
|
46
|
+
/** Volume directory name for a record kind: the kind itself. */
|
|
52
47
|
export function resolveSitianVolumeCategory(kind: string): string {
|
|
53
|
-
if (S4_SUBMISSION_LEDGER_KINDS.has(kind)) {
|
|
54
|
-
return "submission-ledger";
|
|
55
|
-
}
|
|
56
48
|
return kind;
|
|
57
49
|
}
|
|
58
50
|
|
|
@@ -65,6 +57,18 @@ type SitianRecordPath = {
|
|
|
65
57
|
/** Sole records leaf under every sitian volume directory. */
|
|
66
58
|
const SITIAN_RECORDS_LEAF = "records.jsonl" as const;
|
|
67
59
|
|
|
60
|
+
export function sitianVolumeDirectory(sessionDirectory: string, category: string): string {
|
|
61
|
+
return join(sessionDirectory, category);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function sitianRunVolumeDirectory(runDirectory: string, category: string): string {
|
|
65
|
+
return sitianVolumeDirectory(sessionDirectoryOf(runDirectory), category);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function sitianVolumeRecordsFile(volumeDirectory: string): string {
|
|
69
|
+
return join(volumeDirectory, SITIAN_RECORDS_LEAF);
|
|
70
|
+
}
|
|
71
|
+
|
|
68
72
|
/**
|
|
69
73
|
* Ticket-provenance under-book paths (docs/dossier-topology.md sole authority).
|
|
70
74
|
* Writer ticket branch and owner-visible shape share this join — one code path.
|
|
@@ -75,7 +79,7 @@ function ticketProvenanceUnderBookPaths(
|
|
|
75
79
|
ticketId: string,
|
|
76
80
|
): { sessionDir: string; recordFile: string } {
|
|
77
81
|
const sessionDir = join(activationBookDirectory(ledgerHome, bookKey), ticketId);
|
|
78
|
-
return { sessionDir, recordFile:
|
|
82
|
+
return { sessionDir, recordFile: sitianVolumeRecordsFile(sessionDir) };
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
/** Pure topology owner shared by ambient writes and explicit-home submission reads. */
|
|
@@ -85,14 +89,7 @@ export function resolveSitianRecordPathInLedger(
|
|
|
85
89
|
): SitianRecordPath {
|
|
86
90
|
const category = resolveSitianVolumeCategory(input.kind);
|
|
87
91
|
const ticketNumber = category === "ticket-provenance"
|
|
88
|
-
?
|
|
89
|
-
? input.subject
|
|
90
|
-
: typeof input.subject === "object"
|
|
91
|
-
&& typeof input.subject.ticketNumber === "number"
|
|
92
|
-
&& Number.isSafeInteger(input.subject.ticketNumber)
|
|
93
|
-
&& input.subject.ticketNumber > 0
|
|
94
|
-
? String(input.subject.ticketNumber)
|
|
95
|
-
: undefined
|
|
92
|
+
? ticketNumberFromSitianSubject(input.subject)
|
|
96
93
|
: undefined;
|
|
97
94
|
|
|
98
95
|
let sessionDir: string;
|
|
@@ -107,8 +104,10 @@ export function resolveSitianRecordPathInLedger(
|
|
|
107
104
|
sessionDir = paths.sessionDir;
|
|
108
105
|
recordFile = paths.recordFile;
|
|
109
106
|
} else if (category === "ticket-provenance" && input.runDirectory !== undefined) {
|
|
107
|
+
// True-unbound diary staging: the run's history volume (#1161). Never a
|
|
108
|
+
// top-level run/records.jsonl — that leaf is only the ticket-root diary.
|
|
110
109
|
sessionDir = input.runDirectory;
|
|
111
|
-
recordFile = join(sessionDir,
|
|
110
|
+
recordFile = join(sessionDir, RUN_HISTORY_FILE);
|
|
112
111
|
} else {
|
|
113
112
|
if (
|
|
114
113
|
input.sessionParent === undefined
|
|
@@ -117,8 +116,16 @@ export function resolveSitianRecordPathInLedger(
|
|
|
117
116
|
) {
|
|
118
117
|
throw new Error("Sitian record ownership requires a parent session inside the ledger home");
|
|
119
118
|
}
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const sessionParentDir = dirname(input.sessionParent);
|
|
120
|
+
const runDirectory = dirname(sessionParentDir);
|
|
121
|
+
if (basename(sessionParentDir) === "session" && parseRunLeaf(basename(runDirectory)) !== undefined) {
|
|
122
|
+
// A role run's own session: every record kind shares the run's one log (#1161).
|
|
123
|
+
sessionDir = runDirectory;
|
|
124
|
+
recordFile = join(runDirectory, RUN_HISTORY_KINDS.has(input.kind) ? RUN_HISTORY_FILE : RUN_STATE_KINDS.has(input.kind) ? RUN_STATE_FILE : RUN_LOG_FILE);
|
|
125
|
+
} else {
|
|
126
|
+
sessionDir = sitianVolumeDirectory(sessionParentDir, category);
|
|
127
|
+
recordFile = sitianVolumeRecordsFile(sessionDir);
|
|
128
|
+
}
|
|
122
129
|
}
|
|
123
130
|
if (!physicallyContainedIn(ledgerHome, sessionDir)) {
|
|
124
131
|
throw new Error("Sitian record ownership requires a directory inside the ledger home");
|
package/src/sitian-facade.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - sitianReport → appender (SitianRecord rows, append + identity claim)
|
|
7
7
|
* Read paths: readSitianRecords (canonical rows).
|
|
8
8
|
*/
|
|
9
|
+
import { sessionFileOf } from "./role-run-placement.ts";
|
|
9
10
|
import { appendSitianRecord } from "./sitian-appender.ts";
|
|
10
11
|
import type { RecordPointer, SitianRecordInput } from "./sitian-contracts.ts";
|
|
11
12
|
|
|
@@ -20,3 +21,24 @@ export * from "./sitian-reader.ts";
|
|
|
20
21
|
export function sitianReport(input: SitianRecordInput): RecordPointer {
|
|
21
22
|
return appendSitianRecord(input);
|
|
22
23
|
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* One record of a role run: history.jsonl for the history kinds and state.jsonl for the
|
|
27
|
+
* state kinds (see the appender), log.jsonl for the run's own diagnostics (host dossier pointers,
|
|
28
|
+
* dispatch / resume / post-admission faults, host stderr). Same single entry
|
|
29
|
+
* as every other record; the run is named, never a file path.
|
|
30
|
+
*/
|
|
31
|
+
export function reportRunRecord(
|
|
32
|
+
runDirectory: string,
|
|
33
|
+
kind: string,
|
|
34
|
+
payload: unknown,
|
|
35
|
+
source: string,
|
|
36
|
+
): RecordPointer {
|
|
37
|
+
return appendSitianRecord({
|
|
38
|
+
level: "event",
|
|
39
|
+
kind,
|
|
40
|
+
sessionParent: sessionFileOf(runDirectory),
|
|
41
|
+
source,
|
|
42
|
+
payload,
|
|
43
|
+
});
|
|
44
|
+
}
|
package/src/sitian-reader.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* - Zero whitewashing, zero deduplication.
|
|
7
7
|
* - Canonical rows after malformed lines are always reachable.
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
9
|
import { readFile } from "node:fs/promises";
|
|
11
10
|
|
|
12
11
|
import type {
|
|
@@ -15,46 +14,77 @@ import type {
|
|
|
15
14
|
SitianRecord,
|
|
16
15
|
} from "./sitian-contracts.ts";
|
|
17
16
|
|
|
18
|
-
import { isRecord, errorText } from "./unknown-value.ts";
|
|
17
|
+
import { isRecord, errorText, isEnoent } from "./unknown-value.ts";
|
|
19
18
|
|
|
20
|
-
/**
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/** One non-blank JSONL line through the sole Sitian decoder. Blank → undefined. */
|
|
20
|
+
export type SitianLineDecode =
|
|
21
|
+
| { readonly ok: true; readonly record: SitianRecord }
|
|
22
|
+
| { readonly ok: false; readonly diagnostic: SitianMalformedDiagnostic };
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decode one Sitian JSONL line. Callers that must keep original bytes use this
|
|
26
|
+
* with the source line; volume readers fold it into records + diagnostics.
|
|
27
|
+
*/
|
|
28
|
+
export function decodeSitianRecordLine(
|
|
29
|
+
line: string,
|
|
30
|
+
lineNumber: number,
|
|
31
|
+
): SitianLineDecode | undefined {
|
|
32
|
+
if (!line.trim()) return undefined;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(line);
|
|
35
|
+
if (isRecord(parsed)) {
|
|
36
|
+
return { ok: true, record: parsed as unknown as SitianRecord };
|
|
37
|
+
}
|
|
38
|
+
const typeDesc = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
diagnostic: {
|
|
42
|
+
kind: "malformed",
|
|
43
|
+
line: lineNumber,
|
|
44
|
+
raw: line,
|
|
45
|
+
error: `expected JSON object, got ${typeDesc}`,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
diagnostic: {
|
|
52
|
+
kind: "malformed",
|
|
53
|
+
line: lineNumber,
|
|
54
|
+
raw: line,
|
|
55
|
+
error: errorText(error),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
24
58
|
}
|
|
59
|
+
}
|
|
25
60
|
|
|
26
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Sole decoder for one Sitian JSONL volume's text. Sync so the run-dossier
|
|
63
|
+
* renderer and the async file reader share one implementation.
|
|
64
|
+
*/
|
|
65
|
+
export function parseSitianRecordText(text: string): SitianReadResult {
|
|
27
66
|
const lines = text.split("\n");
|
|
28
|
-
|
|
29
67
|
const records: SitianRecord[] = [];
|
|
30
68
|
const diagnostics: SitianMalformedDiagnostic[] = [];
|
|
31
69
|
|
|
32
70
|
for (let index = 0; index < lines.length; index += 1) {
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const parsed = JSON.parse(line);
|
|
38
|
-
if (isRecord(parsed)) {
|
|
39
|
-
records.push(parsed as unknown as SitianRecord);
|
|
40
|
-
} else {
|
|
41
|
-
const typeDesc = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
42
|
-
diagnostics.push({
|
|
43
|
-
kind: "malformed",
|
|
44
|
-
line: index + 1,
|
|
45
|
-
raw: line,
|
|
46
|
-
error: `expected JSON object, got ${typeDesc}`,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
} catch (error) {
|
|
50
|
-
diagnostics.push({
|
|
51
|
-
kind: "malformed",
|
|
52
|
-
line: index + 1,
|
|
53
|
-
raw: line,
|
|
54
|
-
error: errorText(error),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
71
|
+
const decoded = decodeSitianRecordLine(lines[index]!, index + 1);
|
|
72
|
+
if (decoded === undefined) continue;
|
|
73
|
+
if (decoded.ok) records.push(decoded.record);
|
|
74
|
+
else diagnostics.push(decoded.diagnostic);
|
|
57
75
|
}
|
|
58
76
|
|
|
59
77
|
return { records, diagnostics };
|
|
60
78
|
}
|
|
79
|
+
|
|
80
|
+
/** Read a Sitian record volume with full traversal and non-destructive diagnostics. */
|
|
81
|
+
export async function readSitianRecords(recordFile: string): Promise<SitianReadResult> {
|
|
82
|
+
// Direct read: only ENOENT is absence. EACCES/EPERM and other IO keep their cause
|
|
83
|
+
// (Node fs docs: do not existsSync/access before open — #1161 C3).
|
|
84
|
+
try {
|
|
85
|
+
return parseSitianRecordText(await readFile(recordFile, "utf8"));
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (isEnoent(error)) return { records: [], diagnostics: [] };
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/submission-gate.ts
CHANGED
|
@@ -91,7 +91,7 @@ export function createDefaultGateOfficerSummon(options: {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Book a typed pointer
|
|
94
|
+
* Book a typed pointer in the parent run's current.json officers section.
|
|
95
95
|
* Offline mocks without a real session leave no nested volume (lawful zero).
|
|
96
96
|
*/
|
|
97
97
|
function bookDirectOfficerPointer(
|
package/src/submission-ledger.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sessionFileIn, sessionFileOf } from "./role-run-placement.ts";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
resolveActivationLedgerHome,
|
|
@@ -133,7 +133,7 @@ async function submissionRecordFile(cwd: string, runId: string, scope: Submissio
|
|
|
133
133
|
if (sessionParent === undefined) {
|
|
134
134
|
const discoveredRun = await findRunDirectoryById(scope.home, runId);
|
|
135
135
|
if (discoveredRun === undefined) return undefined;
|
|
136
|
-
sessionParent =
|
|
136
|
+
sessionParent = sessionFileOf(discoveredRun);
|
|
137
137
|
}
|
|
138
138
|
return resolveSitianRecordPathInLedger({
|
|
139
139
|
level: "event",
|
|
@@ -432,12 +432,12 @@ async function restoreState(cwd: string, runId: string, scope: SubmissionLedgerR
|
|
|
432
432
|
/** Sole HostContext-derived session parent for ledger restore/append (never process.env). */
|
|
433
433
|
function sessionParentFromHostContext(context: HostContext): string | undefined {
|
|
434
434
|
const runDirectory = runDirectoryFromHostContext(context);
|
|
435
|
-
if (runDirectory !== undefined) return
|
|
435
|
+
if (runDirectory !== undefined) return sessionFileOf(runDirectory);
|
|
436
436
|
const sessionFile = context.sessionManager.getSessionFile?.();
|
|
437
437
|
if (typeof sessionFile === "string" && sessionFile.length > 0) return sessionFile;
|
|
438
438
|
const sessionDir = context.sessionManager.getSessionDir?.();
|
|
439
439
|
if (typeof sessionDir === "string" && sessionDir.length > 0) {
|
|
440
|
-
return
|
|
440
|
+
return sessionFileIn(sessionDir);
|
|
441
441
|
}
|
|
442
442
|
return undefined;
|
|
443
443
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { parseTicketNumber } from "./run-ticket-number.ts";
|
|
1
2
|
import { isRecord } from "./unknown-value.ts";
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* 起居录(ticket-provenance)typed 形状 —— ADR 0075 / ADR 0081 / #1090。
|
|
4
6
|
*
|
|
@@ -63,12 +65,7 @@ export type TicketProvenanceLine = {
|
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
function positiveInteger(value: unknown): number | undefined {
|
|
66
|
-
|
|
67
|
-
if (typeof value === "string" && /^[1-9][0-9]*$/.test(value)) {
|
|
68
|
-
const parsed = Number(value);
|
|
69
|
-
if (Number.isSafeInteger(parsed)) return parsed;
|
|
70
|
-
}
|
|
71
|
-
return undefined;
|
|
68
|
+
return parseTicketNumber(value);
|
|
72
69
|
}
|
|
73
70
|
|
|
74
71
|
function projectBound(value: unknown): TicketProvenanceBound | undefined {
|
package/src/ticket-provenance.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* 一册=一个追加式 records.jsonl。每轮新投影经司天台 appender 追加为不可变提交;
|
|
4
4
|
* 纯追加、不回读历史去重、不折叠。
|
|
5
5
|
*/
|
|
6
|
-
import { readFile
|
|
7
|
-
import {
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
errnoCode,
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
type LedgerSessionLine,
|
|
18
18
|
} from "./ledger-session-read.ts";
|
|
19
19
|
import { isSafePositiveTicketNumber } from "./run-ticket-number.ts";
|
|
20
|
+
import { runDirectoryOfSessionFile, sessionFileOf } from "./role-run-placement.ts";
|
|
21
|
+
import { RUN_HISTORY_FILE } from "./run-dossier-files.ts";
|
|
20
22
|
import { adaptSessionDialogue, nativeEventId } from "./session-dialogue.ts";
|
|
21
23
|
import {
|
|
22
24
|
appendSitianRecordBlock,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
resolveSitianRecordPath,
|
|
25
27
|
type SitianRecordInput,
|
|
26
28
|
} from "./sitian-facade.ts";
|
|
29
|
+
import { decodeSitianRecordLine } from "./sitian-reader.ts";
|
|
27
30
|
import {
|
|
28
31
|
TICKET_PROVENANCE_KIND,
|
|
29
32
|
type TicketProvenanceBound,
|
|
@@ -76,10 +79,7 @@ function isLedgerRoleSessionFile(absolute: string, home?: string): boolean {
|
|
|
76
79
|
: packageMachineHome();
|
|
77
80
|
const ledgerHome = resolveActivationLedgerHome(machineHome);
|
|
78
81
|
if (!physicallyContainedIn(ledgerHome, absolute)) return false;
|
|
79
|
-
return (
|
|
80
|
-
basename(absolute) === "session.jsonl" &&
|
|
81
|
-
basename(dirname(absolute)) === "session"
|
|
82
|
-
);
|
|
82
|
+
return sessionFileOf(runDirectoryOfSessionFile(absolute)) === absolute;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/** Real I/O seam gate: only host session stores or sitian role-run session.jsonl. */
|
|
@@ -152,16 +152,28 @@ export async function rehomeUnboundTicketProvenance(
|
|
|
152
152
|
cwd: string,
|
|
153
153
|
home: string,
|
|
154
154
|
): Promise<void> {
|
|
155
|
-
const
|
|
156
|
-
|
|
155
|
+
const ticketInput = ticketProvenanceRecordInput(ticketNumber, cwd, home);
|
|
156
|
+
|
|
157
|
+
// Live staging: ticket-provenance rows in the run's history.jsonl. Append-only
|
|
158
|
+
// history keeps the staging rows; the ticket-root diary is the home.
|
|
159
|
+
// Old run/records.jsonl is not a runtime input (#1161); only migration tools read it.
|
|
160
|
+
let history: string;
|
|
157
161
|
try {
|
|
158
|
-
|
|
162
|
+
history = await readFile(join(runDirectory, RUN_HISTORY_FILE), "utf8");
|
|
159
163
|
} catch (error) {
|
|
160
164
|
if (errnoCode(error) === "ENOENT") return;
|
|
161
165
|
throw error;
|
|
162
166
|
}
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
const block = history
|
|
168
|
+
.split("\n")
|
|
169
|
+
.flatMap((line, index) => {
|
|
170
|
+
const decoded = decodeSitianRecordLine(line, index + 1);
|
|
171
|
+
if (decoded === undefined || !decoded.ok) return [];
|
|
172
|
+
if (decoded.record.kind !== TICKET_PROVENANCE_KIND) return [];
|
|
173
|
+
return [`${line}\n`];
|
|
174
|
+
})
|
|
175
|
+
.join("");
|
|
176
|
+
if (block !== "") appendSitianRecordBlock(ticketInput, block);
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
/**
|
package/src/ticket-snapshot.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { createGhApiRunner, type GhApiRunner } from "./gh-api-runner.ts";
|
|
15
15
|
|
|
16
16
|
import { isRecord } from "./unknown-value.ts";
|
|
17
|
+
import { isPositiveTicketNumber } from "./run-ticket-number.ts";
|
|
17
18
|
|
|
18
19
|
export type BookRepoBinding = {
|
|
19
20
|
bookKey: string;
|
|
@@ -171,7 +172,7 @@ function parseClosedAt(raw: unknown, label: string): string | null {
|
|
|
171
172
|
function parseTicketNode(raw: unknown, label: string): ParsedTicketNode {
|
|
172
173
|
if (!isRecord(raw)) throw new Error(`${label} is not an object`);
|
|
173
174
|
const number = raw.number;
|
|
174
|
-
if (
|
|
175
|
+
if (!isPositiveTicketNumber(number)) {
|
|
175
176
|
throw new Error(`${label}.number invalid`);
|
|
176
177
|
}
|
|
177
178
|
if (typeof raw.title !== "string") throw new Error(`${label}.title missing`);
|
|
@@ -536,7 +537,7 @@ export function createGhTicketSnapshotTransport(
|
|
|
536
537
|
const closedNumbers = [
|
|
537
538
|
...new Set(
|
|
538
539
|
input.closedIssueNumbers.filter(
|
|
539
|
-
(n) =>
|
|
540
|
+
(n) => isPositiveTicketNumber(n) && !tickets.has(n),
|
|
540
541
|
),
|
|
541
542
|
),
|
|
542
543
|
].sort((a, b) => a - b);
|