@akagilnc/pi-workflow-roles 0.1.5365 → 0.1.5375

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 (170) hide show
  1. package/dist/acp-host/production-host.js +5585 -5655
  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/compliance-transport.js +2 -1
  16. package/dist/countersign-contracts.js +2 -1
  17. package/dist/diarist-contracts.js +3 -2
  18. package/dist/doctor-contracts.js +1 -3
  19. package/dist/doctor-evidence.js +7 -8
  20. package/dist/dossier-resolution.js +1 -3
  21. package/dist/engine-detour-usage.js +1 -3
  22. package/dist/gatekeeper-role.js +2 -9
  23. package/dist/gleaner-left-contracts.js +2 -1
  24. package/dist/headless-host/description.js +14 -17
  25. package/dist/headless-host/production-host.js +1665 -1737
  26. package/dist/host-session-record.js +3 -2
  27. package/dist/inspector-contracts.js +2 -1
  28. package/dist/ledger-session-read.js +1 -3
  29. package/dist/merger-contracts.js +9 -9
  30. package/dist/migrate-book-topology.js +348 -314
  31. package/dist/navigator-attendance.js +9 -11
  32. package/dist/navigator-invocation-identity.js +3 -2
  33. package/dist/navigator-public-session.js +3 -5
  34. package/dist/navigator-session-contracts.js +8 -10
  35. package/dist/navigator-work-base.js +5 -8
  36. package/dist/notary-contracts.js +3 -5
  37. package/dist/package-contracts/auditor-output.js +1 -3
  38. package/dist/package-contracts/fixer-packet.js +3 -6
  39. package/dist/package-contracts/gatekeeper-output.js +1 -3
  40. package/dist/package-contracts/navigator-output.js +8 -8
  41. package/dist/package-contracts/terminating-infrastructure.js +5 -9
  42. package/dist/package-contracts/terminating-tools.js +4 -3
  43. package/dist/public-cli/analyst-run.js +3 -2
  44. package/dist/public-cli/auto-resume.js +4 -9
  45. package/dist/public-cli/cli.js +66 -64
  46. package/dist/public-cli/config.js +12 -11
  47. package/dist/public-cli/countersign-run.js +1 -3
  48. package/dist/public-cli/host-providers.js +7 -14
  49. package/dist/public-cli/instruction-seat-run.js +69 -43
  50. package/dist/public-cli/invocation.js +141 -136
  51. package/dist/public-cli/main.js +1919 -1968
  52. package/dist/public-cli/post-admission.js +89 -89
  53. package/dist/public-cli/role-seat-runtime.js +29 -0
  54. package/dist/public-cli/run-lifecycle.js +20 -32
  55. package/dist/public-cli/settlement.js +10 -26
  56. package/dist/public-cli/turn-request.js +14 -0
  57. package/dist/public-role-summons.js +23 -35
  58. package/dist/receipt-delivery-policy.js +1 -3
  59. package/dist/role-run-placement.js +7 -5
  60. package/dist/role-run-relocation.js +12 -19
  61. package/dist/run-terminal-artifacts.js +1 -11
  62. package/dist/run-ticket-number.js +2 -4
  63. package/dist/session-assistant-usage.js +2 -1
  64. package/dist/session-dialogue.js +1 -14
  65. package/dist/sitian-reader.js +2 -4
  66. package/dist/submission-correctable-error.js +2 -1
  67. package/dist/submission-errors.js +2 -1
  68. package/dist/submission-gate.js +2 -1
  69. package/dist/submission-ledger.js +4 -5
  70. package/dist/ticket-provenance-contracts.js +1 -3
  71. package/dist/ticket-provenance.js +2 -1
  72. package/dist/typed-provider-http.js +4 -7
  73. package/dist/unknown-value.js +28 -0
  74. package/dist/user-dialogue-stdin.js +2 -1
  75. package/package.json +1 -1
  76. package/src/acp-host/role-turn-host.ts +4 -2
  77. package/src/activation-ledger-git.ts +3 -2
  78. package/src/activation-ledger-topology.ts +4 -12
  79. package/src/analyst-entry.ts +3 -11
  80. package/src/analyst-gate-cycles-read.ts +3 -15
  81. package/src/analyst-index.ts +3 -13
  82. package/src/analyst-ledger.ts +2 -16
  83. package/src/analyst-metric-families/acceptance-success-rework.ts +2 -4
  84. package/src/analyst-metric-families/round-timeline.ts +2 -4
  85. package/src/analyst-page.ts +2 -2
  86. package/src/archivist-record-topology.ts +2 -2
  87. package/src/audit-escalation.ts +2 -1
  88. package/src/auditor-soul.ts +3 -1
  89. package/src/book-topology-migration-placement.ts +8 -21
  90. package/src/book-topology-migrators-t11.ts +16 -15
  91. package/src/book-topology-mixed-volume-migrators.ts +3 -6
  92. package/src/book-topology-record-class-migrators.ts +7 -10
  93. package/src/book-topology-runs-migrator.ts +4 -3
  94. package/src/collector-config.ts +7 -9
  95. package/src/compliance-transport.ts +2 -1
  96. package/src/countersign-contracts.ts +2 -1
  97. package/src/diarist-contracts.ts +3 -2
  98. package/src/doctor-contracts.ts +3 -1
  99. package/src/doctor-evidence.ts +8 -7
  100. package/src/doctor-role.ts +4 -2
  101. package/src/dossier-resolution.ts +2 -4
  102. package/src/engine-detour-tool.ts +0 -2
  103. package/src/engine-detour-usage.ts +2 -4
  104. package/src/external-host-turn-loop.ts +3 -1
  105. package/src/factory-board.ts +1 -8
  106. package/src/gatekeeper-role.ts +2 -10
  107. package/src/gleaner-left-contracts.ts +3 -1
  108. package/src/headless-host/description.ts +15 -18
  109. package/src/headless-host/role-turn-host.ts +10 -9
  110. package/src/host-session-record.ts +4 -2
  111. package/src/inspector-contracts.ts +2 -1
  112. package/src/ledger-session-read.ts +2 -5
  113. package/src/merger-contracts.ts +10 -9
  114. package/src/navigator-attendance.ts +10 -12
  115. package/src/navigator-invocation-identity.ts +3 -2
  116. package/src/navigator-public-session.ts +4 -6
  117. package/src/navigator-session-contracts.ts +8 -11
  118. package/src/navigator-work-base.ts +5 -8
  119. package/src/navigator-work-context.ts +2 -1
  120. package/src/notary-contracts.ts +4 -6
  121. package/src/oauth-keepalive.ts +5 -18
  122. package/src/package-contracts/auditor-output.ts +2 -4
  123. package/src/package-contracts/fixer-packet.ts +4 -6
  124. package/src/package-contracts/gatekeeper-output.ts +2 -4
  125. package/src/package-contracts/judge-output.ts +0 -1
  126. package/src/package-contracts/navigator-output.ts +8 -8
  127. package/src/package-contracts/terminating-infrastructure.ts +6 -20
  128. package/src/package-contracts/terminating-tools.ts +8 -12
  129. package/src/pi/adapter.ts +0 -1
  130. package/src/pi/role-turn-host.ts +0 -1
  131. package/src/public-cli/analyst-run.ts +4 -2
  132. package/src/public-cli/auto-resume.ts +5 -12
  133. package/src/public-cli/cli.ts +77 -83
  134. package/src/public-cli/config.ts +16 -27
  135. package/src/public-cli/countersign-run.ts +2 -4
  136. package/src/public-cli/host-providers.ts +10 -27
  137. package/src/public-cli/instruction-seat-run.ts +87 -58
  138. package/src/public-cli/invocation.ts +159 -172
  139. package/src/public-cli/post-admission.ts +115 -103
  140. package/src/public-cli/role-seat-runtime.ts +72 -0
  141. package/src/public-cli/run-lifecycle.ts +27 -67
  142. package/src/public-cli/settlement.ts +11 -33
  143. package/src/public-cli/terminal.ts +2 -17
  144. package/src/public-cli/turn-request.ts +26 -0
  145. package/src/public-role-summons.ts +24 -48
  146. package/src/receipt-delivery-policy.ts +1 -4
  147. package/src/reviewer-role.ts +0 -1
  148. package/src/role-envelope.ts +5 -3
  149. package/src/role-run-placement.ts +9 -10
  150. package/src/role-run-relocation.ts +13 -23
  151. package/src/role-runtime.ts +9 -15
  152. package/src/run-terminal-artifacts.ts +2 -16
  153. package/src/run-ticket-number.ts +4 -10
  154. package/src/session-assistant-usage.ts +3 -1
  155. package/src/session-dialogue.ts +1 -4
  156. package/src/session-identity.ts +0 -1
  157. package/src/sitian-reader.ts +2 -4
  158. package/src/submission-correctable-error.ts +3 -1
  159. package/src/submission-errors.ts +2 -1
  160. package/src/submission-gate.ts +3 -1
  161. package/src/submission-ledger.ts +5 -6
  162. package/src/ticket-provenance-contracts.ts +1 -4
  163. package/src/ticket-provenance.ts +3 -1
  164. package/src/ticket-snapshot.ts +2 -4
  165. package/src/ticket-trajectory.ts +1 -8
  166. package/src/typed-provider-http.ts +5 -15
  167. package/src/unknown-value.ts +34 -0
  168. package/src/user-dialogue-stdin.ts +3 -1
  169. package/src/worker-role.ts +2 -5
  170. package/src/worker-submission-gates.ts +5 -7
@@ -7,6 +7,10 @@ import {
7
7
  import { userInfo } from "node:os";
8
8
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
9
 
10
+ import { errnoCode, errorText } from "./unknown-value.ts";
11
+
12
+ export { errnoCode, errorText };
13
+
10
14
  /**
11
15
  * Typed activation-ledger path/fs failure. Callers discriminate with instanceof/code;
12
16
  * never by parsing message prose. Original filesystem causes are retained.
@@ -128,18 +132,6 @@ export function physicallyContainedIn(root: string, candidate: string): boolean
128
132
  return pathContainedIn(physicalPathIdentity(root), physicalPathIdentity(candidate));
129
133
  }
130
134
 
131
- export function errnoCode(error: unknown): string | undefined {
132
- return error !== null && typeof error === "object" && "code" in error
133
- && typeof (error as { code: unknown }).code === "string"
134
- ? (error as { code: string }).code
135
- : undefined;
136
- }
137
-
138
- export function errorText(error: unknown): string {
139
- if (!(error instanceof Error)) return String(error);
140
- return error.message;
141
- }
142
-
143
135
  /**
144
136
  * Ensure the configured ledger root is a physical directory identity.
145
137
  * lstat before create/realpath: a root symlink is never admitted, even when its
@@ -46,6 +46,8 @@ import {
46
46
  type AnalystUnreadableRun,
47
47
  } from "./analyst-page.ts";
48
48
 
49
+ import { isMissingPathError } from "./unknown-value.ts";
50
+
49
51
  /** #338 compute-if-missing failure — issue identity + real cause (CLI → ControlledFailure). */
50
52
  export class AnalystIssueComputeError extends Error {
51
53
  readonly code = "analyst-issue-compute-failed" as const;
@@ -80,14 +82,6 @@ export class AnalystIssueComputeError extends Error {
80
82
  }
81
83
  }
82
84
 
83
- function isMissingPathError(error: unknown): boolean {
84
- return (
85
- error instanceof Error
86
- && "code" in error
87
- && (error.code === "ENOENT" || error.code === "ENOTDIR")
88
- );
89
- }
90
-
91
85
  /** Issue-mode typed input — book × ticket scope (#399). */
92
86
  export type AnalystIssueModeInput = {
93
87
  readonly mode: "issue";
@@ -123,7 +117,7 @@ export type AnalystIssueModeInput = {
123
117
  * Sole sweep-mode input contract (#298/#329/#337).
124
118
  * Schema is the single definition; TS types are derived (no parallel hand shape).
125
119
  * projectRoot = string (not nonempty); changedLines optional finite non-negative;
126
- * 0 remains typed 空缺; no extra keys.
120
+ * 0 remains typed 空缺. Extra keys are not a reject reason.
127
121
  */
128
122
  export const analystSweepModeInputSchema = Type.Object(
129
123
  {
@@ -137,11 +131,9 @@ export const analystSweepModeInputSchema = Type.Object(
137
131
  Type.Number({ minimum: 0, maximum: Number.MAX_VALUE }),
138
132
  ),
139
133
  },
140
- { additionalProperties: false },
141
134
  ),
142
135
  ),
143
136
  },
144
- { additionalProperties: false },
145
137
  );
146
138
 
147
139
  /** Sweep-mode typed input — 已并 PR 清单 + LOC → 补算缺页 + 维护全库索引. */
@@ -38,6 +38,7 @@ import {
38
38
  type LedgerSessionRow,
39
39
  } from "./ledger-session-read.ts";
40
40
 
41
+ import { isRecord, errorText, isEnoent } from "./unknown-value.ts";
41
42
 
42
43
  /** One completed gate round: direct officer receipt or historical province/officer pair. */
43
44
  /** Honest origin discriminant: direct summons vs historical province dispatch. */
@@ -88,10 +89,6 @@ const OFFICER_ARG_ALIASES: Readonly<Record<string, "inspector" | "notary">> = {
88
89
  notary: "notary",
89
90
  };
90
91
 
91
- function isRecord(value: unknown): value is Record<string, unknown> {
92
- return typeof value === "object" && value !== null && !Array.isArray(value);
93
- }
94
-
95
92
  function isParentAttemptBindingRow(row: LedgerSessionRow): boolean {
96
93
  return row.type === "custom" && row.customType === AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE;
97
94
  }
@@ -104,15 +101,6 @@ function intervalRowsForGateCall(
104
101
  return intervalRowsAroundAnchor(rows, callRowIndex, isParentAttemptBindingRow).rows;
105
102
  }
106
103
 
107
- /** Only true absence (ENOENT). ENOTDIR is damaged topology — must stay loud. */
108
- function isMissingDirectoryError(error: unknown): boolean {
109
- return (
110
- error instanceof Error
111
- && "code" in error
112
- && error.code === "ENOENT"
113
- );
114
- }
115
-
116
104
  function normalizeOfficerArg(raw: unknown): "inspector" | "notary" | undefined {
117
105
  if (typeof raw !== "string") return undefined;
118
106
  return OFFICER_ARG_ALIASES[raw.trim()];
@@ -438,7 +426,7 @@ async function resolveOfficerSessionFromPointerFile(
438
426
  raw = JSON.parse(await readFile(pointerPath, "utf8"));
439
427
  } catch (error) {
440
428
  throw new Error(
441
- `direct officer run pointer unreadable in ${pointerPath}: ${error instanceof Error ? error.message : String(error)}`,
429
+ `direct officer run pointer unreadable in ${pointerPath}: ${errorText(error)}`,
442
430
  { cause: error },
443
431
  );
444
432
  }
@@ -490,7 +478,7 @@ export async function readAnalystGateCyclesFromAuditorRoles(
490
478
  .map((e) => e.name)
491
479
  .sort();
492
480
  } catch (error) {
493
- if (isMissingDirectoryError(error)) continue;
481
+ if (isEnoent(error)) continue;
494
482
  throw error;
495
483
  }
496
484
  for (const name of names) {
@@ -20,6 +20,7 @@
20
20
  */
21
21
  import { open, readFile, unlink } from "node:fs/promises";
22
22
  import { dirname, join } from "node:path";
23
+ import { setTimeout as sleep } from "node:timers/promises";
23
24
 
24
25
  import { writeFileAtomically } from "./atomic-write.ts";
25
26
  import {
@@ -28,6 +29,7 @@ import {
28
29
  physicalPathIdentity,
29
30
  } from "./activation-ledger-topology.ts";
30
31
  import { resolveAnalystBookKey } from "./analyst-book-key.ts";
32
+ import { isMissingPathError } from "./unknown-value.ts";
31
33
  import type {
32
34
  AnalystIssueMetricsPage,
33
35
  AnalystOptionalMetricNumber,
@@ -38,12 +40,6 @@ const LIBRARY_INDEX_LOCK_NAME = ".library-index.lock";
38
40
  const LIBRARY_INDEX_LOCK_TIMEOUT_MS = 30_000;
39
41
  const LIBRARY_INDEX_LOCK_RETRY_MS = 15;
40
42
 
41
- function sleep(ms: number): Promise<void> {
42
- return new Promise((resolve) => {
43
- setTimeout(resolve, ms);
44
- });
45
- }
46
-
47
43
  /**
48
44
  * Exclusive create lock for the library-index read→upsert→write critical section.
49
45
  * Same-directory sibling of the index file; not a second index and not a daemon.
@@ -270,13 +266,7 @@ export async function readAnalystLibraryIndexPage(
270
266
  try {
271
267
  raw = await readFile(path, "utf8");
272
268
  } catch (error) {
273
- if (
274
- error instanceof Error
275
- && "code" in error
276
- && (error.code === "ENOENT" || error.code === "ENOTDIR")
277
- ) {
278
- return undefined;
279
- }
269
+ if (isMissingPathError(error)) return undefined;
280
270
  throw error;
281
271
  }
282
272
  const parsed: unknown = JSON.parse(raw);
@@ -49,23 +49,9 @@ import {
49
49
  type AnalystGateCycleRound,
50
50
  } from "./analyst-gate-cycles-read.ts";
51
51
 
52
- export type { AnalystGateCycleRound } from "./analyst-gate-cycles-read.ts";
53
-
54
- function isMissingPathError(error: unknown): boolean {
55
- return (
56
- error instanceof Error
57
- && "code" in error
58
- && (error.code === "ENOENT" || error.code === "ENOTDIR")
59
- );
60
- }
52
+ import { isRecord, errorText, isMissingPathError } from "./unknown-value.ts";
61
53
 
62
- function errorText(error: unknown): string {
63
- return error instanceof Error ? error.message : String(error);
64
- }
65
-
66
- function isRecord(value: unknown): value is Record<string, unknown> {
67
- return typeof value === "object" && value !== null && !Array.isArray(value);
68
- }
54
+ export type { AnalystGateCycleRound } from "./analyst-gate-cycles-read.ts";
69
55
 
70
56
  /** Existing public-cli run-state live phases — not a new state machine. */
71
57
  const LIVE_RUN_STATES = new Set(["admitted", "running", "resumable"]);
@@ -14,6 +14,8 @@ import { medianNumber } from "../analyst-median.ts";
14
14
  import type { AnalystMetricFamilyModule } from "../analyst-metric-family.ts";
15
15
  import { packagedAnalystTerminal } from "../packaged-role-registry.ts";
16
16
 
17
+ import { isRecord } from "../unknown-value.ts";
18
+
17
19
  const WORKER_ROLES = new Set(["coder", "fixer"]);
18
20
 
19
21
  /** Lawful acceptance vocabulary by role (PRD 受理终态映射). */
@@ -95,10 +97,6 @@ export type AnalystAcceptanceSuccessReworkSection = {
95
97
  readonly rework: AnalystReworkLens;
96
98
  };
97
99
 
98
- function isRecord(value: unknown): value is Record<string, unknown> {
99
- return typeof value === "object" && value !== null && !Array.isArray(value);
100
- }
101
-
102
100
  /** Locate collector groups array: receipt.groups or top-level groups. */
103
101
  function findCollectorGroups(body: Record<string, unknown>): unknown {
104
102
  if (Array.isArray(body.groups)) return body.groups;
@@ -17,6 +17,8 @@ import type {
17
17
  AnalystUnreadableRun,
18
18
  } from "../analyst-page.ts";
19
19
 
20
+ import { isRecord } from "../unknown-value.ts";
21
+
20
22
  export type AnalystRoundTimelineTerminal =
21
23
  | {
22
24
  readonly kind: "receipt";
@@ -66,10 +68,6 @@ export type AnalystRoundTimelineSection = {
66
68
  readonly lanes: readonly AnalystRoundTimelineLane[];
67
69
  };
68
70
 
69
- function isRecord(value: unknown): value is Record<string, unknown> {
70
- return typeof value === "object" && value !== null && !Array.isArray(value);
71
- }
72
-
73
71
  function readClassCount(body: Record<string, unknown>): number | undefined {
74
72
  if (!isRecord(body.outcome)) return undefined;
75
73
  if (!isRecord(body.outcome.decisiveFacts)) return undefined;
@@ -6,9 +6,9 @@
6
6
  * top-level sections via directory discovery — B/C waves add family files
7
7
  * without forking the page writer or editing a shared registry list.
8
8
  */
9
- import { createHash } from "node:crypto";
10
9
  import { dirname, join } from "node:path";
11
10
 
11
+ import { sha256Hex } from "./sha256.ts";
12
12
  import { writeFileAtomically } from "./atomic-write.ts";
13
13
  import {
14
14
  assertLedgerFileInsideHome,
@@ -161,7 +161,7 @@ export function analystIssuePageKey(address: AnalystIssuePageAddress): string {
161
161
  } else if (address.scopeRootIdentity !== undefined) {
162
162
  parts.push("root", physicalPathIdentity(address.scopeRootIdentity));
163
163
  }
164
- return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
164
+ return sha256Hex(parts.join("\0")).slice(0, 32);
165
165
  }
166
166
 
167
167
  export function analystIssuePagePath(
@@ -7,9 +7,9 @@
7
7
  * books/<book>/navigator/<work-subject-digest>/. Callers must not re-hash
8
8
  * subject or re-join book/navigator/digest themselves.
9
9
  */
10
- import { createHash } from "node:crypto";
11
10
  import { join } from "node:path";
12
11
 
12
+ import { sha256Hex } from "./sha256.ts";
13
13
  import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
14
14
  import {
15
15
  activationBookDirectory,
@@ -52,7 +52,7 @@ export function resolveNavigatorWorkSubjectPlacement(input: {
52
52
  }
53
53
  }
54
54
  ledgerHome ??= resolveActivationLedgerHome(input.home);
55
- const digest = createHash("sha256").update(input.subject).digest("hex").slice(0, 32);
55
+ const digest = sha256Hex(input.subject).slice(0, 32);
56
56
  return {
57
57
  ledgerHome,
58
58
  sessionDir: join(
@@ -1,7 +1,8 @@
1
+ import { isRecord } from "./unknown-value.ts";
1
2
  /** Historical audit-escalation discriminator retained for reading pre-#1057 run artifacts. */
2
3
  export const AUDIT_ESCALATION_KIND = "audit_escalation" as const;
3
4
 
4
5
  export function isAuditEscalationResult(value: unknown): value is { readonly kind: typeof AUDIT_ESCALATION_KIND } {
5
- return typeof value === "object" && value !== null && !Array.isArray(value)
6
+ return isRecord(value)
6
7
  && (value as Record<string, unknown>).kind === AUDIT_ESCALATION_KIND;
7
8
  }
@@ -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)
@@ -10,29 +10,20 @@ import { readdir, readFile } from "node:fs/promises";
10
10
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
11
11
 
12
12
  import { pathContainedIn } from "./activation-ledger-topology.ts";
13
- import { roleRunPlacement } from "./role-run-placement.ts";
13
+ import { isUnboundRunDirectory, roleRunPlacement } from "./role-run-placement.ts";
14
+
15
+ export { isUnboundRunDirectory };
14
16
  import {
15
17
  MIGRATION_TICKET_DERIVATION_PAGE,
16
18
  readBoardTicketNumber,
17
19
  readMigrationDerivedTicketNumber,
18
20
  } from "./run-ticket-number.ts";
19
21
 
22
+ import { isRecord, isEnoent } from "./unknown-value.ts";
23
+
20
24
  const TICKET_NUMBER_RE = /^[1-9][0-9]*$/;
21
25
  const RUN_DIR_NAME_RE = /^([^@]+)@([^@]+)$/;
22
26
 
23
- function isRecord(value: unknown): value is Record<string, unknown> {
24
- return typeof value === "object" && value !== null && !Array.isArray(value);
25
- }
26
-
27
- /** Sole migration ENOENT projection — non-ENOENT must propagate. */
28
- export function isMigrationEnoent(error: unknown): boolean {
29
- return (
30
- error instanceof Error &&
31
- "code" in error &&
32
- (error as NodeJS.ErrnoException).code === "ENOENT"
33
- );
34
- }
35
-
36
27
  /** Book-key directories under a books root; missing root → []. */
37
28
  export async function listMigrationBookKeys(
38
29
  booksDirectory: string,
@@ -44,7 +35,7 @@ export async function listMigrationBookKeys(
44
35
  .map((entry) => entry.name)
45
36
  .sort((a, b) => a.localeCompare(b));
46
37
  } catch (error) {
47
- if (isMigrationEnoent(error)) return [];
38
+ if (isEnoent(error)) return [];
48
39
  throw error;
49
40
  }
50
41
  }
@@ -57,7 +48,7 @@ export async function listMigrationDirents(
57
48
  const entries = await readdir(directory, { withFileTypes: true });
58
49
  return [...entries].sort((a, b) => a.name.localeCompare(b.name));
59
50
  } catch (error) {
60
- if (isMigrationEnoent(error)) return [];
51
+ if (isEnoent(error)) return [];
61
52
  throw error;
62
53
  }
63
54
  }
@@ -107,7 +98,7 @@ async function readProjectRootFromRun(
107
98
  return { projectRoot, sourcePage: page };
108
99
  }
109
100
  } catch (error) {
110
- if (isMigrationEnoent(error)) continue;
101
+ if (isEnoent(error)) continue;
111
102
  throw error;
112
103
  }
113
104
  }
@@ -167,10 +158,6 @@ export async function resolveMigratingRunTicket(runDirectory: string): Promise<{
167
158
  };
168
159
  }
169
160
 
170
- export function isUnboundRunDirectory(runDirectory: string): boolean {
171
- return runDirectory.replaceAll("\\", "/").includes("/unbound/runs/");
172
- }
173
-
174
161
  export function bookHistoricalRoots(
175
162
  booksDirectory: string,
176
163
  backupBooksDirectory: string,
@@ -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