@akagilnc/pi-workflow-roles 0.1.5368 → 0.1.5382

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 (188) hide show
  1. package/dist/acp-host/production-host.js +1192 -1195
  2. package/dist/activation-ledger-git.js +2 -2
  3. package/dist/activation-ledger-topology.js +1 -7
  4. package/dist/analyst-entry.js +4 -8
  5. package/dist/analyst-gate-cycles-read.js +3 -11
  6. package/dist/analyst-index.js +3 -9
  7. package/dist/analyst-ledger.js +1 -11
  8. package/dist/analyst-metric-families/acceptance-success-rework.js +1 -3
  9. package/dist/analyst-metric-families/round-timeline.js +1 -3
  10. package/dist/analyst-page.js +2 -2
  11. package/dist/archivist-record-topology.js +2 -2
  12. package/dist/audit-escalation.js +2 -1
  13. package/dist/auditor-soul.js +2 -1
  14. package/dist/collector-config.js +6 -8
  15. package/dist/collector-tool-schemas.js +18 -0
  16. package/dist/compliance-transport.js +2 -1
  17. package/dist/countersign-contracts.js +2 -1
  18. package/dist/diarist-contracts.js +29 -2
  19. package/dist/doctor-contracts.js +1 -3
  20. package/dist/doctor-evidence.js +7 -8
  21. package/dist/dossier-resolution.js +1 -3
  22. package/dist/engine-detour-usage.js +1 -3
  23. package/dist/filed-submission.js +28 -0
  24. package/dist/gatekeeper-role.js +8 -19
  25. package/dist/gleaner-left-contracts.js +18 -1
  26. package/dist/headless-host/description.js +14 -17
  27. package/dist/headless-host/production-host.js +1210 -1215
  28. package/dist/host-session-record.js +3 -2
  29. package/dist/inspector-contracts.js +2 -1
  30. package/dist/judge-role.js +8 -16
  31. package/dist/ledger-session-read.js +1 -3
  32. package/dist/merger-contracts.js +9 -9
  33. package/dist/migrate-book-topology.js +610 -321
  34. package/dist/navigator-attendance.js +9 -11
  35. package/dist/navigator-invocation-identity.js +3 -2
  36. package/dist/navigator-public-session.js +3 -5
  37. package/dist/navigator-session-contracts.js +8 -10
  38. package/dist/navigator-work-base.js +5 -8
  39. package/dist/notary-contracts.js +3 -5
  40. package/dist/package-contracts/auditor-output.js +1 -3
  41. package/dist/package-contracts/fixer-packet.js +3 -6
  42. package/dist/package-contracts/gatekeeper-output.js +1 -3
  43. package/dist/package-contracts/navigator-output.js +8 -8
  44. package/dist/package-contracts/reviewer-output.js +16 -0
  45. package/dist/package-contracts/terminating-infrastructure.js +5 -9
  46. package/dist/package-contracts/terminating-tools.js +4 -3
  47. package/dist/package-contracts/worker-output.js +18 -2
  48. package/dist/public-cli/analyst-run.js +3 -2
  49. package/dist/public-cli/auto-resume.js +3 -8
  50. package/dist/public-cli/cli.js +9 -8
  51. package/dist/public-cli/config.js +8 -11
  52. package/dist/public-cli/countersign-run.js +1 -3
  53. package/dist/public-cli/host-providers.js +7 -14
  54. package/dist/public-cli/instruction-seat-run.js +12 -14
  55. package/dist/public-cli/invocation.js +9 -8
  56. package/dist/public-cli/main.js +1820 -1599
  57. package/dist/public-cli/post-admission.js +4 -3
  58. package/dist/public-cli/run-lifecycle.js +14 -26
  59. package/dist/public-cli/settlement.js +10 -26
  60. package/dist/public-role-summons.js +3 -2
  61. package/dist/receipt-delivery-policy.js +1 -3
  62. package/dist/role-run-placement.js +3 -5
  63. package/dist/role-run-relocation.js +12 -19
  64. package/dist/role-submission-declarations.js +131 -0
  65. package/dist/run-terminal-artifacts.js +1 -11
  66. package/dist/run-ticket-number.js +2 -4
  67. package/dist/secretariat-contracts.js +23 -1
  68. package/dist/session-assistant-usage.js +2 -1
  69. package/dist/session-dialogue.js +1 -14
  70. package/dist/sitian-reader.js +2 -4
  71. package/dist/submission-correctable-error.js +2 -1
  72. package/dist/submission-errors.js +2 -1
  73. package/dist/submission-gate.js +6 -8
  74. package/dist/submission-ledger.js +4 -5
  75. package/dist/ticket-provenance-contracts.js +1 -3
  76. package/dist/ticket-provenance.js +2 -1
  77. package/dist/typed-provider-http.js +4 -7
  78. package/dist/unknown-value.js +28 -0
  79. package/dist/user-dialogue-stdin.js +2 -1
  80. package/package.json +1 -1
  81. package/src/acp-host/role-turn-host.ts +4 -2
  82. package/src/activation-ledger-git.ts +3 -2
  83. package/src/activation-ledger-topology.ts +4 -12
  84. package/src/analyst-entry.ts +3 -11
  85. package/src/analyst-gate-cycles-read.ts +3 -15
  86. package/src/analyst-index.ts +3 -13
  87. package/src/analyst-ledger.ts +2 -16
  88. package/src/analyst-metric-families/acceptance-success-rework.ts +2 -4
  89. package/src/analyst-metric-families/round-timeline.ts +2 -4
  90. package/src/analyst-page.ts +2 -2
  91. package/src/archivist-record-topology.ts +2 -2
  92. package/src/audit-escalation.ts +2 -1
  93. package/src/auditor-role.ts +2 -8
  94. package/src/auditor-soul.ts +3 -1
  95. package/src/book-topology-migration-placement.ts +5 -16
  96. package/src/book-topology-migrators-t11.ts +16 -15
  97. package/src/book-topology-mixed-volume-migrators.ts +3 -6
  98. package/src/book-topology-record-class-migrators.ts +7 -10
  99. package/src/book-topology-runs-migrator.ts +4 -3
  100. package/src/collector-config.ts +7 -9
  101. package/src/collector-role.ts +5 -28
  102. package/src/compliance-transport.ts +2 -1
  103. package/src/countersign-contracts.ts +2 -1
  104. package/src/countersign-role.ts +2 -7
  105. package/src/diarist-contracts.ts +40 -2
  106. package/src/diarist-role.ts +4 -44
  107. package/src/doctor-contracts.ts +3 -1
  108. package/src/doctor-evidence.ts +8 -7
  109. package/src/doctor-role.ts +20 -13
  110. package/src/dossier-resolution.ts +2 -4
  111. package/src/engine-detour-tool.ts +0 -2
  112. package/src/engine-detour-usage.ts +2 -4
  113. package/src/external-host-turn-loop.ts +3 -1
  114. package/src/factory-board.ts +1 -8
  115. package/src/filed-submission.ts +44 -0
  116. package/src/gatekeeper-role.ts +8 -24
  117. package/src/gleaner-left-contracts.ts +29 -2
  118. package/src/gleaner-left-role.ts +4 -32
  119. package/src/headless-host/description.ts +15 -18
  120. package/src/headless-host/role-turn-host.ts +10 -9
  121. package/src/host-session-record.ts +4 -2
  122. package/src/inspector-contracts.ts +2 -1
  123. package/src/inspector-role.ts +2 -7
  124. package/src/judge-role.ts +12 -24
  125. package/src/ledger-session-read.ts +2 -5
  126. package/src/merger-contracts.ts +10 -9
  127. package/src/merger-role.ts +5 -8
  128. package/src/navigator-attendance.ts +10 -12
  129. package/src/navigator-invocation-identity.ts +3 -2
  130. package/src/navigator-public-session.ts +4 -6
  131. package/src/navigator-role.ts +2 -8
  132. package/src/navigator-session-contracts.ts +8 -11
  133. package/src/navigator-work-base.ts +5 -8
  134. package/src/navigator-work-context.ts +2 -1
  135. package/src/notary-contracts.ts +4 -6
  136. package/src/notary-role.ts +5 -20
  137. package/src/oauth-keepalive.ts +5 -18
  138. package/src/package-contracts/auditor-output.ts +2 -4
  139. package/src/package-contracts/fixer-packet.ts +4 -6
  140. package/src/package-contracts/gatekeeper-output.ts +2 -4
  141. package/src/package-contracts/judge-output.ts +0 -1
  142. package/src/package-contracts/navigator-output.ts +8 -8
  143. package/src/package-contracts/reviewer-output.ts +25 -0
  144. package/src/package-contracts/terminating-infrastructure.ts +6 -20
  145. package/src/package-contracts/terminating-tools.ts +8 -12
  146. package/src/package-contracts/worker-output.ts +43 -5
  147. package/src/pi/adapter.ts +0 -1
  148. package/src/pi/role-turn-host.ts +0 -1
  149. package/src/public-cli/analyst-run.ts +4 -2
  150. package/src/public-cli/auto-resume.ts +4 -11
  151. package/src/public-cli/cli.ts +10 -10
  152. package/src/public-cli/config.ts +8 -27
  153. package/src/public-cli/countersign-run.ts +2 -4
  154. package/src/public-cli/host-providers.ts +10 -27
  155. package/src/public-cli/instruction-seat-run.ts +14 -15
  156. package/src/public-cli/invocation.ts +11 -10
  157. package/src/public-cli/post-admission.ts +5 -3
  158. package/src/public-cli/run-lifecycle.ts +20 -41
  159. package/src/public-cli/settlement.ts +11 -33
  160. package/src/public-role-summons.ts +4 -3
  161. package/src/receipt-delivery-policy.ts +1 -4
  162. package/src/reviewer-role.ts +7 -36
  163. package/src/role-envelope.ts +5 -3
  164. package/src/role-run-placement.ts +4 -10
  165. package/src/role-run-relocation.ts +13 -23
  166. package/src/role-runtime.ts +16 -50
  167. package/src/role-submission-declarations.ts +143 -0
  168. package/src/run-terminal-artifacts.ts +2 -16
  169. package/src/run-ticket-number.ts +4 -10
  170. package/src/secretariat-contracts.ts +35 -0
  171. package/src/secretariat-role.ts +4 -40
  172. package/src/session-assistant-usage.ts +3 -1
  173. package/src/session-dialogue.ts +1 -4
  174. package/src/session-identity.ts +0 -1
  175. package/src/sitian-reader.ts +2 -4
  176. package/src/submission-correctable-error.ts +3 -1
  177. package/src/submission-errors.ts +2 -1
  178. package/src/submission-gate.ts +10 -9
  179. package/src/submission-ledger.ts +5 -6
  180. package/src/ticket-provenance-contracts.ts +1 -4
  181. package/src/ticket-provenance.ts +3 -1
  182. package/src/ticket-snapshot.ts +2 -4
  183. package/src/ticket-trajectory.ts +1 -8
  184. package/src/typed-provider-http.ts +5 -15
  185. package/src/unknown-value.ts +34 -0
  186. package/src/user-dialogue-stdin.ts +3 -1
  187. package/src/worker-role.ts +63 -83
  188. package/src/worker-submission-gates.ts +8 -7
@@ -3,8 +3,8 @@
3
3
  */
4
4
  import {
5
5
  AUDITOR_OUTPUT_TOOL_NAME,
6
- auditorOutputSchema,
7
6
  } from "./package-contracts/auditor-output.ts";
7
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
8
8
 
9
9
  export { AUDITOR_OUTPUT_TOOL_NAME };
10
10
 
@@ -12,10 +12,4 @@ export type AuditorRuntimeDependencies = {
12
12
  loadSoul(): Promise<string>;
13
13
  };
14
14
 
15
- export const AUDITOR_TOOL_SPEC = {
16
- name: AUDITOR_OUTPUT_TOOL_NAME,
17
- label: "审刑院输出",
18
- description: "审刑院终局回执,status 为 converged、continue 或 escalate。",
19
- promptSnippet: "审刑院终局回执",
20
- parameters: auditorOutputSchema,
21
- } as const;
15
+ export const AUDITOR_TOOL_SPEC = roleSubmissionDeclaration("auditor");
@@ -1,6 +1,8 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { basename, join } from "node:path";
3
3
 
4
+ import { isRecord } from "./unknown-value.ts";
5
+
4
6
  import {
5
7
  joinPackageMaterials,
6
8
  readPackageMaterial,
@@ -126,7 +128,7 @@ export async function readAuditorResumeBinding(runDirectory: string): Promise<
126
128
  if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
127
129
  throw error;
128
130
  }
129
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return undefined;
131
+ if (!isRecord(raw)) return undefined;
130
132
  const record = raw as { sourceRunPath?: unknown; auditorSubject?: unknown };
131
133
  if (typeof record.sourceRunPath !== "string" || record.sourceRunPath.trim() === "") return undefined;
132
134
  const subject = isAuditorSoulRole(record.auditorSubject)
@@ -19,22 +19,11 @@ import {
19
19
  readMigrationDerivedTicketNumber,
20
20
  } from "./run-ticket-number.ts";
21
21
 
22
+ import { isRecord, isEnoent } from "./unknown-value.ts";
23
+
22
24
  const TICKET_NUMBER_RE = /^[1-9][0-9]*$/;
23
25
  const RUN_DIR_NAME_RE = /^([^@]+)@([^@]+)$/;
24
26
 
25
- function isRecord(value: unknown): value is Record<string, unknown> {
26
- return typeof value === "object" && value !== null && !Array.isArray(value);
27
- }
28
-
29
- /** Sole migration ENOENT projection — non-ENOENT must propagate. */
30
- export function isMigrationEnoent(error: unknown): boolean {
31
- return (
32
- error instanceof Error &&
33
- "code" in error &&
34
- (error as NodeJS.ErrnoException).code === "ENOENT"
35
- );
36
- }
37
-
38
27
  /** Book-key directories under a books root; missing root → []. */
39
28
  export async function listMigrationBookKeys(
40
29
  booksDirectory: string,
@@ -46,7 +35,7 @@ export async function listMigrationBookKeys(
46
35
  .map((entry) => entry.name)
47
36
  .sort((a, b) => a.localeCompare(b));
48
37
  } catch (error) {
49
- if (isMigrationEnoent(error)) return [];
38
+ if (isEnoent(error)) return [];
50
39
  throw error;
51
40
  }
52
41
  }
@@ -59,7 +48,7 @@ export async function listMigrationDirents(
59
48
  const entries = await readdir(directory, { withFileTypes: true });
60
49
  return [...entries].sort((a, b) => a.name.localeCompare(b.name));
61
50
  } catch (error) {
62
- if (isMigrationEnoent(error)) return [];
51
+ if (isEnoent(error)) return [];
63
52
  throw error;
64
53
  }
65
54
  }
@@ -109,7 +98,7 @@ async function readProjectRootFromRun(
109
98
  return { projectRoot, sourcePage: page };
110
99
  }
111
100
  } catch (error) {
112
- if (isMigrationEnoent(error)) continue;
101
+ if (isEnoent(error)) continue;
113
102
  throw error;
114
103
  }
115
104
  }
@@ -25,7 +25,6 @@ import {
25
25
  import {
26
26
  findPlacedMigratingRun,
27
27
  isFlatRunsRelative,
28
- isMigrationEnoent,
29
28
  isTicketNumberString,
30
29
  listBackupRunLeaves,
31
30
  listMigrationBookKeys,
@@ -37,6 +36,8 @@ import {
37
36
  type RunDirectoryPathRewrite,
38
37
  } from "./role-run-relocation.ts";
39
38
 
39
+ import { isRecord, errorText, isEnoent } from "./unknown-value.ts";
40
+
40
41
  const AUDITOR_ROLES_PARTITION = "auditor-roles";
41
42
  const ISSUES_PARTITION = "issues";
42
43
  const DEPRECATED_KINDS_PARTITION = "deprecated-kinds";
@@ -101,7 +102,7 @@ async function directoryExists(path: string): Promise<boolean> {
101
102
  const info = await stat(path);
102
103
  return info.isDirectory();
103
104
  } catch (error) {
104
- if (isMigrationEnoent(error)) return false;
105
+ if (isEnoent(error)) return false;
105
106
  throw error;
106
107
  }
107
108
  }
@@ -219,7 +220,7 @@ async function readJsonlParentSession(path: string): Promise<string | undefined>
219
220
  try {
220
221
  raw = await readFile(path, "utf8");
221
222
  } catch (error) {
222
- if (isMigrationEnoent(error)) return undefined;
223
+ if (isEnoent(error)) return undefined;
223
224
  throw error;
224
225
  }
225
226
  const newline = raw.indexOf("\n");
@@ -227,7 +228,7 @@ async function readJsonlParentSession(path: string): Promise<string | undefined>
227
228
  if (line.length === 0) return undefined;
228
229
  try {
229
230
  const header: unknown = JSON.parse(line);
230
- if (header === null || typeof header !== "object" || Array.isArray(header)) {
231
+ if (!isRecord(header)) {
231
232
  return undefined;
232
233
  }
233
234
  const parentSession = (header as { parentSession?: unknown }).parentSession;
@@ -268,13 +269,13 @@ async function findTrueVolumeParent(
268
269
  try {
269
270
  ledgerRaw = await readFile(join(volumeDirectory, CURRENT_SESSION_LEDGER), "utf8");
270
271
  } catch (error) {
271
- if (isMigrationEnoent(error)) return undefined;
272
+ if (isEnoent(error)) return undefined;
272
273
  throw error;
273
274
  }
274
275
  let sessionFileField: string;
275
276
  try {
276
277
  const ledger: unknown = JSON.parse(ledgerRaw);
277
- if (ledger === null || typeof ledger !== "object" || Array.isArray(ledger)) {
278
+ if (!isRecord(ledger)) {
278
279
  return undefined;
279
280
  }
280
281
  const sessionFile = (ledger as { sessionFile?: unknown }).sessionFile;
@@ -298,7 +299,7 @@ async function findTrueVolumeParent(
298
299
  const info = await lstat(liveSession);
299
300
  if (!info.isFile()) return undefined;
300
301
  } catch (error) {
301
- if (isMigrationEnoent(error)) return undefined;
302
+ if (isEnoent(error)) return undefined;
302
303
  throw error;
303
304
  }
304
305
 
@@ -413,7 +414,7 @@ async function readSessionFileField(path: string): Promise<string | undefined> {
413
414
  try {
414
415
  raw = await readFile(path, "utf8");
415
416
  } catch (error) {
416
- if (isMigrationEnoent(error)) return undefined;
417
+ if (isEnoent(error)) return undefined;
417
418
  throw error;
418
419
  }
419
420
  let parsed: unknown;
@@ -422,12 +423,12 @@ async function readSessionFileField(path: string): Promise<string | undefined> {
422
423
  } catch (error) {
423
424
  throw new Error(
424
425
  `book topology migration cannot read current-session pointer at ${path}: invalid JSON (${
425
- error instanceof Error ? error.message : String(error)
426
+ errorText(error)
426
427
  })`,
427
428
  { cause: error },
428
429
  );
429
430
  }
430
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
431
+ if (!isRecord(parsed)) {
431
432
  throw new Error(
432
433
  `book topology migration cannot read current-session pointer at ${path}: expected a JSON object`,
433
434
  );
@@ -480,7 +481,7 @@ async function mergeRewrittenCurrentSession(
480
481
  srcStat = await stat(srcPath);
481
482
  srcRaw = await readFile(srcPath, "utf8");
482
483
  } catch (error) {
483
- if (isMigrationEnoent(error)) return;
484
+ if (isEnoent(error)) return;
484
485
  throw error;
485
486
  }
486
487
 
@@ -488,7 +489,7 @@ async function mergeRewrittenCurrentSession(
488
489
  let nextSessionFile: string | undefined;
489
490
  try {
490
491
  const parsed: unknown = JSON.parse(srcRaw);
491
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
492
+ if (isRecord(parsed)) {
492
493
  const record = parsed as Record<string, unknown>;
493
494
  if (typeof record.sessionFile === "string") {
494
495
  const rewritten = rewritePathIntoDestination(
@@ -510,7 +511,7 @@ async function mergeRewrittenCurrentSession(
510
511
  try {
511
512
  destStat = await stat(destPath);
512
513
  } catch (error) {
513
- if (!isMigrationEnoent(error)) throw error;
514
+ if (!isEnoent(error)) throw error;
514
515
  }
515
516
  await writeWinningCurrentSession(
516
517
  destPath,
@@ -656,7 +657,7 @@ async function copyIssuesNonRunTree(source: string, destination: string): Promis
656
657
  `book topology migration refuses to overwrite ${to} with ${from}`,
657
658
  );
658
659
  } catch (error) {
659
- if (!isMigrationEnoent(error)) throw error;
660
+ if (!isEnoent(error)) throw error;
660
661
  }
661
662
  await cp(from, to, { preserveTimestamps: true });
662
663
  }
@@ -719,7 +720,7 @@ async function unlinkIfPresent(path: string): Promise<void> {
719
720
  try {
720
721
  await unlink(path);
721
722
  } catch (error) {
722
- if (!isMigrationEnoent(error)) throw error;
723
+ if (!isEnoent(error)) throw error;
723
724
  }
724
725
  }
725
726
 
@@ -17,12 +17,13 @@ import {
17
17
  import {
18
18
  bookHistoricalRoots,
19
19
  findPlacedMigratingRun,
20
- isMigrationEnoent,
21
20
  listMigrationBookKeys,
22
21
  listMigrationDirents,
23
22
  runRefFromBoundPath,
24
23
  } from "./book-topology-migration-placement.ts";
25
24
 
25
+ import { isRecord, isEnoent } from "./unknown-value.ts";
26
+
26
27
  const SITIAN_MIXED_VOLUME_PARTITIONS = [
27
28
  "attendance",
28
29
  "auditor",
@@ -32,10 +33,6 @@ const SITIAN_MIXED_VOLUME_PARTITIONS = [
32
33
 
33
34
  const CURRENT_SESSION_LEDGER = "current-session.json";
34
35
 
35
- function isRecord(value: unknown): value is Record<string, unknown> {
36
- return typeof value === "object" && value !== null && !Array.isArray(value);
37
- }
38
-
39
36
  function posixRelative(from: string, to: string): string {
40
37
  return relative(from, to).split(sep).join("/");
41
38
  }
@@ -98,7 +95,7 @@ async function readRegularBackupFile(path: string): Promise<string | undefined>
98
95
  try {
99
96
  info = await lstat(path);
100
97
  } catch (error) {
101
- if (isMigrationEnoent(error)) return undefined;
98
+ if (isEnoent(error)) return undefined;
102
99
  throw error;
103
100
  }
104
101
  if (!info.isFile()) {
@@ -9,7 +9,6 @@
9
9
  * - malformed → unbound with exact bytes + malformedRows entry
10
10
  * - misplaced: any foreign jsonl whose kind is one of the three classes
11
11
  */
12
- import { createHash } from "node:crypto";
13
12
  import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
14
13
  import { dirname, join, relative, sep } from "node:path";
15
14
 
@@ -18,7 +17,6 @@ import {
18
17
  findBookRunDirectory,
19
18
  findPlacedMigratingRun,
20
19
  findUniquePrincipalPlacedRun,
21
- isMigrationEnoent,
22
20
  isTicketNumberString,
23
21
  listMigrationBookKeys,
24
22
  listMigrationDirents,
@@ -36,6 +34,9 @@ import {
36
34
  import { S4_SUBMISSION_LEDGER_KINDS } from "./sitian-appender.ts";
37
35
  import { projectTicketProvenanceHeader } from "./ticket-provenance-contracts.ts";
38
36
 
37
+ import { sha256Hex } from "./sha256.ts";
38
+ import { isRecord, isEnoent } from "./unknown-value.ts";
39
+
39
40
  const TICKET_PROVENANCE = "ticket-provenance";
40
41
  const SUBMISSION_LEDGER = "submission-ledger";
41
42
  const ATTEMPT_HISTORY = "attempt-history";
@@ -47,10 +48,6 @@ type JsonLine =
47
48
  | { readonly ok: true; readonly value: Record<string, unknown>; readonly raw: string }
48
49
  | { readonly ok: false; readonly raw: string };
49
50
 
50
- function isRecord(value: unknown): value is Record<string, unknown> {
51
- return typeof value === "object" && value !== null && !Array.isArray(value);
52
- }
53
-
54
51
  function parseJsonlLine(raw: string): JsonLine {
55
52
  try {
56
53
  const value: unknown = JSON.parse(raw);
@@ -70,7 +67,7 @@ function lineSource(backupBooksDirectory: string, filePath: string, index: numbe
70
67
  }
71
68
 
72
69
  function stableKey(material: string): string {
73
- return createHash("sha256").update(material).digest("hex").slice(0, 32);
70
+ return sha256Hex(material).slice(0, 32);
74
71
  }
75
72
 
76
73
  async function readJsonlLines(filePath: string): Promise<readonly string[]> {
@@ -78,7 +75,7 @@ async function readJsonlLines(filePath: string): Promise<readonly string[]> {
78
75
  try {
79
76
  text = await readFile(filePath, "utf8");
80
77
  } catch (error) {
81
- if (isMigrationEnoent(error)) return [];
78
+ if (isEnoent(error)) return [];
82
79
  throw error;
83
80
  }
84
81
  if (text.length === 0) return [];
@@ -629,7 +626,7 @@ async function mergeOfferedIdentities(
629
626
  await stat(from);
630
627
  lines = await readJsonlLines(from);
631
628
  } catch (error) {
632
- if (isMigrationEnoent(error)) return;
629
+ if (isEnoent(error)) return;
633
630
  throw error;
634
631
  }
635
632
  const destFile = join(destDir, OFFERED_IDENTITIES);
@@ -704,7 +701,7 @@ async function migrateHomePartitionBook(
704
701
  try {
705
702
  await stat(recordsFile);
706
703
  } catch (error) {
707
- if (isMigrationEnoent(error)) continue;
704
+ if (isEnoent(error)) continue;
708
705
  throw error;
709
706
  }
710
707
  await migrateJsonlFileByKind(context, bookKey, writes, recordsFile, TICKET_PROVENANCE, outcomes);
@@ -10,7 +10,6 @@ import { basename, dirname, join, relative, sep } from "node:path";
10
10
 
11
11
  import {
12
12
  destinationRunDirectory,
13
- isMigrationEnoent,
14
13
  isTicketNumberString,
15
14
  listBackupRunLeaves,
16
15
  listMigrationBookKeys,
@@ -32,6 +31,8 @@ import {
32
31
  readBoardTicketNumber,
33
32
  } from "./run-ticket-number.ts";
34
33
 
34
+ import { isEnoent } from "./unknown-value.ts";
35
+
35
36
  const RUNS_PARTITION = "runs";
36
37
 
37
38
  const RUN_LEAF =
@@ -62,7 +63,7 @@ async function pathExists(path: string): Promise<boolean> {
62
63
  await stat(path);
63
64
  return true;
64
65
  } catch (error) {
65
- if (isMigrationEnoent(error)) return false;
66
+ if (isEnoent(error)) return false;
66
67
  throw error;
67
68
  }
68
69
  }
@@ -307,7 +308,7 @@ export async function relocateBoardBoundUnboundRunsInBook(
307
308
  try {
308
309
  entries = await readdir(unboundRuns, { withFileTypes: true });
309
310
  } catch (error) {
310
- if (isMigrationEnoent(error)) return [];
311
+ if (isEnoent(error)) return [];
311
312
  throw error;
312
313
  }
313
314
 
@@ -1,6 +1,8 @@
1
- import { createHash } from "node:crypto";
2
1
  import { readFile } from "node:fs/promises";
3
2
 
3
+ import { sha256Hex } from "./sha256.ts";
4
+ import { isRecord } from "./unknown-value.ts";
5
+
4
6
  export const COLLECTOR_OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
5
7
  export const COLLECTOR_REPO_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
6
8
 
@@ -51,17 +53,13 @@ export function parseCollectorPrNumber(raw: unknown): number {
51
53
  return value;
52
54
  }
53
55
 
54
- function record(value: unknown): value is Record<string, unknown> {
55
- return typeof value === "object" && value !== null && !Array.isArray(value);
56
- }
57
-
58
56
  function canonicalManifest(requests: readonly CollectorRequestConfig[]): string {
59
57
  return `${JSON.stringify({ requests: requests.map((request) => ({ id: request.id, body: request.requestBody })) })}\n`;
60
58
  }
61
59
 
62
60
  export function emptyCollectorManifest(): CollectorManifest {
63
61
  const canonicalJson = canonicalManifest([]);
64
- return { requests: [], canonicalJson, digest: createHash("sha256").update(canonicalJson).digest("hex") };
62
+ return { requests: [], canonicalJson, digest: sha256Hex(canonicalJson) };
65
63
  }
66
64
 
67
65
  /** Optional request configuration. It names requests, never expected observers. */
@@ -70,17 +68,17 @@ export async function loadCollectorManifest(path: string): Promise<CollectorMani
70
68
  try { bytes = await readFile(path); } catch (error) { fail(`Collector request manifest is unreadable at ${path}`, error); }
71
69
  let parsed: unknown;
72
70
  try { parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)); } catch (error) { fail("Collector request manifest must be UTF-8 JSON", error); }
73
- if (!record(parsed)) fail("Collector request manifest must be an object");
71
+ if (!isRecord(parsed)) fail("Collector request manifest must be an object");
74
72
  const rawRequests = parsed.requests ?? [];
75
73
  if (!Array.isArray(rawRequests)) fail("Collector request manifest requests must be an array");
76
74
  const requests: CollectorRequestConfig[] = [];
77
75
  const ids = new Set<string>();
78
76
  for (const [index, item] of rawRequests.entries()) {
79
- if (!record(item) || typeof item.id !== "string" || item.id.length === 0 || typeof item.body !== "string" || item.body.trim() === "") fail(`Collector request manifest requests[${index}] is invalid`);
77
+ if (!isRecord(item) || typeof item.id !== "string" || item.id.length === 0 || typeof item.body !== "string" || item.body.trim() === "") fail(`Collector request manifest requests[${index}] is invalid`);
80
78
  if (ids.has(item.id)) fail(`Collector request manifest has duplicate request id "${item.id}"`);
81
79
  ids.add(item.id);
82
80
  requests.push({ id: item.id, requestBody: item.body });
83
81
  }
84
82
  const canonicalJson = canonicalManifest(requests);
85
- return { requests, canonicalJson, digest: createHash("sha256").update(canonicalJson).digest("hex"), sourcePath: path };
83
+ return { requests, canonicalJson, digest: sha256Hex(canonicalJson), sourcePath: path };
86
84
  }
@@ -3,8 +3,9 @@
3
3
  * #1088: code no longer observes/requests/merges GitHub findings — the LLM
4
4
  * uses host CLI tools. Envelope owns host-surface + construction seatbelt.
5
5
  */
6
- import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts";
7
- import type { Static } from "typebox";
6
+ import type { RoleHost, HostContext } from "./host-contracts.ts";
7
+ import { registerFiledSubmissionTool } from "./filed-submission.ts";
8
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
8
9
 
9
10
  import {
10
11
  emptyCollectorManifest,
@@ -17,8 +18,6 @@ import {
17
18
  import {
18
19
  COLLECTOR_OUTPUT_TOOL,
19
20
  } from "./package-contracts/collector-output.ts";
20
- import { collectorOutputArgsSchema } from "./collector-tool-schemas.ts";
21
-
22
21
  export { COLLECTOR_OUTPUT_TOOL };
23
22
 
24
23
  export const COLLECTOR_REQUIRED_TOOLS = [COLLECTOR_OUTPUT_TOOL] as const;
@@ -54,8 +53,6 @@ export const COLLECTOR_TRANSPORT_FLAGS = Object.freeze([
54
53
  }),
55
54
  ] as const);
56
55
 
57
- const outputSchema = collectorOutputArgsSchema;
58
- type OutputParams = Static<typeof outputSchema>;
59
56
 
60
57
  export type CollectorRoleDependencies = {
61
58
  loadSoul(): Promise<string>;
@@ -164,28 +161,8 @@ export function createCollectorRoleRuntime(
164
161
  if (toolsRegistered) return;
165
162
  toolsRegistered = true;
166
163
 
167
- pi.registerTool({
168
- name: COLLECTOR_OUTPUT_TOOL,
169
- label: "通进司输出",
170
- description: "提交通进司回执。",
171
- promptSnippet: "提交通进司回执",
172
- parameters: outputSchema,
173
- async execute(
174
- _toolCallId: string,
175
- params: OutputParams,
176
- _signal: AbortSignal | undefined,
177
- _onUpdate: unknown,
178
- _ctx: HostContext,
179
- ): Promise<HostToolResult<unknown>> {
180
- const activation = getActivation();
181
- if (activation === undefined) throw new Error("通进司未激活");
182
- // #1088 / #836: record as submitted — runtime does not merge findings.
183
- return {
184
- content: [],
185
- details: params,
186
- terminate: true as const,
187
- };
188
- },
164
+ registerFiledSubmissionTool(pi, roleSubmissionDeclaration("collector"), {
165
+ readyError: () => (getActivation() === undefined ? "通进司未激活" : undefined),
189
166
  });
190
167
  },
191
168
  };
@@ -8,6 +8,7 @@ import { officerConclusionReask } from "./gatekeeper-role.ts";
8
8
  import { receivedDiscriminator } from "./submission-errors.ts";
9
9
  import { coalesceSubmissionRows } from "./public-cli/terminal.ts";
10
10
  import { readableGateItem } from "./readable-gate-item.ts";
11
+ import { isRecord } from "./unknown-value.ts";
11
12
 
12
13
  export type ComplianceNoReceipt = NoReceiptLifecycleFacts & { status: "no-receipt"; usage?: Usage };
13
14
  /**
@@ -63,7 +64,7 @@ function readListField(value: unknown): readonly unknown[] { return Array.isArra
63
64
 
64
65
  /** Try to project a known three-state compliance decision; undefined when not converged/continue/escalate. */
65
66
  export function tryReadComplianceCandidate(arguments_: unknown, usage?: Usage): ComplianceDecision | undefined {
66
- if (typeof arguments_ !== "object" || arguments_ === null || Array.isArray(arguments_)) {
67
+ if (!isRecord(arguments_)) {
67
68
  return undefined;
68
69
  }
69
70
  const args = arguments_ as Record<string, unknown>;
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
9
+ import { isRecord } from "./unknown-value.ts";
9
10
 
10
11
  export const COUNTERSIGN_OUTPUT_TOOL_NAME = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
11
12
 
@@ -25,7 +26,7 @@ export type CountersignVerdict =
25
26
  };
26
27
 
27
28
  export function validateRecordedCountersignOutput(verdict: unknown): CountersignVerdict {
28
- if (verdict === null || typeof verdict !== "object" || Array.isArray(verdict)) {
29
+ if (!isRecord(verdict)) {
29
30
  throw new Error("Countersign verdict has no execution discriminator");
30
31
  }
31
32
  let status: unknown;
@@ -1,4 +1,5 @@
1
1
  import { reviewSubmissionSchema } from "./review-submission.ts";
2
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
2
3
  import {
3
4
  COUNTERSIGN_OUTPUT_TOOL_NAME,
4
5
  validateRecordedCountersignOutput,
@@ -26,10 +27,4 @@ export type CountersignRuntimeDependencies = {
26
27
  * 决定工具规格。生命周期装配(注册、activate、prompt 注入、singleton 检查、
27
28
  * terminate)归注册信封 owner——src/role-runtime.ts(ADR 0018 / #572 R2 判词)。
28
29
  */
29
- export const COUNTERSIGN_TOOL_SPEC = {
30
- name: COUNTERSIGN_OUTPUT_TOOL_NAME,
31
- label: "给事中输出",
32
- description: "给事中决议。",
33
- promptSnippet: "给事中决议",
34
- parameters: countersignVerdictSchema,
35
- } as const;
30
+ export const COUNTERSIGN_TOOL_SPEC = roleSubmissionDeclaration("countersign");
@@ -7,11 +7,16 @@
7
7
  * #901: LLM submits bounds; the mechanical layer projects source bytes.
8
8
  */
9
9
 
10
+ import { Type } from "typebox";
11
+
12
+ import { openToolObject } from "./open-tool-schema.ts";
13
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
10
14
  import {
11
15
  projectTicketProvenanceSessions,
12
16
  type TicketProvenanceSession,
13
17
  } from "./ticket-provenance-contracts.ts";
14
18
  import { isSafePositiveTicketNumber } from "./run-ticket-number.ts";
19
+ import { isRecord } from "./unknown-value.ts";
15
20
 
16
21
  export const DIARIST_OUTPUT_TOOL_NAME = "ak_diarist_output";
17
22
 
@@ -43,7 +48,7 @@ export type DiaristOutput =
43
48
  };
44
49
 
45
50
  export function validateRecordedDiaristOutput(value: unknown): DiaristOutput {
46
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
51
+ if (!isRecord(value)) {
47
52
  throw new Error("Diarist output has no execution discriminator");
48
53
  }
49
54
  let status: unknown;
@@ -78,7 +83,7 @@ export function projectDiaristTicketSessions(
78
83
  if (!Array.isArray(raw)) return undefined;
79
84
  const perTicket = new Map<number, TicketProvenanceSession[]>();
80
85
  for (const entry of raw) {
81
- if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return undefined;
86
+ if (!isRecord(entry)) return undefined;
82
87
  const { ticketNumber, sessions: rawSessions } = entry as Record<string, unknown>;
83
88
  if (!isSafePositiveTicketNumber(ticketNumber)) return undefined;
84
89
  const sessions = projectTicketProvenanceSessions(rawSessions);
@@ -87,3 +92,36 @@ export function projectDiaristTicketSessions(
87
92
  }
88
93
  return Array.from(perTicket, ([ticketNumber, sessions]) => ({ ticketNumber, sessions }));
89
94
  }
95
+
96
+ /**
97
+ * 起居郎交卷形状。
98
+ * #901:交边界(sessions);正文与不可解析原字节由机械投影。
99
+ * status 的合法词(completed | escalate)写在 description,结算认这些词。
100
+ * sessions / ticketSessions 是投影所读的边界:卷路径或端点读不出时走既有 reask,不中止本轮。
101
+ * 声明只留字段名和语义说明,不留类型、嵌套、长度、必填。
102
+ */
103
+ const DIARIST_SESSIONS_DESCRIPTION =
104
+ "对话边界。每卷含 path(会话卷绝对或可读路径)与 ranges(本卷本轮各区间)。每段区间含 from / to;起点与终点各写 id(原生 id)或 line(本轮行号),二选一。空列表=本轮无对话可划。端点无法指名时走 reask,不中止。" as const;
105
+
106
+ export const diaristOutputSchema = withTerminatingOutputDeclarations(
107
+ openToolObject(
108
+ Type.Object({
109
+ status: Type.Unknown({
110
+ description: "completed | escalate",
111
+ }),
112
+ ticketNumber: Type.Optional(
113
+ Type.Unknown({
114
+ description:
115
+ "本庭对象票号(正整数)或 null/省略=真无票;下游走 typed 键;认不出用 status=escalate,不洗成无录。机械层不重判。",
116
+ }),
117
+ ),
118
+ reason: Type.Unknown({
119
+ description: "status 为 escalate 时:认不出本庭对象的原因",
120
+ }),
121
+ sessions: Type.Optional(Type.Unknown({ description: DIARIST_SESSIONS_DESCRIPTION })),
122
+ ticketSessions: Type.Unknown({
123
+ description: `多票庭逐票对话边界;每票含 ticketNumber(本条边界所归票号,正整数)与 sessions(${DIARIST_SESSIONS_DESCRIPTION})。一次交卷各票各自分录。与单票 sessions 二选一。`,
124
+ }),
125
+ }),
126
+ ),
127
+ );
@@ -1,53 +1,20 @@
1
1
  import type { Static } from "typebox";
2
- import { Type } from "typebox";
3
2
 
4
- import { openToolObject } from "./open-tool-schema.ts";
5
- import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
6
3
  import {
7
4
  DIARIST_OUTPUT_TOOL_NAME,
5
+ diaristOutputSchema,
8
6
  validateRecordedDiaristOutput,
9
7
  type DiaristOutput,
10
8
  } from "./diarist-contracts.ts";
9
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
11
10
 
12
11
  export {
13
12
  DIARIST_OUTPUT_TOOL_NAME,
13
+ diaristOutputSchema,
14
14
  } from "./diarist-contracts.ts";
15
15
  export type { DiaristOutput };
16
16
  export { validateRecordedDiaristOutput };
17
17
 
18
- /**
19
- * 起居郎交卷形状。
20
- * #901:交边界(sessions);正文与不可解析原字节由机械投影。
21
- * status 的合法词(completed | escalate)写在 description,结算认这些词。
22
- * sessions / ticketSessions 是投影所读的边界:卷路径或端点读不出时走既有 reask,不中止本轮。
23
- * 声明只留字段名和语义说明,不留类型、嵌套、长度、必填。
24
- */
25
- const DIARIST_SESSIONS_DESCRIPTION =
26
- "对话边界。每卷含 path(会话卷绝对或可读路径)与 ranges(本卷本轮各区间)。每段区间含 from / to;起点与终点各写 id(原生 id)或 line(本轮行号),二选一。空列表=本轮无对话可划。端点无法指名时走 reask,不中止。" as const;
27
-
28
- export const diaristOutputSchema = withTerminatingOutputDeclarations(
29
- openToolObject(
30
- Type.Object({
31
- status: Type.Unknown({
32
- description: "completed | escalate",
33
- }),
34
- ticketNumber: Type.Optional(
35
- Type.Unknown({
36
- description:
37
- "本庭对象票号(正整数)或 null/省略=真无票;下游走 typed 键;认不出用 status=escalate,不洗成无录。机械层不重判。",
38
- }),
39
- ),
40
- reason: Type.Unknown({
41
- description: "status 为 escalate 时:认不出本庭对象的原因",
42
- }),
43
- sessions: Type.Optional(Type.Unknown({ description: DIARIST_SESSIONS_DESCRIPTION })),
44
- ticketSessions: Type.Unknown({
45
- description: `多票庭逐票对话边界;每票含 ticketNumber(本条边界所归票号,正整数)与 sessions(${DIARIST_SESSIONS_DESCRIPTION})。一次交卷各票各自分录。与单票 sessions 二选一。`,
46
- }),
47
- }),
48
- ),
49
- );
50
-
51
18
  export type DiaristOutputParameters = Static<typeof diaristOutputSchema>;
52
19
 
53
20
  export type DiaristRuntimeDependencies = {
@@ -57,11 +24,4 @@ export type DiaristRuntimeDependencies = {
57
24
  /**
58
25
  * 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
59
26
  */
60
- export const DIARIST_TOOL_SPEC = {
61
- name: DIARIST_OUTPUT_TOOL_NAME,
62
- label: "起居郎输出",
63
- description:
64
- "起居郎交单票 sessions 或多票逐票 ticketSessions 边界;认不出本庭对象则 escalate。",
65
- promptSnippet: "起居郎交边界",
66
- parameters: diaristOutputSchema,
67
- } as const;
27
+ export const DIARIST_TOOL_SPEC = roleSubmissionDeclaration("diarist");