open-multi-agent-kit 0.90.8 → 0.90.9

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 (306) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +7 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/commands/doctor-provider-cli.d.ts +33 -0
  7. package/dist/commands/doctor-provider-cli.d.ts.map +1 -0
  8. package/dist/commands/doctor-provider-cli.js +215 -0
  9. package/dist/commands/doctor-provider-cli.js.map +1 -0
  10. package/dist/commands/doctor-provider-transport.d.ts +35 -0
  11. package/dist/commands/doctor-provider-transport.d.ts.map +1 -0
  12. package/dist/commands/doctor-provider-transport.js +185 -0
  13. package/dist/commands/doctor-provider-transport.js.map +1 -0
  14. package/dist/commands/doctor-provider.d.ts +121 -7
  15. package/dist/commands/doctor-provider.d.ts.map +1 -1
  16. package/dist/commands/doctor-provider.js +1295 -102
  17. package/dist/commands/doctor-provider.js.map +1 -1
  18. package/dist/commands/session-doctor-cli.d.ts +43 -0
  19. package/dist/commands/session-doctor-cli.d.ts.map +1 -0
  20. package/dist/commands/session-doctor-cli.js +494 -0
  21. package/dist/commands/session-doctor-cli.js.map +1 -0
  22. package/dist/commands/verify-ci.d.ts +21 -0
  23. package/dist/commands/verify-ci.d.ts.map +1 -0
  24. package/dist/commands/verify-ci.js +66 -0
  25. package/dist/commands/verify-ci.js.map +1 -0
  26. package/dist/core/agent-session-services.d.ts +3 -0
  27. package/dist/core/agent-session-services.d.ts.map +1 -1
  28. package/dist/core/agent-session-services.js +3 -0
  29. package/dist/core/agent-session-services.js.map +1 -1
  30. package/dist/core/agent-session.d.ts +113 -2
  31. package/dist/core/agent-session.d.ts.map +1 -1
  32. package/dist/core/agent-session.js +993 -72
  33. package/dist/core/agent-session.js.map +1 -1
  34. package/dist/core/agent-tool-settings.d.ts +35 -0
  35. package/dist/core/agent-tool-settings.d.ts.map +1 -0
  36. package/dist/core/agent-tool-settings.js +155 -0
  37. package/dist/core/agent-tool-settings.js.map +1 -0
  38. package/dist/core/atomic-session-file.d.ts +14 -0
  39. package/dist/core/atomic-session-file.d.ts.map +1 -0
  40. package/dist/core/atomic-session-file.js +104 -0
  41. package/dist/core/atomic-session-file.js.map +1 -0
  42. package/dist/core/compaction/compaction.d.ts +6 -0
  43. package/dist/core/compaction/compaction.d.ts.map +1 -1
  44. package/dist/core/compaction/compaction.js +12 -1
  45. package/dist/core/compaction/compaction.js.map +1 -1
  46. package/dist/core/compaction/hysteresis.d.ts +27 -0
  47. package/dist/core/compaction/hysteresis.d.ts.map +1 -0
  48. package/dist/core/compaction/hysteresis.js +84 -0
  49. package/dist/core/compaction/hysteresis.js.map +1 -0
  50. package/dist/core/compaction/index.d.ts +2 -0
  51. package/dist/core/compaction/index.d.ts.map +1 -1
  52. package/dist/core/compaction/index.js +2 -0
  53. package/dist/core/compaction/index.js.map +1 -1
  54. package/dist/core/compaction/transaction.d.ts +172 -0
  55. package/dist/core/compaction/transaction.d.ts.map +1 -0
  56. package/dist/core/compaction/transaction.js +824 -0
  57. package/dist/core/compaction/transaction.js.map +1 -0
  58. package/dist/core/context-budget-governor-v2.d.ts +2 -1
  59. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  60. package/dist/core/context-budget-governor-v2.js +2 -1
  61. package/dist/core/context-budget-governor-v2.js.map +1 -1
  62. package/dist/core/context-budget-reserved-tokens.d.ts +110 -0
  63. package/dist/core/context-budget-reserved-tokens.d.ts.map +1 -0
  64. package/dist/core/context-budget-reserved-tokens.js +188 -0
  65. package/dist/core/context-budget-reserved-tokens.js.map +1 -0
  66. package/dist/core/context-budget-v2-cache-invalidation.d.ts +133 -0
  67. package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -0
  68. package/dist/core/context-budget-v2-cache-invalidation.js +452 -0
  69. package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -0
  70. package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
  71. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  72. package/dist/core/context-budget-v2-cache-keys.js +6 -0
  73. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  74. package/dist/core/context-budget-v2-cache-provider.d.ts +4 -0
  75. package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
  76. package/dist/core/context-budget-v2-cache-provider.js +19 -0
  77. package/dist/core/context-budget-v2-cache-provider.js.map +1 -1
  78. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  79. package/dist/core/context-budget-v2-plan-cache-keys.js +1 -0
  80. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  81. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  82. package/dist/core/context-budget-v2-planner.js +1 -0
  83. package/dist/core/context-budget-v2-planner.js.map +1 -1
  84. package/dist/core/context-budget-v2-types.d.ts +4 -0
  85. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  86. package/dist/core/context-budget-v2-types.js.map +1 -1
  87. package/dist/core/durable-file-directory.d.ts +4 -0
  88. package/dist/core/durable-file-directory.d.ts.map +1 -0
  89. package/dist/core/durable-file-directory.js +76 -0
  90. package/dist/core/durable-file-directory.js.map +1 -0
  91. package/dist/core/durable-file-identity.d.ts +26 -0
  92. package/dist/core/durable-file-identity.d.ts.map +1 -0
  93. package/dist/core/durable-file-identity.js +118 -0
  94. package/dist/core/durable-file-identity.js.map +1 -0
  95. package/dist/core/durable-file-io.d.ts +29 -0
  96. package/dist/core/durable-file-io.d.ts.map +1 -0
  97. package/dist/core/durable-file-io.js +179 -0
  98. package/dist/core/durable-file-io.js.map +1 -0
  99. package/dist/core/durable-file-lock-cleanup.d.ts +12 -0
  100. package/dist/core/durable-file-lock-cleanup.d.ts.map +1 -0
  101. package/dist/core/durable-file-lock-cleanup.js +39 -0
  102. package/dist/core/durable-file-lock-cleanup.js.map +1 -0
  103. package/dist/core/durable-file-lock-owner.d.ts +18 -0
  104. package/dist/core/durable-file-lock-owner.d.ts.map +1 -0
  105. package/dist/core/durable-file-lock-owner.js +78 -0
  106. package/dist/core/durable-file-lock-owner.js.map +1 -0
  107. package/dist/core/durable-file-lock-storage.d.ts +35 -0
  108. package/dist/core/durable-file-lock-storage.d.ts.map +1 -0
  109. package/dist/core/durable-file-lock-storage.js +263 -0
  110. package/dist/core/durable-file-lock-storage.js.map +1 -0
  111. package/dist/core/durable-file-mode.d.ts +8 -0
  112. package/dist/core/durable-file-mode.d.ts.map +1 -0
  113. package/dist/core/durable-file-mode.js +41 -0
  114. package/dist/core/durable-file-mode.js.map +1 -0
  115. package/dist/core/extensions/loader.d.ts +12 -0
  116. package/dist/core/extensions/loader.d.ts.map +1 -1
  117. package/dist/core/extensions/loader.js +46 -37
  118. package/dist/core/extensions/loader.js.map +1 -1
  119. package/dist/core/index.d.ts +6 -2
  120. package/dist/core/index.d.ts.map +1 -1
  121. package/dist/core/index.js +5 -0
  122. package/dist/core/index.js.map +1 -1
  123. package/dist/core/model-resolver.d.ts.map +1 -1
  124. package/dist/core/model-resolver.js +1 -1
  125. package/dist/core/model-resolver.js.map +1 -1
  126. package/dist/core/redaction.d.ts +8 -0
  127. package/dist/core/redaction.d.ts.map +1 -0
  128. package/dist/core/redaction.js +31 -0
  129. package/dist/core/redaction.js.map +1 -0
  130. package/dist/core/resource-loader.d.ts.map +1 -1
  131. package/dist/core/resource-loader.js +36 -0
  132. package/dist/core/resource-loader.js.map +1 -1
  133. package/dist/core/run-journal-store.d.ts +53 -0
  134. package/dist/core/run-journal-store.d.ts.map +1 -0
  135. package/dist/core/run-journal-store.js +278 -0
  136. package/dist/core/run-journal-store.js.map +1 -0
  137. package/dist/core/run-journal.d.ts +172 -0
  138. package/dist/core/run-journal.d.ts.map +1 -0
  139. package/dist/core/run-journal.js +1053 -0
  140. package/dist/core/run-journal.js.map +1 -0
  141. package/dist/core/sdk.d.ts +7 -0
  142. package/dist/core/sdk.d.ts.map +1 -1
  143. package/dist/core/sdk.js +94 -3
  144. package/dist/core/sdk.js.map +1 -1
  145. package/dist/core/session-doctor-plan.d.ts +128 -0
  146. package/dist/core/session-doctor-plan.d.ts.map +1 -0
  147. package/dist/core/session-doctor-plan.js +833 -0
  148. package/dist/core/session-doctor-plan.js.map +1 -0
  149. package/dist/core/session-file-compaction.d.ts +6 -0
  150. package/dist/core/session-file-compaction.d.ts.map +1 -0
  151. package/dist/core/session-file-compaction.js +169 -0
  152. package/dist/core/session-file-compaction.js.map +1 -0
  153. package/dist/core/session-file-head.d.ts +10 -0
  154. package/dist/core/session-file-head.d.ts.map +1 -0
  155. package/dist/core/session-file-head.js +78 -0
  156. package/dist/core/session-file-head.js.map +1 -0
  157. package/dist/core/session-file-persistence.d.ts +30 -0
  158. package/dist/core/session-file-persistence.d.ts.map +1 -0
  159. package/dist/core/session-file-persistence.js +106 -0
  160. package/dist/core/session-file-persistence.js.map +1 -0
  161. package/dist/core/session-integrity.d.ts +43 -0
  162. package/dist/core/session-integrity.d.ts.map +1 -0
  163. package/dist/core/session-integrity.js +420 -0
  164. package/dist/core/session-integrity.js.map +1 -0
  165. package/dist/core/session-manager.d.ts +24 -4
  166. package/dist/core/session-manager.d.ts.map +1 -1
  167. package/dist/core/session-manager.js +242 -125
  168. package/dist/core/session-manager.js.map +1 -1
  169. package/dist/core/session-owner-lease.d.ts +22 -0
  170. package/dist/core/session-owner-lease.d.ts.map +1 -0
  171. package/dist/core/session-owner-lease.js +176 -0
  172. package/dist/core/session-owner-lease.js.map +1 -0
  173. package/dist/core/session-path-policy.d.ts +132 -0
  174. package/dist/core/session-path-policy.d.ts.map +1 -0
  175. package/dist/core/session-path-policy.js +780 -0
  176. package/dist/core/session-path-policy.js.map +1 -0
  177. package/dist/core/session-repair-plan.d.ts +49 -0
  178. package/dist/core/session-repair-plan.d.ts.map +1 -0
  179. package/dist/core/session-repair-plan.js +202 -0
  180. package/dist/core/session-repair-plan.js.map +1 -0
  181. package/dist/core/session-termination.d.ts +100 -0
  182. package/dist/core/session-termination.d.ts.map +1 -0
  183. package/dist/core/session-termination.js +403 -0
  184. package/dist/core/session-termination.js.map +1 -0
  185. package/dist/core/settings-manager.d.ts +22 -0
  186. package/dist/core/settings-manager.d.ts.map +1 -1
  187. package/dist/core/settings-manager.js +37 -5
  188. package/dist/core/settings-manager.js.map +1 -1
  189. package/dist/core/system-prompt.d.ts.map +1 -1
  190. package/dist/core/system-prompt.js +1 -1
  191. package/dist/core/system-prompt.js.map +1 -1
  192. package/dist/core/verified-bash-adapter.d.ts +36 -0
  193. package/dist/core/verified-bash-adapter.d.ts.map +1 -0
  194. package/dist/core/verified-bash-adapter.js +90 -0
  195. package/dist/core/verified-bash-adapter.js.map +1 -0
  196. package/dist/guardrails/command-redaction.d.ts +32 -0
  197. package/dist/guardrails/command-redaction.d.ts.map +1 -0
  198. package/dist/guardrails/command-redaction.js +776 -0
  199. package/dist/guardrails/command-redaction.js.map +1 -0
  200. package/dist/guardrails/evidence-attestation.d.ts +31 -0
  201. package/dist/guardrails/evidence-attestation.d.ts.map +1 -0
  202. package/dist/guardrails/evidence-attestation.js +178 -0
  203. package/dist/guardrails/evidence-attestation.js.map +1 -0
  204. package/dist/guardrails/evidence-receipt-store.d.ts +33 -0
  205. package/dist/guardrails/evidence-receipt-store.d.ts.map +1 -0
  206. package/dist/guardrails/evidence-receipt-store.js +446 -0
  207. package/dist/guardrails/evidence-receipt-store.js.map +1 -0
  208. package/dist/guardrails/evidence-receipt.d.ts +58 -0
  209. package/dist/guardrails/evidence-receipt.d.ts.map +1 -0
  210. package/dist/guardrails/evidence-receipt.js +389 -0
  211. package/dist/guardrails/evidence-receipt.js.map +1 -0
  212. package/dist/guardrails/evidence-system.d.ts +39 -15
  213. package/dist/guardrails/evidence-system.d.ts.map +1 -1
  214. package/dist/guardrails/evidence-system.js +255 -97
  215. package/dist/guardrails/evidence-system.js.map +1 -1
  216. package/dist/guardrails/replay-ledger-io.d.ts +6 -0
  217. package/dist/guardrails/replay-ledger-io.d.ts.map +1 -0
  218. package/dist/guardrails/replay-ledger-io.js +42 -0
  219. package/dist/guardrails/replay-ledger-io.js.map +1 -0
  220. package/dist/guardrails/replay-ledger-lock-owner.d.ts +32 -0
  221. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -0
  222. package/dist/guardrails/replay-ledger-lock-owner.js +138 -0
  223. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -0
  224. package/dist/guardrails/replay-ledger-lock.d.ts +24 -0
  225. package/dist/guardrails/replay-ledger-lock.d.ts.map +1 -0
  226. package/dist/guardrails/replay-ledger-lock.js +135 -0
  227. package/dist/guardrails/replay-ledger-lock.js.map +1 -0
  228. package/dist/guardrails/replay-ledger-mutation-gate.d.ts +24 -0
  229. package/dist/guardrails/replay-ledger-mutation-gate.d.ts.map +1 -0
  230. package/dist/guardrails/replay-ledger-mutation-gate.js +205 -0
  231. package/dist/guardrails/replay-ledger-mutation-gate.js.map +1 -0
  232. package/dist/guardrails/replay-ledger-quarantine.d.ts +2 -0
  233. package/dist/guardrails/replay-ledger-quarantine.d.ts.map +1 -0
  234. package/dist/guardrails/replay-ledger-quarantine.js +19 -0
  235. package/dist/guardrails/replay-ledger-quarantine.js.map +1 -0
  236. package/dist/guardrails/replay-ledger-store.d.ts +32 -0
  237. package/dist/guardrails/replay-ledger-store.d.ts.map +1 -0
  238. package/dist/guardrails/replay-ledger-store.js +211 -0
  239. package/dist/guardrails/replay-ledger-store.js.map +1 -0
  240. package/dist/guardrails/shell-command-name-glob.d.ts +3 -0
  241. package/dist/guardrails/shell-command-name-glob.d.ts.map +1 -0
  242. package/dist/guardrails/shell-command-name-glob.js +233 -0
  243. package/dist/guardrails/shell-command-name-glob.js.map +1 -0
  244. package/dist/guardrails/shell-command-option.d.ts +7 -0
  245. package/dist/guardrails/shell-command-option.d.ts.map +1 -0
  246. package/dist/guardrails/shell-command-option.js +24 -0
  247. package/dist/guardrails/shell-command-option.js.map +1 -0
  248. package/dist/guardrails/shell-command-tokenizer.d.ts +29 -0
  249. package/dist/guardrails/shell-command-tokenizer.d.ts.map +1 -0
  250. package/dist/guardrails/shell-command-tokenizer.js +145 -0
  251. package/dist/guardrails/shell-command-tokenizer.js.map +1 -0
  252. package/dist/guardrails/shell-command-wrapper.d.ts +18 -0
  253. package/dist/guardrails/shell-command-wrapper.d.ts.map +1 -0
  254. package/dist/guardrails/shell-command-wrapper.js +42 -0
  255. package/dist/guardrails/shell-command-wrapper.js.map +1 -0
  256. package/dist/guardrails/verified-executor.d.ts +65 -0
  257. package/dist/guardrails/verified-executor.d.ts.map +1 -0
  258. package/dist/guardrails/verified-executor.js +171 -0
  259. package/dist/guardrails/verified-executor.js.map +1 -0
  260. package/dist/guardrails/workspace-fingerprint.d.ts +23 -0
  261. package/dist/guardrails/workspace-fingerprint.d.ts.map +1 -0
  262. package/dist/guardrails/workspace-fingerprint.js +634 -0
  263. package/dist/guardrails/workspace-fingerprint.js.map +1 -0
  264. package/dist/index.d.ts +15 -3
  265. package/dist/index.d.ts.map +1 -1
  266. package/dist/index.js +13 -1
  267. package/dist/index.js.map +1 -1
  268. package/dist/main.d.ts.map +1 -1
  269. package/dist/main.js +12 -0
  270. package/dist/main.js.map +1 -1
  271. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  272. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  273. package/dist/modes/interactive/interactive-mode.js +59 -9
  274. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  275. package/dist/modes/print-mode.d.ts.map +1 -1
  276. package/dist/modes/print-mode.js +43 -2
  277. package/dist/modes/print-mode.js.map +1 -1
  278. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  279. package/dist/modes/rpc/rpc-mode.js +23 -5
  280. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  281. package/dist/modes/rpc/rpc-types.d.ts +3 -0
  282. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  283. package/dist/modes/rpc/rpc-types.js.map +1 -1
  284. package/dist/types/evidence.d.ts +211 -1
  285. package/dist/types/evidence.d.ts.map +1 -1
  286. package/dist/types/evidence.js.map +1 -1
  287. package/dist/verify-ci.d.ts +3 -0
  288. package/dist/verify-ci.d.ts.map +1 -0
  289. package/dist/verify-ci.js +30 -0
  290. package/dist/verify-ci.js.map +1 -0
  291. package/docs/compaction.md +38 -4
  292. package/docs/extensions.md +2 -0
  293. package/docs/sdk.md +238 -1
  294. package/docs/settings.md +50 -2
  295. package/docs/usage.md +38 -0
  296. package/examples/extensions/custom-provider-anthropic/package-lock.json +5 -5
  297. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  298. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  299. package/examples/extensions/gondolin/package-lock.json +2 -2
  300. package/examples/extensions/gondolin/package.json +1 -1
  301. package/examples/extensions/sandbox/package-lock.json +2 -2
  302. package/examples/extensions/sandbox/package.json +1 -1
  303. package/examples/extensions/with-deps/package-lock.json +4 -4
  304. package/examples/extensions/with-deps/package.json +1 -1
  305. package/npm-shrinkwrap.json +12 -12
  306. package/package.json +4 -4
@@ -0,0 +1,1053 @@
1
+ /**
2
+ * Pure, browser-safe run-journal format and complete-prefix integrity inspector.
3
+ *
4
+ * Runtime purity: this module performs no I/O and uses no Node-specific
5
+ * primitives. It relies only on `TextEncoder`/`TextDecoder`, `JSON`, the pure
6
+ * `Date` string parser, and ordinary language built-ins. It consults no wall
7
+ * clock, draws no entropy, invokes no cryptographic primitives, and mutates no
8
+ * global state. Every record hash is recomputed from a caller-injected
9
+ * synchronous `RunJournalHashFn`, both when records are constructed by
10
+ * {@link RunJournal} and when raw bytes are inspected by
11
+ * {@link inspectRunJournal}. There is no opaque mode that skips hashing.
12
+ */
13
+ import { assertPreRedactedTerminationMessage, SESSION_TERMINATION_SCHEMA_VERSION, } from "./session-termination.js";
14
+ export const RUN_JOURNAL_SCHEMA_VERSION = 1;
15
+ /** Sixty-four zero bits; the `prevHash` of the first record in every chain. */
16
+ export const GENESIS_HASH = "0".repeat(64);
17
+ export const MAX_RUN_JOURNAL_ID_LENGTH = 128;
18
+ const HASH64 = /^[0-9a-f]{64}$/;
19
+ const TEXT_ENCODER = new TextEncoder();
20
+ const TEXT_DECODER = new TextDecoder("utf-8", { fatal: true });
21
+ const RUN_JOURNAL_EVENTS = new Set([
22
+ "run_started",
23
+ "run_finished",
24
+ "run_recovered",
25
+ "run_abandoned",
26
+ "tool_timeout",
27
+ "tool_late_settlement",
28
+ "transcript_repaired",
29
+ ]);
30
+ const TERMINAL_EVENTS = new Set([
31
+ "run_finished",
32
+ "run_recovered",
33
+ "run_abandoned",
34
+ ]);
35
+ const AUDIT_EVENTS = new Set([
36
+ "tool_timeout",
37
+ "tool_late_settlement",
38
+ "transcript_repaired",
39
+ ]);
40
+ const AUDIT_DETAIL_KEYS = [
41
+ "toolCallId",
42
+ "toolName",
43
+ "disposition",
44
+ "outcome",
45
+ "timeoutMs",
46
+ "executionStarted",
47
+ "insertedToolCallIds",
48
+ "reason",
49
+ "sessionRisk",
50
+ ];
51
+ const AUDIT_DISPOSITIONS = new Set([
52
+ "completed",
53
+ "failed",
54
+ "blocked",
55
+ "aborted",
56
+ "timeout",
57
+ "skipped",
58
+ ]);
59
+ const AUDIT_OUTCOMES = new Set(["resolved", "rejected"]);
60
+ const MAX_AUDIT_INSERTED_IDS = 64;
61
+ const MAX_AUDIT_REASON_LENGTH = 512;
62
+ const COMMON_RECORD_KEYS = [
63
+ "schemaVersion",
64
+ "seq",
65
+ "event",
66
+ "runId",
67
+ "sessionId",
68
+ "sessionRevision",
69
+ "timestamp",
70
+ "prevHash",
71
+ "hash",
72
+ ];
73
+ const SESSION_TERMINATION_KINDS = new Set([
74
+ "completed",
75
+ "user_abort",
76
+ "provider_abort",
77
+ "provider_auth",
78
+ "provider_rate_limit",
79
+ "provider_network",
80
+ "provider_protocol",
81
+ "context_overflow",
82
+ "tool_timeout",
83
+ "tool_fatal",
84
+ "compaction",
85
+ "persistence",
86
+ "process_signal",
87
+ "process_crash",
88
+ "transcript_invalid",
89
+ "configuration",
90
+ "internal_error",
91
+ ]);
92
+ const SESSION_TERMINATION_PHASES = new Set([
93
+ "completed",
94
+ "control",
95
+ "preflight",
96
+ "provider",
97
+ "tool",
98
+ "compaction",
99
+ "persistence",
100
+ "process",
101
+ "resume",
102
+ ]);
103
+ const SESSION_TERMINATION_SOURCES = new Set(["observed", "inferred_on_resume"]);
104
+ const SESSION_SIDE_EFFECTS = new Set(["none", "possible", "confirmed"]);
105
+ const SESSION_PROCESS_SIGNALS = new Set(["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"]);
106
+ const SESSION_TRANSCRIPT_ISSUES = new Set([
107
+ "missing_result",
108
+ "duplicate_result",
109
+ "orphan_result",
110
+ "duplicate_call_id",
111
+ "interleaved_non_result",
112
+ "invalid_jsonl",
113
+ "invalid_tree",
114
+ "unsupported_version",
115
+ "trailing_fragment",
116
+ ]);
117
+ const TERMINATION_ALLOWED_KEYS = [
118
+ "schemaVersion",
119
+ "sessionId",
120
+ "runId",
121
+ "kind",
122
+ "phase",
123
+ "source",
124
+ "message",
125
+ "causeCode",
126
+ "nextAction",
127
+ "retryable",
128
+ "safeToAutoRetry",
129
+ "sideEffects",
130
+ "timestamp",
131
+ "retryAfterMs",
132
+ "provider",
133
+ "model",
134
+ "toolCallId",
135
+ "toolName",
136
+ "processSignal",
137
+ "transcriptIssue",
138
+ ];
139
+ /** High-confidence credential shapes; rejected at every trust boundary. */
140
+ const CREDENTIAL_SHAPES = [
141
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/i,
142
+ /\bsk-[A-Za-z0-9_-]{16,}\b/,
143
+ /\bgh[pousr]_[A-Za-z0-9]{16,}\b/,
144
+ /\bgithub_pat_[A-Za-z0-9_]{16,}\b/,
145
+ /\bxox[baprs]-[A-Za-z0-9-]{16,}\b/,
146
+ /\bAKIA[A-Z0-9]{16}\b/,
147
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/,
148
+ /\bBearer\s+[A-Za-z0-9._~+/-]{8,}={0,2}\b/i,
149
+ /(?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]+[_-])*(?:authorization|api[-_ ]?key|access[-_ ]?token|token|password|secret(?:[-_ ]?key)?|private[-_ ]?key|cookie)["']?\s*[:=]/i,
150
+ ];
151
+ function isPlainObject(value) {
152
+ return typeof value === "object" && value !== null && !Array.isArray(value);
153
+ }
154
+ /**
155
+ * Asserts `value` is a plain data record: an object (never an array) with a
156
+ * plain prototype, only data (non-accessor) own properties, and no keys outside
157
+ * `allowedKeys`. Descriptors are inspected without ever executing a getter, so
158
+ * raw accessor execution is never exposed at the trust boundary. Returns void;
159
+ * it narrows nothing so typed callers keep their static field types.
160
+ */
161
+ function assertPlainDataRecord(value, name, allowedKeys) {
162
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
163
+ throw new TypeError(`${name} must be a plain object`);
164
+ }
165
+ const prototype = Object.getPrototypeOf(value);
166
+ if (prototype !== null && prototype !== Object.prototype) {
167
+ throw new TypeError(`${name} must be a plain object`);
168
+ }
169
+ const descriptors = Object.getOwnPropertyDescriptors(value);
170
+ for (const key of Object.keys(descriptors)) {
171
+ const descriptor = descriptors[key];
172
+ if (descriptor !== undefined && !("value" in descriptor)) {
173
+ throw new TypeError(`${name} must not define accessor properties`);
174
+ }
175
+ if (!allowedKeys.includes(key)) {
176
+ throw new TypeError(`${name} must contain only bounded fields`);
177
+ }
178
+ }
179
+ }
180
+ function looksLikeCredential(value) {
181
+ return CREDENTIAL_SHAPES.some((pattern) => pattern.test(value));
182
+ }
183
+ function isBoundedIdentifier(value) {
184
+ return (typeof value === "string" &&
185
+ value.length > 0 &&
186
+ value.length <= MAX_RUN_JOURNAL_ID_LENGTH &&
187
+ !/[\u0000-\u001f\u007f]/.test(value));
188
+ }
189
+ function assertIdentifier(value, name, maxLength) {
190
+ if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
191
+ throw new TypeError(`${name} must be 1-${maxLength} characters of bounded text`);
192
+ }
193
+ if (/[\u0000-\u001f\u007f]/.test(value)) {
194
+ throw new TypeError(`${name} must not contain C0 or DEL control characters`);
195
+ }
196
+ }
197
+ function rejectCredential(value, name) {
198
+ if (looksLikeCredential(value)) {
199
+ throw new TypeError(`${name} contains a credential-shaped literal`);
200
+ }
201
+ }
202
+ function isCanonicalTimestamp(value) {
203
+ if (typeof value !== "string")
204
+ return false;
205
+ const parsed = new Date(value);
206
+ return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
207
+ }
208
+ function assertCanonicalTimestamp(value, name) {
209
+ if (!isCanonicalTimestamp(value)) {
210
+ throw new TypeError(`${name} must be a canonical ISO-8601 UTC instant`);
211
+ }
212
+ }
213
+ function assertSafeNonNegativeInt(value, name) {
214
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
215
+ throw new TypeError(`${name} must be a non-negative safe integer`);
216
+ }
217
+ }
218
+ function isValidHashShape(value) {
219
+ return typeof value === "string" && HASH64.test(value);
220
+ }
221
+ function assertHashShape(value, name) {
222
+ if (!isValidHashShape(value)) {
223
+ throw new TypeError(`${name} must be 64 lowercase hex characters`);
224
+ }
225
+ }
226
+ function deepFreeze(value) {
227
+ if (typeof value !== "object" || value === null || Object.isFrozen(value))
228
+ return value;
229
+ for (const child of Object.values(value))
230
+ deepFreeze(child);
231
+ return Object.freeze(value);
232
+ }
233
+ /** Recursively key-sorted JSON serialization. Deterministic across runtimes and key insertion order. */
234
+ export function canonicalJson(value) {
235
+ if (value === null)
236
+ return "null";
237
+ if (Array.isArray(value))
238
+ return `[${value.map(canonicalJson).join(",")}]`;
239
+ if (typeof value === "object") {
240
+ const object = value;
241
+ const parts = [];
242
+ for (const key of Object.keys(object).sort()) {
243
+ const child = object[key];
244
+ if (child === undefined)
245
+ continue;
246
+ parts.push(`${JSON.stringify(key)}:${canonicalJson(child)}`);
247
+ }
248
+ return `{${parts.join(",")}}`;
249
+ }
250
+ return JSON.stringify(value);
251
+ }
252
+ /** Defensively drop a stray `hash` key so the material never includes it, regardless of caller. */
253
+ function stripHashKey(record) {
254
+ if (typeof record !== "object" || record === null)
255
+ return {};
256
+ const clone = {};
257
+ for (const [key, value] of Object.entries(record)) {
258
+ if (key !== "hash")
259
+ clone[key] = value;
260
+ }
261
+ return clone;
262
+ }
263
+ /** Canonical material of a record (everything except `hash`) over which the hash is computed. */
264
+ export function serializeRunJournalMaterial(record) {
265
+ return canonicalJson(stripHashKey(record));
266
+ }
267
+ /** Canonical, key-sorted serialization of a full record (including `hash`). */
268
+ export function serializeRunJournalLine(record) {
269
+ return canonicalJson(record);
270
+ }
271
+ function assertBoundedSessionTermination(value, expectedRunId, expectedSessionId) {
272
+ assertPlainDataRecord(value, "termination", TERMINATION_ALLOWED_KEYS);
273
+ const v = value;
274
+ if (v.schemaVersion !== SESSION_TERMINATION_SCHEMA_VERSION) {
275
+ throw new TypeError("termination schemaVersion must match the supported version");
276
+ }
277
+ if (typeof v.runId !== "string" || v.runId !== expectedRunId) {
278
+ throw new TypeError("termination runId must match the record runId");
279
+ }
280
+ if (typeof v.sessionId !== "string" || v.sessionId !== expectedSessionId) {
281
+ throw new TypeError("termination sessionId must match the record sessionId");
282
+ }
283
+ if (typeof v.kind !== "string" || !SESSION_TERMINATION_KINDS.has(v.kind)) {
284
+ throw new TypeError("termination kind is out of bounds");
285
+ }
286
+ if (typeof v.phase !== "string" || !SESSION_TERMINATION_PHASES.has(v.phase)) {
287
+ throw new TypeError("termination phase is out of bounds");
288
+ }
289
+ if (typeof v.source !== "string" || !SESSION_TERMINATION_SOURCES.has(v.source)) {
290
+ throw new TypeError("termination source is out of bounds");
291
+ }
292
+ if (typeof v.message !== "string") {
293
+ throw new TypeError("termination message must be a string");
294
+ }
295
+ assertPreRedactedTerminationMessage(v.message);
296
+ if (typeof v.causeCode !== "string" || !/^[a-z_]+\.[a-z_]+$/.test(v.causeCode)) {
297
+ throw new TypeError("termination causeCode is out of bounds");
298
+ }
299
+ if (typeof v.nextAction !== "string") {
300
+ throw new TypeError("termination nextAction must be a string");
301
+ }
302
+ assertPreRedactedTerminationMessage(v.nextAction);
303
+ if (typeof v.retryable !== "boolean" || typeof v.safeToAutoRetry !== "boolean") {
304
+ throw new TypeError("termination retry flags must be booleans");
305
+ }
306
+ if (typeof v.sideEffects !== "string" || !SESSION_SIDE_EFFECTS.has(v.sideEffects)) {
307
+ throw new TypeError("termination sideEffects is out of bounds");
308
+ }
309
+ assertCanonicalTimestamp(v.timestamp, "termination.timestamp");
310
+ if (v.retryAfterMs !== undefined)
311
+ assertSafeNonNegativeInt(v.retryAfterMs, "termination.retryAfterMs");
312
+ if (v.provider !== undefined) {
313
+ assertIdentifier(v.provider, "termination.provider", 128);
314
+ rejectCredential(v.provider, "termination.provider");
315
+ }
316
+ if (v.model !== undefined) {
317
+ assertIdentifier(v.model, "termination.model", 256);
318
+ rejectCredential(v.model, "termination.model");
319
+ }
320
+ if (v.toolCallId !== undefined) {
321
+ assertIdentifier(v.toolCallId, "termination.toolCallId", 512);
322
+ rejectCredential(v.toolCallId, "termination.toolCallId");
323
+ }
324
+ if (v.toolName !== undefined) {
325
+ assertIdentifier(v.toolName, "termination.toolName", 128);
326
+ rejectCredential(v.toolName, "termination.toolName");
327
+ }
328
+ if (v.processSignal !== undefined) {
329
+ if (typeof v.processSignal !== "string" || !SESSION_PROCESS_SIGNALS.has(v.processSignal)) {
330
+ throw new TypeError("termination processSignal is out of bounds");
331
+ }
332
+ }
333
+ if (v.transcriptIssue !== undefined) {
334
+ if (typeof v.transcriptIssue !== "string" || !SESSION_TRANSCRIPT_ISSUES.has(v.transcriptIssue)) {
335
+ throw new TypeError("termination transcriptIssue is out of bounds");
336
+ }
337
+ }
338
+ assertTerminationCoherence(v);
339
+ }
340
+ function assertTerminationCoherence(termination) {
341
+ const dot = termination.causeCode.indexOf(".");
342
+ const area = dot >= 0 ? termination.causeCode.slice(0, dot) : "";
343
+ const suffix = dot >= 0 ? termination.causeCode.slice(dot + 1) : "";
344
+ let expectedKind;
345
+ let expectedPhase;
346
+ let expectedRetryable;
347
+ let processSignalRequired = false;
348
+ let transcriptRequired = false;
349
+ let transcriptSuffix = null;
350
+ switch (area) {
351
+ case "session":
352
+ if (suffix === "completed") {
353
+ expectedKind = "completed";
354
+ expectedPhase = "completed";
355
+ expectedRetryable = false;
356
+ }
357
+ else if (suffix === "user_abort") {
358
+ expectedKind = "user_abort";
359
+ expectedPhase = "control";
360
+ expectedRetryable = false;
361
+ }
362
+ else {
363
+ throw new TypeError("termination causeCode is incoherent");
364
+ }
365
+ break;
366
+ case "provider":
367
+ switch (suffix) {
368
+ case "abort":
369
+ expectedKind = "provider_abort";
370
+ expectedPhase = "provider";
371
+ expectedRetryable = true;
372
+ break;
373
+ case "auth":
374
+ expectedKind = "provider_auth";
375
+ expectedPhase = "provider";
376
+ expectedRetryable = false;
377
+ break;
378
+ case "rate_limit":
379
+ expectedKind = "provider_rate_limit";
380
+ expectedPhase = "provider";
381
+ expectedRetryable = true;
382
+ break;
383
+ case "network":
384
+ expectedKind = "provider_network";
385
+ expectedPhase = "provider";
386
+ expectedRetryable = true;
387
+ break;
388
+ case "protocol":
389
+ expectedKind = "provider_protocol";
390
+ expectedPhase = "provider";
391
+ expectedRetryable = false;
392
+ break;
393
+ case "context_overflow":
394
+ expectedKind = "context_overflow";
395
+ expectedPhase = "provider";
396
+ expectedRetryable = true;
397
+ break;
398
+ default:
399
+ throw new TypeError("termination causeCode is incoherent");
400
+ }
401
+ break;
402
+ case "tool":
403
+ if (suffix === "timeout") {
404
+ expectedKind = "tool_timeout";
405
+ expectedPhase = "tool";
406
+ expectedRetryable = true;
407
+ }
408
+ else if (suffix === "fatal") {
409
+ expectedKind = "tool_fatal";
410
+ expectedPhase = "tool";
411
+ expectedRetryable = false;
412
+ }
413
+ else {
414
+ throw new TypeError("termination causeCode is incoherent");
415
+ }
416
+ break;
417
+ case "compaction":
418
+ if (suffix === "aborted" || suffix === "failed" || suffix === "stale") {
419
+ expectedKind = "compaction";
420
+ expectedPhase = "compaction";
421
+ expectedRetryable = true;
422
+ }
423
+ else {
424
+ throw new TypeError("termination causeCode is incoherent");
425
+ }
426
+ break;
427
+ case "persistence":
428
+ if (suffix === "read_failed" ||
429
+ suffix === "append_failed" ||
430
+ suffix === "replace_failed" ||
431
+ suffix === "fsync_failed" ||
432
+ suffix === "lock_failed") {
433
+ expectedKind = "persistence";
434
+ expectedPhase = "persistence";
435
+ expectedRetryable = true;
436
+ }
437
+ else {
438
+ throw new TypeError("termination causeCode is incoherent");
439
+ }
440
+ break;
441
+ case "process":
442
+ if (suffix === "signal") {
443
+ expectedKind = "process_signal";
444
+ expectedPhase = "process";
445
+ expectedRetryable = false;
446
+ processSignalRequired = true;
447
+ }
448
+ else if (suffix === "crash") {
449
+ expectedKind = "process_crash";
450
+ expectedPhase = "resume";
451
+ expectedRetryable = true;
452
+ }
453
+ else {
454
+ throw new TypeError("termination causeCode is incoherent");
455
+ }
456
+ break;
457
+ case "transcript":
458
+ if (SESSION_TRANSCRIPT_ISSUES.has(suffix)) {
459
+ expectedKind = "transcript_invalid";
460
+ expectedPhase = termination.source === "inferred_on_resume" ? "resume" : "preflight";
461
+ expectedRetryable = false;
462
+ transcriptRequired = true;
463
+ transcriptSuffix = suffix;
464
+ }
465
+ else {
466
+ throw new TypeError("termination causeCode is incoherent");
467
+ }
468
+ break;
469
+ case "configuration":
470
+ if (suffix === "invalid") {
471
+ expectedKind = "configuration";
472
+ expectedPhase = "preflight";
473
+ expectedRetryable = false;
474
+ }
475
+ else {
476
+ throw new TypeError("termination causeCode is incoherent");
477
+ }
478
+ break;
479
+ case "internal":
480
+ if (suffix === "unclassified") {
481
+ expectedKind = "internal_error";
482
+ expectedPhase = "preflight";
483
+ expectedRetryable = false;
484
+ }
485
+ else {
486
+ throw new TypeError("termination causeCode is incoherent");
487
+ }
488
+ break;
489
+ default:
490
+ throw new TypeError("termination causeCode is incoherent");
491
+ }
492
+ if (termination.kind !== expectedKind) {
493
+ throw new TypeError("termination kind is incoherent with causeCode");
494
+ }
495
+ if (termination.phase !== expectedPhase) {
496
+ throw new TypeError("termination phase is incoherent with causeCode");
497
+ }
498
+ if (termination.retryable !== expectedRetryable) {
499
+ throw new TypeError("termination retryable flag is incoherent with causeCode");
500
+ }
501
+ if (processSignalRequired) {
502
+ if (termination.processSignal === undefined) {
503
+ throw new TypeError("termination processSignal is required for signal causes");
504
+ }
505
+ }
506
+ else if (termination.processSignal !== undefined) {
507
+ throw new TypeError("termination processSignal is only valid for signal causes");
508
+ }
509
+ if (transcriptRequired) {
510
+ if (termination.transcriptIssue === undefined) {
511
+ throw new TypeError("termination transcriptIssue is required for transcript causes");
512
+ }
513
+ else if (termination.transcriptIssue !== transcriptSuffix) {
514
+ throw new TypeError("termination transcriptIssue must match the transcript cause");
515
+ }
516
+ }
517
+ else if (termination.transcriptIssue !== undefined) {
518
+ throw new TypeError("termination transcriptIssue is only valid for transcript causes");
519
+ }
520
+ const expectedSafeToAutoRetry = termination.retryable &&
521
+ termination.source === "observed" &&
522
+ termination.sideEffects === "none" &&
523
+ (termination.kind === "provider_rate_limit" || termination.kind === "provider_network");
524
+ if (termination.safeToAutoRetry !== expectedSafeToAutoRetry) {
525
+ throw new TypeError("termination safeToAutoRetry flag is incoherent");
526
+ }
527
+ }
528
+ function assertBoundedAuditDetails(value) {
529
+ assertPlainDataRecord(value, "audit details", AUDIT_DETAIL_KEYS);
530
+ const v = value;
531
+ if (v.toolCallId !== undefined) {
532
+ assertIdentifier(v.toolCallId, "details.toolCallId", 512);
533
+ rejectCredential(v.toolCallId, "details.toolCallId");
534
+ }
535
+ if (v.toolName !== undefined) {
536
+ assertIdentifier(v.toolName, "details.toolName", 128);
537
+ rejectCredential(v.toolName, "details.toolName");
538
+ }
539
+ if (v.disposition !== undefined && (typeof v.disposition !== "string" || !AUDIT_DISPOSITIONS.has(v.disposition))) {
540
+ throw new TypeError("details.disposition is out of bounds");
541
+ }
542
+ if (v.outcome !== undefined && (typeof v.outcome !== "string" || !AUDIT_OUTCOMES.has(v.outcome))) {
543
+ throw new TypeError("details.outcome is out of bounds");
544
+ }
545
+ if (v.timeoutMs !== undefined) {
546
+ assertSafeNonNegativeInt(v.timeoutMs, "details.timeoutMs");
547
+ }
548
+ if (v.executionStarted !== undefined && typeof v.executionStarted !== "boolean") {
549
+ throw new TypeError("details.executionStarted must be a boolean");
550
+ }
551
+ if (v.insertedToolCallIds !== undefined) {
552
+ if (!Array.isArray(v.insertedToolCallIds) || v.insertedToolCallIds.length > MAX_AUDIT_INSERTED_IDS) {
553
+ throw new TypeError("details.insertedToolCallIds must be a bounded array");
554
+ }
555
+ for (const id of v.insertedToolCallIds) {
556
+ assertIdentifier(id, "details.insertedToolCallIds[]", 512);
557
+ rejectCredential(id, "details.insertedToolCallIds[]");
558
+ }
559
+ }
560
+ if (v.reason !== undefined) {
561
+ assertIdentifier(v.reason, "details.reason", MAX_AUDIT_REASON_LENGTH);
562
+ rejectCredential(v.reason, "details.reason");
563
+ }
564
+ if (v.sessionRisk !== undefined && v.sessionRisk !== "elevated") {
565
+ throw new TypeError("details.sessionRisk is out of bounds");
566
+ }
567
+ }
568
+ function isTerminalCoherentWithEvent(event, termination) {
569
+ if (event === "run_recovered") {
570
+ return (termination.source === "inferred_on_resume" &&
571
+ termination.kind === "process_crash" &&
572
+ termination.phase === "resume");
573
+ }
574
+ if (termination.source !== "observed")
575
+ return false;
576
+ // process_crash is only coherent with an inferred_on_resume (run_recovered) source.
577
+ if (termination.kind === "process_crash")
578
+ return false;
579
+ return true;
580
+ }
581
+ function assertTerminalCoherentWithEvent(event, termination) {
582
+ if (isTerminalCoherentWithEvent(event, termination))
583
+ return;
584
+ if (event === "run_recovered") {
585
+ throw new TypeError("run_recovered requires a coherent process_crash termination (source inferred_on_resume, phase resume)");
586
+ }
587
+ if (termination.kind === "process_crash") {
588
+ throw new TypeError("process_crash terminations may only appear on run_recovered events");
589
+ }
590
+ throw new TypeError("run_finished and run_abandoned require a termination source of observed");
591
+ }
592
+ /**
593
+ * Append-only run-journal writer. The constructor records the first
594
+ * `run_started` event (and recomputes its hash); {@link RunJournal.start} opens
595
+ * subsequent runs after a terminal event, while {@link RunJournal.finish},
596
+ * {@link RunJournal.abandon}, and {@link RunJournal.recover} close the currently
597
+ * open run. Exactly one run is open at a time.
598
+ */
599
+ export class RunJournal {
600
+ hashFn;
601
+ sessionId;
602
+ startedRunIds = new Set();
603
+ terminatedRunIds = new Set();
604
+ mutableRecords = [];
605
+ recordsSnapshot = Object.freeze([]);
606
+ lastSessionRevision = 0;
607
+ prevHash;
608
+ nextSeq;
609
+ openRun = null;
610
+ constructor(init) {
611
+ assertPlainDataRecord(init, "RunJournalConstructorInit", [
612
+ "hashFn",
613
+ "sessionId",
614
+ "runId",
615
+ "sessionRevision",
616
+ "timestamp",
617
+ "prevHash",
618
+ "seq",
619
+ "openInitialRun",
620
+ ]);
621
+ if (init.openInitialRun !== undefined && typeof init.openInitialRun !== "boolean") {
622
+ throw new TypeError("openInitialRun must be a boolean");
623
+ }
624
+ if (typeof init.hashFn !== "function") {
625
+ throw new TypeError("hashFn must be a function");
626
+ }
627
+ this.hashFn = init.hashFn;
628
+ assertIdentifier(init.sessionId, "sessionId", MAX_RUN_JOURNAL_ID_LENGTH);
629
+ rejectCredential(init.sessionId, "sessionId");
630
+ this.sessionId = init.sessionId;
631
+ const startSeq = init.seq ?? 0;
632
+ assertSafeNonNegativeInt(startSeq, "seq");
633
+ const startPrevHash = init.prevHash ?? GENESIS_HASH;
634
+ assertHashShape(startPrevHash, "prevHash");
635
+ this.prevHash = startPrevHash;
636
+ this.nextSeq = startSeq;
637
+ if (init.openInitialRun !== false) {
638
+ this.appendStarted({
639
+ runId: init.runId,
640
+ sessionRevision: init.sessionRevision,
641
+ timestamp: init.timestamp,
642
+ });
643
+ }
644
+ else {
645
+ // Audit-first journal: validate the seed runId shape, append nothing.
646
+ assertIdentifier(init.runId, "runId", MAX_RUN_JOURNAL_ID_LENGTH);
647
+ rejectCredential(init.runId, "runId");
648
+ }
649
+ }
650
+ start(input) {
651
+ return this.appendStarted(input);
652
+ }
653
+ finish(input) {
654
+ return this.appendTerminal("run_finished", input);
655
+ }
656
+ abandon(input) {
657
+ return this.appendTerminal("run_abandoned", input);
658
+ }
659
+ recover(input) {
660
+ return this.appendTerminal("run_recovered", input);
661
+ }
662
+ /**
663
+ * Append an audit-only record. Allowed with or without an open run; never
664
+ * opens, closes, or otherwise mutates run state. The record joins the same
665
+ * seq/hash chain as lifecycle records.
666
+ */
667
+ audit(input) {
668
+ assertPlainDataRecord(input, "RunJournalAuditInput", ["event", "details", "sessionRevision", "timestamp"]);
669
+ if (typeof input.event !== "string" || !AUDIT_EVENTS.has(input.event)) {
670
+ throw new TypeError("audit event is out of bounds");
671
+ }
672
+ assertSafeNonNegativeInt(input.sessionRevision, "sessionRevision");
673
+ if (input.sessionRevision < this.lastSessionRevision) {
674
+ throw new Error("sessionRevision must not regress across the journal");
675
+ }
676
+ assertCanonicalTimestamp(input.timestamp, "timestamp");
677
+ assertBoundedAuditDetails(input.details);
678
+ const base = {
679
+ schemaVersion: RUN_JOURNAL_SCHEMA_VERSION,
680
+ seq: this.nextSeq,
681
+ event: input.event,
682
+ runId: this.openRun === null ? "none" : this.openRun.runId,
683
+ sessionId: this.sessionId,
684
+ sessionRevision: input.sessionRevision,
685
+ timestamp: input.timestamp,
686
+ prevHash: this.prevHash,
687
+ details: input.details,
688
+ };
689
+ const record = this.seal(base);
690
+ this.mutableRecords.push(record);
691
+ this.prevHash = record.hash;
692
+ this.nextSeq += 1;
693
+ this.lastSessionRevision = input.sessionRevision;
694
+ this.refreshRecordsSnapshot();
695
+ return record;
696
+ }
697
+ get records() {
698
+ return this.recordsSnapshot;
699
+ }
700
+ get openRunId() {
701
+ return this.openRun === null ? null : this.openRun.runId;
702
+ }
703
+ serialize() {
704
+ if (this.mutableRecords.length === 0)
705
+ return "";
706
+ return `${this.mutableRecords.map((record) => serializeRunJournalLine(record)).join("\n")}\n`;
707
+ }
708
+ toBytes() {
709
+ return TEXT_ENCODER.encode(this.serialize());
710
+ }
711
+ refreshRecordsSnapshot() {
712
+ this.recordsSnapshot = Object.freeze([...this.mutableRecords]);
713
+ }
714
+ appendStarted(input) {
715
+ assertPlainDataRecord(input, "RunJournalStartInput", ["runId", "sessionRevision", "timestamp"]);
716
+ assertIdentifier(input.runId, "runId", MAX_RUN_JOURNAL_ID_LENGTH);
717
+ rejectCredential(input.runId, "runId");
718
+ assertSafeNonNegativeInt(input.sessionRevision, "sessionRevision");
719
+ if (input.sessionRevision < this.lastSessionRevision) {
720
+ throw new Error("sessionRevision must not regress across the journal");
721
+ }
722
+ assertCanonicalTimestamp(input.timestamp, "timestamp");
723
+ if (this.openRun !== null) {
724
+ throw new Error("a run is already open; terminate it before starting another");
725
+ }
726
+ if (this.startedRunIds.has(input.runId)) {
727
+ throw new Error("runId was already started earlier in this journal");
728
+ }
729
+ const base = {
730
+ schemaVersion: RUN_JOURNAL_SCHEMA_VERSION,
731
+ seq: this.nextSeq,
732
+ event: "run_started",
733
+ runId: input.runId,
734
+ sessionId: this.sessionId,
735
+ sessionRevision: input.sessionRevision,
736
+ timestamp: input.timestamp,
737
+ prevHash: this.prevHash,
738
+ };
739
+ const record = this.seal(base);
740
+ this.mutableRecords.push(record);
741
+ this.startedRunIds.add(input.runId);
742
+ this.openRun = Object.freeze({ runId: input.runId, sessionId: this.sessionId });
743
+ this.prevHash = record.hash;
744
+ this.nextSeq += 1;
745
+ this.lastSessionRevision = input.sessionRevision;
746
+ this.refreshRecordsSnapshot();
747
+ return record;
748
+ }
749
+ appendTerminal(event, input) {
750
+ assertPlainDataRecord(input, "RunJournalTerminalInput", ["termination", "sessionRevision", "timestamp"]);
751
+ if (this.openRun === null) {
752
+ throw new Error("no run is open; start a run before terminating it");
753
+ }
754
+ assertSafeNonNegativeInt(input.sessionRevision, "sessionRevision");
755
+ if (input.sessionRevision < this.lastSessionRevision) {
756
+ throw new Error("sessionRevision must not regress across the journal");
757
+ }
758
+ assertCanonicalTimestamp(input.timestamp, "timestamp");
759
+ assertBoundedSessionTermination(input.termination, this.openRun.runId, this.sessionId);
760
+ assertTerminalCoherentWithEvent(event, input.termination);
761
+ if (input.timestamp !== input.termination.timestamp) {
762
+ throw new TypeError("terminal event timestamp must equal termination timestamp");
763
+ }
764
+ if (this.terminatedRunIds.has(this.openRun.runId)) {
765
+ throw new Error("the open run was already terminated");
766
+ }
767
+ const base = {
768
+ schemaVersion: RUN_JOURNAL_SCHEMA_VERSION,
769
+ seq: this.nextSeq,
770
+ event,
771
+ runId: this.openRun.runId,
772
+ sessionId: this.sessionId,
773
+ sessionRevision: input.sessionRevision,
774
+ timestamp: input.timestamp,
775
+ prevHash: this.prevHash,
776
+ termination: input.termination,
777
+ };
778
+ const record = this.seal(base);
779
+ this.mutableRecords.push(record);
780
+ this.terminatedRunIds.add(this.openRun.runId);
781
+ this.openRun = null;
782
+ this.prevHash = record.hash;
783
+ this.nextSeq += 1;
784
+ this.lastSessionRevision = input.sessionRevision;
785
+ this.refreshRecordsSnapshot();
786
+ return record;
787
+ }
788
+ seal(base) {
789
+ const material = serializeRunJournalMaterial(base);
790
+ let hash;
791
+ try {
792
+ hash = this.hashFn(TEXT_ENCODER.encode(material));
793
+ }
794
+ catch (error) {
795
+ throw new TypeError("hashFn threw while sealing a record; failing closed", { cause: error });
796
+ }
797
+ if (!isValidHashShape(hash)) {
798
+ throw new TypeError("hashFn must return 64 lowercase hex characters");
799
+ }
800
+ return deepFreeze({ ...base, hash });
801
+ }
802
+ }
803
+ function validateCommonShape(value) {
804
+ if (!isPlainObject(value))
805
+ return null;
806
+ const rawEvent = value.event;
807
+ const isTerminal = typeof rawEvent === "string" && TERMINAL_EVENTS.has(rawEvent);
808
+ const isAudit = typeof rawEvent === "string" && AUDIT_EVENTS.has(rawEvent);
809
+ const allowed = isTerminal
810
+ ? [...COMMON_RECORD_KEYS, "termination"]
811
+ : isAudit
812
+ ? [...COMMON_RECORD_KEYS, "details"]
813
+ : COMMON_RECORD_KEYS;
814
+ if (Object.keys(value).some((key) => !allowed.includes(key))) {
815
+ return null;
816
+ }
817
+ const hasTermination = "termination" in value;
818
+ if (isTerminal !== hasTermination)
819
+ return null;
820
+ if (isAudit !== "details" in value)
821
+ return null;
822
+ if (value.schemaVersion !== RUN_JOURNAL_SCHEMA_VERSION)
823
+ return null;
824
+ if (typeof value.event !== "string" || !RUN_JOURNAL_EVENTS.has(value.event))
825
+ return null;
826
+ if (typeof value.seq !== "number" || !Number.isSafeInteger(value.seq) || value.seq < 0)
827
+ return null;
828
+ if (!isBoundedIdentifier(value.runId) || looksLikeCredential(value.runId))
829
+ return null;
830
+ if (!isBoundedIdentifier(value.sessionId) || looksLikeCredential(value.sessionId))
831
+ return null;
832
+ if (typeof value.sessionRevision !== "number" ||
833
+ !Number.isSafeInteger(value.sessionRevision) ||
834
+ value.sessionRevision < 0) {
835
+ return null;
836
+ }
837
+ if (!isCanonicalTimestamp(value.timestamp))
838
+ return null;
839
+ if (typeof value.prevHash !== "string" || typeof value.hash !== "string")
840
+ return null;
841
+ return {
842
+ schemaVersion: RUN_JOURNAL_SCHEMA_VERSION,
843
+ seq: value.seq,
844
+ event: value.event,
845
+ runId: value.runId,
846
+ sessionId: value.sessionId,
847
+ sessionRevision: value.sessionRevision,
848
+ timestamp: value.timestamp,
849
+ prevHash: value.prevHash,
850
+ hash: value.hash,
851
+ };
852
+ }
853
+ function probeTermination(rawTermination, runId, sessionId, event) {
854
+ let termination;
855
+ try {
856
+ assertBoundedSessionTermination(rawTermination, runId, sessionId);
857
+ termination = rawTermination;
858
+ }
859
+ catch {
860
+ return { kind: "mismatch" };
861
+ }
862
+ if (!isTerminalCoherentWithEvent(event, termination)) {
863
+ return { kind: "source" };
864
+ }
865
+ return { kind: "ok", termination };
866
+ }
867
+ function addFinding(findings, finding) {
868
+ findings.push(Object.freeze({ ...finding }));
869
+ }
870
+ /**
871
+ * Inspect the complete-line prefix of a run-journal byte sequence without
872
+ * mutating or repairing it. Only bytes through the final newline are parsed;
873
+ * trailing bytes are reported as a fragment and never parsed. Every hash is
874
+ * recomputed with the injected `hashFn`. The returned report is deeply frozen.
875
+ */
876
+ export function inspectRunJournal(bytes, hashFn) {
877
+ const findings = [];
878
+ let lastNewline = -1;
879
+ for (let index = bytes.byteLength - 1; index >= 0; index -= 1) {
880
+ if (bytes[index] === 0x0a) {
881
+ lastNewline = index;
882
+ break;
883
+ }
884
+ }
885
+ const completeByteCount = lastNewline + 1;
886
+ const trailingByteCount = bytes.byteLength - completeByteCount;
887
+ if (trailingByteCount > 0)
888
+ addFinding(findings, { code: "trailing_fragment" });
889
+ const completeBytes = bytes.subarray(0, completeByteCount);
890
+ let lineCount = 0;
891
+ const parsedLines = [];
892
+ if (completeByteCount > 0) {
893
+ let text;
894
+ try {
895
+ text = TEXT_DECODER.decode(completeBytes);
896
+ }
897
+ catch {
898
+ addFinding(findings, { code: "invalid_utf8" });
899
+ }
900
+ if (text !== undefined) {
901
+ const body = text.slice(0, -1);
902
+ const lines = body.length === 0 ? [] : body.split("\n");
903
+ lineCount = lines.length;
904
+ for (let index = 0; index < lines.length; index += 1) {
905
+ const lineNumber = index + 1;
906
+ const line = lines[index];
907
+ if (line.trim().length === 0) {
908
+ addFinding(findings, { code: "malformed_json", line: lineNumber });
909
+ continue;
910
+ }
911
+ try {
912
+ parsedLines.push({ line: lineNumber, value: JSON.parse(line) });
913
+ }
914
+ catch {
915
+ addFinding(findings, { code: "malformed_json", line: lineNumber });
916
+ }
917
+ }
918
+ }
919
+ }
920
+ let openRun = null;
921
+ const seenStarts = new Set();
922
+ const terminatedRuns = new Set();
923
+ let expectedSeq = 0;
924
+ let lastHash = GENESIS_HASH;
925
+ let journalSessionId = null;
926
+ let lastRevision = 0;
927
+ const validRecords = [];
928
+ for (const { line, value } of parsedLines) {
929
+ const common = validateCommonShape(value);
930
+ if (common === null) {
931
+ addFinding(findings, { code: "schema", line });
932
+ continue;
933
+ }
934
+ let auditDetails = null;
935
+ if (AUDIT_EVENTS.has(common.event)) {
936
+ try {
937
+ assertBoundedAuditDetails(value.details);
938
+ auditDetails = value.details;
939
+ }
940
+ catch {
941
+ addFinding(findings, { code: "schema", line });
942
+ continue;
943
+ }
944
+ }
945
+ let termination = null;
946
+ if (TERMINAL_EVENTS.has(common.event)) {
947
+ const probe = probeTermination(value.termination, common.runId, common.sessionId, common.event);
948
+ if (probe.kind === "mismatch") {
949
+ addFinding(findings, { code: "termination_mismatch", line, runId: common.runId });
950
+ continue;
951
+ }
952
+ if (probe.kind === "source") {
953
+ addFinding(findings, { code: "recovered_source_invalid", line, runId: common.runId });
954
+ continue;
955
+ }
956
+ termination = probe.termination;
957
+ if (common.timestamp !== termination.timestamp) {
958
+ addFinding(findings, { code: "termination_mismatch", line, runId: common.runId });
959
+ continue;
960
+ }
961
+ }
962
+ if (journalSessionId === null) {
963
+ journalSessionId = common.sessionId;
964
+ }
965
+ else if (common.sessionId !== journalSessionId) {
966
+ addFinding(findings, { code: "session_mismatch", line, runId: common.runId });
967
+ continue;
968
+ }
969
+ if (common.sessionRevision < lastRevision) {
970
+ addFinding(findings, { code: "session_revision_regression", line, runId: common.runId });
971
+ continue;
972
+ }
973
+ lastRevision = common.sessionRevision;
974
+ const baseRecord = {
975
+ schemaVersion: RUN_JOURNAL_SCHEMA_VERSION,
976
+ seq: common.seq,
977
+ event: common.event,
978
+ runId: common.runId,
979
+ sessionId: common.sessionId,
980
+ sessionRevision: common.sessionRevision,
981
+ timestamp: common.timestamp,
982
+ prevHash: common.prevHash,
983
+ };
984
+ const recordWithoutHash = (termination !== null
985
+ ? { ...baseRecord, termination }
986
+ : auditDetails !== null
987
+ ? { ...baseRecord, details: auditDetails }
988
+ : baseRecord);
989
+ if (common.seq > expectedSeq)
990
+ addFinding(findings, { code: "seq_gap", line });
991
+ else if (common.seq < expectedSeq)
992
+ addFinding(findings, { code: "seq_regression", line });
993
+ expectedSeq = common.seq + 1;
994
+ const hashShapeOk = isValidHashShape(common.hash);
995
+ const prevHashShapeOk = isValidHashShape(common.prevHash);
996
+ if (!hashShapeOk || !prevHashShapeOk)
997
+ addFinding(findings, { code: "hash_shape", line });
998
+ const material = serializeRunJournalMaterial(recordWithoutHash);
999
+ let recomputeOk = false;
1000
+ try {
1001
+ const recomputed = hashFn(TEXT_ENCODER.encode(material));
1002
+ recomputeOk = isValidHashShape(recomputed) && recomputed === common.hash;
1003
+ }
1004
+ catch {
1005
+ recomputeOk = false;
1006
+ }
1007
+ if (!recomputeOk)
1008
+ addFinding(findings, { code: "hash_mismatch", line });
1009
+ if (common.prevHash !== lastHash)
1010
+ addFinding(findings, { code: "hash_chain_break", line });
1011
+ if (hashShapeOk)
1012
+ lastHash = common.hash;
1013
+ if (AUDIT_EVENTS.has(common.event)) {
1014
+ // Audit records never affect the run-state machine.
1015
+ }
1016
+ else if (common.event === "run_started") {
1017
+ if (seenStarts.has(common.runId))
1018
+ addFinding(findings, { code: "duplicate_start", line, runId: common.runId });
1019
+ if (openRun !== null && openRun.runId !== common.runId) {
1020
+ addFinding(findings, { code: "interleaved_run", line, runId: common.runId });
1021
+ }
1022
+ else if (openRun === null) {
1023
+ openRun = { runId: common.runId, sessionId: common.sessionId };
1024
+ }
1025
+ seenStarts.add(common.runId);
1026
+ }
1027
+ else {
1028
+ if (terminatedRuns.has(common.runId)) {
1029
+ addFinding(findings, { code: "duplicate_terminal", line, runId: common.runId });
1030
+ }
1031
+ else if (openRun === null || openRun.runId !== common.runId) {
1032
+ addFinding(findings, { code: "orphan_terminal", line, runId: common.runId });
1033
+ }
1034
+ else {
1035
+ terminatedRuns.add(common.runId);
1036
+ openRun = null;
1037
+ }
1038
+ }
1039
+ const record = { ...recordWithoutHash, hash: common.hash };
1040
+ validRecords.push(deepFreeze(record));
1041
+ }
1042
+ if (openRun !== null)
1043
+ addFinding(findings, { code: "run_unclosed", runId: openRun.runId });
1044
+ return deepFreeze({
1045
+ ok: findings.length === 0,
1046
+ completePrefix: Object.freeze({ byteCount: completeByteCount, lineCount }),
1047
+ trailingByteCount,
1048
+ records: Object.freeze(validRecords),
1049
+ openRunId: openRun === null ? null : openRun.runId,
1050
+ findings: Object.freeze(findings),
1051
+ });
1052
+ }
1053
+ //# sourceMappingURL=run-journal.js.map