@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
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { basename, dirname, isAbsolute, resolve } from "node:path";
3
+ import { errnoCode } from "./unknown-value.js";
3
4
  const GIT_DISCOVERY_ENV_KEYS = [
4
5
  "GIT_DIR",
5
6
  "GIT_COMMON_DIR",
@@ -31,8 +32,7 @@ class ActivationGitRepositoryRequiredError extends Error {
31
32
  }
32
33
  }
33
34
  function isGitSpawnInfrastructureError(error) {
34
- if (error === null || typeof error !== "object" || !("code" in error)) return false;
35
- const code = error.code;
35
+ const code = errnoCode(error);
36
36
  return code === "ENOENT" || code === "EACCES" || code === "EPERM";
37
37
  }
38
38
  function gitChildExitedNonzero(error) {
@@ -6,6 +6,7 @@ import {
6
6
  } from "node:fs";
7
7
  import { userInfo } from "node:os";
8
8
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
+ import { errnoCode, errorText } from "./unknown-value.js";
9
10
  class ActivationLedgerError extends Error {
10
11
  code = "AK_ACTIVATION_LEDGER";
11
12
  constructor(message, options) {
@@ -83,13 +84,6 @@ function physicalPathIdentity(path) {
83
84
  function physicallyContainedIn(root, candidate) {
84
85
  return pathContainedIn(physicalPathIdentity(root), physicalPathIdentity(candidate));
85
86
  }
86
- function errnoCode(error) {
87
- return error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : void 0;
88
- }
89
- function errorText(error) {
90
- if (!(error instanceof Error)) return String(error);
91
- return error.message;
92
- }
93
87
  function assertPhysicalLedgerRoot(absoluteRoot) {
94
88
  let st;
95
89
  try {
@@ -19,6 +19,7 @@ import { scanAnalystIssueRuns, } from "./analyst-ledger.js";
19
19
  import { mergeAnalystLibraryIndexRows, rowFromIssueMetricsPage, } from "./analyst-index.js";
20
20
  import { buildAnalystModelGroupsPage, } from "./analyst-model-groups.js";
21
21
  import { assertAnalystChangedLinesInput, buildAnalystIssueMetricsPage, analystIssuePagePath, writeAnalystIssueMetricsPage, } from "./analyst-page.js";
22
+ import { isMissingPathError } from "./unknown-value.js";
22
23
  /** #338 compute-if-missing failure — issue identity + real cause (CLI → ControlledFailure). */
23
24
  export class AnalystIssueComputeError extends Error {
24
25
  code = "analyst-issue-compute-failed";
@@ -44,16 +45,11 @@ export class AnalystIssueComputeError extends Error {
44
45
  }
45
46
  }
46
47
  }
47
- function isMissingPathError(error) {
48
- return (error instanceof Error
49
- && "code" in error
50
- && (error.code === "ENOENT" || error.code === "ENOTDIR"));
51
- }
52
48
  /**
53
49
  * Sole sweep-mode input contract (#298/#329/#337).
54
50
  * Schema is the single definition; TS types are derived (no parallel hand shape).
55
51
  * projectRoot = string (not nonempty); changedLines optional finite non-negative;
56
- * 0 remains typed 空缺; no extra keys.
52
+ * 0 remains typed 空缺. Extra keys are not a reject reason.
57
53
  */
58
54
  export const analystSweepModeInputSchema = Type.Object({
59
55
  mode: Type.Literal("sweep"),
@@ -61,8 +57,8 @@ export const analystSweepModeInputSchema = Type.Object({
61
57
  projectRoot: Type.String(),
62
58
  /** 排除后改动行数 — omit or 0 → typed 空缺; finite ≥ 0 only. */
63
59
  changedLines: Type.Optional(Type.Number({ minimum: 0, maximum: Number.MAX_VALUE })),
64
- }, { additionalProperties: false })),
65
- }, { additionalProperties: false });
60
+ })),
61
+ });
66
62
  /**
67
63
  * #338 retrieval primitive (sync): use persisted page when present; otherwise
68
64
  * await the sole issue compute kernel (runAnalystIssueMode) which writes via the
@@ -29,6 +29,7 @@ import { readdir } from "node:fs/promises";
29
29
  import { join } from "node:path";
30
30
  import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, } from "./compliance-transport.js";
31
31
  import { extractSessionTimestampSpan, intervalRowsAroundAnchor, readLedgerSessionJsonl, } from "./ledger-session-read.js";
32
+ import { isRecord, errorText, isEnoent } from "./unknown-value.js";
32
33
  const DISPATCH_TOOLS = new Set(["ak_menxia_output", "ak_gatekeeper_output"]);
33
34
  /** Officer terminating tool → current English officer identity. */
34
35
  const OFFICER_TOOL_TO_FACE = {
@@ -44,9 +45,6 @@ const OFFICER_ARG_ALIASES = {
44
45
  fubaolang: "notary",
45
46
  notary: "notary",
46
47
  };
47
- function isRecord(value) {
48
- return typeof value === "object" && value !== null && !Array.isArray(value);
49
- }
50
48
  function isParentAttemptBindingRow(row) {
51
49
  return row.type === "custom" && row.customType === AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE;
52
50
  }
@@ -54,12 +52,6 @@ function isParentAttemptBindingRow(row) {
54
52
  function intervalRowsForGateCall(rows, callRowIndex) {
55
53
  return intervalRowsAroundAnchor(rows, callRowIndex, isParentAttemptBindingRow).rows;
56
54
  }
57
- /** Only true absence (ENOENT). ENOTDIR is damaged topology — must stay loud. */
58
- function isMissingDirectoryError(error) {
59
- return (error instanceof Error
60
- && "code" in error
61
- && error.code === "ENOENT");
62
- }
63
55
  function normalizeOfficerArg(raw) {
64
56
  if (typeof raw !== "string")
65
57
  return undefined;
@@ -325,7 +317,7 @@ async function resolveOfficerSessionFromPointerFile(pointerPath) {
325
317
  raw = JSON.parse(await readFile(pointerPath, "utf8"));
326
318
  }
327
319
  catch (error) {
328
- throw new Error(`direct officer run pointer unreadable in ${pointerPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
320
+ throw new Error(`direct officer run pointer unreadable in ${pointerPath}: ${errorText(error)}`, { cause: error });
329
321
  }
330
322
  if (!isRecord(raw) || raw.kind !== "direct-officer-run-pointer" || raw.version !== 1) {
331
323
  throw new Error(`direct officer run pointer has unknown shape in ${pointerPath}`);
@@ -367,7 +359,7 @@ export async function readAnalystGateCyclesFromAuditorRoles(auditorRolesDirector
367
359
  .sort();
368
360
  }
369
361
  catch (error) {
370
- if (isMissingDirectoryError(error))
362
+ if (isEnoent(error))
371
363
  continue;
372
364
  throw error;
373
365
  }
@@ -20,17 +20,14 @@
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
  import { writeFileAtomically } from "./atomic-write.js";
24
25
  import { assertLedgerFileInsideHome, ensureRealDirectoryTree, physicalPathIdentity, } from "./activation-ledger-topology.js";
25
26
  import { resolveAnalystBookKey } from "./analyst-book-key.js";
27
+ import { isMissingPathError } from "./unknown-value.js";
26
28
  const LIBRARY_INDEX_LOCK_NAME = ".library-index.lock";
27
29
  const LIBRARY_INDEX_LOCK_TIMEOUT_MS = 30_000;
28
30
  const LIBRARY_INDEX_LOCK_RETRY_MS = 15;
29
- function sleep(ms) {
30
- return new Promise((resolve) => {
31
- setTimeout(resolve, ms);
32
- });
33
- }
34
31
  /**
35
32
  * Exclusive create lock for the library-index read→upsert→write critical section.
36
33
  * Same-directory sibling of the index file; not a second index and not a daemon.
@@ -198,11 +195,8 @@ export async function readAnalystLibraryIndexPage(ledgerHome) {
198
195
  raw = await readFile(path, "utf8");
199
196
  }
200
197
  catch (error) {
201
- if (error instanceof Error
202
- && "code" in error
203
- && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
198
+ if (isMissingPathError(error))
204
199
  return undefined;
205
- }
206
200
  throw error;
207
201
  }
208
202
  const parsed = JSON.parse(raw);
@@ -22,17 +22,7 @@ import { readRunTicketNumber } from "./run-ticket-number.js";
22
22
  import { extractSessionModelSequence, extractSessionTimestampSpan, extractSessionToolIntervals, LedgerSessionJsonlError, readLedgerSessionJsonl, } from "./ledger-session-read.js";
23
23
  import { readRunTerminalArtifact, } from "./run-terminal-artifacts.js";
24
24
  import { readAnalystGateCyclesFromAuditorRoles, } from "./analyst-gate-cycles-read.js";
25
- function isMissingPathError(error) {
26
- return (error instanceof Error
27
- && "code" in error
28
- && (error.code === "ENOENT" || error.code === "ENOTDIR"));
29
- }
30
- function errorText(error) {
31
- return error instanceof Error ? error.message : String(error);
32
- }
33
- function isRecord(value) {
34
- return typeof value === "object" && value !== null && !Array.isArray(value);
35
- }
25
+ import { isRecord, errorText, isMissingPathError } from "./unknown-value.js";
36
26
  /** Existing public-cli run-state live phases — not a new state machine. */
37
27
  const LIVE_RUN_STATES = new Set(["admitted", "running", "resumable"]);
38
28
  /**
@@ -12,6 +12,7 @@
12
12
  import { compareAnalystLegs, sumAnalystRunWallMs } from "../analyst-ledger.js";
13
13
  import { medianNumber } from "../analyst-median.js";
14
14
  import { packagedAnalystTerminal } from "../packaged-role-registry.js";
15
+ import { isRecord } from "../unknown-value.js";
15
16
  const WORKER_ROLES = new Set(["coder", "fixer"]);
16
17
  /** Lawful acceptance vocabulary by role (PRD 受理终态映射). */
17
18
  const ACCEPTED_STATUS = {
@@ -32,9 +33,6 @@ const SUCCESS_STATUS = {
32
33
  doctor: new Set(["completed"]),
33
34
  merger: new Set(["completed"]),
34
35
  };
35
- function isRecord(value) {
36
- return typeof value === "object" && value !== null && !Array.isArray(value);
37
- }
38
36
  /** Locate collector groups array: receipt.groups or top-level groups. */
39
37
  function findCollectorGroups(body) {
40
38
  if (Array.isArray(body.groups))
@@ -1,7 +1,5 @@
1
1
  import { extractStatus } from "./acceptance-success-rework.js";
2
- function isRecord(value) {
3
- return typeof value === "object" && value !== null && !Array.isArray(value);
4
- }
2
+ import { isRecord } from "../unknown-value.js";
5
3
  function readClassCount(body) {
6
4
  if (!isRecord(body.outcome))
7
5
  return undefined;
@@ -6,8 +6,8 @@
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";
10
+ import { sha256Hex } from "./sha256.js";
11
11
  import { writeFileAtomically } from "./atomic-write.js";
12
12
  import { assertLedgerFileInsideHome, ensureRealDirectoryTree, physicalPathIdentity, } from "./activation-ledger-topology.js";
13
13
  import { compareAnalystLegs, sumAnalystRunWallMs } from "./analyst-ledger.js";
@@ -21,7 +21,7 @@ export function analystIssuePageKey(address) {
21
21
  else if (address.scopeRootIdentity !== undefined) {
22
22
  parts.push("root", physicalPathIdentity(address.scopeRootIdentity));
23
23
  }
24
- return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
24
+ return sha256Hex(parts.join("\0")).slice(0, 32);
25
25
  }
26
26
  export function analystIssuePagePath(ledgerHome, address) {
27
27
  return join(ledgerHome, "analyst", "issues", `${analystIssuePageKey(address)}.json`);
@@ -1,5 +1,5 @@
1
- import { createHash } from "node:crypto";
2
1
  import { join } from "node:path";
2
+ import { sha256Hex } from "./sha256.js";
3
3
  import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
4
4
  import {
5
5
  activationBookDirectory,
@@ -16,7 +16,7 @@ function resolveNavigatorWorkSubjectPlacement(input) {
16
16
  }
17
17
  }
18
18
  ledgerHome ??= resolveActivationLedgerHome(input.home);
19
- const digest = createHash("sha256").update(input.subject).digest("hex").slice(0, 32);
19
+ const digest = sha256Hex(input.subject).slice(0, 32);
20
20
  return {
21
21
  ledgerHome,
22
22
  sessionDir: join(
@@ -1,6 +1,7 @@
1
+ import { isRecord } from "./unknown-value.js";
1
2
  /** Historical audit-escalation discriminator retained for reading pre-#1057 run artifacts. */
2
3
  export const AUDIT_ESCALATION_KIND = "audit_escalation";
3
4
  export function isAuditEscalationResult(value) {
4
- return typeof value === "object" && value !== null && !Array.isArray(value)
5
+ return isRecord(value)
5
6
  && value.kind === AUDIT_ESCALATION_KIND;
6
7
  }
@@ -1,5 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { basename, join } from "node:path";
3
+ import { isRecord } from "./unknown-value.js";
3
4
  import { joinPackageMaterials, readPackageMaterial, } from "./session-opening-materials.js";
4
5
  /**
5
6
  * Active auditor seats.
@@ -100,7 +101,7 @@ export async function readAuditorResumeBinding(runDirectory) {
100
101
  return undefined;
101
102
  throw error;
102
103
  }
103
- if (raw === null || typeof raw !== "object" || Array.isArray(raw))
104
+ if (!isRecord(raw))
104
105
  return undefined;
105
106
  const record = raw;
106
107
  if (typeof record.sourceRunPath !== "string" || record.sourceRunPath.trim() === "")
@@ -1,5 +1,6 @@
1
- import { createHash } from "node:crypto";
2
1
  import { readFile } from "node:fs/promises";
2
+ import { sha256Hex } from "./sha256.js";
3
+ import { isRecord } from "./unknown-value.js";
3
4
  export const COLLECTOR_OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
4
5
  export const COLLECTOR_REPO_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
5
6
  function fail(message, cause) {
@@ -38,15 +39,12 @@ export function parseCollectorPrNumber(raw) {
38
39
  fail("Collector pull request number must be a positive safe integer");
39
40
  return value;
40
41
  }
41
- function record(value) {
42
- return typeof value === "object" && value !== null && !Array.isArray(value);
43
- }
44
42
  function canonicalManifest(requests) {
45
43
  return `${JSON.stringify({ requests: requests.map((request) => ({ id: request.id, body: request.requestBody })) })}\n`;
46
44
  }
47
45
  export function emptyCollectorManifest() {
48
46
  const canonicalJson = canonicalManifest([]);
49
- return { requests: [], canonicalJson, digest: createHash("sha256").update(canonicalJson).digest("hex") };
47
+ return { requests: [], canonicalJson, digest: sha256Hex(canonicalJson) };
50
48
  }
51
49
  /** Optional request configuration. It names requests, never expected observers. */
52
50
  export async function loadCollectorManifest(path) {
@@ -64,7 +62,7 @@ export async function loadCollectorManifest(path) {
64
62
  catch (error) {
65
63
  fail("Collector request manifest must be UTF-8 JSON", error);
66
64
  }
67
- if (!record(parsed))
65
+ if (!isRecord(parsed))
68
66
  fail("Collector request manifest must be an object");
69
67
  const rawRequests = parsed.requests ?? [];
70
68
  if (!Array.isArray(rawRequests))
@@ -72,7 +70,7 @@ export async function loadCollectorManifest(path) {
72
70
  const requests = [];
73
71
  const ids = new Set();
74
72
  for (const [index, item] of rawRequests.entries()) {
75
- if (!record(item) || typeof item.id !== "string" || item.id.length === 0 || typeof item.body !== "string" || item.body.trim() === "")
73
+ if (!isRecord(item) || typeof item.id !== "string" || item.id.length === 0 || typeof item.body !== "string" || item.body.trim() === "")
76
74
  fail(`Collector request manifest requests[${index}] is invalid`);
77
75
  if (ids.has(item.id))
78
76
  fail(`Collector request manifest has duplicate request id "${item.id}"`);
@@ -80,5 +78,5 @@ export async function loadCollectorManifest(path) {
80
78
  requests.push({ id: item.id, requestBody: item.body });
81
79
  }
82
80
  const canonicalJson = canonicalManifest(requests);
83
- return { requests, canonicalJson, digest: createHash("sha256").update(canonicalJson).digest("hex"), sourcePath: path };
81
+ return { requests, canonicalJson, digest: sha256Hex(canonicalJson), sourcePath: path };
84
82
  }
@@ -3,6 +3,7 @@ import { officerConclusionReask } from "./gatekeeper-role.js";
3
3
  import { receivedDiscriminator } from "./submission-errors.js";
4
4
  import { coalesceSubmissionRows } from "./public-cli/terminal.js";
5
5
  import { readableGateItem } from "./readable-gate-item.js";
6
+ import { isRecord } from "./unknown-value.js";
6
7
  const AUDITOR_DOSSIER_PROMPT = "\u5377\u5B97\u6307\u9488\uFF1A";
7
8
  const COMPLIANCE_RESPONSE_ENTRY_TYPE = "ak_compliance_response";
8
9
  const AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE = "ak_auditor_parent_attempt_binding";
@@ -17,7 +18,7 @@ function readListField(value) {
17
18
  return Array.isArray(value) ? value : value === void 0 ? [] : [value];
18
19
  }
19
20
  function tryReadComplianceCandidate(arguments_, usage) {
20
- if (typeof arguments_ !== "object" || arguments_ === null || Array.isArray(arguments_)) {
21
+ if (!isRecord(arguments_)) {
21
22
  return void 0;
22
23
  }
23
24
  const args = arguments_;
@@ -1,7 +1,8 @@
1
1
  import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.js";
2
+ import { isRecord } from "./unknown-value.js";
2
3
  const COUNTERSIGN_OUTPUT_TOOL_NAME = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
3
4
  function validateRecordedCountersignOutput(verdict) {
4
- if (verdict === null || typeof verdict !== "object" || Array.isArray(verdict)) {
5
+ if (!isRecord(verdict)) {
5
6
  throw new Error("Countersign verdict has no execution discriminator");
6
7
  }
7
8
  let status;
@@ -8,9 +8,10 @@
8
8
  */
9
9
  import { projectTicketProvenanceSessions, } from "./ticket-provenance-contracts.js";
10
10
  import { isSafePositiveTicketNumber } from "./run-ticket-number.js";
11
+ import { isRecord } from "./unknown-value.js";
11
12
  export const DIARIST_OUTPUT_TOOL_NAME = "ak_diarist_output";
12
13
  export function validateRecordedDiaristOutput(value) {
13
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
14
+ if (!isRecord(value)) {
14
15
  throw new Error("Diarist output has no execution discriminator");
15
16
  }
16
17
  let status;
@@ -42,7 +43,7 @@ export function projectDiaristTicketSessions(value) {
42
43
  return undefined;
43
44
  const perTicket = new Map();
44
45
  for (const entry of raw) {
45
- if (entry === null || typeof entry !== "object" || Array.isArray(entry))
46
+ if (!isRecord(entry))
46
47
  return undefined;
47
48
  const { ticketNumber, sessions: rawSessions } = entry;
48
49
  if (!isSafePositiveTicketNumber(ticketNumber))
@@ -2,6 +2,7 @@ import { Type } from "typebox";
2
2
  import { canonicalJson } from "./canonical-json.js";
3
3
  import { openToolObject } from "./open-tool-schema.js";
4
4
  import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.js";
5
+ import { isRecord } from "./unknown-value.js";
5
6
  const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
6
7
  const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
7
8
  const DOCTOR_AUDIT_TOOL_NAME = "ak_doctor_audit_decision";
@@ -35,9 +36,6 @@ const doctorEvidenceReadSchema = Type.Object({ evidenceId: Type.String({ minLeng
35
36
  class DoctorSubmissionContractError extends Error {
36
37
  name = "DoctorSubmissionContractError";
37
38
  }
38
- function isRecord(value) {
39
- return typeof value === "object" && value !== null && !Array.isArray(value);
40
- }
41
39
  function read(value, key) {
42
40
  if (!isRecord(value)) return void 0;
43
41
  try {
@@ -2,7 +2,7 @@ import { readdir, readFile, realpath, stat } from "node:fs/promises";
2
2
  import { dirname, relative, resolve, sep } from "node:path";
3
3
  import { sha256Hex } from "./sha256.js";
4
4
  import { AcceptedDetailsContractError, acceptedFacts, isTerminatingToolName, validateAcceptedDetails } from "./package-contracts/terminating-tools.js";
5
- function record(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
5
+ import { isRecord, isMissingPathError } from "./unknown-value.js";
6
6
  async function discoverCaseFiles(root) { const found = []; async function walk(dir, depth) { for (const item of await readdir(dir, { withFileTypes: true })) {
7
7
  const path = resolve(dir, item.name);
8
8
  if (item.isDirectory()) {
@@ -15,7 +15,6 @@ function sourceList(count, sources) { return { count, sources: [...new Set(sourc
15
15
  function accumulate(metric, value, source) { metric.count += value; if (value)
16
16
  metric.sources.push(source); }
17
17
  function timestamp(row) { return typeof row.timestamp === "string" && Number.isFinite(Date.parse(row.timestamp)) ? row.timestamp : undefined; }
18
- function isMissingPathError(error) { return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"); }
19
18
  async function stableRunsIdentity(root) { let cursor = root; while (true) {
20
19
  try {
21
20
  const git = await stat(resolve(cursor, ".git"));
@@ -38,7 +37,7 @@ function deriveSession(content, id) {
38
37
  if (line.trim()) {
39
38
  try {
40
39
  const row = JSON.parse(line);
41
- if (!record(row)) {
40
+ if (!isRecord(row)) {
42
41
  degradationReasons.push(`non-object session row in ${id}`);
43
42
  break;
44
43
  }
@@ -59,21 +58,21 @@ function deriveSession(content, id) {
59
58
  let accepted, observedCommit, turns = 0, calls = 0, tokens = 0;
60
59
  const statuses = [], commits = [];
61
60
  for (const row of rows) {
62
- if (row.type === "custom" && row.customType === "ak-role-submission-closure" && record(row.data) && row.data.isError === false && typeof row.data.toolName === "string" && isTerminatingToolName(row.data.toolName))
61
+ if (row.type === "custom" && row.customType === "ak-role-submission-closure" && isRecord(row.data) && row.data.isError === false && typeof row.data.toolName === "string" && isTerminatingToolName(row.data.toolName))
63
62
  accepted = row;
64
- const message = record(row.message) ? row.message : undefined;
63
+ const message = isRecord(row.message) ? row.message : undefined;
65
64
  if (message?.role === "assistant") {
66
65
  for (const part of Array.isArray(message.content) ? message.content : [])
67
- if (record(part) && part.type === "toolCall")
66
+ if (isRecord(part) && part.type === "toolCall")
68
67
  calls++;
69
68
  if (typeof message.responseId === "string") {
70
69
  turns++;
71
- const usage = record(message.usage) ? message.usage : undefined;
70
+ const usage = isRecord(message.usage) ? message.usage : undefined;
72
71
  if (usage && typeof usage.output === "number")
73
72
  tokens += usage.output;
74
73
  }
75
74
  }
76
- if (message?.role === "toolResult" && message.isError !== true && typeof message.toolName === "string" && isTerminatingToolName(message.toolName) && record(message.details)) {
75
+ if (message?.role === "toolResult" && message.isError !== true && typeof message.toolName === "string" && isTerminatingToolName(message.toolName) && isRecord(message.details)) {
77
76
  let details;
78
77
  try {
79
78
  details = validateAcceptedDetails(message.toolName, message.details);
@@ -7,6 +7,7 @@
7
7
  import { existsSync, statSync } from "node:fs";
8
8
  import { resolve } from "node:path";
9
9
  import { runDirectoryFromHostContext } from "./host-contracts.js";
10
+ import { isRecord } from "./unknown-value.js";
10
11
  export const AUDIT_RUN_DIR_ENV = "AK_ROLE_RUN_DIR";
11
12
  export const DOCTOR_CANDIDATE_ENTRY_TYPE = "ak_doctor_audit_candidate";
12
13
  function isHostContext(value) {
@@ -45,9 +46,6 @@ export function resolveAuditDossier(source) {
45
46
  }
46
47
  return { status: "ok", runDirectory };
47
48
  }
48
- function isRecord(value) {
49
- return typeof value === "object" && value !== null && !Array.isArray(value);
50
- }
51
49
  /**
52
50
  * Doctor candidate testimony must be recorded before audit.
53
51
  */
@@ -20,6 +20,7 @@ import { readFileSync } from "node:fs";
20
20
  import { dirname, join } from "node:path";
21
21
  import { ENGINE_DETOUR_TOOL_NAME } from "./engine-detour.js";
22
22
  import { readSitianRecords, resolveSitianRecordPath, sitianReport, } from "./sitian-facade.js";
23
+ import { isRecord } from "./unknown-value.js";
23
24
  /** Sitian event kind for one detour-tool call (volume under session/). */
24
25
  export const ENGINE_DETOUR_CALL_KIND = "engine-detour-call";
25
26
  /** decisiveFacts key — name states detour-tool scope, not full engine usage. */
@@ -99,9 +100,6 @@ export function reportEngineDetourCall(input) {
99
100
  recordPointer: pointer,
100
101
  };
101
102
  }
102
- function isRecord(value) {
103
- return typeof value === "object" && value !== null && !Array.isArray(value);
104
- }
105
103
  function callFactFromSitianPayload(payload, pointer) {
106
104
  if (!isRecord(payload))
107
105
  return undefined;
@@ -5,6 +5,7 @@ import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
5
5
  import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.js";
6
6
  import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperOutputSchema, } from "./package-contracts/gatekeeper-output.js";
7
7
  import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
8
+ import { isRecord } from "./unknown-value.js";
8
9
  export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
9
10
  export const NOTARY_OUTPUT_TOOL = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
10
11
  function gateSeatLabel(stage) {
@@ -40,18 +41,10 @@ function failureReason(error) {
40
41
  return error.errors.map(failureReason).join("; ");
41
42
  return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
42
43
  }
43
- function isRecord(value) {
44
- return typeof value === "object" && value !== null && !Array.isArray(value);
45
- }
46
44
  /** Original decision bytes — no sentinel replacement (#836). */
47
45
  function retainedReceipt(decision) {
48
46
  return decision;
49
47
  }
50
- function readRecord(value) {
51
- if (typeof value !== "object" || value === null || Array.isArray(value))
52
- return undefined;
53
- return value;
54
- }
55
48
  /**
56
49
  * Read only the conclusion field for queueing (#753).
57
50
  * converged | continue | escalate → queue signal + raw receipt.
@@ -65,7 +58,7 @@ function reviewQueueStatus(status) {
65
58
  }
66
59
  function projectOfficerDecision(officer, decision, fallbackStatus) {
67
60
  const receipt = retainedReceipt(decision);
68
- const record = readRecord(decision);
61
+ const record = isRecord(decision) ? decision : undefined;
69
62
  const status = (record !== undefined && typeof record.status === "string" ? record.status : undefined)
70
63
  ?? fallbackStatus;
71
64
  const queueStatus = typeof status === "string" ? reviewQueueStatus(status) : undefined;
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "./unknown-value.js";
1
2
  const GLEANER_LEFT_OUTPUT_TOOL_NAME = "ak_gleaner_left_output";
2
3
  const GLEANER_LEFT_BASE_FLAG = {
3
4
  name: "ak-gleaner-left-base",
@@ -7,7 +8,7 @@ const GLEANER_LEFT_BASE_FLAG = {
7
8
  }
8
9
  };
9
10
  function validateRecordedGleanerLeftOutput(value) {
10
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
11
+ if (!isRecord(value)) {
11
12
  throw new Error("Gleaner-left output has no execution discriminator");
12
13
  }
13
14
  let status;
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "../unknown-value.js";
1
2
  export function isClaudePrintDescription(description) {
2
3
  return description.protocol === "claude-print";
3
4
  }
@@ -89,7 +90,7 @@ const CODEX_JSON_VALUE_SCHEMA = Object.freeze({
89
90
  */
90
91
  export function closeJsonSchemaForCodex(schema) {
91
92
  const closed = closeSchemaNode(schema);
92
- const existingDefs = isPlainObject(closed.$defs)
93
+ const existingDefs = isRecord(closed.$defs)
93
94
  ? closed.$defs
94
95
  : {};
95
96
  return {
@@ -100,13 +101,9 @@ export function closeJsonSchemaForCodex(schema) {
100
101
  },
101
102
  };
102
103
  }
103
- /** Shared plain-object guard for headless host schema/event reduction. */
104
- export function isPlainObject(value) {
105
- return typeof value === "object" && value !== null && !Array.isArray(value);
106
- }
107
104
  /** Pure null leaf only — composite type|null is stripped in-leaf, not dropped whole. */
108
105
  function isPureNullTypeSchema(value) {
109
- if (!isPlainObject(value))
106
+ if (!isRecord(value))
110
107
  return false;
111
108
  if (value.type === "null")
112
109
  return true;
@@ -120,7 +117,7 @@ function isPureNullTypeSchema(value) {
120
117
  * Required edges keep the non-null type(s); optional edges re-add null once.
121
118
  */
122
119
  function stripNullFromLeafType(leaf) {
123
- if (!isPlainObject(leaf) || !Array.isArray(leaf.type))
120
+ if (!isRecord(leaf) || !Array.isArray(leaf.type))
124
121
  return leaf;
125
122
  const nonNull = leaf.type.filter((t) => t !== "null");
126
123
  if (nonNull.length === leaf.type.length)
@@ -137,7 +134,7 @@ function stripNullFromLeafType(leaf) {
137
134
  * lack `type`; strict structured output rejects those intermediate nodes.
138
135
  */
139
136
  function flattenUnionLeaves(schema) {
140
- if (!isPlainObject(schema))
137
+ if (!isRecord(schema))
141
138
  return [schema];
142
139
  if (Array.isArray(schema.anyOf)) {
143
140
  return schema.anyOf.flatMap(flattenUnionLeaves);
@@ -155,7 +152,7 @@ function nonNullLeaves(schema) {
155
152
  * Required → closed non-null leaf(s). Optional → closed leaf(s) + null.
156
153
  */
157
154
  function closePropertySchema(schema, optional) {
158
- const description = isPlainObject(schema) && typeof schema.description === "string"
155
+ const description = isRecord(schema) && typeof schema.description === "string"
159
156
  ? { description: schema.description }
160
157
  : {};
161
158
  const leaves = nonNullLeaves(schema).map(closeSchemaNode);
@@ -165,10 +162,10 @@ function closePropertySchema(schema, optional) {
165
162
  const closed = leaves.length === 1 ? leaves[0] : { anyOf: leaves };
166
163
  // Codex strict does not allow sibling keys on $ref; put property guidance
167
164
  // on an enclosing schema instead of losing it or weakening the free-JSON ref.
168
- if (isPlainObject(closed) && typeof closed.$ref === "string") {
165
+ if (isRecord(closed) && typeof closed.$ref === "string") {
169
166
  return { anyOf: [closed], ...description };
170
167
  }
171
- return isPlainObject(closed) ? { ...closed, ...description } : closed;
168
+ return isRecord(closed) ? { ...closed, ...description } : closed;
172
169
  }
173
170
  return { anyOf: [...leaves, { type: "null" }], ...description };
174
171
  }
@@ -182,7 +179,7 @@ function ensureTypedLeaf(schema) {
182
179
  return schema;
183
180
  if (typeof schema.$ref === "string")
184
181
  return schema;
185
- if (isPlainObject(schema.properties) || schema.additionalProperties !== undefined) {
182
+ if (isRecord(schema.properties) || schema.additionalProperties !== undefined) {
186
183
  return { ...schema, type: "object" };
187
184
  }
188
185
  if (schema.items !== undefined) {
@@ -211,7 +208,7 @@ function originalRequiredNames(node) {
211
208
  return new Set(node.required.filter((item) => typeof item === "string"));
212
209
  }
213
210
  function closeSchemaNode(node) {
214
- if (!isPlainObject(node))
211
+ if (!isRecord(node))
215
212
  return node;
216
213
  // Already a ref (free-JSON leaf or pre-existing) — do not retype.
217
214
  if (typeof node.$ref === "string")
@@ -229,10 +226,10 @@ function closeSchemaNode(node) {
229
226
  if (node.items !== undefined) {
230
227
  out.items = closeSchemaNode(node.items);
231
228
  }
232
- if (isPlainObject(node.$defs)) {
229
+ if (isRecord(node.$defs)) {
233
230
  out.$defs = Object.fromEntries(Object.entries(node.$defs).map(([key, value]) => [key, closeSchemaNode(value)]));
234
231
  }
235
- const hasProperties = isPlainObject(node.properties);
232
+ const hasProperties = isRecord(node.properties);
236
233
  const isObjectType = node.type === "object"
237
234
  || (Array.isArray(node.type) && node.type.includes("object"))
238
235
  || hasProperties
@@ -273,11 +270,11 @@ function closeSchemaNode(node) {
273
270
  function stringEnvironment(value) {
274
271
  const entries = Array.isArray(value)
275
272
  ? value.flatMap((item) => {
276
- if (!isPlainObject(item) || typeof item.name !== "string" || typeof item.value !== "string")
273
+ if (!isRecord(item) || typeof item.name !== "string" || typeof item.value !== "string")
277
274
  return [];
278
275
  return [[item.name, item.value]];
279
276
  })
280
- : isPlainObject(value)
277
+ : isRecord(value)
281
278
  ? Object.entries(value).filter((entry) => typeof entry[1] === "string")
282
279
  : [];
283
280
  return entries.length === 0 ? undefined : Object.fromEntries(entries);