open-multi-agent-kit 0.98.4 → 0.98.5

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 (252) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +3 -2
  3. package/dist/commands/run-command.d.ts +2 -1
  4. package/dist/commands/run-command.d.ts.map +1 -1
  5. package/dist/commands/run-command.js +9 -1
  6. package/dist/commands/run-command.js.map +1 -1
  7. package/dist/commands/verified-run-cli.d.ts +6 -0
  8. package/dist/commands/verified-run-cli.d.ts.map +1 -0
  9. package/dist/commands/verified-run-cli.js +190 -0
  10. package/dist/commands/verified-run-cli.js.map +1 -0
  11. package/dist/core/agent-session-services.d.ts +8 -1
  12. package/dist/core/agent-session-services.d.ts.map +1 -1
  13. package/dist/core/agent-session-services.js +41 -0
  14. package/dist/core/agent-session-services.js.map +1 -1
  15. package/dist/core/agent-session.d.ts +7 -8
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +61 -41
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/prompt-settlement.d.ts +4 -3
  20. package/dist/core/prompt-settlement.d.ts.map +1 -1
  21. package/dist/core/prompt-settlement.js.map +1 -1
  22. package/dist/core/run-budget-policy.d.ts +18 -0
  23. package/dist/core/run-budget-policy.d.ts.map +1 -0
  24. package/dist/core/run-budget-policy.js +52 -0
  25. package/dist/core/run-budget-policy.js.map +1 -0
  26. package/dist/core/run-budget.d.ts +33 -0
  27. package/dist/core/run-budget.d.ts.map +1 -0
  28. package/dist/core/run-budget.js +89 -0
  29. package/dist/core/run-budget.js.map +1 -0
  30. package/dist/core/run-execution-api.d.ts +15 -0
  31. package/dist/core/run-execution-api.d.ts.map +1 -0
  32. package/dist/core/run-execution-api.js +7 -0
  33. package/dist/core/run-execution-api.js.map +1 -0
  34. package/dist/core/run-journal.d.ts.map +1 -1
  35. package/dist/core/run-journal.js +30 -208
  36. package/dist/core/run-journal.js.map +1 -1
  37. package/dist/core/session-bash-service.d.ts +2 -2
  38. package/dist/core/session-bash-service.d.ts.map +1 -1
  39. package/dist/core/session-bash-service.js +21 -9
  40. package/dist/core/session-bash-service.js.map +1 -1
  41. package/dist/core/session-failure-cause.d.ts.map +1 -1
  42. package/dist/core/session-failure-cause.js +5 -0
  43. package/dist/core/session-failure-cause.js.map +1 -1
  44. package/dist/core/session-prompt-lifecycle.d.ts +25 -0
  45. package/dist/core/session-prompt-lifecycle.d.ts.map +1 -0
  46. package/dist/core/session-prompt-lifecycle.js +87 -0
  47. package/dist/core/session-prompt-lifecycle.js.map +1 -0
  48. package/dist/core/session-run-budget.d.ts +28 -0
  49. package/dist/core/session-run-budget.d.ts.map +1 -0
  50. package/dist/core/session-run-budget.js +127 -0
  51. package/dist/core/session-run-budget.js.map +1 -0
  52. package/dist/core/session-run-termination.d.ts.map +1 -1
  53. package/dist/core/session-run-termination.js +14 -3
  54. package/dist/core/session-run-termination.js.map +1 -1
  55. package/dist/core/session-termination-types.d.ts +97 -0
  56. package/dist/core/session-termination-types.d.ts.map +1 -0
  57. package/dist/core/session-termination-types.js +26 -0
  58. package/dist/core/session-termination-types.js.map +1 -0
  59. package/dist/core/session-termination.d.ts +3 -97
  60. package/dist/core/session-termination.d.ts.map +1 -1
  61. package/dist/core/session-termination.js +24 -28
  62. package/dist/core/session-termination.js.map +1 -1
  63. package/dist/core/slash-commands.d.ts.map +1 -1
  64. package/dist/core/slash-commands.js +1 -0
  65. package/dist/core/slash-commands.js.map +1 -1
  66. package/dist/core/subagent-lane-launcher.d.ts +3 -2
  67. package/dist/core/subagent-lane-launcher.d.ts.map +1 -1
  68. package/dist/core/subagent-lane-launcher.js +24 -13
  69. package/dist/core/subagent-lane-launcher.js.map +1 -1
  70. package/dist/core/verified-run/broker.d.ts +26 -0
  71. package/dist/core/verified-run/broker.d.ts.map +1 -0
  72. package/dist/core/verified-run/broker.js +210 -0
  73. package/dist/core/verified-run/broker.js.map +1 -0
  74. package/dist/core/verified-run/candidate.d.ts +24 -0
  75. package/dist/core/verified-run/candidate.d.ts.map +1 -0
  76. package/dist/core/verified-run/candidate.js +167 -0
  77. package/dist/core/verified-run/candidate.js.map +1 -0
  78. package/dist/core/verified-run/check-receipt.d.ts +18 -0
  79. package/dist/core/verified-run/check-receipt.d.ts.map +1 -0
  80. package/dist/core/verified-run/check-receipt.js +92 -0
  81. package/dist/core/verified-run/check-receipt.js.map +1 -0
  82. package/dist/core/verified-run/coordinator.d.ts +39 -0
  83. package/dist/core/verified-run/coordinator.d.ts.map +1 -0
  84. package/dist/core/verified-run/coordinator.js +190 -0
  85. package/dist/core/verified-run/coordinator.js.map +1 -0
  86. package/dist/core/verified-run/dag-candidates.d.ts +8 -0
  87. package/dist/core/verified-run/dag-candidates.d.ts.map +1 -0
  88. package/dist/core/verified-run/dag-candidates.js +65 -0
  89. package/dist/core/verified-run/dag-candidates.js.map +1 -0
  90. package/dist/core/verified-run/dag-phase.d.ts +7 -0
  91. package/dist/core/verified-run/dag-phase.d.ts.map +1 -0
  92. package/dist/core/verified-run/dag-phase.js +92 -0
  93. package/dist/core/verified-run/dag-phase.js.map +1 -0
  94. package/dist/core/verified-run/dag-projection.d.ts +7 -0
  95. package/dist/core/verified-run/dag-projection.d.ts.map +1 -0
  96. package/dist/core/verified-run/dag-projection.js +90 -0
  97. package/dist/core/verified-run/dag-projection.js.map +1 -0
  98. package/dist/core/verified-run/dag-recovery.d.ts +13 -0
  99. package/dist/core/verified-run/dag-recovery.d.ts.map +1 -0
  100. package/dist/core/verified-run/dag-recovery.js +98 -0
  101. package/dist/core/verified-run/dag-recovery.js.map +1 -0
  102. package/dist/core/verified-run/dag-retry-projection.d.ts +9 -0
  103. package/dist/core/verified-run/dag-retry-projection.d.ts.map +1 -0
  104. package/dist/core/verified-run/dag-retry-projection.js +43 -0
  105. package/dist/core/verified-run/dag-retry-projection.js.map +1 -0
  106. package/dist/core/verified-run/dag-types.d.ts +47 -0
  107. package/dist/core/verified-run/dag-types.d.ts.map +1 -0
  108. package/dist/core/verified-run/dag-types.js +2 -0
  109. package/dist/core/verified-run/dag-types.js.map +1 -0
  110. package/dist/core/verified-run/event-parser.d.ts +3 -0
  111. package/dist/core/verified-run/event-parser.d.ts.map +1 -0
  112. package/dist/core/verified-run/event-parser.js +153 -0
  113. package/dist/core/verified-run/event-parser.js.map +1 -0
  114. package/dist/core/verified-run/events.d.ts +4 -0
  115. package/dist/core/verified-run/events.d.ts.map +1 -0
  116. package/dist/core/verified-run/events.js +3 -0
  117. package/dist/core/verified-run/events.js.map +1 -0
  118. package/dist/core/verified-run/evidence-binding.d.ts +18 -0
  119. package/dist/core/verified-run/evidence-binding.d.ts.map +1 -0
  120. package/dist/core/verified-run/evidence-binding.js +81 -0
  121. package/dist/core/verified-run/evidence-binding.js.map +1 -0
  122. package/dist/core/verified-run/evidence.d.ts +27 -0
  123. package/dist/core/verified-run/evidence.d.ts.map +1 -0
  124. package/dist/core/verified-run/evidence.js +100 -0
  125. package/dist/core/verified-run/evidence.js.map +1 -0
  126. package/dist/core/verified-run/journal.d.ts +32 -0
  127. package/dist/core/verified-run/journal.d.ts.map +1 -0
  128. package/dist/core/verified-run/journal.js +103 -0
  129. package/dist/core/verified-run/journal.js.map +1 -0
  130. package/dist/core/verified-run/namespace-identity.d.ts +11 -0
  131. package/dist/core/verified-run/namespace-identity.d.ts.map +1 -0
  132. package/dist/core/verified-run/namespace-identity.js +77 -0
  133. package/dist/core/verified-run/namespace-identity.js.map +1 -0
  134. package/dist/core/verified-run/owned-execution.d.ts +21 -0
  135. package/dist/core/verified-run/owned-execution.d.ts.map +1 -0
  136. package/dist/core/verified-run/owned-execution.js +43 -0
  137. package/dist/core/verified-run/owned-execution.js.map +1 -0
  138. package/dist/core/verified-run/phase-context.d.ts +9 -0
  139. package/dist/core/verified-run/phase-context.d.ts.map +1 -0
  140. package/dist/core/verified-run/phase-context.js +2 -0
  141. package/dist/core/verified-run/phase-context.js.map +1 -0
  142. package/dist/core/verified-run/process-gate.d.ts +5 -0
  143. package/dist/core/verified-run/process-gate.d.ts.map +1 -0
  144. package/dist/core/verified-run/process-gate.js +31 -0
  145. package/dist/core/verified-run/process-gate.js.map +1 -0
  146. package/dist/core/verified-run/projection.d.ts +4 -0
  147. package/dist/core/verified-run/projection.d.ts.map +1 -0
  148. package/dist/core/verified-run/projection.js +219 -0
  149. package/dist/core/verified-run/projection.js.map +1 -0
  150. package/dist/core/verified-run/recovery-clock.d.ts +19 -0
  151. package/dist/core/verified-run/recovery-clock.d.ts.map +1 -0
  152. package/dist/core/verified-run/recovery-clock.js +69 -0
  153. package/dist/core/verified-run/recovery-clock.js.map +1 -0
  154. package/dist/core/verified-run/recovery-command.d.ts +11 -0
  155. package/dist/core/verified-run/recovery-command.d.ts.map +1 -0
  156. package/dist/core/verified-run/recovery-command.js +73 -0
  157. package/dist/core/verified-run/recovery-command.js.map +1 -0
  158. package/dist/core/verified-run/recovery-projection.d.ts +12 -0
  159. package/dist/core/verified-run/recovery-projection.d.ts.map +1 -0
  160. package/dist/core/verified-run/recovery-projection.js +88 -0
  161. package/dist/core/verified-run/recovery-projection.js.map +1 -0
  162. package/dist/core/verified-run/recovery.d.ts +12 -0
  163. package/dist/core/verified-run/recovery.d.ts.map +1 -0
  164. package/dist/core/verified-run/recovery.js +115 -0
  165. package/dist/core/verified-run/recovery.js.map +1 -0
  166. package/dist/core/verified-run/run-types.d.ts +103 -0
  167. package/dist/core/verified-run/run-types.d.ts.map +1 -0
  168. package/dist/core/verified-run/run-types.js +2 -0
  169. package/dist/core/verified-run/run-types.js.map +1 -0
  170. package/dist/core/verified-run/scripted-writer.d.ts +15 -0
  171. package/dist/core/verified-run/scripted-writer.d.ts.map +1 -0
  172. package/dist/core/verified-run/scripted-writer.js +94 -0
  173. package/dist/core/verified-run/scripted-writer.js.map +1 -0
  174. package/dist/core/verified-run/session-port.d.ts +27 -0
  175. package/dist/core/verified-run/session-port.d.ts.map +1 -0
  176. package/dist/core/verified-run/session-port.js +2 -0
  177. package/dist/core/verified-run/session-port.js.map +1 -0
  178. package/dist/core/verified-run/storage.d.ts +15 -0
  179. package/dist/core/verified-run/storage.d.ts.map +1 -0
  180. package/dist/core/verified-run/storage.js +96 -0
  181. package/dist/core/verified-run/storage.js.map +1 -0
  182. package/dist/core/verified-run/verification-phase.d.ts +5 -0
  183. package/dist/core/verified-run/verification-phase.d.ts.map +1 -0
  184. package/dist/core/verified-run/verification-phase.js +56 -0
  185. package/dist/core/verified-run/verification-phase.js.map +1 -0
  186. package/dist/core/verified-run/work-recovery.d.ts +8 -0
  187. package/dist/core/verified-run/work-recovery.d.ts.map +1 -0
  188. package/dist/core/verified-run/work-recovery.js +44 -0
  189. package/dist/core/verified-run/work-recovery.js.map +1 -0
  190. package/dist/core/verified-run/writer-completion.d.ts +4 -0
  191. package/dist/core/verified-run/writer-completion.d.ts.map +1 -0
  192. package/dist/core/verified-run/writer-completion.js +27 -0
  193. package/dist/core/verified-run/writer-completion.js.map +1 -0
  194. package/dist/core/verified-run/writer-phase.d.ts +8 -0
  195. package/dist/core/verified-run/writer-phase.d.ts.map +1 -0
  196. package/dist/core/verified-run/writer-phase.js +76 -0
  197. package/dist/core/verified-run/writer-phase.js.map +1 -0
  198. package/dist/core/verified-run/writer-projection.d.ts +11 -0
  199. package/dist/core/verified-run/writer-projection.d.ts.map +1 -0
  200. package/dist/core/verified-run/writer-projection.js +70 -0
  201. package/dist/core/verified-run/writer-projection.js.map +1 -0
  202. package/dist/core/verified-run/writer-recovery.d.ts +16 -0
  203. package/dist/core/verified-run/writer-recovery.d.ts.map +1 -0
  204. package/dist/core/verified-run/writer-recovery.js +107 -0
  205. package/dist/core/verified-run/writer-recovery.js.map +1 -0
  206. package/dist/core/workload-permit-pool.d.ts +1 -3
  207. package/dist/core/workload-permit-pool.d.ts.map +1 -1
  208. package/dist/core/workload-permit-pool.js +11 -11
  209. package/dist/core/workload-permit-pool.js.map +1 -1
  210. package/dist/index.d.ts +3 -2
  211. package/dist/index.d.ts.map +1 -1
  212. package/dist/index.js +2 -2
  213. package/dist/index.js.map +1 -1
  214. package/dist/main.d.ts.map +1 -1
  215. package/dist/main.js +2 -8
  216. package/dist/main.js.map +1 -1
  217. package/dist/modes/interactive/components/session-failure.d.ts +13 -0
  218. package/dist/modes/interactive/components/session-failure.d.ts.map +1 -0
  219. package/dist/modes/interactive/components/session-failure.js +81 -0
  220. package/dist/modes/interactive/components/session-failure.js.map +1 -0
  221. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  222. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  223. package/dist/modes/interactive/interactive-mode.js +45 -31
  224. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  225. package/dist/modes/interactive/tui-diagnostics.d.ts +36 -0
  226. package/dist/modes/interactive/tui-diagnostics.d.ts.map +1 -0
  227. package/dist/modes/interactive/tui-diagnostics.js +105 -0
  228. package/dist/modes/interactive/tui-diagnostics.js.map +1 -0
  229. package/dist/modes/interactive/tui-runtime-info.d.ts +20 -0
  230. package/dist/modes/interactive/tui-runtime-info.d.ts.map +1 -0
  231. package/dist/modes/interactive/tui-runtime-info.js +76 -0
  232. package/dist/modes/interactive/tui-runtime-info.js.map +1 -0
  233. package/docs/development.md +10 -0
  234. package/docs/keybindings.md +1 -1
  235. package/docs/release-audit-0.98.5.md +84 -0
  236. package/docs/run-protocol.md +33 -1
  237. package/docs/runtime-algorithms.md +26 -0
  238. package/docs/sdk.md +193 -0
  239. package/docs/usage.md +54 -0
  240. package/docs/verified-run-testing.md +417 -0
  241. package/docs/verified-run.md +388 -0
  242. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  243. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  244. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  245. package/examples/extensions/gondolin/package-lock.json +2 -2
  246. package/examples/extensions/gondolin/package.json +1 -1
  247. package/examples/extensions/sandbox/package-lock.json +2 -2
  248. package/examples/extensions/sandbox/package.json +1 -1
  249. package/examples/extensions/with-deps/package-lock.json +2 -2
  250. package/examples/extensions/with-deps/package.json +1 -1
  251. package/npm-shrinkwrap.json +18 -18
  252. package/package.json +6 -6
@@ -0,0 +1,3 @@
1
+ export { parseRunEvent } from "./event-parser.js";
2
+ export { projectRun } from "./projection.js";
3
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/core/verified-run/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["export { parseRunEvent } from \"./event-parser.ts\";\nexport { projectRun } from \"./projection.ts\";\nexport type { RunEvent, RunProjection } from \"./run-types.ts\";\n"]}
@@ -0,0 +1,18 @@
1
+ import { type RunContract } from "omk-protocol";
2
+ export interface CheckObservation {
3
+ readonly claimId: string;
4
+ readonly executionId: string;
5
+ readonly stdoutDigest: string;
6
+ readonly stderrDigest: string;
7
+ readonly exitCode: number | null;
8
+ readonly failure: string | null;
9
+ readonly receiptCoreDigest?: string;
10
+ }
11
+ export declare function parseCheckObservations(value: unknown, nativeRequired: boolean): readonly CheckObservation[];
12
+ /** Only the authenticated adapter supplies these observations; the reducer itself grants no trust. */
13
+ export declare function closesRunClaims(contract: RunContract, context: {
14
+ readonly candidate: string;
15
+ readonly environment: string;
16
+ readonly checks: readonly CheckObservation[];
17
+ }): boolean;
18
+ //# sourceMappingURL=evidence-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-binding.d.ts","sourceRoot":"","sources":["../../../src/core/verified-run/evidence-binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAGlG,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,GAAG,SAAS,gBAAgB,EAAE,CAuC3G;AAED,sGAAsG;AACtG,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE,GACjH,OAAO,CA2CT","sourcesContent":["import { CLAIM_GRAPH_SCHEMA_VERSION, evaluateProofClosure, type RunContract } from \"omk-protocol\";\nimport { digestBytes, VerifiedRunError } from \"./storage.ts\";\n\nexport interface CheckObservation {\n\treadonly claimId: string;\n\treadonly executionId: string;\n\treadonly stdoutDigest: string;\n\treadonly stderrDigest: string;\n\treadonly exitCode: number | null;\n\treadonly failure: string | null;\n\treadonly receiptCoreDigest?: string;\n}\n\nexport function parseCheckObservations(value: unknown, nativeRequired: boolean): readonly CheckObservation[] {\n\tif (!Array.isArray(value) || value.length > 32) throw new VerifiedRunError(\"integrity\");\n\tconst rawChecks: readonly unknown[] = value;\n\treturn Object.freeze(\n\t\trawChecks.map((raw): CheckObservation => {\n\t\t\tif (\n\t\t\t\ttypeof raw !== \"object\" ||\n\t\t\t\traw === null ||\n\t\t\t\t!(\"claimId\" in raw) ||\n\t\t\t\ttypeof raw.claimId !== \"string\" ||\n\t\t\t\t!(\"executionId\" in raw) ||\n\t\t\t\ttypeof raw.executionId !== \"string\" ||\n\t\t\t\t!(\"stdoutDigest\" in raw) ||\n\t\t\t\ttypeof raw.stdoutDigest !== \"string\" ||\n\t\t\t\t!/^[a-f0-9]{64}$/.test(raw.stdoutDigest) ||\n\t\t\t\t!(\"stderrDigest\" in raw) ||\n\t\t\t\ttypeof raw.stderrDigest !== \"string\" ||\n\t\t\t\t!/^[a-f0-9]{64}$/.test(raw.stderrDigest) ||\n\t\t\t\t!(\"exitCode\" in raw) ||\n\t\t\t\t(raw.exitCode !== null && (typeof raw.exitCode !== \"number\" || !Number.isSafeInteger(raw.exitCode))) ||\n\t\t\t\t!(\"failure\" in raw) ||\n\t\t\t\t(raw.failure !== null && typeof raw.failure !== \"string\")\n\t\t\t)\n\t\t\t\tthrow new VerifiedRunError(\"integrity\");\n\t\t\tconst native = \"receiptCoreDigest\" in raw ? raw.receiptCoreDigest : undefined;\n\t\t\tif (nativeRequired && (typeof native !== \"string\" || !/^[a-f0-9]{64}$/.test(native)))\n\t\t\t\tthrow new VerifiedRunError(\"integrity\");\n\t\t\tif (!nativeRequired && native !== undefined) throw new VerifiedRunError(\"integrity\");\n\t\t\treturn Object.freeze({\n\t\t\t\tclaimId: raw.claimId,\n\t\t\t\texecutionId: raw.executionId,\n\t\t\t\tstdoutDigest: raw.stdoutDigest,\n\t\t\t\tstderrDigest: raw.stderrDigest,\n\t\t\t\texitCode: raw.exitCode,\n\t\t\t\tfailure: raw.failure,\n\t\t\t\t...(typeof native === \"string\" ? { receiptCoreDigest: native } : {}),\n\t\t\t});\n\t\t}),\n\t);\n}\n\n/** Only the authenticated adapter supplies these observations; the reducer itself grants no trust. */\nexport function closesRunClaims(\n\tcontract: RunContract,\n\tcontext: { readonly candidate: string; readonly environment: string; readonly checks: readonly CheckObservation[] },\n): boolean {\n\tconst graph = {\n\t\tschemaVersion: CLAIM_GRAPH_SCHEMA_VERSION,\n\t\tclaims: contract.checks.map((check) => ({\n\t\t\tclaimId: check.claimId,\n\t\t\tkind: \"requirement\" as const,\n\t\t\tstatement: `Exact stdout for ${check.claimId}`,\n\t\t\tseverity: \"required\" as const,\n\t\t\tsatisfaction: { rule: \"all\" as const, inputs: [] },\n\t\t\ttrustFloor: \"trusted_attestation\" as const,\n\t\t\tinvalidationKeys: [],\n\t\t\tscopeSensitive: true,\n\t\t})),\n\t};\n\tconst observations = context.checks.map((check) => ({\n\t\tobservationId: check.executionId,\n\t\tclaimIds: [check.claimId],\n\t\tsource: \"trusted_attestation\" as const,\n\t\tpolarity:\n\t\t\tcheck.exitCode === 0 &&\n\t\t\tcheck.failure === null &&\n\t\t\tcontract.checks.some(\n\t\t\t\t(expected) => expected.claimId === check.claimId && digestBytes(expected.stdout) === check.stdoutDigest,\n\t\t\t)\n\t\t\t\t? (\"supports\" as const)\n\t\t\t\t: (\"violates\" as const),\n\t\tsourceRoot: context.candidate,\n\t\tenvironmentDigest: context.environment,\n\t\tindependenceGroup: check.executionId,\n\t}));\n\treturn (\n\t\tevaluateProofClosure({\n\t\t\tgraph,\n\t\t\tobservations,\n\t\t\twitnessIndependence: \"explicit-groups\",\n\t\t\twaivers: [],\n\t\t\tsourceRoot: context.candidate,\n\t\t\tenvironmentDigest: context.environment,\n\t\t\tworkspaceCompleteness: \"complete\",\n\t\t\tunresolvedEffectIds: [],\n\t\t\tnow: \"1970-01-01T00:00:00.000Z\",\n\t\t}).verdict === \"verified\"\n\t);\n}\n"]}
@@ -0,0 +1,81 @@
1
+ import { CLAIM_GRAPH_SCHEMA_VERSION, evaluateProofClosure } from "omk-protocol";
2
+ import { digestBytes, VerifiedRunError } from "./storage.js";
3
+ export function parseCheckObservations(value, nativeRequired) {
4
+ if (!Array.isArray(value) || value.length > 32)
5
+ throw new VerifiedRunError("integrity");
6
+ const rawChecks = value;
7
+ return Object.freeze(rawChecks.map((raw) => {
8
+ if (typeof raw !== "object" ||
9
+ raw === null ||
10
+ !("claimId" in raw) ||
11
+ typeof raw.claimId !== "string" ||
12
+ !("executionId" in raw) ||
13
+ typeof raw.executionId !== "string" ||
14
+ !("stdoutDigest" in raw) ||
15
+ typeof raw.stdoutDigest !== "string" ||
16
+ !/^[a-f0-9]{64}$/.test(raw.stdoutDigest) ||
17
+ !("stderrDigest" in raw) ||
18
+ typeof raw.stderrDigest !== "string" ||
19
+ !/^[a-f0-9]{64}$/.test(raw.stderrDigest) ||
20
+ !("exitCode" in raw) ||
21
+ (raw.exitCode !== null && (typeof raw.exitCode !== "number" || !Number.isSafeInteger(raw.exitCode))) ||
22
+ !("failure" in raw) ||
23
+ (raw.failure !== null && typeof raw.failure !== "string"))
24
+ throw new VerifiedRunError("integrity");
25
+ const native = "receiptCoreDigest" in raw ? raw.receiptCoreDigest : undefined;
26
+ if (nativeRequired && (typeof native !== "string" || !/^[a-f0-9]{64}$/.test(native)))
27
+ throw new VerifiedRunError("integrity");
28
+ if (!nativeRequired && native !== undefined)
29
+ throw new VerifiedRunError("integrity");
30
+ return Object.freeze({
31
+ claimId: raw.claimId,
32
+ executionId: raw.executionId,
33
+ stdoutDigest: raw.stdoutDigest,
34
+ stderrDigest: raw.stderrDigest,
35
+ exitCode: raw.exitCode,
36
+ failure: raw.failure,
37
+ ...(typeof native === "string" ? { receiptCoreDigest: native } : {}),
38
+ });
39
+ }));
40
+ }
41
+ /** Only the authenticated adapter supplies these observations; the reducer itself grants no trust. */
42
+ export function closesRunClaims(contract, context) {
43
+ const graph = {
44
+ schemaVersion: CLAIM_GRAPH_SCHEMA_VERSION,
45
+ claims: contract.checks.map((check) => ({
46
+ claimId: check.claimId,
47
+ kind: "requirement",
48
+ statement: `Exact stdout for ${check.claimId}`,
49
+ severity: "required",
50
+ satisfaction: { rule: "all", inputs: [] },
51
+ trustFloor: "trusted_attestation",
52
+ invalidationKeys: [],
53
+ scopeSensitive: true,
54
+ })),
55
+ };
56
+ const observations = context.checks.map((check) => ({
57
+ observationId: check.executionId,
58
+ claimIds: [check.claimId],
59
+ source: "trusted_attestation",
60
+ polarity: check.exitCode === 0 &&
61
+ check.failure === null &&
62
+ contract.checks.some((expected) => expected.claimId === check.claimId && digestBytes(expected.stdout) === check.stdoutDigest)
63
+ ? "supports"
64
+ : "violates",
65
+ sourceRoot: context.candidate,
66
+ environmentDigest: context.environment,
67
+ independenceGroup: check.executionId,
68
+ }));
69
+ return (evaluateProofClosure({
70
+ graph,
71
+ observations,
72
+ witnessIndependence: "explicit-groups",
73
+ waivers: [],
74
+ sourceRoot: context.candidate,
75
+ environmentDigest: context.environment,
76
+ workspaceCompleteness: "complete",
77
+ unresolvedEffectIds: [],
78
+ now: "1970-01-01T00:00:00.000Z",
79
+ }).verdict === "verified");
80
+ }
81
+ //# sourceMappingURL=evidence-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-binding.js","sourceRoot":"","sources":["../../../src/core/verified-run/evidence-binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAoB,MAAM,cAAc,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAY7D,MAAM,UAAU,sBAAsB,CAAC,KAAc,EAAE,cAAuB,EAA+B;IAC5G,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE;QAAE,MAAM,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACxF,MAAM,SAAS,GAAuB,KAAK,CAAC;IAC5C,OAAO,MAAM,CAAC,MAAM,CACnB,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAoB,EAAE,CAAC;QACxC,IACC,OAAO,GAAG,KAAK,QAAQ;YACvB,GAAG,KAAK,IAAI;YACZ,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC;YACnB,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;YAC/B,CAAC,CAAC,aAAa,IAAI,GAAG,CAAC;YACvB,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ;YACnC,CAAC,CAAC,cAAc,IAAI,GAAG,CAAC;YACxB,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ;YACpC,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACxC,CAAC,CAAC,cAAc,IAAI,GAAG,CAAC;YACxB,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ;YACpC,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACxC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC;YACpB,CAAC,GAAG,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;YACpG,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC;YACnB,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC;YAEzD,MAAM,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,mBAAmB,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9E,IAAI,cAAc,IAAI,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnF,MAAM,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACzC,IAAI,CAAC,cAAc,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACrF,OAAO,MAAM,CAAC,MAAM,CAAC;YACpB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,GAAG,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpE,CAAC,CAAC;IAAA,CACH,CAAC,CACF,CAAC;AAAA,CACF;AAED,sGAAsG;AACtG,MAAM,UAAU,eAAe,CAC9B,QAAqB,EACrB,OAAmH,EACzG;IACV,MAAM,KAAK,GAAG;QACb,aAAa,EAAE,0BAA0B;QACzC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACvC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,aAAsB;YAC5B,SAAS,EAAE,oBAAoB,KAAK,CAAC,OAAO,EAAE;YAC9C,QAAQ,EAAE,UAAmB;YAC7B,YAAY,EAAE,EAAE,IAAI,EAAE,KAAc,EAAE,MAAM,EAAE,EAAE,EAAE;YAClD,UAAU,EAAE,qBAA8B;YAC1C,gBAAgB,EAAE,EAAE;YACpB,cAAc,EAAE,IAAI;SACpB,CAAC,CAAC;KACH,CAAC;IACF,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACnD,aAAa,EAAE,KAAK,CAAC,WAAW;QAChC,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;QACzB,MAAM,EAAE,qBAA8B;QACtC,QAAQ,EACP,KAAK,CAAC,QAAQ,KAAK,CAAC;YACpB,KAAK,CAAC,OAAO,KAAK,IAAI;YACtB,QAAQ,CAAC,MAAM,CAAC,IAAI,CACnB,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC,YAAY,CACvG;YACA,CAAC,CAAE,UAAoB;YACvB,CAAC,CAAE,UAAoB;QACzB,UAAU,EAAE,OAAO,CAAC,SAAS;QAC7B,iBAAiB,EAAE,OAAO,CAAC,WAAW;QACtC,iBAAiB,EAAE,KAAK,CAAC,WAAW;KACpC,CAAC,CAAC,CAAC;IACJ,OAAO,CACN,oBAAoB,CAAC;QACpB,KAAK;QACL,YAAY;QACZ,mBAAmB,EAAE,iBAAiB;QACtC,OAAO,EAAE,EAAE;QACX,UAAU,EAAE,OAAO,CAAC,SAAS;QAC7B,iBAAiB,EAAE,OAAO,CAAC,WAAW;QACtC,qBAAqB,EAAE,UAAU;QACjC,mBAAmB,EAAE,EAAE;QACvB,GAAG,EAAE,0BAA0B;KAC/B,CAAC,CAAC,OAAO,KAAK,UAAU,CACzB,CAAC;AAAA,CACF","sourcesContent":["import { CLAIM_GRAPH_SCHEMA_VERSION, evaluateProofClosure, type RunContract } from \"omk-protocol\";\nimport { digestBytes, VerifiedRunError } from \"./storage.ts\";\n\nexport interface CheckObservation {\n\treadonly claimId: string;\n\treadonly executionId: string;\n\treadonly stdoutDigest: string;\n\treadonly stderrDigest: string;\n\treadonly exitCode: number | null;\n\treadonly failure: string | null;\n\treadonly receiptCoreDigest?: string;\n}\n\nexport function parseCheckObservations(value: unknown, nativeRequired: boolean): readonly CheckObservation[] {\n\tif (!Array.isArray(value) || value.length > 32) throw new VerifiedRunError(\"integrity\");\n\tconst rawChecks: readonly unknown[] = value;\n\treturn Object.freeze(\n\t\trawChecks.map((raw): CheckObservation => {\n\t\t\tif (\n\t\t\t\ttypeof raw !== \"object\" ||\n\t\t\t\traw === null ||\n\t\t\t\t!(\"claimId\" in raw) ||\n\t\t\t\ttypeof raw.claimId !== \"string\" ||\n\t\t\t\t!(\"executionId\" in raw) ||\n\t\t\t\ttypeof raw.executionId !== \"string\" ||\n\t\t\t\t!(\"stdoutDigest\" in raw) ||\n\t\t\t\ttypeof raw.stdoutDigest !== \"string\" ||\n\t\t\t\t!/^[a-f0-9]{64}$/.test(raw.stdoutDigest) ||\n\t\t\t\t!(\"stderrDigest\" in raw) ||\n\t\t\t\ttypeof raw.stderrDigest !== \"string\" ||\n\t\t\t\t!/^[a-f0-9]{64}$/.test(raw.stderrDigest) ||\n\t\t\t\t!(\"exitCode\" in raw) ||\n\t\t\t\t(raw.exitCode !== null && (typeof raw.exitCode !== \"number\" || !Number.isSafeInteger(raw.exitCode))) ||\n\t\t\t\t!(\"failure\" in raw) ||\n\t\t\t\t(raw.failure !== null && typeof raw.failure !== \"string\")\n\t\t\t)\n\t\t\t\tthrow new VerifiedRunError(\"integrity\");\n\t\t\tconst native = \"receiptCoreDigest\" in raw ? raw.receiptCoreDigest : undefined;\n\t\t\tif (nativeRequired && (typeof native !== \"string\" || !/^[a-f0-9]{64}$/.test(native)))\n\t\t\t\tthrow new VerifiedRunError(\"integrity\");\n\t\t\tif (!nativeRequired && native !== undefined) throw new VerifiedRunError(\"integrity\");\n\t\t\treturn Object.freeze({\n\t\t\t\tclaimId: raw.claimId,\n\t\t\t\texecutionId: raw.executionId,\n\t\t\t\tstdoutDigest: raw.stdoutDigest,\n\t\t\t\tstderrDigest: raw.stderrDigest,\n\t\t\t\texitCode: raw.exitCode,\n\t\t\t\tfailure: raw.failure,\n\t\t\t\t...(typeof native === \"string\" ? { receiptCoreDigest: native } : {}),\n\t\t\t});\n\t\t}),\n\t);\n}\n\n/** Only the authenticated adapter supplies these observations; the reducer itself grants no trust. */\nexport function closesRunClaims(\n\tcontract: RunContract,\n\tcontext: { readonly candidate: string; readonly environment: string; readonly checks: readonly CheckObservation[] },\n): boolean {\n\tconst graph = {\n\t\tschemaVersion: CLAIM_GRAPH_SCHEMA_VERSION,\n\t\tclaims: contract.checks.map((check) => ({\n\t\t\tclaimId: check.claimId,\n\t\t\tkind: \"requirement\" as const,\n\t\t\tstatement: `Exact stdout for ${check.claimId}`,\n\t\t\tseverity: \"required\" as const,\n\t\t\tsatisfaction: { rule: \"all\" as const, inputs: [] },\n\t\t\ttrustFloor: \"trusted_attestation\" as const,\n\t\t\tinvalidationKeys: [],\n\t\t\tscopeSensitive: true,\n\t\t})),\n\t};\n\tconst observations = context.checks.map((check) => ({\n\t\tobservationId: check.executionId,\n\t\tclaimIds: [check.claimId],\n\t\tsource: \"trusted_attestation\" as const,\n\t\tpolarity:\n\t\t\tcheck.exitCode === 0 &&\n\t\t\tcheck.failure === null &&\n\t\t\tcontract.checks.some(\n\t\t\t\t(expected) => expected.claimId === check.claimId && digestBytes(expected.stdout) === check.stdoutDigest,\n\t\t\t)\n\t\t\t\t? (\"supports\" as const)\n\t\t\t\t: (\"violates\" as const),\n\t\tsourceRoot: context.candidate,\n\t\tenvironmentDigest: context.environment,\n\t\tindependenceGroup: check.executionId,\n\t}));\n\treturn (\n\t\tevaluateProofClosure({\n\t\t\tgraph,\n\t\t\tobservations,\n\t\t\twitnessIndependence: \"explicit-groups\",\n\t\t\twaivers: [],\n\t\t\tsourceRoot: context.candidate,\n\t\t\tenvironmentDigest: context.environment,\n\t\t\tworkspaceCompleteness: \"complete\",\n\t\t\tunresolvedEffectIds: [],\n\t\t\tnow: \"1970-01-01T00:00:00.000Z\",\n\t\t}).verdict === \"verified\"\n\t);\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import type { RunContract } from "omk-protocol";
2
+ import type { EvidenceReceipt } from "../../types/evidence.ts";
3
+ import { type CandidateManifest } from "./candidate.ts";
4
+ import { type CheckObservation } from "./evidence-binding.ts";
5
+ import type { JournalSnapshot } from "./journal.ts";
6
+ export type { CheckObservation } from "./evidence-binding.ts";
7
+ export interface VerifiedRunEvidence {
8
+ readonly candidateDigest: string;
9
+ readonly manifest: CandidateManifest;
10
+ readonly receiptDigest: string;
11
+ readonly verified: boolean;
12
+ readonly checks: readonly CheckObservation[];
13
+ readonly receiptFormat: "v3" | "legacy";
14
+ readonly receipts: readonly EvidenceReceipt[];
15
+ }
16
+ export declare function createRunIssuer(runPath: string): void;
17
+ export declare function issueRunEvidence(runPath: string, contract: RunContract, material: {
18
+ readonly generation: number;
19
+ readonly candidateDigest: string;
20
+ readonly environmentDigest: string;
21
+ readonly checks: readonly CheckObservation[];
22
+ }): {
23
+ readonly digest: string;
24
+ readonly verified: boolean;
25
+ };
26
+ export declare function readRunEvidence(runPath: string, journal: JournalSnapshot, environment: string): VerifiedRunEvidence;
27
+ //# sourceMappingURL=evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../../src/core/verified-run/evidence.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,EAAE,KAAK,iBAAiB,EAAiB,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EAAE,KAAK,gBAAgB,EAA2C,MAAM,uBAAuB,CAAC;AACvG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAa9D,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,IAAI,GAAG,QAAQ,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CAC9C;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGrD;AAQD,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE;IACT,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC7C,GACC;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAuBzD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,GAAG,mBAAmB,CAoEnH","sourcesContent":["import { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { join } from \"node:path\";\nimport type { RunContract } from \"omk-protocol\";\nimport type { EvidenceReceipt } from \"../../types/evidence.ts\";\nimport { canonicalJson } from \"../run-journal.ts\";\nimport { type CandidateManifest, loadCandidate } from \"./candidate.ts\";\nimport { readCheckReceipts } from \"./check-receipt.ts\";\nimport { type CheckObservation, closesRunClaims, parseCheckObservations } from \"./evidence-binding.ts\";\nimport type { JournalSnapshot } from \"./journal.ts\";\nimport { digestObject, publishBytes, publishObject, readJson, readRegularFile, VerifiedRunError } from \"./storage.ts\";\n\nexport type { CheckObservation } from \"./evidence-binding.ts\";\n\ninterface Attestation {\n\treadonly version: 1 | 2 | 3;\n\treadonly runId: string;\n\treadonly generation: number;\n\treadonly contractDigest: string;\n\treadonly candidateDigest: string;\n\treadonly environmentDigest: string;\n\treadonly verifierDigest: string;\n\treadonly checks: readonly CheckObservation[];\n\treadonly verified: boolean;\n}\nexport interface VerifiedRunEvidence {\n\treadonly candidateDigest: string;\n\treadonly manifest: CandidateManifest;\n\treadonly receiptDigest: string;\n\treadonly verified: boolean;\n\treadonly checks: readonly CheckObservation[];\n\treadonly receiptFormat: \"v3\" | \"legacy\";\n\treadonly receipts: readonly EvidenceReceipt[];\n}\n\nexport function createRunIssuer(runPath: string): void {\n\t// Recovery must never replace a missing key and re-sign old evidence.\n\tpublishBytes(join(runPath, \"issuer.key\"), randomBytes(32));\n}\n\nfunction tag(runPath: string, receipt: unknown): Buffer {\n\tconst key = readRegularFile(join(runPath, \"issuer.key\"), 32);\n\tif (key.length !== 32) throw new VerifiedRunError(\"integrity\");\n\treturn createHmac(\"sha256\", key).update(\"omk.verified-run.attestation.v1\\0\").update(canonicalJson(receipt)).digest();\n}\n\nexport function issueRunEvidence(\n\trunPath: string,\n\tcontract: RunContract,\n\tmaterial: {\n\t\treadonly generation: number;\n\t\treadonly candidateDigest: string;\n\t\treadonly environmentDigest: string;\n\t\treadonly checks: readonly CheckObservation[];\n\t},\n): { readonly digest: string; readonly verified: boolean } {\n\tconst checks = parseCheckObservations(material.checks, true);\n\tconst candidate = loadCandidate(runPath, material.candidateDigest, contract.budget);\n\treadCheckReceipts(runPath, contract, { manifest: candidate.manifest, checks });\n\tconst receipt: Attestation = {\n\t\tversion: 3,\n\t\trunId: contract.runId,\n\t\tgeneration: material.generation,\n\t\tcontractDigest: digestObject(contract),\n\t\tcandidateDigest: material.candidateDigest,\n\t\tenvironmentDigest: material.environmentDigest,\n\t\tverifierDigest: digestObject(contract.checks),\n\t\tchecks,\n\t\tverified: closesRunClaims(contract, {\n\t\t\tcandidate: material.candidateDigest,\n\t\t\tenvironment: material.environmentDigest,\n\t\t\tchecks,\n\t\t}),\n\t};\n\tconst envelope = { receipt, authenticationTag: tag(runPath, receipt).toString(\"hex\") };\n\tconst digest = digestObject(envelope);\n\tpublishObject(join(runPath, \"attestations\", `${digest}.json`), envelope);\n\treturn { digest, verified: receipt.verified };\n}\n\nexport function readRunEvidence(runPath: string, journal: JournalSnapshot, environment: string): VerifiedRunEvidence {\n\tconst first = journal.records[0]?.event;\n\tconst { candidateDigest, receiptDigest } = journal.state;\n\tif (first?.kind !== \"created\" || !candidateDigest || !receiptDigest) throw new VerifiedRunError(\"evidence_missing\");\n\tconst raw = readJson(join(runPath, \"attestations\", `${receiptDigest}.json`));\n\tif (\n\t\tdigestObject(raw) !== receiptDigest ||\n\t\ttypeof raw !== \"object\" ||\n\t\traw === null ||\n\t\t!(\"receipt\" in raw) ||\n\t\t!(\"authenticationTag\" in raw) ||\n\t\ttypeof raw.authenticationTag !== \"string\" ||\n\t\t!/^[a-f0-9]{64}$/.test(raw.authenticationTag) ||\n\t\t!timingSafeEqual(tag(runPath, raw.receipt), Buffer.from(raw.authenticationTag, \"hex\"))\n\t)\n\t\tthrow new VerifiedRunError(\"integrity\");\n\tconst receipt = raw.receipt;\n\tif (\n\t\ttypeof receipt !== \"object\" ||\n\t\treceipt === null ||\n\t\t!(\"checks\" in receipt) ||\n\t\t!(\"version\" in receipt) ||\n\t\t(receipt.version !== 1 && receipt.version !== 2 && receipt.version !== 3)\n\t)\n\t\tthrow new VerifiedRunError(\"integrity\");\n\tconst checks = parseCheckObservations(receipt.checks, receipt.version !== 1);\n\tconst contract = first.contract;\n\tconst dispatches = journal.records.flatMap(({ event, generation }) =>\n\t\tgeneration === journal.state.generation && event.kind === \"dispatch\" && event.role === \"verifier\" ? [event] : [],\n\t);\n\tif (\n\t\tchecks.length !== contract.checks.length ||\n\t\tchecks.length !== dispatches.length ||\n\t\tnew Set(checks.map((check) => check.claimId)).size !== checks.length ||\n\t\tchecks.some(\n\t\t\t(check) =>\n\t\t\t\t!dispatches.some((event) => event.executionId === check.executionId && event.claimId === check.claimId),\n\t\t)\n\t)\n\t\tthrow new VerifiedRunError(\"integrity\");\n\tconst verified = closesRunClaims(contract, { candidate: candidateDigest, environment, checks });\n\tconst expected: Attestation = {\n\t\tversion: receipt.version,\n\t\trunId: contract.runId,\n\t\tgeneration: journal.state.generation,\n\t\tcontractDigest: digestObject(contract),\n\t\tcandidateDigest,\n\t\tenvironmentDigest: environment,\n\t\tverifierDigest: digestObject(contract.checks),\n\t\tchecks,\n\t\tverified,\n\t};\n\tif (canonicalJson(receipt) !== canonicalJson(expected) || verified !== (journal.state.verification === \"verified\"))\n\t\tthrow new VerifiedRunError(\"integrity\");\n\tconst candidate = loadCandidate(runPath, candidateDigest, contract.budget);\n\tconst receipts =\n\t\treceipt.version !== 1\n\t\t\t? readCheckReceipts(runPath, contract, { manifest: candidate.manifest, checks })\n\t\t\t: Object.freeze([]);\n\treturn Object.freeze({\n\t\tcandidateDigest,\n\t\tmanifest: candidate.manifest,\n\t\treceiptDigest,\n\t\tverified,\n\t\tchecks,\n\t\treceiptFormat: receipt.version !== 1 ? \"v3\" : \"legacy\",\n\t\treceipts,\n\t});\n}\n"]}
@@ -0,0 +1,100 @@
1
+ import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { canonicalJson } from "../run-journal.js";
4
+ import { loadCandidate } from "./candidate.js";
5
+ import { readCheckReceipts } from "./check-receipt.js";
6
+ import { closesRunClaims, parseCheckObservations } from "./evidence-binding.js";
7
+ import { digestObject, publishBytes, publishObject, readJson, readRegularFile, VerifiedRunError } from "./storage.js";
8
+ export function createRunIssuer(runPath) {
9
+ // Recovery must never replace a missing key and re-sign old evidence.
10
+ publishBytes(join(runPath, "issuer.key"), randomBytes(32));
11
+ }
12
+ function tag(runPath, receipt) {
13
+ const key = readRegularFile(join(runPath, "issuer.key"), 32);
14
+ if (key.length !== 32)
15
+ throw new VerifiedRunError("integrity");
16
+ return createHmac("sha256", key).update("omk.verified-run.attestation.v1\0").update(canonicalJson(receipt)).digest();
17
+ }
18
+ export function issueRunEvidence(runPath, contract, material) {
19
+ const checks = parseCheckObservations(material.checks, true);
20
+ const candidate = loadCandidate(runPath, material.candidateDigest, contract.budget);
21
+ readCheckReceipts(runPath, contract, { manifest: candidate.manifest, checks });
22
+ const receipt = {
23
+ version: 3,
24
+ runId: contract.runId,
25
+ generation: material.generation,
26
+ contractDigest: digestObject(contract),
27
+ candidateDigest: material.candidateDigest,
28
+ environmentDigest: material.environmentDigest,
29
+ verifierDigest: digestObject(contract.checks),
30
+ checks,
31
+ verified: closesRunClaims(contract, {
32
+ candidate: material.candidateDigest,
33
+ environment: material.environmentDigest,
34
+ checks,
35
+ }),
36
+ };
37
+ const envelope = { receipt, authenticationTag: tag(runPath, receipt).toString("hex") };
38
+ const digest = digestObject(envelope);
39
+ publishObject(join(runPath, "attestations", `${digest}.json`), envelope);
40
+ return { digest, verified: receipt.verified };
41
+ }
42
+ export function readRunEvidence(runPath, journal, environment) {
43
+ const first = journal.records[0]?.event;
44
+ const { candidateDigest, receiptDigest } = journal.state;
45
+ if (first?.kind !== "created" || !candidateDigest || !receiptDigest)
46
+ throw new VerifiedRunError("evidence_missing");
47
+ const raw = readJson(join(runPath, "attestations", `${receiptDigest}.json`));
48
+ if (digestObject(raw) !== receiptDigest ||
49
+ typeof raw !== "object" ||
50
+ raw === null ||
51
+ !("receipt" in raw) ||
52
+ !("authenticationTag" in raw) ||
53
+ typeof raw.authenticationTag !== "string" ||
54
+ !/^[a-f0-9]{64}$/.test(raw.authenticationTag) ||
55
+ !timingSafeEqual(tag(runPath, raw.receipt), Buffer.from(raw.authenticationTag, "hex")))
56
+ throw new VerifiedRunError("integrity");
57
+ const receipt = raw.receipt;
58
+ if (typeof receipt !== "object" ||
59
+ receipt === null ||
60
+ !("checks" in receipt) ||
61
+ !("version" in receipt) ||
62
+ (receipt.version !== 1 && receipt.version !== 2 && receipt.version !== 3))
63
+ throw new VerifiedRunError("integrity");
64
+ const checks = parseCheckObservations(receipt.checks, receipt.version !== 1);
65
+ const contract = first.contract;
66
+ const dispatches = journal.records.flatMap(({ event, generation }) => generation === journal.state.generation && event.kind === "dispatch" && event.role === "verifier" ? [event] : []);
67
+ if (checks.length !== contract.checks.length ||
68
+ checks.length !== dispatches.length ||
69
+ new Set(checks.map((check) => check.claimId)).size !== checks.length ||
70
+ checks.some((check) => !dispatches.some((event) => event.executionId === check.executionId && event.claimId === check.claimId)))
71
+ throw new VerifiedRunError("integrity");
72
+ const verified = closesRunClaims(contract, { candidate: candidateDigest, environment, checks });
73
+ const expected = {
74
+ version: receipt.version,
75
+ runId: contract.runId,
76
+ generation: journal.state.generation,
77
+ contractDigest: digestObject(contract),
78
+ candidateDigest,
79
+ environmentDigest: environment,
80
+ verifierDigest: digestObject(contract.checks),
81
+ checks,
82
+ verified,
83
+ };
84
+ if (canonicalJson(receipt) !== canonicalJson(expected) || verified !== (journal.state.verification === "verified"))
85
+ throw new VerifiedRunError("integrity");
86
+ const candidate = loadCandidate(runPath, candidateDigest, contract.budget);
87
+ const receipts = receipt.version !== 1
88
+ ? readCheckReceipts(runPath, contract, { manifest: candidate.manifest, checks })
89
+ : Object.freeze([]);
90
+ return Object.freeze({
91
+ candidateDigest,
92
+ manifest: candidate.manifest,
93
+ receiptDigest,
94
+ verified,
95
+ checks,
96
+ receiptFormat: receipt.version !== 1 ? "v3" : "legacy",
97
+ receipts,
98
+ });
99
+ }
100
+ //# sourceMappingURL=evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../../src/core/verified-run/evidence.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { join } from \"node:path\";\nimport type { RunContract } from \"omk-protocol\";\nimport type { EvidenceReceipt } from \"../../types/evidence.ts\";\nimport { canonicalJson } from \"../run-journal.ts\";\nimport { type CandidateManifest, loadCandidate } from \"./candidate.ts\";\nimport { readCheckReceipts } from \"./check-receipt.ts\";\nimport { type CheckObservation, closesRunClaims, parseCheckObservations } from \"./evidence-binding.ts\";\nimport type { JournalSnapshot } from \"./journal.ts\";\nimport { digestObject, publishBytes, publishObject, readJson, readRegularFile, VerifiedRunError } from \"./storage.ts\";\n\nexport type { CheckObservation } from \"./evidence-binding.ts\";\n\ninterface Attestation {\n\treadonly version: 1 | 2 | 3;\n\treadonly runId: string;\n\treadonly generation: number;\n\treadonly contractDigest: string;\n\treadonly candidateDigest: string;\n\treadonly environmentDigest: string;\n\treadonly verifierDigest: string;\n\treadonly checks: readonly CheckObservation[];\n\treadonly verified: boolean;\n}\nexport interface VerifiedRunEvidence {\n\treadonly candidateDigest: string;\n\treadonly manifest: CandidateManifest;\n\treadonly receiptDigest: string;\n\treadonly verified: boolean;\n\treadonly checks: readonly CheckObservation[];\n\treadonly receiptFormat: \"v3\" | \"legacy\";\n\treadonly receipts: readonly EvidenceReceipt[];\n}\n\nexport function createRunIssuer(runPath: string): void {\n\t// Recovery must never replace a missing key and re-sign old evidence.\n\tpublishBytes(join(runPath, \"issuer.key\"), randomBytes(32));\n}\n\nfunction tag(runPath: string, receipt: unknown): Buffer {\n\tconst key = readRegularFile(join(runPath, \"issuer.key\"), 32);\n\tif (key.length !== 32) throw new VerifiedRunError(\"integrity\");\n\treturn createHmac(\"sha256\", key).update(\"omk.verified-run.attestation.v1\\0\").update(canonicalJson(receipt)).digest();\n}\n\nexport function issueRunEvidence(\n\trunPath: string,\n\tcontract: RunContract,\n\tmaterial: {\n\t\treadonly generation: number;\n\t\treadonly candidateDigest: string;\n\t\treadonly environmentDigest: string;\n\t\treadonly checks: readonly CheckObservation[];\n\t},\n): { readonly digest: string; readonly verified: boolean } {\n\tconst checks = parseCheckObservations(material.checks, true);\n\tconst candidate = loadCandidate(runPath, material.candidateDigest, contract.budget);\n\treadCheckReceipts(runPath, contract, { manifest: candidate.manifest, checks });\n\tconst receipt: Attestation = {\n\t\tversion: 3,\n\t\trunId: contract.runId,\n\t\tgeneration: material.generation,\n\t\tcontractDigest: digestObject(contract),\n\t\tcandidateDigest: material.candidateDigest,\n\t\tenvironmentDigest: material.environmentDigest,\n\t\tverifierDigest: digestObject(contract.checks),\n\t\tchecks,\n\t\tverified: closesRunClaims(contract, {\n\t\t\tcandidate: material.candidateDigest,\n\t\t\tenvironment: material.environmentDigest,\n\t\t\tchecks,\n\t\t}),\n\t};\n\tconst envelope = { receipt, authenticationTag: tag(runPath, receipt).toString(\"hex\") };\n\tconst digest = digestObject(envelope);\n\tpublishObject(join(runPath, \"attestations\", `${digest}.json`), envelope);\n\treturn { digest, verified: receipt.verified };\n}\n\nexport function readRunEvidence(runPath: string, journal: JournalSnapshot, environment: string): VerifiedRunEvidence {\n\tconst first = journal.records[0]?.event;\n\tconst { candidateDigest, receiptDigest } = journal.state;\n\tif (first?.kind !== \"created\" || !candidateDigest || !receiptDigest) throw new VerifiedRunError(\"evidence_missing\");\n\tconst raw = readJson(join(runPath, \"attestations\", `${receiptDigest}.json`));\n\tif (\n\t\tdigestObject(raw) !== receiptDigest ||\n\t\ttypeof raw !== \"object\" ||\n\t\traw === null ||\n\t\t!(\"receipt\" in raw) ||\n\t\t!(\"authenticationTag\" in raw) ||\n\t\ttypeof raw.authenticationTag !== \"string\" ||\n\t\t!/^[a-f0-9]{64}$/.test(raw.authenticationTag) ||\n\t\t!timingSafeEqual(tag(runPath, raw.receipt), Buffer.from(raw.authenticationTag, \"hex\"))\n\t)\n\t\tthrow new VerifiedRunError(\"integrity\");\n\tconst receipt = raw.receipt;\n\tif (\n\t\ttypeof receipt !== \"object\" ||\n\t\treceipt === null ||\n\t\t!(\"checks\" in receipt) ||\n\t\t!(\"version\" in receipt) ||\n\t\t(receipt.version !== 1 && receipt.version !== 2 && receipt.version !== 3)\n\t)\n\t\tthrow new VerifiedRunError(\"integrity\");\n\tconst checks = parseCheckObservations(receipt.checks, receipt.version !== 1);\n\tconst contract = first.contract;\n\tconst dispatches = journal.records.flatMap(({ event, generation }) =>\n\t\tgeneration === journal.state.generation && event.kind === \"dispatch\" && event.role === \"verifier\" ? [event] : [],\n\t);\n\tif (\n\t\tchecks.length !== contract.checks.length ||\n\t\tchecks.length !== dispatches.length ||\n\t\tnew Set(checks.map((check) => check.claimId)).size !== checks.length ||\n\t\tchecks.some(\n\t\t\t(check) =>\n\t\t\t\t!dispatches.some((event) => event.executionId === check.executionId && event.claimId === check.claimId),\n\t\t)\n\t)\n\t\tthrow new VerifiedRunError(\"integrity\");\n\tconst verified = closesRunClaims(contract, { candidate: candidateDigest, environment, checks });\n\tconst expected: Attestation = {\n\t\tversion: receipt.version,\n\t\trunId: contract.runId,\n\t\tgeneration: journal.state.generation,\n\t\tcontractDigest: digestObject(contract),\n\t\tcandidateDigest,\n\t\tenvironmentDigest: environment,\n\t\tverifierDigest: digestObject(contract.checks),\n\t\tchecks,\n\t\tverified,\n\t};\n\tif (canonicalJson(receipt) !== canonicalJson(expected) || verified !== (journal.state.verification === \"verified\"))\n\t\tthrow new VerifiedRunError(\"integrity\");\n\tconst candidate = loadCandidate(runPath, candidateDigest, contract.budget);\n\tconst receipts =\n\t\treceipt.version !== 1\n\t\t\t? readCheckReceipts(runPath, contract, { manifest: candidate.manifest, checks })\n\t\t\t: Object.freeze([]);\n\treturn Object.freeze({\n\t\tcandidateDigest,\n\t\tmanifest: candidate.manifest,\n\t\treceiptDigest,\n\t\tverified,\n\t\tchecks,\n\t\treceiptFormat: receipt.version !== 1 ? \"v3\" : \"legacy\",\n\t\treceipts,\n\t});\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { appendFileDurablySync } from "../durable-file-io.ts";
2
+ import type { SessionOwnerLease } from "../session-owner-lease.ts";
3
+ import { type RunEvent, type RunProjection } from "./events.ts";
4
+ interface RecordV2 {
5
+ readonly version: 2;
6
+ readonly seq: number;
7
+ readonly generation: number;
8
+ readonly previous: string;
9
+ readonly event: RunEvent;
10
+ readonly hash: string;
11
+ }
12
+ export interface JournalSnapshot {
13
+ readonly records: readonly RecordV2[];
14
+ readonly state: RunProjection;
15
+ readonly bytesDigest: string;
16
+ }
17
+ export declare const journalPath: (runPath: string) => string;
18
+ /** Read-only: no implicit recovery, tail truncation, mode changes, or model calls. */
19
+ export declare function readRunJournal(runPath: string): JournalSnapshot | null;
20
+ /** v2 event store reuses the v1 durable append and mutation/owner locking primitives. */
21
+ export declare class VerifiedRunJournal {
22
+ private head;
23
+ private poisoned;
24
+ private readonly runPath;
25
+ private readonly owner;
26
+ private readonly persist;
27
+ constructor(runPath: string, owner: SessionOwnerLease, persist?: typeof appendFileDurablySync);
28
+ get state(): RunProjection;
29
+ append(event: RunEvent): RunProjection;
30
+ }
31
+ export {};
32
+ //# sourceMappingURL=journal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../../../src/core/verified-run/journal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAA6B,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAG3F,UAAU,QAAQ;IACjB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AACD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AACD,eAAO,MAAM,WAAW,6BAAiE,CAAC;AAE1F,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA+BtE;AAED,yFAAyF;AACzF,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4C;IAEpE,YAAY,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,+BAAwB,EAKrF;IAED,IAAI,KAAK,IAAI,aAAa,CAGzB;IAED,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,aAAa,CA+BrC;CACD","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { acquireDurableFileMutationLockSync } from \"../durable-file-identity.ts\";\nimport { appendFileDurablySync } from \"../durable-file-io.ts\";\nimport { canonicalJson } from \"../run-journal.ts\";\nimport type { SessionOwnerLease } from \"../session-owner-lease.ts\";\nimport { parseRunEvent, projectRun, type RunEvent, type RunProjection } from \"./events.ts\";\nimport { digestBytes, digestObject, readRegularFile, VerifiedRunError } from \"./storage.ts\";\n\ninterface RecordV2 {\n\treadonly version: 2;\n\treadonly seq: number;\n\treadonly generation: number;\n\treadonly previous: string;\n\treadonly event: RunEvent;\n\treadonly hash: string;\n}\nexport interface JournalSnapshot {\n\treadonly records: readonly RecordV2[];\n\treadonly state: RunProjection;\n\treadonly bytesDigest: string;\n}\nexport const journalPath = (runPath: string): string => join(runPath, \"journal.v2.jsonl\");\n\n/** Read-only: no implicit recovery, tail truncation, mode changes, or model calls. */\nexport function readRunJournal(runPath: string): JournalSnapshot | null {\n\tconst path = journalPath(runPath);\n\tif (!existsSync(path)) return null;\n\tconst bytes = readRegularFile(path, 8388608);\n\tif (!bytes.length || bytes.at(-1) !== 10) throw new VerifiedRunError(\"journal_truncated\");\n\tconst lines = new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes).slice(0, -1).split(\"\\n\");\n\tconst records: RecordV2[] = [];\n\tlet previous = \"0\".repeat(64);\n\tlet generation = 1;\n\tfor (const [index, line] of lines.entries()) {\n\t\tlet raw: unknown;\n\t\ttry {\n\t\t\traw = JSON.parse(line);\n\t\t} catch {\n\t\t\tthrow new VerifiedRunError(\"integrity\");\n\t\t}\n\t\tif (typeof raw !== \"object\" || raw === null || !(\"event\" in raw)) throw new VerifiedRunError(\"integrity\");\n\t\tconst event = parseRunEvent(raw.event);\n\t\tif (event.kind === \"resumed\" || event.kind === \"writer_restarted\" || event.kind === \"tasks_retried\")\n\t\t\tgeneration += 1;\n\t\tconst material = { version: 2 as const, seq: index + 1, generation, previous, event };\n\t\tconst record = Object.freeze({ ...material, hash: digestObject(material) });\n\t\tif (canonicalJson(raw) !== canonicalJson(record)) throw new VerifiedRunError(\"integrity\");\n\t\trecords.push(record);\n\t\tprevious = record.hash;\n\t}\n\treturn {\n\t\trecords: Object.freeze(records),\n\t\tstate: projectRun(records.map((record) => record.event)),\n\t\tbytesDigest: digestBytes(bytes),\n\t};\n}\n\n/** v2 event store reuses the v1 durable append and mutation/owner locking primitives. */\nexport class VerifiedRunJournal {\n\tprivate head: JournalSnapshot | null;\n\tprivate poisoned = false;\n\tprivate readonly runPath: string;\n\tprivate readonly owner: SessionOwnerLease;\n\tprivate readonly persist: (path: string, bytes: Uint8Array) => void;\n\n\tconstructor(runPath: string, owner: SessionOwnerLease, persist = appendFileDurablySync) {\n\t\tthis.runPath = runPath;\n\t\tthis.owner = owner;\n\t\tthis.persist = persist;\n\t\tthis.head = readRunJournal(runPath);\n\t}\n\n\tget state(): RunProjection {\n\t\tif (!this.head) throw new VerifiedRunError(\"missing_run\");\n\t\treturn this.head.state;\n\t}\n\n\tappend(event: RunEvent): RunProjection {\n\t\tconst path = journalPath(this.runPath);\n\t\tif (this.poisoned || !this.owner.owns(path)) throw new VerifiedRunError(\"stale_owner\");\n\t\tconst lock = acquireDurableFileMutationLockSync(path);\n\t\ttry {\n\t\t\tconst current = readRunJournal(this.runPath);\n\t\t\tif (current?.bytesDigest !== this.head?.bytesDigest) throw new VerifiedRunError(\"stale_revision\");\n\t\t\tconst records = current?.records ?? [];\n\t\t\tconst candidate = projectRun([...records.map((record) => record.event), event]);\n\t\t\tconst material = {\n\t\t\t\tversion: 2 as const,\n\t\t\t\tseq: records.length + 1,\n\t\t\t\tgeneration: candidate.generation,\n\t\t\t\tprevious: records.at(-1)?.hash ?? \"0\".repeat(64),\n\t\t\t\tevent,\n\t\t\t};\n\t\t\tconst record: RecordV2 = { ...material, hash: digestObject(material) };\n\t\t\ttry {\n\t\t\t\tthis.persist(path, Buffer.from(`${canonicalJson(record)}\\n`));\n\t\t\t\tconst next = readRunJournal(this.runPath);\n\t\t\t\tif (!next || next.records.at(-1)?.hash !== record.hash) throw new VerifiedRunError(\"integrity\");\n\t\t\t\tthis.owner.refresh(true);\n\t\t\t\tthis.head = next;\n\t\t\t} catch (error) {\n\t\t\t\tthis.poisoned = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn this.state;\n\t\t} finally {\n\t\t\tlock.release();\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,103 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { acquireDurableFileMutationLockSync } from "../durable-file-identity.js";
4
+ import { appendFileDurablySync } from "../durable-file-io.js";
5
+ import { canonicalJson } from "../run-journal.js";
6
+ import { parseRunEvent, projectRun } from "./events.js";
7
+ import { digestBytes, digestObject, readRegularFile, VerifiedRunError } from "./storage.js";
8
+ export const journalPath = (runPath) => join(runPath, "journal.v2.jsonl");
9
+ /** Read-only: no implicit recovery, tail truncation, mode changes, or model calls. */
10
+ export function readRunJournal(runPath) {
11
+ const path = journalPath(runPath);
12
+ if (!existsSync(path))
13
+ return null;
14
+ const bytes = readRegularFile(path, 8388608);
15
+ if (!bytes.length || bytes.at(-1) !== 10)
16
+ throw new VerifiedRunError("journal_truncated");
17
+ const lines = new TextDecoder("utf-8", { fatal: true }).decode(bytes).slice(0, -1).split("\n");
18
+ const records = [];
19
+ let previous = "0".repeat(64);
20
+ let generation = 1;
21
+ for (const [index, line] of lines.entries()) {
22
+ let raw;
23
+ try {
24
+ raw = JSON.parse(line);
25
+ }
26
+ catch {
27
+ throw new VerifiedRunError("integrity");
28
+ }
29
+ if (typeof raw !== "object" || raw === null || !("event" in raw))
30
+ throw new VerifiedRunError("integrity");
31
+ const event = parseRunEvent(raw.event);
32
+ if (event.kind === "resumed" || event.kind === "writer_restarted" || event.kind === "tasks_retried")
33
+ generation += 1;
34
+ const material = { version: 2, seq: index + 1, generation, previous, event };
35
+ const record = Object.freeze({ ...material, hash: digestObject(material) });
36
+ if (canonicalJson(raw) !== canonicalJson(record))
37
+ throw new VerifiedRunError("integrity");
38
+ records.push(record);
39
+ previous = record.hash;
40
+ }
41
+ return {
42
+ records: Object.freeze(records),
43
+ state: projectRun(records.map((record) => record.event)),
44
+ bytesDigest: digestBytes(bytes),
45
+ };
46
+ }
47
+ /** v2 event store reuses the v1 durable append and mutation/owner locking primitives. */
48
+ export class VerifiedRunJournal {
49
+ head;
50
+ poisoned = false;
51
+ runPath;
52
+ owner;
53
+ persist;
54
+ constructor(runPath, owner, persist = appendFileDurablySync) {
55
+ this.runPath = runPath;
56
+ this.owner = owner;
57
+ this.persist = persist;
58
+ this.head = readRunJournal(runPath);
59
+ }
60
+ get state() {
61
+ if (!this.head)
62
+ throw new VerifiedRunError("missing_run");
63
+ return this.head.state;
64
+ }
65
+ append(event) {
66
+ const path = journalPath(this.runPath);
67
+ if (this.poisoned || !this.owner.owns(path))
68
+ throw new VerifiedRunError("stale_owner");
69
+ const lock = acquireDurableFileMutationLockSync(path);
70
+ try {
71
+ const current = readRunJournal(this.runPath);
72
+ if (current?.bytesDigest !== this.head?.bytesDigest)
73
+ throw new VerifiedRunError("stale_revision");
74
+ const records = current?.records ?? [];
75
+ const candidate = projectRun([...records.map((record) => record.event), event]);
76
+ const material = {
77
+ version: 2,
78
+ seq: records.length + 1,
79
+ generation: candidate.generation,
80
+ previous: records.at(-1)?.hash ?? "0".repeat(64),
81
+ event,
82
+ };
83
+ const record = { ...material, hash: digestObject(material) };
84
+ try {
85
+ this.persist(path, Buffer.from(`${canonicalJson(record)}\n`));
86
+ const next = readRunJournal(this.runPath);
87
+ if (!next || next.records.at(-1)?.hash !== record.hash)
88
+ throw new VerifiedRunError("integrity");
89
+ this.owner.refresh(true);
90
+ this.head = next;
91
+ }
92
+ catch (error) {
93
+ this.poisoned = true;
94
+ throw error;
95
+ }
96
+ return this.state;
97
+ }
98
+ finally {
99
+ lock.release();
100
+ }
101
+ }
102
+ }
103
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../../../src/core/verified-run/journal.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { acquireDurableFileMutationLockSync } from \"../durable-file-identity.ts\";\nimport { appendFileDurablySync } from \"../durable-file-io.ts\";\nimport { canonicalJson } from \"../run-journal.ts\";\nimport type { SessionOwnerLease } from \"../session-owner-lease.ts\";\nimport { parseRunEvent, projectRun, type RunEvent, type RunProjection } from \"./events.ts\";\nimport { digestBytes, digestObject, readRegularFile, VerifiedRunError } from \"./storage.ts\";\n\ninterface RecordV2 {\n\treadonly version: 2;\n\treadonly seq: number;\n\treadonly generation: number;\n\treadonly previous: string;\n\treadonly event: RunEvent;\n\treadonly hash: string;\n}\nexport interface JournalSnapshot {\n\treadonly records: readonly RecordV2[];\n\treadonly state: RunProjection;\n\treadonly bytesDigest: string;\n}\nexport const journalPath = (runPath: string): string => join(runPath, \"journal.v2.jsonl\");\n\n/** Read-only: no implicit recovery, tail truncation, mode changes, or model calls. */\nexport function readRunJournal(runPath: string): JournalSnapshot | null {\n\tconst path = journalPath(runPath);\n\tif (!existsSync(path)) return null;\n\tconst bytes = readRegularFile(path, 8388608);\n\tif (!bytes.length || bytes.at(-1) !== 10) throw new VerifiedRunError(\"journal_truncated\");\n\tconst lines = new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes).slice(0, -1).split(\"\\n\");\n\tconst records: RecordV2[] = [];\n\tlet previous = \"0\".repeat(64);\n\tlet generation = 1;\n\tfor (const [index, line] of lines.entries()) {\n\t\tlet raw: unknown;\n\t\ttry {\n\t\t\traw = JSON.parse(line);\n\t\t} catch {\n\t\t\tthrow new VerifiedRunError(\"integrity\");\n\t\t}\n\t\tif (typeof raw !== \"object\" || raw === null || !(\"event\" in raw)) throw new VerifiedRunError(\"integrity\");\n\t\tconst event = parseRunEvent(raw.event);\n\t\tif (event.kind === \"resumed\" || event.kind === \"writer_restarted\" || event.kind === \"tasks_retried\")\n\t\t\tgeneration += 1;\n\t\tconst material = { version: 2 as const, seq: index + 1, generation, previous, event };\n\t\tconst record = Object.freeze({ ...material, hash: digestObject(material) });\n\t\tif (canonicalJson(raw) !== canonicalJson(record)) throw new VerifiedRunError(\"integrity\");\n\t\trecords.push(record);\n\t\tprevious = record.hash;\n\t}\n\treturn {\n\t\trecords: Object.freeze(records),\n\t\tstate: projectRun(records.map((record) => record.event)),\n\t\tbytesDigest: digestBytes(bytes),\n\t};\n}\n\n/** v2 event store reuses the v1 durable append and mutation/owner locking primitives. */\nexport class VerifiedRunJournal {\n\tprivate head: JournalSnapshot | null;\n\tprivate poisoned = false;\n\tprivate readonly runPath: string;\n\tprivate readonly owner: SessionOwnerLease;\n\tprivate readonly persist: (path: string, bytes: Uint8Array) => void;\n\n\tconstructor(runPath: string, owner: SessionOwnerLease, persist = appendFileDurablySync) {\n\t\tthis.runPath = runPath;\n\t\tthis.owner = owner;\n\t\tthis.persist = persist;\n\t\tthis.head = readRunJournal(runPath);\n\t}\n\n\tget state(): RunProjection {\n\t\tif (!this.head) throw new VerifiedRunError(\"missing_run\");\n\t\treturn this.head.state;\n\t}\n\n\tappend(event: RunEvent): RunProjection {\n\t\tconst path = journalPath(this.runPath);\n\t\tif (this.poisoned || !this.owner.owns(path)) throw new VerifiedRunError(\"stale_owner\");\n\t\tconst lock = acquireDurableFileMutationLockSync(path);\n\t\ttry {\n\t\t\tconst current = readRunJournal(this.runPath);\n\t\t\tif (current?.bytesDigest !== this.head?.bytesDigest) throw new VerifiedRunError(\"stale_revision\");\n\t\t\tconst records = current?.records ?? [];\n\t\t\tconst candidate = projectRun([...records.map((record) => record.event), event]);\n\t\t\tconst material = {\n\t\t\t\tversion: 2 as const,\n\t\t\t\tseq: records.length + 1,\n\t\t\t\tgeneration: candidate.generation,\n\t\t\t\tprevious: records.at(-1)?.hash ?? \"0\".repeat(64),\n\t\t\t\tevent,\n\t\t\t};\n\t\t\tconst record: RecordV2 = { ...material, hash: digestObject(material) };\n\t\t\ttry {\n\t\t\t\tthis.persist(path, Buffer.from(`${canonicalJson(record)}\\n`));\n\t\t\t\tconst next = readRunJournal(this.runPath);\n\t\t\t\tif (!next || next.records.at(-1)?.hash !== record.hash) throw new VerifiedRunError(\"integrity\");\n\t\t\t\tthis.owner.refresh(true);\n\t\t\t\tthis.head = next;\n\t\t\t} catch (error) {\n\t\t\t\tthis.poisoned = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn this.state;\n\t\t} finally {\n\t\t\tlock.release();\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,11 @@
1
+ export interface NamespaceIdentity {
2
+ readonly pid: number;
3
+ readonly startTicks: string;
4
+ readonly namespace: string;
5
+ readonly bootId: string;
6
+ }
7
+ export declare function parseNamespaceIdentity(raw: unknown): NamespaceIdentity;
8
+ export declare function captureNamespaceIdentity(pid: number): NamespaceIdentity;
9
+ /** Read only. Never signal a PID that may have been reused by another process. */
10
+ export declare function probeNamespace(identity: NamespaceIdentity): "alive" | "gone" | "unknown";
11
+ //# sourceMappingURL=namespace-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace-identity.d.ts","sourceRoot":"","sources":["../../../src/core/verified-run/namespace-identity.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAqBtE;AAcD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAevE;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAiBxF","sourcesContent":["import { readFileSync, readlinkSync } from \"node:fs\";\nimport { readRunClock } from \"./recovery-clock.ts\";\nimport { VerifiedRunError } from \"./storage.ts\";\n\nexport interface NamespaceIdentity {\n\treadonly pid: number;\n\treadonly startTicks: string;\n\treadonly namespace: string;\n\treadonly bootId: string;\n}\n\nexport function parseNamespaceIdentity(raw: unknown): NamespaceIdentity {\n\tif (\n\t\ttypeof raw !== \"object\" ||\n\t\traw === null ||\n\t\t!(\"pid\" in raw) ||\n\t\ttypeof raw.pid !== \"number\" ||\n\t\t!Number.isSafeInteger(raw.pid) ||\n\t\traw.pid <= 0 ||\n\t\t!(\"startTicks\" in raw) ||\n\t\ttypeof raw.startTicks !== \"string\" ||\n\t\t!/^\\d+$/.test(raw.startTicks) ||\n\t\traw.startTicks.length > 32 ||\n\t\t!(\"namespace\" in raw) ||\n\t\ttypeof raw.namespace !== \"string\" ||\n\t\t!/^pid:\\[\\d+\\]$/.test(raw.namespace) ||\n\t\t!(\"bootId\" in raw) ||\n\t\ttypeof raw.bootId !== \"string\" ||\n\t\t!/^[a-f0-9-]{36}$/.test(raw.bootId)\n\t)\n\t\tthrow new VerifiedRunError(\"integrity\");\n\treturn Object.freeze({ pid: raw.pid, startTicks: raw.startTicks, namespace: raw.namespace, bootId: raw.bootId });\n}\n\nfunction processStat(pid: number): { readonly startTicks: string; readonly state: string } {\n\tconst stat = readFileSync(`/proc/${pid}/stat`, \"utf8\");\n\tconst fields = stat\n\t\t.slice(stat.lastIndexOf(\")\") + 2)\n\t\t.trim()\n\t\t.split(/\\s+/);\n\tconst startTicks = fields[19];\n\tconst state = fields[0];\n\tif (!startTicks || !/^\\d+$/.test(startTicks) || !state) throw new VerifiedRunError(\"process_identity\");\n\treturn { startTicks, state };\n}\n\nexport function captureNamespaceIdentity(pid: number): NamespaceIdentity {\n\tif (!Number.isSafeInteger(pid) || pid <= 0) throw new VerifiedRunError(\"process_identity\");\n\tconst before = processStat(pid);\n\tconst status = readFileSync(`/proc/${pid}/status`, \"utf8\");\n\tconst namespacePids = status\n\t\t.match(/^NSpid:\\s+(.+)$/m)?.[1]\n\t\t?.trim()\n\t\t.split(/\\s+/);\n\tif (!namespacePids || namespacePids.length < 2 || namespacePids.at(-1) !== \"1\")\n\t\tthrow new VerifiedRunError(\"process_identity\");\n\tconst namespace = readlinkSync(`/proc/${pid}/ns/pid`);\n\tconst after = processStat(pid);\n\tif (before.startTicks !== after.startTicks || before.state === \"Z\" || after.state === \"Z\")\n\t\tthrow new VerifiedRunError(\"process_identity\");\n\treturn parseNamespaceIdentity({ pid, startTicks: before.startTicks, namespace, bootId: readRunClock().bootId });\n}\n\n/** Read only. Never signal a PID that may have been reused by another process. */\nexport function probeNamespace(identity: NamespaceIdentity): \"alive\" | \"gone\" | \"unknown\" {\n\ttry {\n\t\tif (readRunClock().bootId !== identity.bootId) return \"unknown\";\n\t} catch {\n\t\treturn \"unknown\";\n\t}\n\ttry {\n\t\tconst current = processStat(identity.pid);\n\t\tif (current.startTicks !== identity.startTicks) return \"gone\";\n\t\t// Linux finishes PID namespace teardown before its init becomes a zombie.\n\t\tif (current.state === \"Z\" || current.state === \"X\") return \"gone\";\n\t\treturn readlinkSync(`/proc/${identity.pid}/ns/pid`) === identity.namespace ? \"alive\" : \"unknown\";\n\t} catch (error) {\n\t\tif (error instanceof Error && \"code\" in error && (error.code === \"ENOENT\" || error.code === \"ESRCH\"))\n\t\t\treturn \"gone\";\n\t\treturn \"unknown\";\n\t}\n}\n"]}
@@ -0,0 +1,77 @@
1
+ import { readFileSync, readlinkSync } from "node:fs";
2
+ import { readRunClock } from "./recovery-clock.js";
3
+ import { VerifiedRunError } from "./storage.js";
4
+ export function parseNamespaceIdentity(raw) {
5
+ if (typeof raw !== "object" ||
6
+ raw === null ||
7
+ !("pid" in raw) ||
8
+ typeof raw.pid !== "number" ||
9
+ !Number.isSafeInteger(raw.pid) ||
10
+ raw.pid <= 0 ||
11
+ !("startTicks" in raw) ||
12
+ typeof raw.startTicks !== "string" ||
13
+ !/^\d+$/.test(raw.startTicks) ||
14
+ raw.startTicks.length > 32 ||
15
+ !("namespace" in raw) ||
16
+ typeof raw.namespace !== "string" ||
17
+ !/^pid:\[\d+\]$/.test(raw.namespace) ||
18
+ !("bootId" in raw) ||
19
+ typeof raw.bootId !== "string" ||
20
+ !/^[a-f0-9-]{36}$/.test(raw.bootId))
21
+ throw new VerifiedRunError("integrity");
22
+ return Object.freeze({ pid: raw.pid, startTicks: raw.startTicks, namespace: raw.namespace, bootId: raw.bootId });
23
+ }
24
+ function processStat(pid) {
25
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
26
+ const fields = stat
27
+ .slice(stat.lastIndexOf(")") + 2)
28
+ .trim()
29
+ .split(/\s+/);
30
+ const startTicks = fields[19];
31
+ const state = fields[0];
32
+ if (!startTicks || !/^\d+$/.test(startTicks) || !state)
33
+ throw new VerifiedRunError("process_identity");
34
+ return { startTicks, state };
35
+ }
36
+ export function captureNamespaceIdentity(pid) {
37
+ if (!Number.isSafeInteger(pid) || pid <= 0)
38
+ throw new VerifiedRunError("process_identity");
39
+ const before = processStat(pid);
40
+ const status = readFileSync(`/proc/${pid}/status`, "utf8");
41
+ const namespacePids = status
42
+ .match(/^NSpid:\s+(.+)$/m)?.[1]
43
+ ?.trim()
44
+ .split(/\s+/);
45
+ if (!namespacePids || namespacePids.length < 2 || namespacePids.at(-1) !== "1")
46
+ throw new VerifiedRunError("process_identity");
47
+ const namespace = readlinkSync(`/proc/${pid}/ns/pid`);
48
+ const after = processStat(pid);
49
+ if (before.startTicks !== after.startTicks || before.state === "Z" || after.state === "Z")
50
+ throw new VerifiedRunError("process_identity");
51
+ return parseNamespaceIdentity({ pid, startTicks: before.startTicks, namespace, bootId: readRunClock().bootId });
52
+ }
53
+ /** Read only. Never signal a PID that may have been reused by another process. */
54
+ export function probeNamespace(identity) {
55
+ try {
56
+ if (readRunClock().bootId !== identity.bootId)
57
+ return "unknown";
58
+ }
59
+ catch {
60
+ return "unknown";
61
+ }
62
+ try {
63
+ const current = processStat(identity.pid);
64
+ if (current.startTicks !== identity.startTicks)
65
+ return "gone";
66
+ // Linux finishes PID namespace teardown before its init becomes a zombie.
67
+ if (current.state === "Z" || current.state === "X")
68
+ return "gone";
69
+ return readlinkSync(`/proc/${identity.pid}/ns/pid`) === identity.namespace ? "alive" : "unknown";
70
+ }
71
+ catch (error) {
72
+ if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ESRCH"))
73
+ return "gone";
74
+ return "unknown";
75
+ }
76
+ }
77
+ //# sourceMappingURL=namespace-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace-identity.js","sourceRoot":"","sources":["../../../src/core/verified-run/namespace-identity.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, readlinkSync } from \"node:fs\";\nimport { readRunClock } from \"./recovery-clock.ts\";\nimport { VerifiedRunError } from \"./storage.ts\";\n\nexport interface NamespaceIdentity {\n\treadonly pid: number;\n\treadonly startTicks: string;\n\treadonly namespace: string;\n\treadonly bootId: string;\n}\n\nexport function parseNamespaceIdentity(raw: unknown): NamespaceIdentity {\n\tif (\n\t\ttypeof raw !== \"object\" ||\n\t\traw === null ||\n\t\t!(\"pid\" in raw) ||\n\t\ttypeof raw.pid !== \"number\" ||\n\t\t!Number.isSafeInteger(raw.pid) ||\n\t\traw.pid <= 0 ||\n\t\t!(\"startTicks\" in raw) ||\n\t\ttypeof raw.startTicks !== \"string\" ||\n\t\t!/^\\d+$/.test(raw.startTicks) ||\n\t\traw.startTicks.length > 32 ||\n\t\t!(\"namespace\" in raw) ||\n\t\ttypeof raw.namespace !== \"string\" ||\n\t\t!/^pid:\\[\\d+\\]$/.test(raw.namespace) ||\n\t\t!(\"bootId\" in raw) ||\n\t\ttypeof raw.bootId !== \"string\" ||\n\t\t!/^[a-f0-9-]{36}$/.test(raw.bootId)\n\t)\n\t\tthrow new VerifiedRunError(\"integrity\");\n\treturn Object.freeze({ pid: raw.pid, startTicks: raw.startTicks, namespace: raw.namespace, bootId: raw.bootId });\n}\n\nfunction processStat(pid: number): { readonly startTicks: string; readonly state: string } {\n\tconst stat = readFileSync(`/proc/${pid}/stat`, \"utf8\");\n\tconst fields = stat\n\t\t.slice(stat.lastIndexOf(\")\") + 2)\n\t\t.trim()\n\t\t.split(/\\s+/);\n\tconst startTicks = fields[19];\n\tconst state = fields[0];\n\tif (!startTicks || !/^\\d+$/.test(startTicks) || !state) throw new VerifiedRunError(\"process_identity\");\n\treturn { startTicks, state };\n}\n\nexport function captureNamespaceIdentity(pid: number): NamespaceIdentity {\n\tif (!Number.isSafeInteger(pid) || pid <= 0) throw new VerifiedRunError(\"process_identity\");\n\tconst before = processStat(pid);\n\tconst status = readFileSync(`/proc/${pid}/status`, \"utf8\");\n\tconst namespacePids = status\n\t\t.match(/^NSpid:\\s+(.+)$/m)?.[1]\n\t\t?.trim()\n\t\t.split(/\\s+/);\n\tif (!namespacePids || namespacePids.length < 2 || namespacePids.at(-1) !== \"1\")\n\t\tthrow new VerifiedRunError(\"process_identity\");\n\tconst namespace = readlinkSync(`/proc/${pid}/ns/pid`);\n\tconst after = processStat(pid);\n\tif (before.startTicks !== after.startTicks || before.state === \"Z\" || after.state === \"Z\")\n\t\tthrow new VerifiedRunError(\"process_identity\");\n\treturn parseNamespaceIdentity({ pid, startTicks: before.startTicks, namespace, bootId: readRunClock().bootId });\n}\n\n/** Read only. Never signal a PID that may have been reused by another process. */\nexport function probeNamespace(identity: NamespaceIdentity): \"alive\" | \"gone\" | \"unknown\" {\n\ttry {\n\t\tif (readRunClock().bootId !== identity.bootId) return \"unknown\";\n\t} catch {\n\t\treturn \"unknown\";\n\t}\n\ttry {\n\t\tconst current = processStat(identity.pid);\n\t\tif (current.startTicks !== identity.startTicks) return \"gone\";\n\t\t// Linux finishes PID namespace teardown before its init becomes a zombie.\n\t\tif (current.state === \"Z\" || current.state === \"X\") return \"gone\";\n\t\treturn readlinkSync(`/proc/${identity.pid}/ns/pid`) === identity.namespace ? \"alive\" : \"unknown\";\n\t} catch (error) {\n\t\tif (error instanceof Error && \"code\" in error && (error.code === \"ENOENT\" || error.code === \"ESRCH\"))\n\t\t\treturn \"gone\";\n\t\treturn \"unknown\";\n\t}\n}\n"]}