@xoxoai/checkmate 0.6.7 → 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,384 @@
1
+ import type { SQLInputValue } from 'node:sqlite'
2
+ import { DatabaseSync } from 'node:sqlite'
3
+ import { createHash, randomBytes } from 'node:crypto'
4
+ import { mkdirSync, rmdirSync, rmSync } from 'node:fs'
5
+ import { join } from 'node:path'
6
+
7
+ export type Status =
8
+ 'completed' | 'needs_decision' | 'interrupted' | 'operational_error' | 'rejected' | 'invalid_request'
9
+ export type Reply = { status: Status; subject: string; reasons: string[]; data: Record<string, unknown> }
10
+ export type Change = { reply: Reply; subjects: string[] }
11
+ export type Store = { db: DatabaseSync; workspace: string; path: string }
12
+ export type Operation = { actor: string | undefined; command: string; subject: string; input: unknown }
13
+
14
+ export const contractVersion = 'toolkit-2'
15
+ const schemaVersion = '2'
16
+
17
+ const operationReference = 'REFERENCES operations(id) DEFERRABLE INITIALLY DEFERRED'
18
+ const tables = `
19
+ CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL) STRICT;
20
+ CREATE TABLE operations (
21
+ id TEXT PRIMARY KEY,
22
+ at TEXT NOT NULL,
23
+ actor TEXT NOT NULL,
24
+ command TEXT NOT NULL,
25
+ arguments TEXT NOT NULL,
26
+ status TEXT NOT NULL,
27
+ response TEXT NOT NULL,
28
+ subjects TEXT NOT NULL
29
+ ) STRICT;
30
+ CREATE TABLE blobs (sha256 TEXT PRIMARY KEY, size INTEGER NOT NULL, bytes BLOB NOT NULL) STRICT;
31
+ CREATE TABLE target_requests (
32
+ target_id TEXT PRIMARY KEY,
33
+ body TEXT NOT NULL,
34
+ operation_id TEXT NOT NULL ${operationReference}
35
+ ) STRICT;
36
+ CREATE TABLE profiles (
37
+ target_id TEXT NOT NULL REFERENCES target_requests(target_id),
38
+ revision INTEGER NOT NULL,
39
+ sha256 TEXT NOT NULL,
40
+ body TEXT NOT NULL,
41
+ operation_id TEXT NOT NULL ${operationReference},
42
+ PRIMARY KEY (target_id, revision)
43
+ ) STRICT;
44
+ CREATE TABLE plan_requests (
45
+ plan_id TEXT PRIMARY KEY,
46
+ target_id TEXT NOT NULL,
47
+ target_revision INTEGER NOT NULL,
48
+ body TEXT NOT NULL,
49
+ operation_id TEXT NOT NULL ${operationReference},
50
+ FOREIGN KEY (target_id, target_revision) REFERENCES profiles(target_id, revision)
51
+ ) STRICT;
52
+ CREATE TABLE plan_revisions (
53
+ plan_id TEXT NOT NULL REFERENCES plan_requests(plan_id),
54
+ revision INTEGER NOT NULL,
55
+ previous_revision INTEGER,
56
+ sha256 TEXT NOT NULL,
57
+ body TEXT NOT NULL,
58
+ operation_id TEXT NOT NULL ${operationReference},
59
+ PRIMARY KEY (plan_id, revision),
60
+ CHECK ((revision = 1 AND previous_revision IS NULL) OR previous_revision = revision - 1)
61
+ ) STRICT;
62
+ CREATE TABLE decisions (
63
+ id TEXT PRIMARY KEY,
64
+ plan_id TEXT NOT NULL,
65
+ base_revision INTEGER NOT NULL,
66
+ gap_id TEXT NOT NULL,
67
+ kind TEXT NOT NULL,
68
+ value TEXT NOT NULL,
69
+ source TEXT,
70
+ rationale TEXT NOT NULL,
71
+ actor TEXT NOT NULL,
72
+ applied_revision INTEGER,
73
+ operation_id TEXT NOT NULL ${operationReference},
74
+ FOREIGN KEY (plan_id, base_revision) REFERENCES plan_revisions(plan_id, revision),
75
+ UNIQUE (plan_id, base_revision, gap_id)
76
+ ) STRICT;
77
+ CREATE TABLE approvals (
78
+ id TEXT PRIMARY KEY,
79
+ subject_kind TEXT NOT NULL,
80
+ subject_id TEXT NOT NULL,
81
+ subject_sha256 TEXT NOT NULL,
82
+ verdict TEXT NOT NULL,
83
+ rationale TEXT NOT NULL,
84
+ actor TEXT NOT NULL,
85
+ operation_id TEXT NOT NULL ${operationReference}
86
+ ) STRICT;
87
+ CREATE TABLE runs (
88
+ run_id TEXT PRIMARY KEY,
89
+ plan_id TEXT NOT NULL,
90
+ plan_revision INTEGER NOT NULL,
91
+ plan_sha256 TEXT NOT NULL,
92
+ target_id TEXT NOT NULL,
93
+ target_revision INTEGER NOT NULL,
94
+ application_build TEXT NOT NULL,
95
+ request TEXT NOT NULL,
96
+ started_at TEXT NOT NULL,
97
+ browser_session TEXT NOT NULL,
98
+ deadline_seconds INTEGER NOT NULL,
99
+ operation_id TEXT NOT NULL ${operationReference},
100
+ FOREIGN KEY (plan_id, plan_revision) REFERENCES plan_revisions(plan_id, revision),
101
+ FOREIGN KEY (target_id, target_revision) REFERENCES profiles(target_id, revision)
102
+ ) STRICT;
103
+ CREATE TABLE run_attempts (
104
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
105
+ attempt INTEGER NOT NULL,
106
+ started_at TEXT NOT NULL,
107
+ actor TEXT NOT NULL,
108
+ PRIMARY KEY (run_id, attempt)
109
+ ) STRICT;
110
+ CREATE TABLE assertion_results (
111
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
112
+ test_id TEXT NOT NULL,
113
+ assertion_id TEXT NOT NULL,
114
+ attempt INTEGER NOT NULL,
115
+ result TEXT NOT NULL,
116
+ observed TEXT,
117
+ reason TEXT,
118
+ recorded_at TEXT NOT NULL,
119
+ operation_id TEXT NOT NULL ${operationReference},
120
+ PRIMARY KEY (run_id, test_id, assertion_id),
121
+ FOREIGN KEY (run_id, attempt) REFERENCES run_attempts(run_id, attempt)
122
+ ) STRICT;
123
+ CREATE TABLE outcomes (
124
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
125
+ test_id TEXT NOT NULL,
126
+ attempt INTEGER NOT NULL,
127
+ outcome TEXT NOT NULL,
128
+ reason TEXT,
129
+ recorded_at TEXT NOT NULL,
130
+ operation_id TEXT NOT NULL ${operationReference},
131
+ PRIMARY KEY (run_id, test_id),
132
+ FOREIGN KEY (run_id, attempt) REFERENCES run_attempts(run_id, attempt)
133
+ ) STRICT;
134
+ CREATE TABLE evidence (
135
+ id TEXT PRIMARY KEY,
136
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
137
+ test_id TEXT NOT NULL,
138
+ assertion_id TEXT,
139
+ kind TEXT NOT NULL,
140
+ name TEXT NOT NULL,
141
+ media_type TEXT NOT NULL,
142
+ sha256 TEXT NOT NULL REFERENCES blobs(sha256),
143
+ operation_id TEXT NOT NULL ${operationReference}
144
+ ) STRICT;
145
+ CREATE TABLE findings (
146
+ id TEXT NOT NULL,
147
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
148
+ body TEXT NOT NULL,
149
+ operation_id TEXT NOT NULL ${operationReference},
150
+ PRIMARY KEY (run_id, id)
151
+ ) STRICT;
152
+ CREATE TABLE run_gaps (
153
+ id TEXT NOT NULL,
154
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
155
+ body TEXT NOT NULL,
156
+ operation_id TEXT NOT NULL ${operationReference},
157
+ PRIMARY KEY (run_id, id)
158
+ ) STRICT;
159
+ CREATE TABLE results (
160
+ run_id TEXT PRIMARY KEY REFERENCES runs(run_id),
161
+ completion TEXT NOT NULL,
162
+ coverage TEXT NOT NULL,
163
+ finished_at TEXT NOT NULL,
164
+ sha256 TEXT NOT NULL,
165
+ body TEXT NOT NULL,
166
+ operation_id TEXT NOT NULL ${operationReference}
167
+ ) STRICT;
168
+ CREATE VIEW latest_plans AS
169
+ SELECT p.* FROM plan_revisions p
170
+ WHERE p.revision = (SELECT max(revision) FROM plan_revisions WHERE plan_id = p.plan_id);
171
+ CREATE VIEW plan_tests AS
172
+ SELECT p.plan_id, p.revision, t.value ->> '$.id' AS test_id, t.value AS test
173
+ FROM plan_revisions p, json_each(p.body, '$.tests') t;
174
+ CREATE VIEW plan_gaps AS
175
+ SELECT p.plan_id, p.revision, g.value ->> '$.id' AS gap_id,
176
+ g.value ->> '$.status' AS status, g.value ->> '$.severity' AS severity, g.value AS gap
177
+ FROM plan_revisions p, json_each(p.body, '$.gaps') g;
178
+ `
179
+ const tableNames = [...tables.matchAll(/CREATE TABLE (\w+)/g)].map((match) => match[1])
180
+ const decisionColumns = [
181
+ 'id',
182
+ 'plan_id',
183
+ 'base_revision',
184
+ 'gap_id',
185
+ 'kind',
186
+ 'value',
187
+ 'source',
188
+ 'rationale',
189
+ 'actor',
190
+ 'operation_id',
191
+ ]
192
+ function updateTrigger(table: string) {
193
+ if (table === 'decisions')
194
+ return `CREATE TRIGGER decisions_update BEFORE UPDATE ON decisions
195
+ WHEN OLD.applied_revision IS NOT NULL OR NEW.applied_revision IS NULL OR ${decisionColumns
196
+ .map((column) => `NEW.${column} IS NOT OLD.${column}`)
197
+ .join(' OR ')}
198
+ BEGIN SELECT RAISE(ABORT, 'Decisions are immutable. Only applied_revision can change, once, from NULL'); END;`
199
+ if (table === 'plan_revisions')
200
+ return `CREATE TRIGGER plan_revisions_update BEFORE UPDATE ON plan_revisions
201
+ WHEN NEW.plan_id IS NOT OLD.plan_id OR NEW.revision IS NOT OLD.revision
202
+ OR NEW.previous_revision IS NOT OLD.previous_revision
203
+ OR EXISTS (SELECT 1 FROM approvals WHERE subject_kind = 'plan'
204
+ AND subject_id = OLD.plan_id || '@' || OLD.revision AND verdict = 'approved')
205
+ OR EXISTS (SELECT 1 FROM runs WHERE plan_id = OLD.plan_id AND plan_revision = OLD.revision)
206
+ OR EXISTS (SELECT 1 FROM plan_revisions WHERE plan_id = OLD.plan_id AND revision > OLD.revision)
207
+ BEGIN SELECT RAISE(ABORT, 'Rows of plan_revisions are immutable once approved, run, or revised'); END;`
208
+ return `CREATE TRIGGER ${table}_update BEFORE UPDATE ON ${table}
209
+ BEGIN SELECT RAISE(ABORT, 'Rows of ${table} are immutable'); END;`
210
+ }
211
+ const triggers = tableNames
212
+ .map(
213
+ (table) => `${updateTrigger(table)}
214
+ CREATE TRIGGER ${table}_delete BEFORE DELETE ON ${table}
215
+ BEGIN SELECT RAISE(ABORT, 'Rows of ${table} are immutable'); END;`
216
+ )
217
+ .join('\n')
218
+
219
+ export function completed(subject: string, data: Record<string, unknown> = {}): Reply {
220
+ return { status: 'completed', subject, reasons: [], data }
221
+ }
222
+
223
+ export function databasePath(workspace: string) {
224
+ return join(workspace, '.checkmate', 'checkmate.db')
225
+ }
226
+
227
+ export function removeTemporaryDirectory(store: Store, name: string) {
228
+ const temporary = join(store.workspace, '.checkmate', 'tmp')
229
+ rmSync(join(temporary, name), { recursive: true, force: true })
230
+ try {
231
+ rmdirSync(temporary)
232
+ } catch {}
233
+ }
234
+
235
+ export function openStore(workspace: string): Store {
236
+ const path = databasePath(workspace)
237
+ mkdirSync(join(workspace, '.checkmate'), { recursive: true })
238
+ const db = new DatabaseSync(path)
239
+ db.exec('PRAGMA journal_mode = WAL')
240
+ db.exec('PRAGMA busy_timeout = 5000')
241
+ db.exec('PRAGMA foreign_keys = ON')
242
+ db.exec('BEGIN IMMEDIATE')
243
+ try {
244
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'meta'").get()) {
245
+ db.exec(tables)
246
+ db.exec(triggers)
247
+ const insert = db.prepare('INSERT INTO meta (key, value) VALUES (?, ?)')
248
+ insert.run('contractVersion', contractVersion)
249
+ insert.run('schemaVersion', schemaVersion)
250
+ }
251
+ db.exec('COMMIT')
252
+ } catch (error) {
253
+ db.exec('ROLLBACK')
254
+ db.close()
255
+ throw error
256
+ }
257
+ return checkedStore(db, workspace, path)
258
+ }
259
+
260
+ export function openReadOnlyStore(workspace: string): Store {
261
+ const path = databasePath(workspace)
262
+ closeStore(openStore(workspace))
263
+ const db = new DatabaseSync(path, { readOnly: true })
264
+ db.exec('PRAGMA busy_timeout = 5000')
265
+ return checkedStore(db, workspace, path)
266
+ }
267
+
268
+ function checkedStore(db: DatabaseSync, workspace: string, path: string): Store {
269
+ const row = db.prepare("SELECT value FROM meta WHERE key = 'schemaVersion'").get()
270
+ if (row?.value !== schemaVersion) {
271
+ db.close()
272
+ throw new Error(`Database ${path} has schema version ${row?.value ?? 'none'}. Expected ${schemaVersion}.`)
273
+ }
274
+ return { db, workspace, path }
275
+ }
276
+
277
+ export function closeStore(store: Store) {
278
+ store.db.close()
279
+ }
280
+
281
+ export function one<T>(store: Store, sql: string, ...parameters: SQLInputValue[]): T | undefined {
282
+ return store.db.prepare(sql).get(...parameters) as T | undefined
283
+ }
284
+
285
+ export function all<T>(store: Store, sql: string, ...parameters: SQLInputValue[]): T[] {
286
+ return store.db.prepare(sql).all(...parameters) as T[]
287
+ }
288
+
289
+ export function execute(store: Store, sql: string, ...parameters: SQLInputValue[]) {
290
+ store.db.prepare(sql).run(...parameters)
291
+ }
292
+
293
+ export function write(
294
+ store: Store,
295
+ operation: Operation,
296
+ change: (operationId: string, at: string, actor: string) => Change
297
+ ) {
298
+ const actor = operation.actor?.trim()
299
+ if (!actor) throw new Error('A write needs an actor. Use --actor <name> or set CHECKMATE_ACTOR.')
300
+ const id = ulid()
301
+ const at = new Date().toISOString()
302
+ store.db.exec('BEGIN IMMEDIATE')
303
+ try {
304
+ const { reply, subjects } = change(id, at, actor)
305
+ recordOperation(store, { ...operation, id, at, actor }, reply, subjects)
306
+ store.db.exec('COMMIT')
307
+ return reply
308
+ } catch (error) {
309
+ if (store.db.isTransaction) store.db.exec('ROLLBACK')
310
+ const reason = error instanceof Error ? error.message : String(error)
311
+ store.db.exec('BEGIN IMMEDIATE')
312
+ recordOperation(
313
+ store,
314
+ { ...operation, id: ulid(), at: new Date().toISOString(), actor },
315
+ { status: 'rejected', subject: operation.subject, reasons: [reason], data: {} },
316
+ []
317
+ )
318
+ store.db.exec('COMMIT')
319
+ throw error
320
+ }
321
+ }
322
+
323
+ function recordOperation(
324
+ store: Store,
325
+ operation: Operation & { id: string; at: string; actor: string },
326
+ reply: Reply,
327
+ subjects: string[]
328
+ ) {
329
+ execute(
330
+ store,
331
+ 'INSERT INTO operations (id, at, actor, command, arguments, status, response, subjects) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
332
+ operation.id,
333
+ operation.at,
334
+ operation.actor,
335
+ operation.command,
336
+ canonical(operation.input ?? null),
337
+ reply.status,
338
+ canonical(reply),
339
+ canonical(subjects)
340
+ )
341
+ }
342
+
343
+ export function canonical(value: unknown): string {
344
+ return JSON.stringify(sorted(value))
345
+ }
346
+
347
+ function sorted(value: unknown): unknown {
348
+ if (Array.isArray(value)) return value.map(sorted)
349
+ if (value && typeof value === 'object')
350
+ return Object.fromEntries(
351
+ Object.keys(value)
352
+ .sort()
353
+ .map((key) => [key, sorted((value as Record<string, unknown>)[key])])
354
+ )
355
+ return value
356
+ }
357
+
358
+ export function digest(content: string | Uint8Array) {
359
+ return createHash('sha256').update(content).digest('hex')
360
+ }
361
+
362
+ const crockford = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
363
+ let lastTime = 0
364
+ let lastRandom = 0n
365
+
366
+ export function ulid(now = Date.now()) {
367
+ if (now > lastTime) {
368
+ lastTime = now
369
+ lastRandom = BigInt(`0x${randomBytes(10).toString('hex')}`)
370
+ } else {
371
+ lastRandom += 1n
372
+ if (lastRandom >= 1n << 80n) throw new Error('ULID random part overflowed in one millisecond')
373
+ }
374
+ return base32(BigInt(lastTime), 10) + base32(lastRandom, 16)
375
+ }
376
+
377
+ function base32(value: bigint, length: number) {
378
+ let text = ''
379
+ for (let i = 0; i < length; i++) {
380
+ text = crockford[Number(value % 32n)] + text
381
+ value /= 32n
382
+ }
383
+ return text
384
+ }
@@ -0,0 +1,205 @@
1
+ import type { Profile } from './types.ts'
2
+ import type { Store } from './store.ts'
3
+ import { canonical, completed, digest, execute, one, removeTemporaryDirectory, write } from './store.ts'
4
+ import {
5
+ profileCheck,
6
+ requireChoice,
7
+ requireCondition,
8
+ requireIdentifier,
9
+ requireText,
10
+ revisionReference,
11
+ same,
12
+ approvalPolicy,
13
+ } from './contracts.ts'
14
+
15
+ export type ToolKind = Profile['tools'][number]['kind']
16
+ export type RequestedTool = { name: string; kind: ToolKind; purpose: string }
17
+ export type TargetRequest = {
18
+ targetId: string
19
+ baseUrl: string
20
+ capabilities: string[]
21
+ allowedOperations: string[]
22
+ tools: RequestedTool[]
23
+ }
24
+
25
+ export const toolKinds = ['cli', 'mcp', 'skill'] as const
26
+ export type TargetRevision = { profile: Profile; revision: number; sha256: string }
27
+
28
+ export function openTarget(
29
+ store: Store,
30
+ actor: string | undefined,
31
+ input: Omit<TargetRequest, 'tools'> & { tools?: RequestedTool[] }
32
+ ) {
33
+ return write(store, { actor, command: 'target open', subject: input.targetId, input }, (operationId) => {
34
+ const request = targetRequestCheck(input)
35
+ const existing = one<{ body: string }>(
36
+ store,
37
+ 'SELECT body FROM target_requests WHERE target_id = ?',
38
+ request.targetId
39
+ )
40
+ if (existing) {
41
+ requireCondition(
42
+ existing.body === canonical(request),
43
+ `Target ${request.targetId} is open with other values. Use a new target ID for a different scope.`
44
+ )
45
+ return { reply: completed(request.targetId, { request, recorded: false }), subjects: [] }
46
+ }
47
+ execute(
48
+ store,
49
+ 'INSERT INTO target_requests (target_id, body, operation_id) VALUES (?, ?, ?)',
50
+ request.targetId,
51
+ canonical(request),
52
+ operationId
53
+ )
54
+ return {
55
+ reply: completed(request.targetId, { request, recorded: true }),
56
+ subjects: [`target:${request.targetId}`],
57
+ }
58
+ })
59
+ }
60
+
61
+ function targetRequestCheck(input: Omit<TargetRequest, 'tools'> & { tools?: RequestedTool[] }): TargetRequest {
62
+ requireIdentifier(input.targetId, 'target ID')
63
+ let url: URL
64
+ try {
65
+ url = new URL(input.baseUrl)
66
+ } catch {
67
+ throw new Error(`Invalid base URL: ${input.baseUrl}`)
68
+ }
69
+ requireCondition(['http:', 'https:'].includes(url.protocol), 'Target must use HTTP or HTTPS')
70
+ for (const [name, list] of [
71
+ ['capabilities', input.capabilities],
72
+ ['operations', input.allowedOperations],
73
+ ] as const) {
74
+ requireCondition(Array.isArray(list) && list.length > 0, `Missing ${name}`)
75
+ list.forEach((item) => requireIdentifier(item, name))
76
+ requireCondition(new Set(list).size === list.length, `Duplicate item in ${name}`)
77
+ }
78
+ const tools = (input.tools ?? []).map((tool) => toolCheck(tool))
79
+ requireCondition(new Set(tools.map((tool) => tool.name)).size === tools.length, 'Duplicate tool name')
80
+ return {
81
+ targetId: input.targetId,
82
+ baseUrl: input.baseUrl,
83
+ capabilities: input.capabilities,
84
+ allowedOperations: input.allowedOperations,
85
+ tools,
86
+ }
87
+ }
88
+
89
+ export function toolCheck(tool: RequestedTool): RequestedTool {
90
+ return {
91
+ name: requireText(tool.name, 'tool name').trim(),
92
+ kind: requireChoice(tool.kind, toolKinds, `kind of tool ${tool.name}`),
93
+ purpose: requireText(tool.purpose, `purpose of tool ${tool.name}`),
94
+ }
95
+ }
96
+
97
+ export function targetRequest(store: Store, targetId: string): TargetRequest {
98
+ const row = one<{ body: string }>(store, 'SELECT body FROM target_requests WHERE target_id = ?', targetId)
99
+ requireCondition(row, `Unknown target: ${targetId}. Run target open first.`)
100
+ return JSON.parse(row.body)
101
+ }
102
+
103
+ export function showTargetRequest(store: Store, targetId: string) {
104
+ return completed(targetId, { request: targetRequest(store, targetId) })
105
+ }
106
+
107
+ export function saveTarget(store: Store, actor: string | undefined, input: { targetId: string; profile: unknown }) {
108
+ const reply = save(store, actor, input)
109
+ removeTemporaryDirectory(store, `setup-${input.targetId}`)
110
+ return reply
111
+ }
112
+
113
+ function save(store: Store, actor: string | undefined, input: { targetId: string; profile: unknown }) {
114
+ return write(store, { actor, command: 'target save', subject: input.targetId, input }, (operationId) => {
115
+ const request = targetRequest(store, input.targetId)
116
+ const profile = profileCheck(input.profile)
117
+ requireCondition(
118
+ profile.targetId === request.targetId,
119
+ `Profile targetId ${profile.targetId} differs from ${request.targetId}`
120
+ )
121
+ requireCondition(profile.baseUrl === request.baseUrl, `Profile baseUrl differs from the setup request`)
122
+ requireCondition(
123
+ same(profile.capabilities, request.capabilities),
124
+ 'Profile capabilities differ from the setup request'
125
+ )
126
+ requireCondition(
127
+ same(profile.allowedOperations, request.allowedOperations),
128
+ 'Profile allowedOperations differ from the setup request'
129
+ )
130
+ for (const requested of request.tools) {
131
+ const tool = profile.tools.find((tool) => tool.name === requested.name)
132
+ requireCondition(tool, `Profile has no entry for the requested tool ${requested.name}`)
133
+ requireCondition(
134
+ tool.kind === requested.kind,
135
+ `Tool ${tool.name} is ${tool.kind} in the profile, not ${requested.kind}`
136
+ )
137
+ }
138
+ const unreachable = profile.tools
139
+ .filter((tool) => !tool.reachable && request.tools.some((requested) => requested.name === tool.name))
140
+ .map((tool) => `Requested tool ${tool.name} (${tool.kind}) is not reachable`)
141
+ requireCondition(
142
+ !unreachable.length || !profile.readyForLiveExecution,
143
+ `${unreachable.join('. ')}. Set readyForLiveExecution to false.`
144
+ )
145
+ const body = canonical(profile)
146
+ const latest = one<{ revision: number; body: string }>(
147
+ store,
148
+ 'SELECT revision, body FROM profiles WHERE target_id = ? ORDER BY revision DESC LIMIT 1',
149
+ profile.targetId
150
+ )
151
+ const subject = `${profile.targetId}@${profile.revision}`
152
+ const reasons = profile.readyForLiveExecution
153
+ ? []
154
+ : [...unreachable, 'Target prerequisites are not ready. Inspect the profile.']
155
+ const status = profile.readyForLiveExecution ? ('completed' as const) : ('needs_decision' as const)
156
+ const data = { targetId: profile.targetId, revision: profile.revision, sha256: digest(body) }
157
+ if (latest && latest.revision === profile.revision) {
158
+ requireCondition(
159
+ latest.body === body,
160
+ `Profile ${subject} exists with other content. Save revision ${latest.revision + 1} for changes.`
161
+ )
162
+ return { reply: { status, subject, reasons, data: { ...data, recorded: false } }, subjects: [] }
163
+ }
164
+ const next = (latest?.revision ?? 0) + 1
165
+ requireCondition(profile.revision === next, `Profile revision must be ${next}, not ${profile.revision}`)
166
+ execute(
167
+ store,
168
+ 'INSERT INTO profiles (target_id, revision, sha256, body, operation_id) VALUES (?, ?, ?, ?, ?)',
169
+ profile.targetId,
170
+ profile.revision,
171
+ data.sha256,
172
+ body,
173
+ operationId
174
+ )
175
+ return {
176
+ reply: { status, subject, reasons, data: { ...data, recorded: true } },
177
+ subjects: [`target:${profile.targetId}`],
178
+ }
179
+ })
180
+ }
181
+
182
+ export function targetRevision(store: Store, targetId: string, revision: number | null = null): TargetRevision {
183
+ const row = one<{ revision: number; sha256: string; body: string }>(
184
+ store,
185
+ revision === null
186
+ ? 'SELECT revision, sha256, body FROM profiles WHERE target_id = ? ORDER BY revision DESC LIMIT 1'
187
+ : 'SELECT revision, sha256, body FROM profiles WHERE target_id = ? AND revision = ?',
188
+ targetId,
189
+ ...(revision === null ? [] : [revision])
190
+ )
191
+ requireCondition(row, `Unknown target profile: ${targetId}${revision === null ? '' : `@${revision}`}`)
192
+ return { profile: JSON.parse(row.body), revision: row.revision, sha256: row.sha256 }
193
+ }
194
+
195
+ export function showTarget(store: Store, reference: string) {
196
+ const { id, revision } = revisionReference(reference, 'target reference')
197
+ const target = targetRevision(store, id, revision)
198
+ return completed(`${id}@${target.revision}`, {
199
+ targetId: id,
200
+ revision: target.revision,
201
+ sha256: target.sha256,
202
+ approvals: approvalPolicy(target.profile),
203
+ profile: target.profile,
204
+ })
205
+ }