@akagilnc/pi-workflow-roles 0.1.5478 → 0.1.5504
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/dist/acp-host/production-host.js +4945 -4980
- package/dist/analyst-gate-cycles-read.js +11 -29
- package/dist/analyst-ledger.js +10 -12
- package/dist/archivist-record-entry.js +4 -3
- package/dist/archivist-record-pointer.js +59 -32
- package/dist/atomic-write.js +2 -2
- package/dist/auditor-dossier-tool.js +8 -4
- package/dist/auditor-soul.js +2 -1
- package/dist/collector-config.js +6 -5
- package/dist/doctor-evidence.js +5 -1
- package/dist/engine-detour-usage.js +4 -10
- package/dist/headless-host/description.js +251 -0
- package/dist/headless-host/production-host.js +2787 -2658
- package/dist/ledger-session-read.js +5 -0
- package/dist/migrate-book-topology.js +129 -100
- package/dist/navigator-public-session.js +13 -17
- package/dist/notary-source-run.js +7 -15
- package/dist/packaged-role-registry.js +0 -12
- package/dist/pi/durable-principal.js +2 -3
- package/dist/pi/role-turn-host.js +27 -13
- package/dist/public-cli/instruction-seat-run.js +4 -4
- package/dist/public-cli/invocation.js +17 -45
- package/dist/public-cli/main.js +1074 -1085
- package/dist/public-cli/run-lifecycle.js +17 -47
- package/dist/public-cli/settlement.js +28 -128
- package/dist/public-cli/terminal.js +0 -12
- package/dist/role-run-placement.js +52 -5
- package/dist/role-run-relocation.js +39 -2
- package/dist/run-terminal-artifacts.js +2 -6
- package/dist/run-ticket-number.js +33 -4
- package/dist/session-assistant-usage.js +9 -18
- package/dist/session-identity.js +3 -4
- package/dist/sitian-appender.js +20 -6
- package/dist/submission-ledger.js +4 -4
- package/dist/ticket-provenance-contracts.js +2 -8
- package/dist/ticket-provenance.js +5 -4
- package/package.json +1 -1
- package/src/acp-host/role-turn-host.ts +2 -2
- package/src/analyst-gate-cycles-read.ts +11 -34
- package/src/analyst-ledger.ts +14 -14
- package/src/archivist-record-entry.ts +4 -3
- package/src/archivist-record-pointer.ts +70 -34
- package/src/atomic-write.ts +3 -1
- package/src/auditor-dossier-tool.ts +8 -4
- package/src/auditor-soul.ts +2 -1
- package/src/collector-config.ts +5 -3
- package/src/doctor-evidence.ts +5 -1
- package/src/engine-detour-usage.ts +10 -13
- package/src/factory-board.ts +16 -81
- package/src/headless-host/description.ts +248 -1
- package/src/headless-host/production-host.ts +1 -2
- package/src/headless-host/role-turn-host.ts +10 -9
- 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 +7 -17
- package/src/packaged-role-registry.ts +0 -15
- package/src/pi/durable-principal.ts +2 -4
- package/src/pi/role-turn-host.ts +35 -12
- package/src/public-cli/instruction-seat-run.ts +4 -4
- package/src/public-cli/invocation.ts +29 -57
- package/src/public-cli/run-lifecycle.ts +20 -43
- package/src/public-cli/settlement.ts +30 -173
- package/src/public-cli/terminal.ts +0 -60
- package/src/role-envelope.ts +4 -2
- package/src/role-run-placement.ts +63 -4
- package/src/role-run-relocation.ts +39 -2
- package/src/role-runtime.ts +40 -59
- package/src/run-terminal-artifacts.ts +3 -5
- package/src/run-ticket-number.ts +41 -3
- package/src/session-assistant-usage.ts +8 -20
- package/src/session-identity.ts +3 -4
- package/src/sitian-appender.ts +20 -12
- package/src/submission-ledger.ts +4 -4
- package/src/ticket-provenance-contracts.ts +3 -6
- package/src/ticket-provenance.ts +5 -6
- package/src/ticket-snapshot.ts +3 -2
- package/src/ticket-trajectory.ts +85 -99
package/src/role-runtime.ts
CHANGED
|
@@ -12,12 +12,14 @@ import {
|
|
|
12
12
|
|
|
13
13
|
import { Value } from "typebox/value";
|
|
14
14
|
import { sitianReport } from "./sitian-facade.ts";
|
|
15
|
+
import { sitianReportSafe } from "./host-session-record.ts";
|
|
15
16
|
import { createSubmissionLedgerHost, sealAcceptedSubmission } from "./submission-ledger.ts";
|
|
16
17
|
import { registerFiledSubmissionTool, type FiledSubmissionBeforeAccept } from "./filed-submission.ts";
|
|
17
18
|
import type { RoleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
18
19
|
|
|
19
20
|
import { activationTraceRecordSchema, namedActivationCause, type ActivationTraceRecord, type ActivationTraceWriter } from "./activation-trace.ts";
|
|
20
21
|
import { homeFromRunDirectory } from "./activation-ledger-topology.ts";
|
|
22
|
+
import { isSafePositiveTicketNumber, parseTicketNumber } from "./run-ticket-number.ts";
|
|
21
23
|
import {
|
|
22
24
|
durableSessionPointer,
|
|
23
25
|
resolveBookKeyFromGit,
|
|
@@ -207,12 +209,9 @@ function decodeReviewerAdmittedInputs(getFlag: (name: string) => unknown): Revie
|
|
|
207
209
|
authorityRefs = Object.freeze(parsed as string[]);
|
|
208
210
|
}
|
|
209
211
|
|
|
210
|
-
let ticketNumber: number | undefined;
|
|
211
212
|
const rawTicketNumber = getFlag("ak-review-ticket-number");
|
|
212
213
|
// Shape-invalid flag values do not abort: omit typed candidate; branch→commit→degrade continues.
|
|
213
|
-
|
|
214
|
-
ticketNumber = Number(rawTicketNumber);
|
|
215
|
-
}
|
|
214
|
+
const ticketNumber = parseTicketNumber(rawTicketNumber);
|
|
216
215
|
|
|
217
216
|
const baseRevision = getFlag("ak-review-base");
|
|
218
217
|
if (typeof baseRevision !== "string" || !baseRevision.trim()) {
|
|
@@ -760,15 +759,8 @@ function readDiaristTicketAssertion(
|
|
|
760
759
|
}
|
|
761
760
|
const raw = submitted.ticketNumber;
|
|
762
761
|
if (raw === null) return { kind: "true-unbound" };
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
if (typeof raw === "string" && /^[1-9]\d*$/.test(raw)) {
|
|
767
|
-
const n = Number(raw);
|
|
768
|
-
if (Number.isSafeInteger(n) && n >= 1) {
|
|
769
|
-
return { kind: "ticket", ticketNumber: n };
|
|
770
|
-
}
|
|
771
|
-
}
|
|
762
|
+
const ticketNumber = parseTicketNumber(raw);
|
|
763
|
+
if (ticketNumber !== undefined) return { kind: "ticket", ticketNumber };
|
|
772
764
|
return { kind: "invalid" };
|
|
773
765
|
}
|
|
774
766
|
|
|
@@ -802,12 +794,9 @@ function readDiaristRunCoordinates(ctx: HostContext): {
|
|
|
802
794
|
readonly boundTicketNumber?: number;
|
|
803
795
|
} {
|
|
804
796
|
const coordinates = readRoleRunCoordinates(ctx, "diarist accept");
|
|
805
|
-
const bound =
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
coordinates.admitted.ticketNumber >= 1
|
|
809
|
-
? coordinates.admitted.ticketNumber
|
|
810
|
-
: undefined;
|
|
797
|
+
const bound = isSafePositiveTicketNumber(coordinates.admitted.ticketNumber)
|
|
798
|
+
? coordinates.admitted.ticketNumber
|
|
799
|
+
: undefined;
|
|
811
800
|
return {
|
|
812
801
|
runDirectory: coordinates.runDirectory,
|
|
813
802
|
projectRoot: coordinates.projectRoot,
|
|
@@ -1344,16 +1333,14 @@ export function createRoleRuntimeExtension(
|
|
|
1344
1333
|
attemptPointer: receiptAttemptPointer(runPointer, ctx.invocationScopeId),
|
|
1345
1334
|
});
|
|
1346
1335
|
envelopeHost.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
});
|
|
1356
|
-
} catch {}
|
|
1336
|
+
sitianReportSafe({
|
|
1337
|
+
level: "event",
|
|
1338
|
+
kind: "no-receipt-lifecycle",
|
|
1339
|
+
cwd: ctx.cwd,
|
|
1340
|
+
sessionParent: ctx.sessionManager.getSessionFile(),
|
|
1341
|
+
payload: facts,
|
|
1342
|
+
source: "role-runtime",
|
|
1343
|
+
});
|
|
1357
1344
|
}
|
|
1358
1345
|
}
|
|
1359
1346
|
return;
|
|
@@ -1367,16 +1354,14 @@ export function createRoleRuntimeExtension(
|
|
|
1367
1354
|
RECEIPT_DELIVERY_REQUEST_ENTRY,
|
|
1368
1355
|
scopeId.length > 0 ? { invocationScopeId: scopeId } : undefined,
|
|
1369
1356
|
);
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
});
|
|
1379
|
-
} catch {}
|
|
1357
|
+
sitianReportSafe({
|
|
1358
|
+
level: "event",
|
|
1359
|
+
kind: "receipt-delivery",
|
|
1360
|
+
cwd: ctx.cwd,
|
|
1361
|
+
sessionParent: ctx.sessionManager.getSessionFile(),
|
|
1362
|
+
payload: { type: "ak-receipt-delivery-request" },
|
|
1363
|
+
source: "role-runtime",
|
|
1364
|
+
});
|
|
1380
1365
|
envelopeHost.sendMessage({
|
|
1381
1366
|
customType: "ak-receipt-delivery-prompt",
|
|
1382
1367
|
content: JSON.stringify(receiptDelivery.deliveryState()),
|
|
@@ -1391,16 +1376,14 @@ export function createRoleRuntimeExtension(
|
|
|
1391
1376
|
attemptPointer: receiptAttemptPointer(runPointer, ctx.invocationScopeId),
|
|
1392
1377
|
});
|
|
1393
1378
|
envelopeHost.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
});
|
|
1403
|
-
} catch {}
|
|
1379
|
+
sitianReportSafe({
|
|
1380
|
+
level: "event",
|
|
1381
|
+
kind: "no-receipt-lifecycle",
|
|
1382
|
+
cwd: ctx.cwd,
|
|
1383
|
+
sessionParent: ctx.sessionManager.getSessionFile(),
|
|
1384
|
+
payload: facts,
|
|
1385
|
+
source: "role-runtime",
|
|
1386
|
+
});
|
|
1404
1387
|
}
|
|
1405
1388
|
}
|
|
1406
1389
|
});
|
|
@@ -1765,16 +1748,14 @@ export function createRoleRuntimeExtension(
|
|
|
1765
1748
|
subjectKey: work.subjectKey,
|
|
1766
1749
|
};
|
|
1767
1750
|
envelopeHost.appendEntry(NAVIGATOR_INVOCATION_ENTRY, data);
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
});
|
|
1777
|
-
} catch {}
|
|
1751
|
+
sitianReportSafe({
|
|
1752
|
+
level: "event",
|
|
1753
|
+
kind: "attendance",
|
|
1754
|
+
cwd: ctx.cwd,
|
|
1755
|
+
sessionParent: ctx.sessionManager.getSessionFile(),
|
|
1756
|
+
payload: { type: NAVIGATOR_INVOCATION_ENTRY, ...data },
|
|
1757
|
+
source: "role-runtime",
|
|
1758
|
+
});
|
|
1778
1759
|
}
|
|
1779
1760
|
const activation = navigatorActivation;
|
|
1780
1761
|
navigatorAttendance = await dependencies.createNavigatorAttendance({
|
|
@@ -10,7 +10,8 @@ import { randomUUID } from "node:crypto";
|
|
|
10
10
|
import { readdir, readFile } from "node:fs/promises";
|
|
11
11
|
import { basename, dirname, join } from "node:path";
|
|
12
12
|
|
|
13
|
-
import { roleRunArtifactsDirectory } from "./role-run-placement.ts";
|
|
13
|
+
import { parseRunLeaf, roleRunArtifactsDirectory } from "./role-run-placement.ts";
|
|
14
|
+
|
|
14
15
|
import { isRecord, errorText, isMissingPathError } from "./unknown-value.ts";
|
|
15
16
|
|
|
16
17
|
export const RUN_TERMINAL_REPORT_FILE = "report.json" as const;
|
|
@@ -155,10 +156,7 @@ async function listUniqueErrorFallbackPaths(
|
|
|
155
156
|
* Sole authority for runDirectory → runId (last `@` split).
|
|
156
157
|
*/
|
|
157
158
|
export function runIdFromRunDirectory(runDirectory: string): string | undefined {
|
|
158
|
-
|
|
159
|
-
const at = name.lastIndexOf("@");
|
|
160
|
-
if (at <= 0 || at === name.length - 1) return undefined;
|
|
161
|
-
return name.slice(0, at);
|
|
159
|
+
return parseRunLeaf(basename(runDirectory))?.runId;
|
|
162
160
|
}
|
|
163
161
|
|
|
164
162
|
/**
|
package/src/run-ticket-number.ts
CHANGED
|
@@ -14,9 +14,48 @@ import { isEnoent, isRecord } from "./unknown-value.ts";
|
|
|
14
14
|
export const MIGRATION_TICKET_DERIVATION_PAGE =
|
|
15
15
|
"migration-ticket-derivation.json" as const;
|
|
16
16
|
|
|
17
|
+
/** Positive-integer contract for display / snapshot inputs; binding uses the safe subset below. */
|
|
18
|
+
export function isPositiveTicketNumber(value: unknown): value is number {
|
|
19
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
/** Sole safe-positive ticket invariant (bind / admission / placement / readers). */
|
|
18
23
|
export function isSafePositiveTicketNumber(value: unknown): value is number {
|
|
19
|
-
return
|
|
24
|
+
return isPositiveTicketNumber(value) && Number.isSafeInteger(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Shared digit spelling, also used for consumer-specific rejection diagnostics. */
|
|
28
|
+
export function isTicketNumberString(value: unknown): value is string {
|
|
29
|
+
return typeof value === "string" && /^[1-9][0-9]*$/.test(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ticket spelling shared by placement, migration, and sitian subject routing.
|
|
34
|
+
* A number, or a digit string with no sign, zero-pad, decimal, or `#`.
|
|
35
|
+
* Unsafe integers are unidentified. Leading `#N` stays on readDeclaredTicketNumber.
|
|
36
|
+
*/
|
|
37
|
+
export function parseTicketNumber(value: unknown): number | undefined {
|
|
38
|
+
if (isSafePositiveTicketNumber(value)) return value;
|
|
39
|
+
if (!isTicketNumberString(value)) return undefined;
|
|
40
|
+
const parsed = Number(value);
|
|
41
|
+
return isSafePositiveTicketNumber(parsed) ? parsed : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Ticket number carried by a sitian subject, when the subject is a ticket. */
|
|
45
|
+
export function sitianSubjectTicketNumber(subject: unknown): number | undefined {
|
|
46
|
+
if (typeof subject === "string" || typeof subject === "number") {
|
|
47
|
+
return parseTicketNumber(subject);
|
|
48
|
+
}
|
|
49
|
+
if (subject !== null && typeof subject === "object" && !Array.isArray(subject)) {
|
|
50
|
+
return parseTicketNumber((subject as { ticketNumber?: unknown }).ticketNumber);
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Ticket-provenance directory id from a sitian subject, when the subject is a ticket. */
|
|
56
|
+
export function ticketNumberFromSitianSubject(subject: unknown): string | undefined {
|
|
57
|
+
const parsed = sitianSubjectTicketNumber(subject);
|
|
58
|
+
return parsed === undefined ? undefined : String(parsed);
|
|
20
59
|
}
|
|
21
60
|
|
|
22
61
|
/**
|
|
@@ -35,8 +74,7 @@ export function readDeclaredTicketNumber(value: unknown): number | undefined {
|
|
|
35
74
|
// (word-boundary alone would truncate at the decimal point).
|
|
36
75
|
const match = /^#?([1-9]\d*)(?!\.\d)(?:\b|$)/.exec(trimmed);
|
|
37
76
|
if (match === null) return undefined;
|
|
38
|
-
|
|
39
|
-
return isSafePositiveTicketNumber(ticketNumber) ? ticketNumber : undefined;
|
|
77
|
+
return parseTicketNumber(match[1]);
|
|
40
78
|
}
|
|
41
79
|
|
|
42
80
|
/** Loud reject for non-safe-positive ticket numbers before placement or bind. */
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Accumulates real cost when rows carry it; never invents cost zeros (#675).
|
|
4
4
|
*/
|
|
5
5
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
6
|
-
import {
|
|
6
|
+
import { ROLE_RUN_SESSION_FILENAME, sessionFileOf } from "./role-run-placement.ts";
|
|
7
|
+
import { readStrictPiSessionJsonl } from "./ledger-session-read.ts";
|
|
7
8
|
|
|
8
9
|
import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
9
10
|
|
|
@@ -12,25 +13,12 @@ import { errorText } from "./unknown-value.ts";
|
|
|
12
13
|
export async function readAssistantUsageFromSessionFile(
|
|
13
14
|
sessionFile: string,
|
|
14
15
|
): Promise<Usage | undefined> {
|
|
15
|
-
const { readFile } = await import("node:fs/promises");
|
|
16
|
-
let text: string;
|
|
17
|
-
try {
|
|
18
|
-
text = await readFile(sessionFile, "utf8");
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined;
|
|
21
|
-
throw error;
|
|
22
|
-
}
|
|
23
16
|
let rows: Array<{ type?: string; message?: { role?: string; usage?: Usage } }>;
|
|
24
17
|
try {
|
|
25
|
-
rows =
|
|
26
|
-
.trim()
|
|
27
|
-
.split("\n")
|
|
28
|
-
.filter(Boolean)
|
|
29
|
-
.map((line) => JSON.parse(line) as {
|
|
30
|
-
type?: string;
|
|
31
|
-
message?: { role?: string; usage?: Usage };
|
|
32
|
-
});
|
|
18
|
+
rows = await readStrictPiSessionJsonl(sessionFile) as typeof rows;
|
|
33
19
|
} catch (error) {
|
|
20
|
+
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined;
|
|
21
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
34
22
|
throw new Error(
|
|
35
23
|
`session usage parse failed (${sessionFile}): ${errorText(error)}`,
|
|
36
24
|
{ cause: error },
|
|
@@ -95,18 +83,18 @@ export function sessionFileFromPublicSummon(
|
|
|
95
83
|
summoned: PublicSummonResult,
|
|
96
84
|
): string | undefined {
|
|
97
85
|
if (typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== "") {
|
|
98
|
-
return
|
|
86
|
+
return sessionFileOf(summoned.runDirectory);
|
|
99
87
|
}
|
|
100
88
|
const fromArtifacts = summoned.terminal?.artifacts
|
|
101
89
|
?.map((a) => (a as { path?: string }).path)
|
|
102
|
-
.find((p): p is string => typeof p === "string" && p.endsWith(
|
|
90
|
+
.find((p): p is string => typeof p === "string" && p.endsWith(ROLE_RUN_SESSION_FILENAME));
|
|
103
91
|
if (fromArtifacts !== undefined) return fromArtifacts;
|
|
104
92
|
const outcome = summoned.terminal?.roleOutcome;
|
|
105
93
|
if (outcome === undefined) return undefined;
|
|
106
94
|
const facts = (outcome as { decisiveFacts?: Record<string, unknown> }).decisiveFacts;
|
|
107
95
|
const pointer = facts?.runPointer;
|
|
108
96
|
if (typeof pointer === "string" && pointer.trim() !== "") {
|
|
109
|
-
return
|
|
97
|
+
return sessionFileOf(pointer);
|
|
110
98
|
}
|
|
111
99
|
return undefined;
|
|
112
100
|
}
|
package/src/session-identity.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { mkdir, readFile
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { writeFileAtomically } from "./atomic-write.ts";
|
|
4
5
|
import type { DurablePrincipal, DurablePrincipalAuthority } from "./host-contracts.ts";
|
|
5
6
|
import {
|
|
6
7
|
DEFAULT_ROLE_TURN_HOST,
|
|
@@ -35,9 +36,7 @@ export function createSessionIdentityAuthority(
|
|
|
35
36
|
async bind(principal, sessionId) {
|
|
36
37
|
const target = bindingPath(principal);
|
|
37
38
|
await mkdir(dirname(target), { recursive: true });
|
|
38
|
-
|
|
39
|
-
await writeFile(temporary, `${JSON.stringify({ sessionId })}\n`, { encoding: "utf8", mode: 0o600 });
|
|
40
|
-
await rename(temporary, target);
|
|
39
|
+
await writeFileAtomically(target, `${JSON.stringify({ sessionId })}\n`, { mode: 0o600 });
|
|
41
40
|
},
|
|
42
41
|
};
|
|
43
42
|
}
|
package/src/sitian-appender.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
import { appendFileSync } from "node:fs";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
|
|
10
|
+
import { sessionDirectoryOf } from "./role-run-placement.ts";
|
|
11
|
+
import { ticketNumberFromSitianSubject } from "./run-ticket-number.ts";
|
|
12
|
+
|
|
10
13
|
import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
|
|
11
14
|
import {
|
|
12
15
|
activationBookDirectory,
|
|
@@ -65,6 +68,18 @@ type SitianRecordPath = {
|
|
|
65
68
|
/** Sole records leaf under every sitian volume directory. */
|
|
66
69
|
const SITIAN_RECORDS_LEAF = "records.jsonl" as const;
|
|
67
70
|
|
|
71
|
+
export function sitianVolumeDirectory(sessionDirectory: string, category: string): string {
|
|
72
|
+
return join(sessionDirectory, category);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function sitianRunVolumeDirectory(runDirectory: string, category: string): string {
|
|
76
|
+
return sitianVolumeDirectory(sessionDirectoryOf(runDirectory), category);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function sitianVolumeRecordsFile(volumeDirectory: string): string {
|
|
80
|
+
return join(volumeDirectory, SITIAN_RECORDS_LEAF);
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
/**
|
|
69
84
|
* Ticket-provenance under-book paths (docs/dossier-topology.md sole authority).
|
|
70
85
|
* Writer ticket branch and owner-visible shape share this join — one code path.
|
|
@@ -75,7 +90,7 @@ function ticketProvenanceUnderBookPaths(
|
|
|
75
90
|
ticketId: string,
|
|
76
91
|
): { sessionDir: string; recordFile: string } {
|
|
77
92
|
const sessionDir = join(activationBookDirectory(ledgerHome, bookKey), ticketId);
|
|
78
|
-
return { sessionDir, recordFile:
|
|
93
|
+
return { sessionDir, recordFile: sitianVolumeRecordsFile(sessionDir) };
|
|
79
94
|
}
|
|
80
95
|
|
|
81
96
|
/** Pure topology owner shared by ambient writes and explicit-home submission reads. */
|
|
@@ -85,14 +100,7 @@ export function resolveSitianRecordPathInLedger(
|
|
|
85
100
|
): SitianRecordPath {
|
|
86
101
|
const category = resolveSitianVolumeCategory(input.kind);
|
|
87
102
|
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
|
|
103
|
+
? ticketNumberFromSitianSubject(input.subject)
|
|
96
104
|
: undefined;
|
|
97
105
|
|
|
98
106
|
let sessionDir: string;
|
|
@@ -108,7 +116,7 @@ export function resolveSitianRecordPathInLedger(
|
|
|
108
116
|
recordFile = paths.recordFile;
|
|
109
117
|
} else if (category === "ticket-provenance" && input.runDirectory !== undefined) {
|
|
110
118
|
sessionDir = input.runDirectory;
|
|
111
|
-
recordFile =
|
|
119
|
+
recordFile = sitianVolumeRecordsFile(sessionDir);
|
|
112
120
|
} else {
|
|
113
121
|
if (
|
|
114
122
|
input.sessionParent === undefined
|
|
@@ -117,8 +125,8 @@ export function resolveSitianRecordPathInLedger(
|
|
|
117
125
|
) {
|
|
118
126
|
throw new Error("Sitian record ownership requires a parent session inside the ledger home");
|
|
119
127
|
}
|
|
120
|
-
sessionDir =
|
|
121
|
-
recordFile =
|
|
128
|
+
sessionDir = sitianVolumeDirectory(dirname(input.sessionParent), category);
|
|
129
|
+
recordFile = sitianVolumeRecordsFile(sessionDir);
|
|
122
130
|
}
|
|
123
131
|
if (!physicallyContainedIn(ledgerHome, sessionDir)) {
|
|
124
132
|
throw new Error("Sitian record ownership requires a directory inside the ledger home");
|
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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 纯追加、不回读历史去重、不折叠。
|
|
5
5
|
*/
|
|
6
6
|
import { readFile, unlink } from "node:fs/promises";
|
|
7
|
-
import {
|
|
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 { sitianVolumeRecordsFile } from "./sitian-appender.ts";
|
|
20
22
|
import { adaptSessionDialogue, nativeEventId } from "./session-dialogue.ts";
|
|
21
23
|
import {
|
|
22
24
|
appendSitianRecordBlock,
|
|
@@ -76,10 +78,7 @@ function isLedgerRoleSessionFile(absolute: string, home?: string): boolean {
|
|
|
76
78
|
: packageMachineHome();
|
|
77
79
|
const ledgerHome = resolveActivationLedgerHome(machineHome);
|
|
78
80
|
if (!physicallyContainedIn(ledgerHome, absolute)) return false;
|
|
79
|
-
return (
|
|
80
|
-
basename(absolute) === "session.jsonl" &&
|
|
81
|
-
basename(dirname(absolute)) === "session"
|
|
82
|
-
);
|
|
81
|
+
return sessionFileOf(runDirectoryOfSessionFile(absolute)) === absolute;
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
/** Real I/O seam gate: only host session stores or sitian role-run session.jsonl. */
|
|
@@ -152,7 +151,7 @@ export async function rehomeUnboundTicketProvenance(
|
|
|
152
151
|
cwd: string,
|
|
153
152
|
home: string,
|
|
154
153
|
): Promise<void> {
|
|
155
|
-
const source =
|
|
154
|
+
const source = sitianVolumeRecordsFile(runDirectory);
|
|
156
155
|
let content: string;
|
|
157
156
|
try {
|
|
158
157
|
content = await readFile(source, "utf8");
|
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);
|