@xoxoai/checkmate 0.6.6 → 0.7.0

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 (382) hide show
  1. package/dist/checkmate.js +13375 -0
  2. package/package.json +14 -100
  3. package/plugin/.claude-plugin/plugin.json +10 -0
  4. package/plugin/bin/checkmate.ts +3 -0
  5. package/plugin/contracts/finding.schema.json +78 -0
  6. package/plugin/contracts/gap.schema.json +56 -0
  7. package/plugin/contracts/plan-candidate.schema.json +103 -0
  8. package/plugin/contracts/plan-delta.schema.json +63 -0
  9. package/plugin/contracts/plan-patch.schema.json +116 -0
  10. package/plugin/contracts/plan-request.schema.json +108 -0
  11. package/plugin/contracts/plan.schema.json +313 -0
  12. package/plugin/contracts/profile.schema.json +154 -0
  13. package/plugin/contracts/response.schema.json +25 -0
  14. package/plugin/contracts/result.schema.json +410 -0
  15. package/plugin/dist/checkmate.js +13012 -0
  16. package/plugin/package.json +4 -0
  17. package/plugin/references/browser.md +35 -0
  18. package/plugin/references/http.md +30 -0
  19. package/plugin/references/workflow.md +122 -0
  20. package/plugin/skills/execute/SKILL.md +92 -0
  21. package/plugin/skills/plan/SKILL.md +94 -0
  22. package/plugin/skills/report/SKILL.md +28 -0
  23. package/plugin/skills/revise/SKILL.md +43 -0
  24. package/plugin/skills/setup/SKILL.md +82 -0
  25. package/plugin/skills/status/SKILL.md +20 -0
  26. package/plugin/skills/ui/SKILL.md +26 -0
  27. package/plugin/src/actor.ts +21 -0
  28. package/plugin/src/cli.ts +1133 -0
  29. package/plugin/src/contracts.ts +342 -0
  30. package/plugin/src/index.ts +52 -0
  31. package/plugin/src/plans.ts +1028 -0
  32. package/plugin/src/render.ts +544 -0
  33. package/plugin/src/runs.ts +868 -0
  34. package/plugin/src/store.ts +384 -0
  35. package/plugin/src/targets.ts +205 -0
  36. package/plugin/src/types.ts +319 -0
  37. package/plugin/src/ui/assets/client.js +501 -0
  38. package/plugin/src/ui/assets/favicon.svg +1 -0
  39. package/plugin/src/ui/assets/fonts/inter-OFL.txt +93 -0
  40. package/plugin/src/ui/assets/fonts/inter.woff2 +0 -0
  41. package/plugin/src/ui/assets/fonts/jetbrains-mono-OFL.txt +93 -0
  42. package/plugin/src/ui/assets/fonts/jetbrains-mono.woff2 +0 -0
  43. package/plugin/src/ui/assets/fonts/space-grotesk-OFL.txt +93 -0
  44. package/plugin/src/ui/assets/fonts/space-grotesk.woff2 +0 -0
  45. package/plugin/src/ui/assets/style.css +1926 -0
  46. package/plugin/src/ui/edit.ts +305 -0
  47. package/plugin/src/ui/forms.ts +209 -0
  48. package/plugin/src/ui/html.ts +130 -0
  49. package/plugin/src/ui/layout.ts +198 -0
  50. package/plugin/src/ui/lists.ts +645 -0
  51. package/plugin/src/ui/plan.ts +577 -0
  52. package/plugin/src/ui/run.ts +489 -0
  53. package/plugin/src/ui/server.ts +684 -0
  54. package/plugin/src/validators.d.ts +13 -0
  55. package/plugin/src/validators.js +1 -0
  56. package/plugin/src/views.ts +839 -0
  57. package/LICENSE +0 -21
  58. package/README.md +0 -294
  59. package/bin/checkmate.js +0 -13
  60. package/dist/ai/client.d.ts +0 -64
  61. package/dist/ai/client.d.ts.map +0 -1
  62. package/dist/ai/client.js +0 -313
  63. package/dist/ai/client.js.map +0 -1
  64. package/dist/ai/message-handler.d.ts +0 -9
  65. package/dist/ai/message-handler.d.ts.map +0 -1
  66. package/dist/ai/message-handler.js +0 -50
  67. package/dist/ai/message-handler.js.map +0 -1
  68. package/dist/ai/message-history.d.ts +0 -17
  69. package/dist/ai/message-history.d.ts.map +0 -1
  70. package/dist/ai/message-history.js +0 -45
  71. package/dist/ai/message-history.js.map +0 -1
  72. package/dist/ai/prompts.d.ts +0 -4
  73. package/dist/ai/prompts.d.ts.map +0 -1
  74. package/dist/ai/prompts.js +0 -24
  75. package/dist/ai/prompts.js.map +0 -1
  76. package/dist/ai/rate-limit-policy.d.ts +0 -9
  77. package/dist/ai/rate-limit-policy.d.ts.map +0 -1
  78. package/dist/ai/rate-limit-policy.js +0 -34
  79. package/dist/ai/rate-limit-policy.js.map +0 -1
  80. package/dist/ai/tool-response-handler.d.ts +0 -16
  81. package/dist/ai/tool-response-handler.d.ts.map +0 -1
  82. package/dist/ai/tool-response-handler.js +0 -85
  83. package/dist/ai/tool-response-handler.js.map +0 -1
  84. package/dist/ai/turn-processor.d.ts +0 -34
  85. package/dist/ai/turn-processor.d.ts.map +0 -1
  86. package/dist/ai/turn-processor.js +0 -107
  87. package/dist/ai/turn-processor.js.map +0 -1
  88. package/dist/api/allocate-run-identity.d.ts +0 -8
  89. package/dist/api/allocate-run-identity.d.ts.map +0 -1
  90. package/dist/api/allocate-run-identity.js +0 -10
  91. package/dist/api/allocate-run-identity.js.map +0 -1
  92. package/dist/api/execute-prepared-run.d.ts +0 -22
  93. package/dist/api/execute-prepared-run.d.ts.map +0 -1
  94. package/dist/api/execute-prepared-run.js +0 -184
  95. package/dist/api/execute-prepared-run.js.map +0 -1
  96. package/dist/api/index.d.ts +0 -25
  97. package/dist/api/index.d.ts.map +0 -1
  98. package/dist/api/index.js +0 -69
  99. package/dist/api/index.js.map +0 -1
  100. package/dist/api/prepare-run.d.ts +0 -44
  101. package/dist/api/prepare-run.d.ts.map +0 -1
  102. package/dist/api/prepare-run.js +0 -261
  103. package/dist/api/prepare-run.js.map +0 -1
  104. package/dist/cli/diagnostic-relay.d.ts +0 -6
  105. package/dist/cli/diagnostic-relay.d.ts.map +0 -1
  106. package/dist/cli/diagnostic-relay.js +0 -6
  107. package/dist/cli/diagnostic-relay.js.map +0 -1
  108. package/dist/cli/main.d.ts +0 -21
  109. package/dist/cli/main.d.ts.map +0 -1
  110. package/dist/cli/main.js +0 -166
  111. package/dist/cli/main.js.map +0 -1
  112. package/dist/cli/protocol.d.ts +0 -57
  113. package/dist/cli/protocol.d.ts.map +0 -1
  114. package/dist/cli/protocol.js +0 -116
  115. package/dist/cli/protocol.js.map +0 -1
  116. package/dist/cli/reconcile-result.d.ts +0 -13
  117. package/dist/cli/reconcile-result.d.ts.map +0 -1
  118. package/dist/cli/reconcile-result.js +0 -300
  119. package/dist/cli/reconcile-result.js.map +0 -1
  120. package/dist/cli/run-parent.d.ts +0 -37
  121. package/dist/cli/run-parent.d.ts.map +0 -1
  122. package/dist/cli/run-parent.js +0 -491
  123. package/dist/cli/run-parent.js.map +0 -1
  124. package/dist/cli/signals.d.ts +0 -36
  125. package/dist/cli/signals.d.ts.map +0 -1
  126. package/dist/cli/signals.js +0 -85
  127. package/dist/cli/signals.js.map +0 -1
  128. package/dist/cli/static-commands.d.ts +0 -11
  129. package/dist/cli/static-commands.d.ts.map +0 -1
  130. package/dist/cli/static-commands.js +0 -83
  131. package/dist/cli/static-commands.js.map +0 -1
  132. package/dist/cli/worker-stream-guard.d.ts +0 -20
  133. package/dist/cli/worker-stream-guard.d.ts.map +0 -1
  134. package/dist/cli/worker-stream-guard.js +0 -41
  135. package/dist/cli/worker-stream-guard.js.map +0 -1
  136. package/dist/cli/worker.d.ts +0 -3
  137. package/dist/cli/worker.d.ts.map +0 -1
  138. package/dist/cli/worker.js +0 -111
  139. package/dist/cli/worker.js.map +0 -1
  140. package/dist/config/ingestion.d.ts +0 -58
  141. package/dist/config/ingestion.d.ts.map +0 -1
  142. package/dist/config/ingestion.js +0 -283
  143. package/dist/config/ingestion.js.map +0 -1
  144. package/dist/config/manifest.d.ts +0 -20
  145. package/dist/config/manifest.d.ts.map +0 -1
  146. package/dist/config/manifest.js +0 -91
  147. package/dist/config/manifest.js.map +0 -1
  148. package/dist/config/model-egress.d.ts +0 -10
  149. package/dist/config/model-egress.d.ts.map +0 -1
  150. package/dist/config/model-egress.js +0 -62
  151. package/dist/config/model-egress.js.map +0 -1
  152. package/dist/config/package-resolution.d.ts +0 -4
  153. package/dist/config/package-resolution.d.ts.map +0 -1
  154. package/dist/config/package-resolution.js +0 -24
  155. package/dist/config/package-resolution.js.map +0 -1
  156. package/dist/config/policy.d.ts +0 -44
  157. package/dist/config/policy.d.ts.map +0 -1
  158. package/dist/config/policy.js +0 -141
  159. package/dist/config/policy.js.map +0 -1
  160. package/dist/config/record.d.ts +0 -4
  161. package/dist/config/record.d.ts.map +0 -1
  162. package/dist/config/record.js +0 -10
  163. package/dist/config/record.js.map +0 -1
  164. package/dist/config/secrets.d.ts +0 -11
  165. package/dist/config/secrets.d.ts.map +0 -1
  166. package/dist/config/secrets.js +0 -42
  167. package/dist/config/secrets.js.map +0 -1
  168. package/dist/contracts/diagnostics.d.ts +0 -4
  169. package/dist/contracts/diagnostics.d.ts.map +0 -1
  170. package/dist/contracts/diagnostics.js +0 -31
  171. package/dist/contracts/diagnostics.js.map +0 -1
  172. package/dist/contracts/serialize.d.ts +0 -2
  173. package/dist/contracts/serialize.d.ts.map +0 -1
  174. package/dist/contracts/serialize.js +0 -90
  175. package/dist/contracts/serialize.js.map +0 -1
  176. package/dist/contracts/types.d.ts +0 -316
  177. package/dist/contracts/types.d.ts.map +0 -1
  178. package/dist/contracts/types.js +0 -74
  179. package/dist/contracts/types.js.map +0 -1
  180. package/dist/contracts/validator.d.ts +0 -8
  181. package/dist/contracts/validator.d.ts.map +0 -1
  182. package/dist/contracts/validator.js +0 -71
  183. package/dist/contracts/validator.js.map +0 -1
  184. package/dist/driver.d.ts +0 -97
  185. package/dist/driver.d.ts.map +0 -1
  186. package/dist/driver.js +0 -24
  187. package/dist/driver.js.map +0 -1
  188. package/dist/drivers/descriptor.d.ts +0 -15
  189. package/dist/drivers/descriptor.d.ts.map +0 -1
  190. package/dist/drivers/descriptor.js +0 -122
  191. package/dist/drivers/descriptor.js.map +0 -1
  192. package/dist/drivers/loader.d.ts +0 -18
  193. package/dist/drivers/loader.d.ts.map +0 -1
  194. package/dist/drivers/loader.js +0 -97
  195. package/dist/drivers/loader.js.map +0 -1
  196. package/dist/drivers/web/checkmate-driver.json +0 -47
  197. package/dist/drivers/web/index.d.ts +0 -6
  198. package/dist/drivers/web/index.d.ts.map +0 -1
  199. package/dist/drivers/web/index.js +0 -27
  200. package/dist/drivers/web/index.js.map +0 -1
  201. package/dist/drivers/web/session.d.ts +0 -25
  202. package/dist/drivers/web/session.d.ts.map +0 -1
  203. package/dist/drivers/web/session.js +0 -132
  204. package/dist/drivers/web/session.js.map +0 -1
  205. package/dist/drivers/web/tools/network-request-recorder.d.ts +0 -46
  206. package/dist/drivers/web/tools/network-request-recorder.d.ts.map +0 -1
  207. package/dist/drivers/web/tools/network-request-recorder.js +0 -164
  208. package/dist/drivers/web/tools/network-request-recorder.js.map +0 -1
  209. package/dist/drivers/web/tools/screenshot-service.d.ts +0 -10
  210. package/dist/drivers/web/tools/screenshot-service.d.ts.map +0 -1
  211. package/dist/drivers/web/tools/screenshot-service.js +0 -23
  212. package/dist/drivers/web/tools/screenshot-service.js.map +0 -1
  213. package/dist/drivers/web/tools/snapshot-filter/index.d.ts +0 -4
  214. package/dist/drivers/web/tools/snapshot-filter/index.d.ts.map +0 -1
  215. package/dist/drivers/web/tools/snapshot-filter/index.js +0 -4
  216. package/dist/drivers/web/tools/snapshot-filter/index.js.map +0 -1
  217. package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.d.ts +0 -15
  218. package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.d.ts.map +0 -1
  219. package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.js +0 -99
  220. package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.js.map +0 -1
  221. package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.d.ts +0 -5
  222. package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.d.ts.map +0 -1
  223. package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.js +0 -56
  224. package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.js.map +0 -1
  225. package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.d.ts +0 -3
  226. package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.d.ts.map +0 -1
  227. package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.js +0 -85
  228. package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.js.map +0 -1
  229. package/dist/drivers/web/tools/snapshot-service.d.ts +0 -31
  230. package/dist/drivers/web/tools/snapshot-service.d.ts.map +0 -1
  231. package/dist/drivers/web/tools/snapshot-service.js +0 -96
  232. package/dist/drivers/web/tools/snapshot-service.js.map +0 -1
  233. package/dist/drivers/web/tools/tool.d.ts +0 -76
  234. package/dist/drivers/web/tools/tool.d.ts.map +0 -1
  235. package/dist/drivers/web/tools/tool.js +0 -562
  236. package/dist/drivers/web/tools/tool.js.map +0 -1
  237. package/dist/drivers/web/tools/transient-state-tracker.d.ts +0 -26
  238. package/dist/drivers/web/tools/transient-state-tracker.d.ts.map +0 -1
  239. package/dist/drivers/web/tools/transient-state-tracker.js +0 -301
  240. package/dist/drivers/web/tools/transient-state-tracker.js.map +0 -1
  241. package/dist/drivers/web/tools/types.d.ts +0 -11
  242. package/dist/drivers/web/tools/types.d.ts.map +0 -1
  243. package/dist/drivers/web/tools/types.js +0 -2
  244. package/dist/drivers/web/tools/types.js.map +0 -1
  245. package/dist/evidence/atomic-file.d.ts +0 -23
  246. package/dist/evidence/atomic-file.d.ts.map +0 -1
  247. package/dist/evidence/atomic-file.js +0 -104
  248. package/dist/evidence/atomic-file.js.map +0 -1
  249. package/dist/evidence/layout.d.ts +0 -30
  250. package/dist/evidence/layout.d.ts.map +0 -1
  251. package/dist/evidence/layout.js +0 -152
  252. package/dist/evidence/layout.js.map +0 -1
  253. package/dist/evidence/retention.d.ts +0 -5
  254. package/dist/evidence/retention.d.ts.map +0 -1
  255. package/dist/evidence/retention.js +0 -10
  256. package/dist/evidence/retention.js.map +0 -1
  257. package/dist/evidence/store.d.ts +0 -101
  258. package/dist/evidence/store.d.ts.map +0 -1
  259. package/dist/evidence/store.js +0 -375
  260. package/dist/evidence/store.js.map +0 -1
  261. package/dist/evidence/terminal-finalizer.d.ts +0 -19
  262. package/dist/evidence/terminal-finalizer.d.ts.map +0 -1
  263. package/dist/evidence/terminal-finalizer.js +0 -56
  264. package/dist/evidence/terminal-finalizer.js.map +0 -1
  265. package/dist/index.d.ts +0 -9
  266. package/dist/index.d.ts.map +0 -1
  267. package/dist/index.js +0 -12
  268. package/dist/index.js.map +0 -1
  269. package/dist/logging/invocation-logger.d.ts +0 -4
  270. package/dist/logging/invocation-logger.d.ts.map +0 -1
  271. package/dist/logging/invocation-logger.js +0 -25
  272. package/dist/logging/invocation-logger.js.map +0 -1
  273. package/dist/logging/level-logger.d.ts +0 -11
  274. package/dist/logging/level-logger.d.ts.map +0 -1
  275. package/dist/logging/level-logger.js +0 -82
  276. package/dist/logging/level-logger.js.map +0 -1
  277. package/dist/logging/types.d.ts +0 -8
  278. package/dist/logging/types.d.ts.map +0 -1
  279. package/dist/logging/types.js +0 -7
  280. package/dist/logging/types.js.map +0 -1
  281. package/dist/redaction/diagnostic-sanitizer.d.ts +0 -7
  282. package/dist/redaction/diagnostic-sanitizer.d.ts.map +0 -1
  283. package/dist/redaction/diagnostic-sanitizer.js +0 -42
  284. package/dist/redaction/diagnostic-sanitizer.js.map +0 -1
  285. package/dist/redaction/redactor.d.ts +0 -40
  286. package/dist/redaction/redactor.d.ts.map +0 -1
  287. package/dist/redaction/redactor.js +0 -176
  288. package/dist/redaction/redactor.js.map +0 -1
  289. package/dist/redaction/scrub.d.ts +0 -29
  290. package/dist/redaction/scrub.d.ts.map +0 -1
  291. package/dist/redaction/scrub.js +0 -54
  292. package/dist/redaction/scrub.js.map +0 -1
  293. package/dist/runtime/config.d.ts +0 -16
  294. package/dist/runtime/config.d.ts.map +0 -1
  295. package/dist/runtime/config.js +0 -2
  296. package/dist/runtime/config.js.map +0 -1
  297. package/dist/runtime/driver-boundary.d.ts +0 -16
  298. package/dist/runtime/driver-boundary.d.ts.map +0 -1
  299. package/dist/runtime/driver-boundary.js +0 -71
  300. package/dist/runtime/driver-boundary.js.map +0 -1
  301. package/dist/runtime/driver-session.d.ts +0 -9
  302. package/dist/runtime/driver-session.d.ts.map +0 -1
  303. package/dist/runtime/driver-session.js +0 -64
  304. package/dist/runtime/driver-session.js.map +0 -1
  305. package/dist/runtime/internal-step-evidence.d.ts +0 -30
  306. package/dist/runtime/internal-step-evidence.d.ts.map +0 -1
  307. package/dist/runtime/internal-step-evidence.js +0 -82
  308. package/dist/runtime/internal-step-evidence.js.map +0 -1
  309. package/dist/runtime/result-adapter.d.ts +0 -9
  310. package/dist/runtime/result-adapter.d.ts.map +0 -1
  311. package/dist/runtime/result-adapter.js +0 -77
  312. package/dist/runtime/result-adapter.js.map +0 -1
  313. package/dist/runtime/runner.d.ts +0 -43
  314. package/dist/runtime/runner.d.ts.map +0 -1
  315. package/dist/runtime/runner.js +0 -72
  316. package/dist/runtime/runner.js.map +0 -1
  317. package/dist/runtime/scenario-control.d.ts +0 -40
  318. package/dist/runtime/scenario-control.d.ts.map +0 -1
  319. package/dist/runtime/scenario-control.js +0 -94
  320. package/dist/runtime/scenario-control.js.map +0 -1
  321. package/dist/runtime/scenario-runner.d.ts +0 -41
  322. package/dist/runtime/scenario-runner.d.ts.map +0 -1
  323. package/dist/runtime/scenario-runner.js +0 -274
  324. package/dist/runtime/scenario-runner.js.map +0 -1
  325. package/dist/runtime/scenario-state.d.ts +0 -55
  326. package/dist/runtime/scenario-state.d.ts.map +0 -1
  327. package/dist/runtime/scenario-state.js +0 -128
  328. package/dist/runtime/scenario-state.js.map +0 -1
  329. package/dist/runtime/step-execution.d.ts +0 -27
  330. package/dist/runtime/step-execution.d.ts.map +0 -1
  331. package/dist/runtime/step-execution.js +0 -143
  332. package/dist/runtime/step-execution.js.map +0 -1
  333. package/dist/runtime/text.d.ts +0 -10
  334. package/dist/runtime/text.d.ts.map +0 -1
  335. package/dist/runtime/text.js +0 -25
  336. package/dist/runtime/text.js.map +0 -1
  337. package/dist/runtime/types.d.ts +0 -53
  338. package/dist/runtime/types.d.ts.map +0 -1
  339. package/dist/runtime/types.js +0 -2
  340. package/dist/runtime/types.js.map +0 -1
  341. package/dist/runtime/usage-tracker.d.ts +0 -37
  342. package/dist/runtime/usage-tracker.d.ts.map +0 -1
  343. package/dist/runtime/usage-tracker.js +0 -110
  344. package/dist/runtime/usage-tracker.js.map +0 -1
  345. package/dist/tools/define-agent-tool.d.ts +0 -20
  346. package/dist/tools/define-agent-tool.d.ts.map +0 -1
  347. package/dist/tools/define-agent-tool.js +0 -30
  348. package/dist/tools/define-agent-tool.js.map +0 -1
  349. package/dist/tools/dispatcher.d.ts +0 -19
  350. package/dist/tools/dispatcher.d.ts.map +0 -1
  351. package/dist/tools/dispatcher.js +0 -91
  352. package/dist/tools/dispatcher.js.map +0 -1
  353. package/dist/tools/loop-detector.d.ts +0 -27
  354. package/dist/tools/loop-detector.d.ts.map +0 -1
  355. package/dist/tools/loop-detector.js +0 -76
  356. package/dist/tools/loop-detector.js.map +0 -1
  357. package/dist/tools/registry.d.ts +0 -19
  358. package/dist/tools/registry.d.ts.map +0 -1
  359. package/dist/tools/registry.js +0 -54
  360. package/dist/tools/registry.js.map +0 -1
  361. package/dist/tools/step/result-tool.d.ts +0 -7
  362. package/dist/tools/step/result-tool.d.ts.map +0 -1
  363. package/dist/tools/step/result-tool.js +0 -34
  364. package/dist/tools/step/result-tool.js.map +0 -1
  365. package/dist/tools/tool-contract.d.ts +0 -3
  366. package/dist/tools/tool-contract.d.ts.map +0 -1
  367. package/dist/tools/tool-contract.js +0 -2
  368. package/dist/tools/tool-contract.js.map +0 -1
  369. package/dist/tools/types.d.ts +0 -210
  370. package/dist/tools/types.d.ts.map +0 -1
  371. package/dist/tools/types.js +0 -4
  372. package/dist/tools/types.js.map +0 -1
  373. package/docs/CLI.md +0 -59
  374. package/docs/CONFIGURATION.md +0 -105
  375. package/docs/DRIVERS.md +0 -79
  376. package/docs/EVIDENCE.md +0 -64
  377. package/schemas/checkmate-config.v1.json +0 -176
  378. package/schemas/describe-result.v1.json +0 -414
  379. package/schemas/driver-descriptor.v1.json +0 -66
  380. package/schemas/run-request.v1.json +0 -67
  381. package/schemas/run-result.v1.json +0 -333
  382. package/schemas/validation-result.v1.json +0 -86
@@ -0,0 +1,544 @@
1
+ import type { Plan, Result } from './types.ts'
2
+ import type { Store } from './store.ts'
3
+ import type { Approval } from './plans.ts'
4
+ import type { ApprovalLabel } from './views.ts'
5
+ import { requireChoice, requireCondition, revisionReference } from './contracts.ts'
6
+ import { planRevision } from './plans.ts'
7
+ import { outcomeCounts, resultAcceptance, runResult, runState } from './runs.ts'
8
+ import { approvalLabel, evidenceBytes } from './views.ts'
9
+
10
+ type Block =
11
+ | { heading: string; level?: 2 | 3 }
12
+ | { text: string }
13
+ | { table: string[]; rows: string[][] }
14
+ | { list: string[]; numbered?: boolean }
15
+ | { stats: Array<{ label: string; value: string }> }
16
+ | { evidence: Array<EvidenceItem> }
17
+ | { record: unknown }
18
+ type Report = { title: string; note: string; blocks: Block[] }
19
+ type EvidenceItem = { label: string; name: string; mediaType: string; size: number; image?: string; text?: string }
20
+ export type Format = 'md' | 'html' | 'json'
21
+ export type RunReport = {
22
+ result: Result
23
+ sha256: string
24
+ acceptance: { state: ApprovalLabel; latest: Approval | null }
25
+ plan: Pick<Plan, 'planId' | 'revision' | 'objective' | 'requirements' | 'gaps' | 'decisions' | 'assumptions'>
26
+ inline: Record<string, { image?: string; text?: string }>
27
+ }
28
+
29
+ const formats = ['md', 'html', 'json'] as const
30
+ const maxInlineImage = 512 * 1024
31
+ const maxInlineText = 16 * 1024
32
+ const inlineImages = ['image/png', 'image/jpeg', 'image/webp', 'image/gif']
33
+ const inlineTexts = ['text/plain', 'application/json', 'text/markdown']
34
+ const tones: Record<string, string> = {
35
+ passed: 'pass',
36
+ approved: 'pass',
37
+ completed: 'pass',
38
+ complete: 'pass',
39
+ tested: 'pass',
40
+ resolved: 'pass',
41
+ failed: 'fail',
42
+ rejected: 'fail',
43
+ operational_error: 'fail',
44
+ error: 'warn',
45
+ inconclusive: 'warn',
46
+ interrupted: 'warn',
47
+ partial: 'warn',
48
+ pending: 'warn',
49
+ open: 'warn',
50
+ blocking: 'warn',
51
+ uncovered: 'warn',
52
+ blocked: 'muted',
53
+ not_run: 'muted',
54
+ not_checked: 'muted',
55
+ deferred: 'muted',
56
+ not_executed: 'muted',
57
+ }
58
+ const none = '—'
59
+ const join = (values: string[]) => (values.length ? values.join(', ') : none)
60
+
61
+ function markdownText(value: string) {
62
+ return value
63
+ .replace(/\\/g, '\\\\')
64
+ .replace(/([|[\]*_`])/g, '\\$1')
65
+ .replace(/</g, '&lt;')
66
+ .replace(/>/g, '&gt;')
67
+ .replace(/\r?\n/g, ' ')
68
+ }
69
+
70
+ function fence(text: string, language = '') {
71
+ const ticks = '`'.repeat(Math.max(3, ...[...text.matchAll(/`+/g)].map((match) => match[0].length + 1)))
72
+ return `${ticks}${language}\n${text}\n${ticks}`
73
+ }
74
+
75
+ function jsonFence(value: unknown) {
76
+ return fence(JSON.stringify(value, null, 2), 'json')
77
+ }
78
+
79
+ function indent(text: string) {
80
+ return text
81
+ .split('\n')
82
+ .map((line) => ` ${line}`)
83
+ .join('\n')
84
+ }
85
+
86
+ function markdown(report: Report) {
87
+ const parts = [`# ${markdownText(report.title)}`, markdownText(report.note)]
88
+ for (const block of report.blocks) {
89
+ if ('heading' in block) parts.push(`${'#'.repeat(block.level ?? 2)} ${markdownText(block.heading)}`)
90
+ else if ('text' in block) parts.push(markdownText(block.text))
91
+ else if ('table' in block)
92
+ parts.push(
93
+ [
94
+ `| ${block.table.map(markdownText).join(' | ')} |`,
95
+ `| ${block.table.map(() => '---').join(' | ')} |`,
96
+ ...block.rows.map((row) => `| ${row.map(markdownText).join(' | ')} |`),
97
+ ].join('\n')
98
+ )
99
+ else if ('list' in block)
100
+ parts.push(
101
+ block.list.map((item, i) => `${block.numbered ? `${i + 1}.` : '-'} ${markdownText(item)}`).join('\n')
102
+ )
103
+ else if ('stats' in block)
104
+ parts.push(
105
+ block.stats.map((stat) => `- **${markdownText(stat.label)}:** ${markdownText(stat.value)}`).join('\n')
106
+ )
107
+ else if ('evidence' in block)
108
+ parts.push(
109
+ block.evidence
110
+ .map((item) =>
111
+ [
112
+ `- ${markdownText(item.label)}: ${markdownText(item.name)} (${item.mediaType}, ${item.size} bytes)`,
113
+ ...(item.text === undefined ? [] : ['', indent(fence(item.text))]),
114
+ ].join('\n')
115
+ )
116
+ .join('\n')
117
+ )
118
+ else
119
+ parts.push(`<details>\n<summary>Complete JSON record</summary>\n\n${jsonFence(block.record)}\n\n</details>`)
120
+ }
121
+ return parts.join('\n\n') + '\n'
122
+ }
123
+
124
+ function escapeHtml(value: string) {
125
+ return value.replace(
126
+ /[&<>"']/g,
127
+ (character) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[character]!
128
+ )
129
+ }
130
+
131
+ const styles = `
132
+ :root{--ink:#1b2130;--muted:#5d6678;--line:#dde2ea;--soft:#f4f6f9;--accent:#3b4fd8;
133
+ --pass:#1f7a4d;--pass-bg:#e5f4ec;--fail:#b42318;--fail-bg:#fdecea;--warn:#9a5b00;--warn-bg:#fdf3e1;--muted-bg:#eef0f4}
134
+ *{box-sizing:border-box}
135
+ body{font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;color:var(--ink);background:#fff;margin:0}
136
+ main{max-width:72rem;margin:0 auto;padding:2.5rem 1.5rem 4rem}
137
+ header{border-bottom:3px solid var(--accent);padding-bottom:1rem;margin-bottom:1.5rem}
138
+ h1{font-size:1.8rem;line-height:1.2;margin:0 0 .4rem}
139
+ h2{font-size:1.2rem;margin:2.2rem 0 .7rem;padding-bottom:.3rem;border-bottom:1px solid var(--line)}
140
+ h3{font-size:1rem;margin:1.4rem 0 .5rem}
141
+ .note{color:var(--muted);margin:0}
142
+ table{border-collapse:collapse;width:100%;margin:.4rem 0 1.2rem;font-size:.92rem}
143
+ th,td{padding:.45rem .6rem;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);overflow-wrap:break-word}
144
+ .token{word-break:break-all}
145
+ th{font-weight:600;background:var(--soft);font-size:.82rem;text-transform:uppercase;letter-spacing:.03em;color:var(--muted)}
146
+ tbody tr:nth-child(even) td{background:#fafbfc}
147
+ .pill{display:inline-block;padding:.05rem .5rem;border-radius:999px;font-size:.8rem;font-weight:600;white-space:nowrap}
148
+ .pass{color:var(--pass);background:var(--pass-bg)}.fail{color:var(--fail);background:var(--fail-bg)}
149
+ .warn{color:var(--warn);background:var(--warn-bg)}.muted{color:var(--muted);background:var(--muted-bg)}
150
+ .stats{display:grid;grid-template-columns:repeat(auto-fill,minmax(9.5rem,1fr));gap:.6rem;margin:1rem 0 1.5rem}
151
+ .stat{border:1px solid var(--line);border-radius:.5rem;padding:.6rem .8rem}
152
+ .stat b{display:block;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:600}
153
+ .stat span{font-size:1.05rem;font-weight:600;overflow-wrap:anywhere}
154
+ .evidence{display:grid;grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));gap:1rem;margin:.5rem 0 1.5rem}
155
+ figure{margin:0;border:1px solid var(--line);border-radius:.5rem;padding:.6rem;break-inside:avoid}
156
+ figure img{display:block;max-width:100%;height:auto;border-radius:.3rem}
157
+ figcaption{font-size:.82rem;color:var(--muted);margin-top:.4rem;overflow-wrap:anywhere}
158
+ pre{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--soft);padding:.8rem;border-radius:.4rem;font-size:.82rem;margin:0}
159
+ details{margin-top:2.5rem}summary{cursor:pointer;font-weight:600}
160
+ @page{size:A4;margin:16mm 14mm}
161
+ @media print{
162
+ body{font-size:10.5pt}main{max-width:none;padding:0}
163
+ h2{break-after:avoid}h3{break-after:avoid}tr,figure,.stat{break-inside:avoid}thead{display:table-header-group}
164
+ tbody tr:nth-child(even) td{background:none}details{display:none}
165
+ *{-webkit-print-color-adjust:exact;print-color-adjust:exact}}
166
+ `
167
+
168
+ function cell(value: string) {
169
+ const tone = tones[value]
170
+ if (tone) return `<span class="pill ${tone}">${escapeHtml(value)}</span>`
171
+ return value
172
+ .split(/(\s+)/)
173
+ .map((part) => (part.length > 24 ? `<span class="token">${escapeHtml(part)}</span>` : escapeHtml(part)))
174
+ .join('')
175
+ }
176
+
177
+ function figure(item: EvidenceItem) {
178
+ const body = item.image
179
+ ? `<img src="${item.image}" alt="${escapeHtml(item.name)}">`
180
+ : item.text !== undefined
181
+ ? `<pre>${escapeHtml(item.text)}</pre>`
182
+ : ''
183
+ return `<figure>${body}<figcaption>${escapeHtml(item.label)} · ${escapeHtml(item.name)} · ${escapeHtml(item.mediaType)} · ${item.size} bytes</figcaption></figure>`
184
+ }
185
+
186
+ function html(report: Report) {
187
+ const parts = [
188
+ `<header><h1>${escapeHtml(report.title)}</h1><p class="note">${escapeHtml(report.note)}</p></header>`,
189
+ ]
190
+ for (const block of report.blocks) {
191
+ if ('heading' in block) parts.push(`<h${block.level ?? 2}>${escapeHtml(block.heading)}</h${block.level ?? 2}>`)
192
+ else if ('text' in block) parts.push(`<p>${escapeHtml(block.text)}</p>`)
193
+ else if ('table' in block)
194
+ parts.push(
195
+ `<table><thead><tr>${block.table.map((head) => `<th>${escapeHtml(head)}</th>`).join('')}</tr></thead><tbody>${block.rows
196
+ .map((row) => `<tr>${row.map((value) => `<td>${cell(value)}</td>`).join('')}</tr>`)
197
+ .join('')}</tbody></table>`
198
+ )
199
+ else if ('list' in block) {
200
+ const tag = block.numbered ? 'ol' : 'ul'
201
+ parts.push(`<${tag}>${block.list.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</${tag}>`)
202
+ } else if ('stats' in block)
203
+ parts.push(
204
+ `<div class="stats">${block.stats
205
+ .map(
206
+ (stat) =>
207
+ `<div class="stat"><b>${escapeHtml(stat.label)}</b><span>${cell(stat.value)}</span></div>`
208
+ )
209
+ .join('')}</div>`
210
+ )
211
+ else if ('evidence' in block) parts.push(`<div class="evidence">${block.evidence.map(figure).join('')}</div>`)
212
+ else
213
+ parts.push(
214
+ `<details><summary>Complete JSON record</summary><pre>${escapeHtml(JSON.stringify(block.record, null, 2))}</pre></details>`
215
+ )
216
+ }
217
+ return (
218
+ '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">' +
219
+ `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:">` +
220
+ `<title>${escapeHtml(report.title)}</title><style>${styles.trim()}</style></head><body><main>` +
221
+ parts.join('') +
222
+ '</main></body></html>\n'
223
+ )
224
+ }
225
+
226
+ function runDocument(report: RunReport): Report {
227
+ const { result, sha256, acceptance, plan } = report
228
+ const evidence = new Map(result.evidence.map((entry) => [entry.id, entry]))
229
+ const files = (ids: string[]) => join(ids.map((id) => `${evidence.get(id)?.name ?? id} (${id})`))
230
+ const counts = outcomeCounts(result.tests.filter((test) => result.selectedTestIds.includes(test.testId)))
231
+ const assertions = result.tests.flatMap((test) => test.assertions)
232
+ const blocks: Block[] = [
233
+ {
234
+ stats: [
235
+ { label: 'Completion', value: result.completion },
236
+ { label: 'Coverage', value: result.coverage.status },
237
+ { label: 'Acceptance', value: acceptance.state },
238
+ { label: 'Selected tests', value: String(result.selectedTestIds.length) },
239
+ ...Object.entries(counts).map(([outcome, count]) => ({ label: outcome, value: String(count) })),
240
+ {
241
+ label: 'Assertions passed',
242
+ value: `${assertions.filter((assertion) => assertion.result === 'passed').length} of ${assertions.length}`,
243
+ },
244
+ { label: 'Findings', value: String(result.findings.length) },
245
+ { label: 'New gaps', value: String(result.newGaps.length) },
246
+ ],
247
+ },
248
+ { heading: 'Summary' },
249
+ {
250
+ table: ['Field', 'Value'],
251
+ rows: [
252
+ ['Run', result.runId],
253
+ ['Objective', plan.objective],
254
+ ['Application build', result.applicationBuild],
255
+ ['Plan', `${result.plan.planId}@${result.plan.revision}`],
256
+ ['Target profile', `${result.target.targetId}@${result.target.revision}`],
257
+ ['Started', result.startedAt],
258
+ ['Finished', result.finishedAt],
259
+ ['Attempts', String(result.attempts)],
260
+ ['Retest of', result.retestOf ?? none],
261
+ ['Cleanup', `${result.cleanup.status}: ${result.cleanup.reason}`],
262
+ ['Result digest', sha256],
263
+ ],
264
+ },
265
+ { heading: 'Acceptance' },
266
+ acceptance.latest
267
+ ? {
268
+ table: ['Verdict', 'Actor', 'At', 'Rationale', 'Digest'],
269
+ rows: [
270
+ [
271
+ acceptance.latest.verdict,
272
+ acceptance.latest.actor,
273
+ acceptance.latest.at,
274
+ acceptance.latest.rationale,
275
+ acceptance.latest.sha256,
276
+ ],
277
+ ],
278
+ }
279
+ : {
280
+ text:
281
+ acceptance.state === 'not_required'
282
+ ? 'The target policy does not require acceptance.'
283
+ : 'No decision yet.',
284
+ },
285
+ { heading: 'Tests' },
286
+ {
287
+ table: ['Test', 'Outcome', 'Assertions', 'Reason'],
288
+ rows: result.tests
289
+ .filter((test) => result.selectedTestIds.includes(test.testId))
290
+ .map((test) => [
291
+ test.testId,
292
+ test.outcome,
293
+ `${test.assertions.filter((assertion) => assertion.result === 'passed').length} of ${test.assertions.length} passed`,
294
+ test.reason ?? none,
295
+ ]),
296
+ },
297
+ { heading: 'Assertions' },
298
+ {
299
+ table: ['Assertion', 'Result', 'Check kind', 'Check', 'Observed', 'Reason', 'Evidence'],
300
+ rows: result.tests.flatMap((test) =>
301
+ test.assertions.map((assertion) => [
302
+ `${test.testId}.${assertion.assertionId}`,
303
+ assertion.result,
304
+ assertion.checkKind,
305
+ assertion.check,
306
+ assertion.observed ?? none,
307
+ assertion.reason ?? none,
308
+ files(assertion.evidenceIds),
309
+ ])
310
+ ),
311
+ },
312
+ { heading: 'Findings' },
313
+ ]
314
+ if (!result.findings.length) blocks.push({ text: 'No findings.' })
315
+ for (const finding of result.findings) {
316
+ blocks.push({ heading: `${finding.id} · ${finding.type} · ${finding.severity}: ${finding.title}`, level: 3 })
317
+ blocks.push({
318
+ table: ['Field', 'Value'],
319
+ rows: [
320
+ ['Assertions', join(finding.assertionIds)],
321
+ ['Requirements', join(finding.requirementIds)],
322
+ ['Expected', finding.expected],
323
+ ['Observed', finding.observed],
324
+ ['Evidence', files(finding.evidenceIds)],
325
+ ],
326
+ })
327
+ blocks.push({ list: finding.reproduction, numbered: true })
328
+ }
329
+ blocks.push({ heading: 'New gaps' })
330
+ if (!result.newGaps.length) blocks.push({ text: 'No new gaps.' })
331
+ else
332
+ blocks.push({
333
+ table: ['Gap', 'Severity', 'Question', 'Consequence', 'Requirements', 'Tests'],
334
+ rows: result.newGaps.map((gap) => [
335
+ gap.id,
336
+ gap.severity,
337
+ gap.question,
338
+ gap.consequence,
339
+ join(gap.requirementIds),
340
+ join(gap.testIds),
341
+ ]),
342
+ })
343
+ blocks.push({ heading: `Plan gaps and decisions (${plan.planId}@${plan.revision})` })
344
+ if (!plan.gaps.length) blocks.push({ text: 'The plan has no gaps.' })
345
+ else
346
+ blocks.push({
347
+ table: ['Gap', 'Severity', 'Status', 'Question', 'Decision', 'Value', 'Actor', 'Rationale'],
348
+ rows: plan.gaps.map((gap) => {
349
+ const decision = plan.decisions.find((item) => item.id === gap.decisionId)
350
+ return [
351
+ gap.id,
352
+ gap.severity,
353
+ gap.status,
354
+ gap.question,
355
+ decision?.kind ?? none,
356
+ decision?.value ?? none,
357
+ decision?.actor ?? none,
358
+ decision?.rationale ?? none,
359
+ ]
360
+ }),
361
+ })
362
+ if (plan.assumptions.length)
363
+ blocks.push({
364
+ list: plan.assumptions.map((assumption) => `Assumption ${assumption.decisionId}: ${assumption.text}`),
365
+ })
366
+ blocks.push({ heading: 'Evidence' })
367
+ if (!result.evidence.length) blocks.push({ text: 'No evidence.' })
368
+ else {
369
+ blocks.push({
370
+ evidence: result.evidence.map((entry) => ({
371
+ label: entry.assertionId ? `${entry.testId}.${entry.assertionId}` : entry.testId,
372
+ name: entry.name,
373
+ mediaType: entry.mediaType,
374
+ size: entry.size,
375
+ ...report.inline[entry.id],
376
+ })),
377
+ })
378
+ blocks.push({
379
+ table: ['Evidence', 'Assertion', 'Kind', 'Name', 'Media type', 'Size', 'SHA-256'],
380
+ rows: result.evidence.map((entry) => [
381
+ entry.id,
382
+ entry.assertionId ? `${entry.testId}.${entry.assertionId}` : entry.testId,
383
+ entry.kind,
384
+ entry.name,
385
+ entry.mediaType,
386
+ String(entry.size),
387
+ entry.sha256,
388
+ ]),
389
+ })
390
+ }
391
+ blocks.push(
392
+ { heading: 'Coverage' },
393
+ {
394
+ table: ['Requirement', 'Text', 'Status', 'Tests', 'Reason'],
395
+ rows: result.coverage.requirements.map((row) => [
396
+ row.requirementId,
397
+ plan.requirements.find((requirement) => requirement.id === row.requirementId)?.text ?? none,
398
+ row.status,
399
+ join(row.testIds),
400
+ row.reason ?? none,
401
+ ]),
402
+ },
403
+ { record: result }
404
+ )
405
+ return {
406
+ title: `Test report: ${result.runId}`,
407
+ note: `${result.plan.planId}@${result.plan.revision} on build ${result.applicationBuild}. Generated from the Checkmate database. Completion, test outcomes, and coverage are separate.`,
408
+ blocks,
409
+ }
410
+ }
411
+
412
+ function planReport(plan: Plan, sha256: string): Report {
413
+ const coverage = new Map(plan.coverage.map((row) => [row.requirementId, row.status]))
414
+ const blocks: Block[] = [
415
+ {
416
+ table: ['Field', 'Value'],
417
+ rows: [
418
+ ['Plan', plan.planId],
419
+ ['Revision', String(plan.revision)],
420
+ ['Previous revision', plan.previousRevision === null ? none : String(plan.previousRevision)],
421
+ ['Target', `${plan.target.targetId}@${plan.target.revision}`],
422
+ ['Objective', plan.objective],
423
+ ['Plan digest', sha256],
424
+ ],
425
+ },
426
+ { heading: 'Requirements' },
427
+ {
428
+ table: ['Requirement', 'Text', 'Source', 'Coverage'],
429
+ rows: plan.requirements.map((requirement) => [
430
+ requirement.id,
431
+ requirement.text,
432
+ requirement.source,
433
+ coverage.get(requirement.id) ?? none,
434
+ ]),
435
+ },
436
+ { heading: 'Tests' },
437
+ {
438
+ table: ['Test', 'Title', 'Readiness', 'Requirements', 'Depends on', 'Preconditions', 'Action'],
439
+ rows: plan.tests.map((test) => [
440
+ test.id,
441
+ test.title,
442
+ test.readiness,
443
+ join(test.requirementIds),
444
+ join(test.dependsOn),
445
+ join(test.preconditions),
446
+ test.action,
447
+ ]),
448
+ },
449
+ { heading: 'Assertions' },
450
+ {
451
+ table: ['Assertion', 'Check kind', 'Check'],
452
+ rows: plan.tests.flatMap((test) =>
453
+ test.assertions.map((assertion) => [`${test.id}.${assertion.id}`, assertion.checkKind, assertion.check])
454
+ ),
455
+ },
456
+ { heading: 'Gaps' },
457
+ ]
458
+ if (!plan.gaps.length) blocks.push({ text: 'No gaps.' })
459
+ else
460
+ blocks.push({
461
+ table: ['Gap', 'Severity', 'Status', 'Question', 'Consequence', 'Decision'],
462
+ rows: plan.gaps.map((gap) => [
463
+ gap.id,
464
+ gap.severity,
465
+ gap.status,
466
+ gap.question,
467
+ gap.consequence,
468
+ gap.decisionId ?? none,
469
+ ]),
470
+ })
471
+ blocks.push({ heading: 'Decisions' })
472
+ if (!plan.decisions.length) blocks.push({ text: 'No decisions.' })
473
+ else
474
+ blocks.push({
475
+ table: ['Decision', 'Gap', 'Kind', 'Value', 'Actor', 'Rationale', 'Source'],
476
+ rows: plan.decisions.map((decision) => [
477
+ decision.id,
478
+ decision.gapId,
479
+ decision.kind,
480
+ decision.value,
481
+ decision.actor,
482
+ decision.rationale,
483
+ decision.source ?? none,
484
+ ]),
485
+ })
486
+ blocks.push({ heading: 'Assumptions' })
487
+ if (!plan.assumptions.length) blocks.push({ text: 'No accepted assumptions.' })
488
+ else blocks.push({ list: plan.assumptions.map((assumption) => `${assumption.decisionId}: ${assumption.text}`) })
489
+ blocks.push({ record: plan })
490
+ return {
491
+ title: `Test plan: ${plan.planId} revision ${plan.revision}`,
492
+ note: 'Generated from the Checkmate database. Record a decision and revise the plan to change it.',
493
+ blocks,
494
+ }
495
+ }
496
+
497
+ function rendered(report: Report, record: unknown, format: Format) {
498
+ if (format === 'json') return JSON.stringify(record, null, 2) + '\n'
499
+ return format === 'md' ? markdown(report) : html(report)
500
+ }
501
+
502
+ export function renderPlan(plan: Plan, sha256: string, format: Format) {
503
+ return rendered(planReport(plan, sha256), plan, format)
504
+ }
505
+
506
+ export function renderRun(report: RunReport, format: Format) {
507
+ const { inline, ...record } = report
508
+ return rendered(runDocument(report), record, format)
509
+ }
510
+
511
+ export function exportPlan(store: Store, reference: string, format: string) {
512
+ const { id, revision } = revisionReference(reference, 'plan reference')
513
+ const selected = planRevision(store, id, revision)
514
+ return renderPlan(selected.plan, selected.sha256, requireChoice(format, formats, 'format (--format)'))
515
+ }
516
+
517
+ export function runReport(store: Store, runId: string): RunReport {
518
+ const run = runState(store, runId)
519
+ const finished = runResult(store, runId)
520
+ requireCondition(finished, `Run ${runId} has no result. Finish it before you export it.`)
521
+ const state = resultAcceptance(store, run.row, finished.sha256)
522
+ const { planId, revision, objective, requirements, gaps, decisions, assumptions } = run.plan
523
+ const inline: RunReport['inline'] = {}
524
+ for (const entry of finished.result.evidence) {
525
+ const image = inlineImages.includes(entry.mediaType) && entry.size <= maxInlineImage
526
+ const text = inlineTexts.includes(entry.mediaType) && entry.size <= maxInlineText
527
+ if (!image && !text) continue
528
+ const bytes = Buffer.from(evidenceBytes(store, entry.id).bytes)
529
+ inline[entry.id] = image
530
+ ? { image: `data:${entry.mediaType};base64,${bytes.toString('base64')}` }
531
+ : { text: bytes.toString('utf8') }
532
+ }
533
+ return {
534
+ result: finished.result,
535
+ sha256: finished.sha256,
536
+ acceptance: { state: approvalLabel(state), latest: state.latest },
537
+ plan: { planId, revision, objective, requirements, gaps, decisions, assumptions },
538
+ inline,
539
+ }
540
+ }
541
+
542
+ export function exportRun(store: Store, runId: string, format: string) {
543
+ return renderRun(runReport(store, runId), requireChoice(format, formats, 'format (--format)'))
544
+ }