@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.
Files changed (112) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +6312 -6376
  4. package/dist/analyst-gate-cycles-read.js +28 -78
  5. package/dist/analyst-ledger.js +26 -42
  6. package/dist/analyst-metric-families/acceptance-success-rework.js +25 -1
  7. package/dist/analyst-metric-families/round-timeline.js +2 -5
  8. package/dist/archivist-record-entry.js +4 -3
  9. package/dist/archivist-record-pointer.js +71 -36
  10. package/dist/atomic-write.js +2 -2
  11. package/dist/auditor-dossier-tool.js +11 -6
  12. package/dist/auditor-soul.js +5 -12
  13. package/dist/collector-config.js +6 -5
  14. package/dist/doctor-evidence.js +8 -4
  15. package/dist/engine-detour-usage.js +8 -22
  16. package/dist/headless-host/production-host.js +5969 -6011
  17. package/dist/host-descriptions.js +0 -4
  18. package/dist/host-session-record.js +266 -267
  19. package/dist/ledger-session-read.js +5 -0
  20. package/dist/migrate-book-topology.js +1369 -722
  21. package/dist/navigator-public-session.js +13 -17
  22. package/dist/notary-source-run.js +8 -16
  23. package/dist/packaged-role-registry.js +5 -56
  24. package/dist/pi/durable-principal.js +2 -3
  25. package/dist/pi/role-turn-host.js +79 -15
  26. package/dist/public-cli/auto-resume.js +8 -47
  27. package/dist/public-cli/instruction-seat-run.js +9 -7
  28. package/dist/public-cli/invocation.js +67 -100
  29. package/dist/public-cli/main.js +2689 -2532
  30. package/dist/public-cli/post-admission.js +96 -55
  31. package/dist/public-cli/run-lifecycle.js +44 -100
  32. package/dist/public-cli/settlement.js +184 -429
  33. package/dist/public-cli/terminal.js +0 -12
  34. package/dist/role-run-placement.js +53 -16
  35. package/dist/role-run-relocation.js +111 -52
  36. package/dist/run-dossier-files.js +30 -0
  37. package/dist/run-dossier.js +322 -0
  38. package/dist/run-terminal-artifacts.js +69 -202
  39. package/dist/run-ticket-number.js +49 -7
  40. package/dist/session-assistant-usage.js +9 -18
  41. package/dist/session-identity.js +49 -19
  42. package/dist/sitian-appender.js +29 -18
  43. package/dist/sitian-facade.js +11 -0
  44. package/dist/sitian-reader.js +44 -29
  45. package/dist/submission-gate.js +1 -1
  46. package/dist/submission-ledger.js +4 -4
  47. package/dist/ticket-provenance-contracts.js +2 -8
  48. package/dist/ticket-provenance.js +25 -9
  49. package/package.json +1 -1
  50. package/scripts/build-package.mjs +2 -0
  51. package/src/acp-host/production-host.ts +1 -1
  52. package/src/acp-host/role-turn-host.ts +11 -4
  53. package/src/analyst-gate-cycles-read.ts +31 -88
  54. package/src/analyst-ledger.ts +36 -51
  55. package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
  56. package/src/analyst-metric-families/gate-cycles.ts +1 -1
  57. package/src/analyst-metric-families/round-timeline.ts +2 -5
  58. package/src/analyst-page.ts +3 -3
  59. package/src/archivist-record-entry.ts +6 -5
  60. package/src/archivist-record-pointer.ts +92 -42
  61. package/src/atomic-write.ts +3 -1
  62. package/src/auditor-dossier-tool.ts +14 -8
  63. package/src/auditor-soul.ts +5 -10
  64. package/src/book-topology-migration-placement.ts +16 -3
  65. package/src/book-topology-record-class-migrators.ts +4 -2
  66. package/src/book-topology-runs-migrator.ts +6 -3
  67. package/src/collector-config.ts +5 -3
  68. package/src/doctor-contracts.ts +1 -1
  69. package/src/doctor-evidence.ts +7 -3
  70. package/src/engine-detour-tool.ts +1 -1
  71. package/src/engine-detour-usage.ts +12 -25
  72. package/src/external-host-turn-loop.ts +25 -0
  73. package/src/factory-board.ts +16 -81
  74. package/src/headless-host/production-host.ts +1 -4
  75. package/src/headless-host/role-turn-host.ts +56 -21
  76. package/src/host-descriptions.ts +0 -6
  77. package/src/host-session-record.ts +224 -140
  78. package/src/ledger-session-read.ts +6 -0
  79. package/src/navigator-public-session.ts +13 -18
  80. package/src/navigator-work-context.ts +3 -1
  81. package/src/notary-role.ts +2 -1
  82. package/src/notary-source-run.ts +8 -18
  83. package/src/packaged-role-registry.ts +1 -65
  84. package/src/pi/adapter.ts +50 -7
  85. package/src/pi/durable-principal.ts +2 -4
  86. package/src/pi/role-turn-host.ts +85 -14
  87. package/src/public-cli/auto-resume.ts +7 -43
  88. package/src/public-cli/instruction-seat-run.ts +13 -6
  89. package/src/public-cli/invocation.ts +90 -144
  90. package/src/public-cli/post-admission.ts +111 -72
  91. package/src/public-cli/run-lifecycle.ts +47 -115
  92. package/src/public-cli/settlement.ts +221 -589
  93. package/src/public-cli/terminal.ts +1 -61
  94. package/src/role-envelope.ts +4 -2
  95. package/src/role-run-placement.ts +65 -17
  96. package/src/role-run-relocation.ts +110 -70
  97. package/src/role-runtime.ts +46 -69
  98. package/src/run-dossier-files.ts +33 -0
  99. package/src/run-dossier.ts +357 -0
  100. package/src/run-terminal-artifacts.ts +84 -240
  101. package/src/run-ticket-number.ts +82 -14
  102. package/src/session-assistant-usage.ts +8 -20
  103. package/src/session-identity.ts +52 -21
  104. package/src/sitian-appender.ts +33 -26
  105. package/src/sitian-facade.ts +22 -0
  106. package/src/sitian-reader.ts +62 -32
  107. package/src/submission-gate.ts +1 -1
  108. package/src/submission-ledger.ts +4 -4
  109. package/src/ticket-provenance-contracts.ts +3 -6
  110. package/src/ticket-provenance.ts +23 -11
  111. package/src/ticket-snapshot.ts +3 -2
  112. package/src/ticket-trajectory.ts +95 -109
@@ -1,9 +1,35 @@
1
+ import { readPageSync } from "./run-dossier.js";
1
2
  import { readFile } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
4
  import { isEnoent, isRecord } from "./unknown-value.js";
4
5
  const MIGRATION_TICKET_DERIVATION_PAGE = "migration-ticket-derivation.json";
6
+ function isPositiveTicketNumber(value) {
7
+ return typeof value === "number" && Number.isInteger(value) && value >= 1;
8
+ }
5
9
  function isSafePositiveTicketNumber(value) {
6
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
10
+ return isPositiveTicketNumber(value) && Number.isSafeInteger(value);
11
+ }
12
+ function isTicketNumberString(value) {
13
+ return typeof value === "string" && /^[1-9][0-9]*$/.test(value);
14
+ }
15
+ function parseTicketNumber(value) {
16
+ if (isSafePositiveTicketNumber(value)) return value;
17
+ if (!isTicketNumberString(value)) return void 0;
18
+ const parsed = Number(value);
19
+ return isSafePositiveTicketNumber(parsed) ? parsed : void 0;
20
+ }
21
+ function sitianSubjectTicketNumber(subject) {
22
+ if (typeof subject === "string" || typeof subject === "number") {
23
+ return parseTicketNumber(subject);
24
+ }
25
+ if (subject !== null && typeof subject === "object" && !Array.isArray(subject)) {
26
+ return parseTicketNumber(subject.ticketNumber);
27
+ }
28
+ return void 0;
29
+ }
30
+ function ticketNumberFromSitianSubject(subject) {
31
+ const parsed = sitianSubjectTicketNumber(subject);
32
+ return parsed === void 0 ? void 0 : String(parsed);
7
33
  }
8
34
  function readDeclaredTicketNumber(value) {
9
35
  if (isSafePositiveTicketNumber(value)) return value;
@@ -11,8 +37,7 @@ function readDeclaredTicketNumber(value) {
11
37
  const trimmed = value.trim();
12
38
  const match = /^#?([1-9]\d*)(?!\.\d)(?:\b|$)/.exec(trimmed);
13
39
  if (match === null) return void 0;
14
- const ticketNumber = Number(match[1]);
15
- return isSafePositiveTicketNumber(ticketNumber) ? ticketNumber : void 0;
40
+ return parseTicketNumber(match[1]);
16
41
  }
17
42
  function requireSafePositiveTicketNumber(ticketNumber, context = "ticketNumber") {
18
43
  if (!isSafePositiveTicketNumber(ticketNumber)) {
@@ -37,13 +62,24 @@ async function readJsonObject(path) {
37
62
  throw error;
38
63
  }
39
64
  }
40
- async function readBoardPageTicketNumber(runDirectory, page) {
41
- const record = await readJsonObject(join(runDirectory, page));
65
+ const LEGACY_BOARD_PAGE = {
66
+ admitted: "admitted-request.json",
67
+ invocation: "invocation.json"
68
+ };
69
+ function readBoardPageTicketNumberFromRows(runDirectory, section) {
70
+ const fromRows = readPageSync(runDirectory, section);
71
+ return fromRows === void 0 ? void 0 : ticketFromRecord(fromRows);
72
+ }
73
+ async function readBoardPageTicketNumberFromLegacy(runDirectory, section) {
74
+ const record = await readJsonObject(join(runDirectory, LEGACY_BOARD_PAGE[section]));
42
75
  if (record === void 0) return void 0;
43
76
  return ticketFromRecord(record);
44
77
  }
45
78
  async function readBoardTicketNumber(runDirectory) {
46
- return await readBoardPageTicketNumber(runDirectory, "admitted-request.json") ?? await readBoardPageTicketNumber(runDirectory, "invocation.json");
79
+ return readBoardPageTicketNumberFromRows(runDirectory, "admitted") ?? readBoardPageTicketNumberFromRows(runDirectory, "invocation");
80
+ }
81
+ async function readMigrationBoardTicketNumber(runDirectory) {
82
+ return await readBoardTicketNumber(runDirectory) ?? await readBoardPageTicketNumberFromLegacy(runDirectory, "admitted") ?? await readBoardPageTicketNumberFromLegacy(runDirectory, "invocation");
47
83
  }
48
84
  async function readMigrationDerivedTicketNumber(runDirectory) {
49
85
  const record = await readJsonObject(
@@ -58,10 +94,16 @@ async function readRunTicketNumber(runDirectory) {
58
94
  }
59
95
  export {
60
96
  MIGRATION_TICKET_DERIVATION_PAGE,
97
+ isPositiveTicketNumber,
61
98
  isSafePositiveTicketNumber,
99
+ isTicketNumberString,
100
+ parseTicketNumber,
62
101
  readBoardTicketNumber,
63
102
  readDeclaredTicketNumber,
103
+ readMigrationBoardTicketNumber,
64
104
  readMigrationDerivedTicketNumber,
65
105
  readRunTicketNumber,
66
- requireSafePositiveTicketNumber
106
+ requireSafePositiveTicketNumber,
107
+ sitianSubjectTicketNumber,
108
+ ticketNumberFromSitianSubject
67
109
  };
@@ -1,25 +1,16 @@
1
- import { join } from "node:path";
1
+ import { ROLE_RUN_SESSION_FILENAME, sessionFileOf } from "./role-run-placement.js";
2
+ import { readStrictPiSessionJsonl } from "./ledger-session-read.js";
2
3
  import { errorText } from "./unknown-value.js";
3
4
  export async function readAssistantUsageFromSessionFile(sessionFile) {
4
- const { readFile } = await import("node:fs/promises");
5
- let text;
5
+ let rows;
6
6
  try {
7
- text = await readFile(sessionFile, "utf8");
7
+ rows = await readStrictPiSessionJsonl(sessionFile);
8
8
  }
9
9
  catch (error) {
10
10
  if (error?.code === "ENOENT")
11
11
  return undefined;
12
- throw error;
13
- }
14
- let rows;
15
- try {
16
- rows = text
17
- .trim()
18
- .split("\n")
19
- .filter(Boolean)
20
- .map((line) => JSON.parse(line));
21
- }
22
- catch (error) {
12
+ if (!(error instanceof SyntaxError))
13
+ throw error;
23
14
  throw new Error(`session usage parse failed (${sessionFile}): ${errorText(error)}`, { cause: error });
24
15
  }
25
16
  let total = 0;
@@ -82,11 +73,11 @@ export async function readAssistantUsageFromSessionFile(sessionFile) {
82
73
  */
83
74
  export function sessionFileFromPublicSummon(summoned) {
84
75
  if (typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== "") {
85
- return join(summoned.runDirectory, "session", "session.jsonl");
76
+ return sessionFileOf(summoned.runDirectory);
86
77
  }
87
78
  const fromArtifacts = summoned.terminal?.artifacts
88
79
  ?.map((a) => a.path)
89
- .find((p) => typeof p === "string" && p.endsWith("session.jsonl"));
80
+ .find((p) => typeof p === "string" && p.endsWith(ROLE_RUN_SESSION_FILENAME));
90
81
  if (fromArtifacts !== undefined)
91
82
  return fromArtifacts;
92
83
  const outcome = summoned.terminal?.roleOutcome;
@@ -95,7 +86,7 @@ export function sessionFileFromPublicSummon(summoned) {
95
86
  const facts = outcome.decisiveFacts;
96
87
  const pointer = facts?.runPointer;
97
88
  if (typeof pointer === "string" && pointer.trim() !== "") {
98
- return join(pointer, "session", "session.jsonl");
89
+ return sessionFileOf(pointer);
99
90
  }
100
91
  return undefined;
101
92
  }
@@ -1,33 +1,63 @@
1
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
1
  import { dirname, join } from "node:path";
3
2
  import { DEFAULT_ROLE_TURN_HOST, lookupHeadlessHostDescription, lookupHostDescription, } from "./host-descriptions.js";
4
- /** Durable session binding stored beside the host-owned session principal. */
5
- export function createSessionIdentityAuthority(authority, sessionBindingFile) {
6
- const bindingPath = (principal) => join(authority.decode(principal).sessionDirectory, sessionBindingFile);
3
+ import { renderCurrentSync, RUN_STATE_FILE } from "./run-dossier.js";
4
+ import { readSitianRecords, reportRunRecord } from "./sitian-facade.js";
5
+ import { errorText, isRecord } from "./unknown-value.js";
6
+ /** Log record kind of one host-session binding (a host's native session id for this run). */
7
+ export const HOST_SESSION_ID_RECORD_KIND = "host-session-id";
8
+ /**
9
+ * Durable session binding: one state row per bind, scoped by host name — a run
10
+ * that changes hosts on resume must never hand one host's native session id to
11
+ * another. The latest record for the host wins. The public call projects
12
+ * `current.json` at the bind so host.sessions appears before the host CLI
13
+ * starts; a refused projection does not undo the binding fact or become a
14
+ * host/session failure (facts stay in state.jsonl; settlement still renders).
15
+ */
16
+ export function createSessionIdentityAuthority(authority, hostName) {
17
+ const runDirectoryOf = (principal) => dirname(authority.decode(principal).sessionDirectory);
7
18
  return {
8
19
  resolveSessionFile(principal) {
9
20
  return authority.decode(principal).sessionFile;
10
21
  },
11
22
  async load(principal) {
12
- try {
13
- const value = JSON.parse(await readFile(bindingPath(principal), "utf8"));
14
- if (typeof value !== "object" || value === null || typeof value.sessionId !== "string") {
23
+ const runDirectory = runDirectoryOf(principal);
24
+ const { records, diagnostics } = await readSitianRecords(join(runDirectory, RUN_STATE_FILE));
25
+ // Control path fails closed on damaged rows — same class as readPageSync.
26
+ // Rendering may keep reachable facts; dispatch must not reuse a prior binding
27
+ // after syntax damage (#1161 C3).
28
+ if (diagnostics.length > 0) {
29
+ throw new Error(`${RUN_STATE_FILE} has ${diagnostics.length} malformed row(s); refusing stale session binding: ${runDirectory}`);
30
+ }
31
+ let bound;
32
+ for (const record of records) {
33
+ if (record.kind !== HOST_SESSION_ID_RECORD_KIND)
34
+ continue;
35
+ // Non-object / cannot name a host: unattributable damage — refuse
36
+ // (settled C3-payload). Never treat as a skippable "other host" row.
37
+ if (!isRecord(record.payload) || typeof record.payload.host !== "string") {
15
38
  throw new Error("durable session binding is invalid");
16
39
  }
17
- return value.sessionId;
18
- }
19
- catch (error) {
20
- if (error.code === "ENOENT")
21
- return undefined;
22
- throw error;
40
+ // Explicit other host only: that host's bad id must not block this one.
41
+ if (record.payload.host !== hostName)
42
+ continue;
43
+ // This host's null / non-string sessionId: refuse — do not reuse a prior
44
+ // binding for the same host (#1161 C3).
45
+ if (typeof record.payload.sessionId !== "string") {
46
+ throw new Error("durable session binding is invalid");
47
+ }
48
+ bound = record.payload.sessionId;
23
49
  }
50
+ return bound;
24
51
  },
25
52
  async bind(principal, sessionId) {
26
- const target = bindingPath(principal);
27
- await mkdir(dirname(target), { recursive: true });
28
- const temporary = `${target}.${process.pid}.tmp`;
29
- await writeFile(temporary, `${JSON.stringify({ sessionId })}\n`, { encoding: "utf8", mode: 0o600 });
30
- await rename(temporary, target);
53
+ const runDirectory = runDirectoryOf(principal);
54
+ reportRunRecord(runDirectory, HOST_SESSION_ID_RECORD_KIND, { host: hostName, sessionId }, "session-identity");
55
+ try {
56
+ renderCurrentSync(runDirectory);
57
+ }
58
+ catch (error) {
59
+ process.stderr.write(`[session-identity] current.json render refused after host-session-id; binding fact kept: ${runDirectory}: ${errorText(error)}\n`);
60
+ }
31
61
  },
32
62
  };
33
63
  }
@@ -42,7 +72,7 @@ export async function readStoredHostSessionId(host, authority, principal) {
42
72
  const description = lookupHostDescription(host) ?? lookupHeadlessHostDescription(host);
43
73
  if (description === undefined)
44
74
  return undefined;
45
- const sessionId = await createSessionIdentityAuthority(authority, description.sessionBindingFile).load(principal);
75
+ const sessionId = await createSessionIdentityAuthority(authority, host).load(principal);
46
76
  if (sessionId === undefined || sessionId === "")
47
77
  return undefined;
48
78
  return sessionId;
@@ -1,6 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { appendFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { parseRunLeaf, sessionDirectoryOf } from "./role-run-placement.js";
5
+ import { RUN_HISTORY_FILE, RUN_HISTORY_KINDS, RUN_LOG_FILE, RUN_STATE_FILE, RUN_STATE_KINDS } from "./run-dossier-files.js";
6
+ import { ticketNumberFromSitianSubject } from "./run-ticket-number.js";
4
7
  import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
5
8
  import {
6
9
  activationBookDirectory,
@@ -27,27 +30,26 @@ function appendSitianRecordBlock(input, block) {
27
30
  );
28
31
  }
29
32
  }
30
- const S4_SUBMISSION_LEDGER_KINDS = /* @__PURE__ */ new Set([
31
- "candidate",
32
- "roundContext",
33
- "outcome",
34
- "sealed",
35
- "post-seal-anomaly"
36
- ]);
37
33
  function resolveSitianVolumeCategory(kind) {
38
- if (S4_SUBMISSION_LEDGER_KINDS.has(kind)) {
39
- return "submission-ledger";
40
- }
41
34
  return kind;
42
35
  }
43
36
  const SITIAN_RECORDS_LEAF = "records.jsonl";
37
+ function sitianVolumeDirectory(sessionDirectory, category) {
38
+ return join(sessionDirectory, category);
39
+ }
40
+ function sitianRunVolumeDirectory(runDirectory, category) {
41
+ return sitianVolumeDirectory(sessionDirectoryOf(runDirectory), category);
42
+ }
43
+ function sitianVolumeRecordsFile(volumeDirectory) {
44
+ return join(volumeDirectory, SITIAN_RECORDS_LEAF);
45
+ }
44
46
  function ticketProvenanceUnderBookPaths(ledgerHome, bookKey, ticketId) {
45
47
  const sessionDir = join(activationBookDirectory(ledgerHome, bookKey), ticketId);
46
- return { sessionDir, recordFile: join(sessionDir, SITIAN_RECORDS_LEAF) };
48
+ return { sessionDir, recordFile: sitianVolumeRecordsFile(sessionDir) };
47
49
  }
48
50
  function resolveSitianRecordPathInLedger(input, ledgerHome) {
49
51
  const category = resolveSitianVolumeCategory(input.kind);
50
- const ticketNumber = category === "ticket-provenance" ? typeof input.subject === "string" && /^[1-9][0-9]*$/.test(input.subject) ? input.subject : typeof input.subject === "object" && typeof input.subject.ticketNumber === "number" && Number.isSafeInteger(input.subject.ticketNumber) && input.subject.ticketNumber > 0 ? String(input.subject.ticketNumber) : void 0 : void 0;
52
+ const ticketNumber = category === "ticket-provenance" ? ticketNumberFromSitianSubject(input.subject) : void 0;
51
53
  let sessionDir;
52
54
  let recordFile;
53
55
  if (ticketNumber !== void 0) {
@@ -60,13 +62,20 @@ function resolveSitianRecordPathInLedger(input, ledgerHome) {
60
62
  recordFile = paths.recordFile;
61
63
  } else if (category === "ticket-provenance" && input.runDirectory !== void 0) {
62
64
  sessionDir = input.runDirectory;
63
- recordFile = join(sessionDir, SITIAN_RECORDS_LEAF);
65
+ recordFile = join(sessionDir, RUN_HISTORY_FILE);
64
66
  } else {
65
67
  if (input.sessionParent === void 0 || input.sessionParent.length === 0 || !physicallyContainedIn(ledgerHome, input.sessionParent)) {
66
68
  throw new Error("Sitian record ownership requires a parent session inside the ledger home");
67
69
  }
68
- sessionDir = join(dirname(input.sessionParent), category);
69
- recordFile = join(sessionDir, SITIAN_RECORDS_LEAF);
70
+ const sessionParentDir = dirname(input.sessionParent);
71
+ const runDirectory = dirname(sessionParentDir);
72
+ if (basename(sessionParentDir) === "session" && parseRunLeaf(basename(runDirectory)) !== void 0) {
73
+ sessionDir = runDirectory;
74
+ recordFile = join(runDirectory, RUN_HISTORY_KINDS.has(input.kind) ? RUN_HISTORY_FILE : RUN_STATE_KINDS.has(input.kind) ? RUN_STATE_FILE : RUN_LOG_FILE);
75
+ } else {
76
+ sessionDir = sitianVolumeDirectory(sessionParentDir, category);
77
+ recordFile = sitianVolumeRecordsFile(sessionDir);
78
+ }
70
79
  }
71
80
  if (!physicallyContainedIn(ledgerHome, sessionDir)) {
72
81
  throw new Error("Sitian record ownership requires a directory inside the ledger home");
@@ -115,10 +124,12 @@ function appendSitianRecord(input) {
115
124
  }
116
125
  }
117
126
  export {
118
- S4_SUBMISSION_LEDGER_KINDS,
119
127
  appendSitianRecord,
120
128
  appendSitianRecordBlock,
121
129
  resolveSitianRecordPath,
122
130
  resolveSitianRecordPathInLedger,
123
- resolveSitianVolumeCategory
131
+ resolveSitianVolumeCategory,
132
+ sitianRunVolumeDirectory,
133
+ sitianVolumeDirectory,
134
+ sitianVolumeRecordsFile
124
135
  };
@@ -1,3 +1,4 @@
1
+ import { sessionFileOf } from "./role-run-placement.js";
1
2
  import { appendSitianRecord } from "./sitian-appender.js";
2
3
  export * from "./sitian-contracts.js";
3
4
  export * from "./sitian-appender.js";
@@ -5,6 +6,16 @@ export * from "./sitian-reader.js";
5
6
  function sitianReport(input) {
6
7
  return appendSitianRecord(input);
7
8
  }
9
+ function reportRunRecord(runDirectory, kind, payload, source) {
10
+ return appendSitianRecord({
11
+ level: "event",
12
+ kind,
13
+ sessionParent: sessionFileOf(runDirectory),
14
+ source,
15
+ payload
16
+ });
17
+ }
8
18
  export {
19
+ reportRunRecord,
9
20
  sitianReport
10
21
  };
@@ -1,41 +1,56 @@
1
- import { existsSync } from "node:fs";
2
1
  import { readFile } from "node:fs/promises";
3
- import { isRecord, errorText } from "./unknown-value.js";
4
- async function readSitianRecords(recordFile) {
5
- if (!existsSync(recordFile)) {
6
- return { records: [], diagnostics: [] };
2
+ import { isRecord, errorText, isEnoent } from "./unknown-value.js";
3
+ function decodeSitianRecordLine(line, lineNumber) {
4
+ if (!line.trim()) return void 0;
5
+ try {
6
+ const parsed = JSON.parse(line);
7
+ if (isRecord(parsed)) {
8
+ return { ok: true, record: parsed };
9
+ }
10
+ const typeDesc = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
11
+ return {
12
+ ok: false,
13
+ diagnostic: {
14
+ kind: "malformed",
15
+ line: lineNumber,
16
+ raw: line,
17
+ error: `expected JSON object, got ${typeDesc}`
18
+ }
19
+ };
20
+ } catch (error) {
21
+ return {
22
+ ok: false,
23
+ diagnostic: {
24
+ kind: "malformed",
25
+ line: lineNumber,
26
+ raw: line,
27
+ error: errorText(error)
28
+ }
29
+ };
7
30
  }
8
- const text = await readFile(recordFile, "utf8");
31
+ }
32
+ function parseSitianRecordText(text) {
9
33
  const lines = text.split("\n");
10
34
  const records = [];
11
35
  const diagnostics = [];
12
36
  for (let index = 0; index < lines.length; index += 1) {
13
- const line = lines[index];
14
- if (!line.trim()) continue;
15
- try {
16
- const parsed = JSON.parse(line);
17
- if (isRecord(parsed)) {
18
- records.push(parsed);
19
- } else {
20
- const typeDesc = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
21
- diagnostics.push({
22
- kind: "malformed",
23
- line: index + 1,
24
- raw: line,
25
- error: `expected JSON object, got ${typeDesc}`
26
- });
27
- }
28
- } catch (error) {
29
- diagnostics.push({
30
- kind: "malformed",
31
- line: index + 1,
32
- raw: line,
33
- error: errorText(error)
34
- });
35
- }
37
+ const decoded = decodeSitianRecordLine(lines[index], index + 1);
38
+ if (decoded === void 0) continue;
39
+ if (decoded.ok) records.push(decoded.record);
40
+ else diagnostics.push(decoded.diagnostic);
36
41
  }
37
42
  return { records, diagnostics };
38
43
  }
44
+ async function readSitianRecords(recordFile) {
45
+ try {
46
+ return parseSitianRecordText(await readFile(recordFile, "utf8"));
47
+ } catch (error) {
48
+ if (isEnoent(error)) return { records: [], diagnostics: [] };
49
+ throw error;
50
+ }
51
+ }
39
52
  export {
53
+ decodeSitianRecordLine,
54
+ parseSitianRecordText,
40
55
  readSitianRecords
41
56
  };
@@ -52,7 +52,7 @@ export function createDefaultGateOfficerSummon(options) {
52
52
  };
53
53
  }
54
54
  /**
55
- * Book a typed pointer under parent session/auditor-roles (archivist-owned write).
55
+ * Book a typed pointer in the parent run's current.json officers section.
56
56
  * Offline mocks without a real session leave no nested volume (lawful zero).
57
57
  */
58
58
  function bookDirectOfficerPointer(context, officer, result, summoned, toolCallId) {
@@ -1,4 +1,4 @@
1
- import { join } from "node:path";
1
+ import { sessionFileIn, sessionFileOf } from "./role-run-placement.js";
2
2
  import { resolveActivationLedgerHome, tryHomeFromAkRolesPath, } from "./activation-ledger-topology.js";
3
3
  import { courtAttemptIdFromHostContext, runDirectoryFromHostContext, } from "./host-contracts.js";
4
4
  import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
@@ -49,7 +49,7 @@ async function submissionRecordFile(cwd, runId, scope) {
49
49
  const discoveredRun = await findRunDirectoryById(scope.home, runId);
50
50
  if (discoveredRun === undefined)
51
51
  return undefined;
52
- sessionParent = join(discoveredRun, "session", "session.jsonl");
52
+ sessionParent = sessionFileOf(discoveredRun);
53
53
  }
54
54
  return resolveSitianRecordPathInLedger({
55
55
  level: "event",
@@ -265,13 +265,13 @@ async function restoreState(cwd, runId, scope) {
265
265
  function sessionParentFromHostContext(context) {
266
266
  const runDirectory = runDirectoryFromHostContext(context);
267
267
  if (runDirectory !== undefined)
268
- return join(runDirectory, "session", "session.jsonl");
268
+ return sessionFileOf(runDirectory);
269
269
  const sessionFile = context.sessionManager.getSessionFile?.();
270
270
  if (typeof sessionFile === "string" && sessionFile.length > 0)
271
271
  return sessionFile;
272
272
  const sessionDir = context.sessionManager.getSessionDir?.();
273
273
  if (typeof sessionDir === "string" && sessionDir.length > 0) {
274
- return join(sessionDir, "session.jsonl");
274
+ return sessionFileIn(sessionDir);
275
275
  }
276
276
  return undefined;
277
277
  }
@@ -1,3 +1,4 @@
1
+ import { parseTicketNumber } from "./run-ticket-number.js";
1
2
  import { isRecord } from "./unknown-value.js";
2
3
  /**
3
4
  * 起居录(ticket-provenance)typed 形状 —— ADR 0075 / ADR 0081 / #1090。
@@ -9,14 +10,7 @@ import { isRecord } from "./unknown-value.js";
9
10
  /** Sitian kind for per-ticket court diary volumes. */
10
11
  export const TICKET_PROVENANCE_KIND = "ticket-provenance";
11
12
  function positiveInteger(value) {
12
- if (typeof value === "number" && Number.isSafeInteger(value) && value >= 1)
13
- return value;
14
- if (typeof value === "string" && /^[1-9][0-9]*$/.test(value)) {
15
- const parsed = Number(value);
16
- if (Number.isSafeInteger(parsed))
17
- return parsed;
18
- }
19
- return undefined;
13
+ return parseTicketNumber(value);
20
14
  }
21
15
  function projectBound(value) {
22
16
  if (!isRecord(value))
@@ -3,13 +3,16 @@
3
3
  * 一册=一个追加式 records.jsonl。每轮新投影经司天台 appender 追加为不可变提交;
4
4
  * 纯追加、不回读历史去重、不折叠。
5
5
  */
6
- import { readFile, unlink } from "node:fs/promises";
7
- import { basename, dirname, join, resolve } from "node:path";
6
+ import { readFile } from "node:fs/promises";
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
+ import { runDirectoryOfSessionFile, sessionFileOf } from "./role-run-placement.js";
12
+ import { RUN_HISTORY_FILE } from "./run-dossier-files.js";
11
13
  import { adaptSessionDialogue, nativeEventId } from "./session-dialogue.js";
12
14
  import { appendSitianRecordBlock, appendSitianRecord, resolveSitianRecordPath, } from "./sitian-facade.js";
15
+ import { decodeSitianRecordLine } from "./sitian-reader.js";
13
16
  import { TICKET_PROVENANCE_KIND, } from "./ticket-provenance-contracts.js";
14
17
  import { errorText } from "./unknown-value.js";
15
18
  /**
@@ -48,8 +51,7 @@ function isLedgerRoleSessionFile(absolute, home) {
48
51
  const ledgerHome = resolveActivationLedgerHome(machineHome);
49
52
  if (!physicallyContainedIn(ledgerHome, absolute))
50
53
  return false;
51
- return (basename(absolute) === "session.jsonl" &&
52
- basename(dirname(absolute)) === "session");
54
+ return sessionFileOf(runDirectoryOfSessionFile(absolute)) === absolute;
53
55
  }
54
56
  /** Real I/O seam gate: only host session stores or sitian role-run session.jsonl. */
55
57
  function assertDialogueSessionSourcePath(path, home) {
@@ -87,18 +89,32 @@ export function resolveTicketProvenanceVolume(ticketNumber, cwd, home) {
87
89
  }
88
90
  /** Assign a run-owned unbound diary as its original block through Sitian. */
89
91
  export async function rehomeUnboundTicketProvenance(runDirectory, ticketNumber, cwd, home) {
90
- const source = join(runDirectory, "records.jsonl");
91
- let content;
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;
92
97
  try {
93
- content = await readFile(source, "utf8");
98
+ history = await readFile(join(runDirectory, RUN_HISTORY_FILE), "utf8");
94
99
  }
95
100
  catch (error) {
96
101
  if (errnoCode(error) === "ENOENT")
97
102
  return;
98
103
  throw error;
99
104
  }
100
- appendSitianRecordBlock(ticketProvenanceRecordInput(ticketNumber, cwd, home), content);
101
- await unlink(source);
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);
102
118
  }
103
119
  /**
104
120
  * Resolve a bound endpoint against this round's session lines.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5483",
3
+ "version": "0.1.5571",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -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, description.sessionBindingFile),
64
+ sessionIdentity: createSessionIdentityAuthority(principalAuthority, hostName),
65
65
  modelPassing: description.modelPassing,
66
66
  ...(description.setModelId === undefined ? {} : { setModelId: description.setModelId }),
67
67
  roleRuntimeDependencies: createRoleRuntimeDependencies(packageRoot),
@@ -1,14 +1,15 @@
1
- import { join } from "node:path";
2
1
  import { spawn } from "node:child_process";
3
2
  import { createInterface } from "node:readline";
4
3
 
5
4
  import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
5
+ import { sessionDirectoryOf } from "../role-run-placement.ts";
6
6
  import {
7
7
  createSerializedRoleTurnHost,
8
8
  driveExternalRoleTurnRounds,
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;
@@ -496,10 +505,8 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
496
505
  host: config.hostName,
497
506
  sessionId,
498
507
  cwd: request.cwd,
499
- sessionDirectory: join(request.runDirectory, "session"),
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) {