approval-md 0.0.1 → 0.2.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 (722) hide show
  1. package/LICENSE +176 -0
  2. package/NOTICE +5 -0
  3. package/README.md +940 -4
  4. package/SPEC.md +476 -34
  5. package/cli.js +29 -3
  6. package/dist/src/adapters/agentmail.d.ts +426 -0
  7. package/dist/src/adapters/agentmail.js +1200 -0
  8. package/dist/src/adapters/agentmail.js.map +1 -0
  9. package/dist/src/adapters/conformance.d.ts +149 -0
  10. package/dist/src/adapters/conformance.js +461 -0
  11. package/dist/src/adapters/conformance.js.map +1 -0
  12. package/dist/src/adapters/contract.d.ts +628 -0
  13. package/dist/src/adapters/contract.js +1035 -0
  14. package/dist/src/adapters/contract.js.map +1 -0
  15. package/dist/src/adapters/email.d.ts +324 -0
  16. package/dist/src/adapters/email.js +749 -0
  17. package/dist/src/adapters/email.js.map +1 -0
  18. package/dist/src/adapters/env-passphrase.d.ts +93 -0
  19. package/dist/src/adapters/env-passphrase.js +132 -0
  20. package/dist/src/adapters/env-passphrase.js.map +1 -0
  21. package/dist/src/adapters/public.d.ts +11 -0
  22. package/dist/src/adapters/public.js +11 -0
  23. package/dist/src/adapters/public.js.map +1 -0
  24. package/dist/src/adapters/registry.d.ts +59 -0
  25. package/dist/src/adapters/registry.js +77 -0
  26. package/dist/src/adapters/registry.js.map +1 -0
  27. package/dist/src/adapters/smtp.d.ts +213 -0
  28. package/dist/src/adapters/smtp.js +499 -0
  29. package/dist/src/adapters/smtp.js.map +1 -0
  30. package/dist/src/adapters/vault-provider.d.ts +114 -0
  31. package/dist/src/adapters/vault-provider.js +161 -0
  32. package/dist/src/adapters/vault-provider.js.map +1 -0
  33. package/dist/src/adapters/zzz.d.ts +66 -0
  34. package/dist/src/adapters/zzz.js +299 -0
  35. package/dist/src/adapters/zzz.js.map +1 -0
  36. package/dist/src/channels/batch.d.ts +109 -0
  37. package/dist/src/channels/batch.js +121 -0
  38. package/dist/src/channels/batch.js.map +1 -0
  39. package/dist/src/channels/cli.d.ts +193 -0
  40. package/dist/src/channels/cli.js +468 -0
  41. package/dist/src/channels/cli.js.map +1 -0
  42. package/dist/src/channels/conformance.d.ts +92 -0
  43. package/dist/src/channels/conformance.js +445 -0
  44. package/dist/src/channels/conformance.js.map +1 -0
  45. package/dist/src/channels/contract.d.ts +623 -0
  46. package/dist/src/channels/contract.js +494 -0
  47. package/dist/src/channels/contract.js.map +1 -0
  48. package/dist/src/channels/payload-view.d.ts +35 -0
  49. package/dist/src/channels/payload-view.js +43 -0
  50. package/dist/src/channels/payload-view.js.map +1 -0
  51. package/dist/src/channels/render-queue.d.ts +149 -0
  52. package/dist/src/channels/render-queue.js +564 -0
  53. package/dist/src/channels/render-queue.js.map +1 -0
  54. package/dist/src/channels/tagging.d.ts +196 -0
  55. package/dist/src/channels/tagging.js +723 -0
  56. package/dist/src/channels/tagging.js.map +1 -0
  57. package/dist/src/channels/telegram.d.ts +1832 -0
  58. package/dist/src/channels/telegram.js +3190 -0
  59. package/dist/src/channels/telegram.js.map +1 -0
  60. package/dist/src/channels/web.d.ts +341 -0
  61. package/dist/src/channels/web.js +903 -0
  62. package/dist/src/channels/web.js.map +1 -0
  63. package/dist/src/cli/adapter.d.ts +90 -0
  64. package/dist/src/cli/adapter.js +288 -0
  65. package/dist/src/cli/adapter.js.map +1 -0
  66. package/dist/src/cli/amend.d.ts +59 -0
  67. package/dist/src/cli/amend.js +2171 -0
  68. package/dist/src/cli/amend.js.map +1 -0
  69. package/dist/src/cli/args.d.ts +43 -0
  70. package/dist/src/cli/args.js +86 -0
  71. package/dist/src/cli/args.js.map +1 -0
  72. package/dist/src/cli/attest.d.ts +41 -0
  73. package/dist/src/cli/attest.js +307 -0
  74. package/dist/src/cli/attest.js.map +1 -0
  75. package/dist/src/cli/audit-card.d.ts +62 -0
  76. package/dist/src/cli/audit-card.js +201 -0
  77. package/dist/src/cli/audit-card.js.map +1 -0
  78. package/dist/src/cli/audit.d.ts +59 -0
  79. package/dist/src/cli/audit.js +460 -0
  80. package/dist/src/cli/audit.js.map +1 -0
  81. package/dist/src/cli/channel-telegram.d.ts +806 -0
  82. package/dist/src/cli/channel-telegram.js +2063 -0
  83. package/dist/src/cli/channel-telegram.js.map +1 -0
  84. package/dist/src/cli/channel-web.d.ts +131 -0
  85. package/dist/src/cli/channel-web.js +357 -0
  86. package/dist/src/cli/channel-web.js.map +1 -0
  87. package/dist/src/cli/channel.d.ts +71 -0
  88. package/dist/src/cli/channel.js +438 -0
  89. package/dist/src/cli/channel.js.map +1 -0
  90. package/dist/src/cli/checkpoint-tap.d.ts +169 -0
  91. package/dist/src/cli/checkpoint-tap.js +238 -0
  92. package/dist/src/cli/checkpoint-tap.js.map +1 -0
  93. package/dist/src/cli/codex.d.ts +2 -0
  94. package/dist/src/cli/codex.js +172 -0
  95. package/dist/src/cli/codex.js.map +1 -0
  96. package/dist/src/cli/coverage.d.ts +61 -0
  97. package/dist/src/cli/coverage.js +343 -0
  98. package/dist/src/cli/coverage.js.map +1 -0
  99. package/dist/src/cli/daemon.d.ts +120 -0
  100. package/dist/src/cli/daemon.js +631 -0
  101. package/dist/src/cli/daemon.js.map +1 -0
  102. package/dist/src/cli/doctor.d.ts +129 -0
  103. package/dist/src/cli/doctor.js +2762 -0
  104. package/dist/src/cli/doctor.js.map +1 -0
  105. package/dist/src/cli/env.d.ts +65 -0
  106. package/dist/src/cli/env.js +302 -0
  107. package/dist/src/cli/env.js.map +1 -0
  108. package/dist/src/cli/execute.d.ts +202 -0
  109. package/dist/src/cli/execute.js +1682 -0
  110. package/dist/src/cli/execute.js.map +1 -0
  111. package/dist/src/cli/exit-codes.d.ts +73 -0
  112. package/dist/src/cli/exit-codes.js +82 -0
  113. package/dist/src/cli/exit-codes.js.map +1 -0
  114. package/dist/src/cli/feedback.d.ts +60 -0
  115. package/dist/src/cli/feedback.js +205 -0
  116. package/dist/src/cli/feedback.js.map +1 -0
  117. package/dist/src/cli/gate-window.d.ts +40 -0
  118. package/dist/src/cli/gate-window.js +294 -0
  119. package/dist/src/cli/gate-window.js.map +1 -0
  120. package/dist/src/cli/gate.d.ts +68 -0
  121. package/dist/src/cli/gate.js +557 -0
  122. package/dist/src/cli/gate.js.map +1 -0
  123. package/dist/src/cli/git-scope.d.ts +190 -0
  124. package/dist/src/cli/git-scope.js +295 -0
  125. package/dist/src/cli/git-scope.js.map +1 -0
  126. package/dist/src/cli/gloss-attach.d.ts +85 -0
  127. package/dist/src/cli/gloss-attach.js +107 -0
  128. package/dist/src/cli/gloss-attach.js.map +1 -0
  129. package/dist/src/cli/gloss-codex-child.d.ts +9 -0
  130. package/dist/src/cli/gloss-codex-child.js +149 -0
  131. package/dist/src/cli/gloss-codex-child.js.map +1 -0
  132. package/dist/src/cli/gloss-codex.d.ts +24 -0
  133. package/dist/src/cli/gloss-codex.js +255 -0
  134. package/dist/src/cli/gloss-codex.js.map +1 -0
  135. package/dist/src/cli/gloss-options.d.ts +42 -0
  136. package/dist/src/cli/gloss-options.js +79 -0
  137. package/dist/src/cli/gloss-options.js.map +1 -0
  138. package/dist/src/cli/gloss.d.ts +265 -0
  139. package/dist/src/cli/gloss.js +362 -0
  140. package/dist/src/cli/gloss.js.map +1 -0
  141. package/dist/src/cli/help.d.ts +103 -0
  142. package/dist/src/cli/help.js +2339 -0
  143. package/dist/src/cli/help.js.map +1 -0
  144. package/dist/src/cli/hook-codex.d.ts +78 -0
  145. package/dist/src/cli/hook-codex.js +167 -0
  146. package/dist/src/cli/hook-codex.js.map +1 -0
  147. package/dist/src/cli/hook.d.ts +331 -0
  148. package/dist/src/cli/hook.js +2849 -0
  149. package/dist/src/cli/hook.js.map +1 -0
  150. package/dist/src/cli/import.d.ts +35 -0
  151. package/dist/src/cli/import.js +175 -0
  152. package/dist/src/cli/import.js.map +1 -0
  153. package/dist/src/cli/init.d.ts +84 -0
  154. package/dist/src/cli/init.js +336 -0
  155. package/dist/src/cli/init.js.map +1 -0
  156. package/dist/src/cli/instructions.d.ts +23 -0
  157. package/dist/src/cli/instructions.js +262 -0
  158. package/dist/src/cli/instructions.js.map +1 -0
  159. package/dist/src/cli/journal.d.ts +41 -0
  160. package/dist/src/cli/journal.js +238 -0
  161. package/dist/src/cli/journal.js.map +1 -0
  162. package/dist/src/cli/log-advance.d.ts +287 -0
  163. package/dist/src/cli/log-advance.js +840 -0
  164. package/dist/src/cli/log-advance.js.map +1 -0
  165. package/dist/src/cli/log-anchor.d.ts +176 -0
  166. package/dist/src/cli/log-anchor.js +387 -0
  167. package/dist/src/cli/log-anchor.js.map +1 -0
  168. package/dist/src/cli/log-checkpoint.d.ts +22 -0
  169. package/dist/src/cli/log-checkpoint.js +128 -0
  170. package/dist/src/cli/log-checkpoint.js.map +1 -0
  171. package/dist/src/cli/log-sync.d.ts +243 -0
  172. package/dist/src/cli/log-sync.js +849 -0
  173. package/dist/src/cli/log-sync.js.map +1 -0
  174. package/dist/src/cli/log-verbs.d.ts +16 -0
  175. package/dist/src/cli/log-verbs.js +360 -0
  176. package/dist/src/cli/log-verbs.js.map +1 -0
  177. package/dist/src/cli/long-help.d.ts +70 -0
  178. package/dist/src/cli/long-help.js +148 -0
  179. package/dist/src/cli/long-help.js.map +1 -0
  180. package/dist/src/cli/main.d.ts +77 -0
  181. package/dist/src/cli/main.js +1206 -0
  182. package/dist/src/cli/main.js.map +1 -0
  183. package/dist/src/cli/mcp.d.ts +52 -0
  184. package/dist/src/cli/mcp.js +306 -0
  185. package/dist/src/cli/mcp.js.map +1 -0
  186. package/dist/src/cli/paths.d.ts +56 -0
  187. package/dist/src/cli/paths.js +79 -0
  188. package/dist/src/cli/paths.js.map +1 -0
  189. package/dist/src/cli/payload.d.ts +58 -0
  190. package/dist/src/cli/payload.js +253 -0
  191. package/dist/src/cli/payload.js.map +1 -0
  192. package/dist/src/cli/policy.d.ts +43 -0
  193. package/dist/src/cli/policy.js +229 -0
  194. package/dist/src/cli/policy.js.map +1 -0
  195. package/dist/src/cli/preflight.d.ts +363 -0
  196. package/dist/src/cli/preflight.js +1175 -0
  197. package/dist/src/cli/preflight.js.map +1 -0
  198. package/dist/src/cli/progress.d.ts +78 -0
  199. package/dist/src/cli/progress.js +112 -0
  200. package/dist/src/cli/progress.js.map +1 -0
  201. package/dist/src/cli/prompt.d.ts +209 -0
  202. package/dist/src/cli/prompt.js +312 -0
  203. package/dist/src/cli/prompt.js.map +1 -0
  204. package/dist/src/cli/quickstart.d.ts +46 -0
  205. package/dist/src/cli/quickstart.js +297 -0
  206. package/dist/src/cli/quickstart.js.map +1 -0
  207. package/dist/src/cli/records.d.ts +34 -0
  208. package/dist/src/cli/records.js +66 -0
  209. package/dist/src/cli/records.js.map +1 -0
  210. package/dist/src/cli/render.d.ts +22 -0
  211. package/dist/src/cli/render.js +132 -0
  212. package/dist/src/cli/render.js.map +1 -0
  213. package/dist/src/cli/sandbox.d.ts +51 -0
  214. package/dist/src/cli/sandbox.js +150 -0
  215. package/dist/src/cli/sandbox.js.map +1 -0
  216. package/dist/src/cli/scaffold.d.ts +79 -0
  217. package/dist/src/cli/scaffold.js +137 -0
  218. package/dist/src/cli/scaffold.js.map +1 -0
  219. package/dist/src/cli/setup-adapter.d.ts +137 -0
  220. package/dist/src/cli/setup-adapter.js +509 -0
  221. package/dist/src/cli/setup-adapter.js.map +1 -0
  222. package/dist/src/cli/setup-channel.d.ts +117 -0
  223. package/dist/src/cli/setup-channel.js +635 -0
  224. package/dist/src/cli/setup-channel.js.map +1 -0
  225. package/dist/src/cli/setup-checkpoint.d.ts +57 -0
  226. package/dist/src/cli/setup-checkpoint.js +196 -0
  227. package/dist/src/cli/setup-checkpoint.js.map +1 -0
  228. package/dist/src/cli/setup-common.d.ts +275 -0
  229. package/dist/src/cli/setup-common.js +376 -0
  230. package/dist/src/cli/setup-common.js.map +1 -0
  231. package/dist/src/cli/setup-flow.d.ts +287 -0
  232. package/dist/src/cli/setup-flow.js +476 -0
  233. package/dist/src/cli/setup-flow.js.map +1 -0
  234. package/dist/src/cli/setup-service.d.ts +96 -0
  235. package/dist/src/cli/setup-service.js +308 -0
  236. package/dist/src/cli/setup-service.js.map +1 -0
  237. package/dist/src/cli/setup.d.ts +202 -0
  238. package/dist/src/cli/setup.js +473 -0
  239. package/dist/src/cli/setup.js.map +1 -0
  240. package/dist/src/cli/style.d.ts +320 -0
  241. package/dist/src/cli/style.js +469 -0
  242. package/dist/src/cli/style.js.map +1 -0
  243. package/dist/src/cli/token.d.ts +39 -0
  244. package/dist/src/cli/token.js +274 -0
  245. package/dist/src/cli/token.js.map +1 -0
  246. package/dist/src/cli/up.d.ts +155 -0
  247. package/dist/src/cli/up.js +849 -0
  248. package/dist/src/cli/up.js.map +1 -0
  249. package/dist/src/cli/usage.d.ts +37 -0
  250. package/dist/src/cli/usage.js +91 -0
  251. package/dist/src/cli/usage.js.map +1 -0
  252. package/dist/src/cli/values.d.ts +40 -0
  253. package/dist/src/cli/values.js +189 -0
  254. package/dist/src/cli/values.js.map +1 -0
  255. package/dist/src/cli/vault.d.ts +59 -0
  256. package/dist/src/cli/vault.js +362 -0
  257. package/dist/src/cli/vault.js.map +1 -0
  258. package/dist/src/cli/verb-registry.d.ts +76 -0
  259. package/dist/src/cli/verb-registry.js +2341 -0
  260. package/dist/src/cli/verb-registry.js.map +1 -0
  261. package/dist/src/cli/wordmark.d.ts +31 -0
  262. package/dist/src/cli/wordmark.js +52 -0
  263. package/dist/src/cli/wordmark.js.map +1 -0
  264. package/dist/src/codex/doctor.d.ts +13 -0
  265. package/dist/src/codex/doctor.js +41 -0
  266. package/dist/src/codex/doctor.js.map +1 -0
  267. package/dist/src/codex/manifest.d.ts +49 -0
  268. package/dist/src/codex/manifest.js +103 -0
  269. package/dist/src/codex/manifest.js.map +1 -0
  270. package/dist/src/codex/templates.d.ts +41 -0
  271. package/dist/src/codex/templates.js +319 -0
  272. package/dist/src/codex/templates.js.map +1 -0
  273. package/dist/src/codex/trust.d.ts +19 -0
  274. package/dist/src/codex/trust.js +183 -0
  275. package/dist/src/codex/trust.js.map +1 -0
  276. package/dist/src/codex/workspace-plan.d.ts +131 -0
  277. package/dist/src/codex/workspace-plan.js +561 -0
  278. package/dist/src/codex/workspace-plan.js.map +1 -0
  279. package/dist/src/core/actor.d.ts +2 -0
  280. package/dist/src/core/actor.js +5 -0
  281. package/dist/src/core/actor.js.map +1 -0
  282. package/dist/src/core/advance-cycle.d.ts +170 -0
  283. package/dist/src/core/advance-cycle.js +200 -0
  284. package/dist/src/core/advance-cycle.js.map +1 -0
  285. package/dist/src/core/agents-md.d.ts +276 -0
  286. package/dist/src/core/agents-md.js +747 -0
  287. package/dist/src/core/agents-md.js.map +1 -0
  288. package/dist/src/core/apply-patch.d.ts +49 -0
  289. package/dist/src/core/apply-patch.js +266 -0
  290. package/dist/src/core/apply-patch.js.map +1 -0
  291. package/dist/src/core/attest.d.ts +420 -0
  292. package/dist/src/core/attest.js +589 -0
  293. package/dist/src/core/attest.js.map +1 -0
  294. package/dist/src/core/audit.d.ts +492 -0
  295. package/dist/src/core/audit.js +882 -0
  296. package/dist/src/core/audit.js.map +1 -0
  297. package/dist/src/core/budgets.d.ts +238 -0
  298. package/dist/src/core/budgets.js +449 -0
  299. package/dist/src/core/budgets.js.map +1 -0
  300. package/dist/src/core/checkpoint.d.ts +500 -0
  301. package/dist/src/core/checkpoint.js +738 -0
  302. package/dist/src/core/checkpoint.js.map +1 -0
  303. package/dist/src/core/child-env.d.ts +88 -0
  304. package/dist/src/core/child-env.js +86 -0
  305. package/dist/src/core/child-env.js.map +1 -0
  306. package/dist/src/core/clock.d.ts +52 -0
  307. package/dist/src/core/clock.js +43 -0
  308. package/dist/src/core/clock.js.map +1 -0
  309. package/dist/src/core/command-class.d.ts +543 -0
  310. package/dist/src/core/command-class.js +2356 -0
  311. package/dist/src/core/command-class.js.map +1 -0
  312. package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
  313. package/dist/src/core/coverage-sources/adapter.js +71 -0
  314. package/dist/src/core/coverage-sources/adapter.js.map +1 -0
  315. package/dist/src/core/coverage-sources/gh.d.ts +48 -0
  316. package/dist/src/core/coverage-sources/gh.js +136 -0
  317. package/dist/src/core/coverage-sources/gh.js.map +1 -0
  318. package/dist/src/core/coverage-sources/git.d.ts +101 -0
  319. package/dist/src/core/coverage-sources/git.js +269 -0
  320. package/dist/src/core/coverage-sources/git.js.map +1 -0
  321. package/dist/src/core/coverage.d.ts +217 -0
  322. package/dist/src/core/coverage.js +337 -0
  323. package/dist/src/core/coverage.js.map +1 -0
  324. package/dist/src/core/credential-spec.d.ts +72 -0
  325. package/dist/src/core/credential-spec.js +23 -0
  326. package/dist/src/core/credential-spec.js.map +1 -0
  327. package/dist/src/core/dark-session.d.ts +331 -0
  328. package/dist/src/core/dark-session.js +714 -0
  329. package/dist/src/core/dark-session.js.map +1 -0
  330. package/dist/src/core/decision-refusal.d.ts +185 -0
  331. package/dist/src/core/decision-refusal.js +265 -0
  332. package/dist/src/core/decision-refusal.js.map +1 -0
  333. package/dist/src/core/env-file.d.ts +450 -0
  334. package/dist/src/core/env-file.js +837 -0
  335. package/dist/src/core/env-file.js.map +1 -0
  336. package/dist/src/core/execute.d.ts +858 -0
  337. package/dist/src/core/execute.js +1271 -0
  338. package/dist/src/core/execute.js.map +1 -0
  339. package/dist/src/core/frontmatter.d.ts +78 -0
  340. package/dist/src/core/frontmatter.js +100 -0
  341. package/dist/src/core/frontmatter.js.map +1 -0
  342. package/dist/src/core/gate-window.d.ts +312 -0
  343. package/dist/src/core/gate-window.js +506 -0
  344. package/dist/src/core/gate-window.js.map +1 -0
  345. package/dist/src/core/gate.d.ts +1364 -0
  346. package/dist/src/core/gate.js +3002 -0
  347. package/dist/src/core/gate.js.map +1 -0
  348. package/dist/src/core/git-run.d.ts +73 -0
  349. package/dist/src/core/git-run.js +93 -0
  350. package/dist/src/core/git-run.js.map +1 -0
  351. package/dist/src/core/harness-version.d.ts +157 -0
  352. package/dist/src/core/harness-version.js +211 -0
  353. package/dist/src/core/harness-version.js.map +1 -0
  354. package/dist/src/core/harness-wait.d.ts +55 -0
  355. package/dist/src/core/harness-wait.js +58 -0
  356. package/dist/src/core/harness-wait.js.map +1 -0
  357. package/dist/src/core/head-retry.d.ts +107 -0
  358. package/dist/src/core/head-retry.js +121 -0
  359. package/dist/src/core/head-retry.js.map +1 -0
  360. package/dist/src/core/instance.d.ts +253 -0
  361. package/dist/src/core/instance.js +319 -0
  362. package/dist/src/core/instance.js.map +1 -0
  363. package/dist/src/core/intake-limits.d.ts +247 -0
  364. package/dist/src/core/intake-limits.js +350 -0
  365. package/dist/src/core/intake-limits.js.map +1 -0
  366. package/dist/src/core/jcs.d.ts +52 -0
  367. package/dist/src/core/jcs.js +132 -0
  368. package/dist/src/core/jcs.js.map +1 -0
  369. package/dist/src/core/journal.d.ts +144 -0
  370. package/dist/src/core/journal.js +200 -0
  371. package/dist/src/core/journal.js.map +1 -0
  372. package/dist/src/core/live-draw.d.ts +436 -0
  373. package/dist/src/core/live-draw.js +703 -0
  374. package/dist/src/core/live-draw.js.map +1 -0
  375. package/dist/src/core/log-reconcile.d.ts +89 -0
  376. package/dist/src/core/log-reconcile.js +136 -0
  377. package/dist/src/core/log-reconcile.js.map +1 -0
  378. package/dist/src/core/log-subscribe.d.ts +36 -0
  379. package/dist/src/core/log-subscribe.js +162 -0
  380. package/dist/src/core/log-subscribe.js.map +1 -0
  381. package/dist/src/core/log.d.ts +278 -0
  382. package/dist/src/core/log.js +546 -0
  383. package/dist/src/core/log.js.map +1 -0
  384. package/dist/src/core/loop.d.ts +274 -0
  385. package/dist/src/core/loop.js +487 -0
  386. package/dist/src/core/loop.js.map +1 -0
  387. package/dist/src/core/md-fence.d.ts +41 -0
  388. package/dist/src/core/md-fence.js +74 -0
  389. package/dist/src/core/md-fence.js.map +1 -0
  390. package/dist/src/core/money.d.ts +147 -0
  391. package/dist/src/core/money.js +195 -0
  392. package/dist/src/core/money.js.map +1 -0
  393. package/dist/src/core/payload-census.d.ts +74 -0
  394. package/dist/src/core/payload-census.js +146 -0
  395. package/dist/src/core/payload-census.js.map +1 -0
  396. package/dist/src/core/payload-store.d.ts +175 -0
  397. package/dist/src/core/payload-store.js +340 -0
  398. package/dist/src/core/payload-store.js.map +1 -0
  399. package/dist/src/core/payload.d.ts +71 -0
  400. package/dist/src/core/payload.js +80 -0
  401. package/dist/src/core/payload.js.map +1 -0
  402. package/dist/src/core/policy-diff.d.ts +292 -0
  403. package/dist/src/core/policy-diff.js +588 -0
  404. package/dist/src/core/policy-diff.js.map +1 -0
  405. package/dist/src/core/policy-expectations.d.ts +199 -0
  406. package/dist/src/core/policy-expectations.js +394 -0
  407. package/dist/src/core/policy-expectations.js.map +1 -0
  408. package/dist/src/core/policy-explain.d.ts +150 -0
  409. package/dist/src/core/policy-explain.js +258 -0
  410. package/dist/src/core/policy-explain.js.map +1 -0
  411. package/dist/src/core/policy-load.d.ts +527 -0
  412. package/dist/src/core/policy-load.js +536 -0
  413. package/dist/src/core/policy-load.js.map +1 -0
  414. package/dist/src/core/policy-match.d.ts +281 -0
  415. package/dist/src/core/policy-match.js +478 -0
  416. package/dist/src/core/policy-match.js.map +1 -0
  417. package/dist/src/core/policy-proposal.d.ts +265 -0
  418. package/dist/src/core/policy-proposal.js +458 -0
  419. package/dist/src/core/policy-proposal.js.map +1 -0
  420. package/dist/src/core/prompt-layout.d.ts +221 -0
  421. package/dist/src/core/prompt-layout.js +422 -0
  422. package/dist/src/core/prompt-layout.js.map +1 -0
  423. package/dist/src/core/protected-path-guard.d.ts +453 -0
  424. package/dist/src/core/protected-path-guard.js +1566 -0
  425. package/dist/src/core/protected-path-guard.js.map +1 -0
  426. package/dist/src/core/registration.d.ts +25 -0
  427. package/dist/src/core/registration.js +39 -0
  428. package/dist/src/core/registration.js.map +1 -0
  429. package/dist/src/core/reindex.d.ts +99 -0
  430. package/dist/src/core/reindex.js +336 -0
  431. package/dist/src/core/reindex.js.map +1 -0
  432. package/dist/src/core/sampler.d.ts +313 -0
  433. package/dist/src/core/sampler.js +388 -0
  434. package/dist/src/core/sampler.js.map +1 -0
  435. package/dist/src/core/sandbox.d.ts +290 -0
  436. package/dist/src/core/sandbox.js +424 -0
  437. package/dist/src/core/sandbox.js.map +1 -0
  438. package/dist/src/core/seal.d.ts +165 -0
  439. package/dist/src/core/seal.js +290 -0
  440. package/dist/src/core/seal.js.map +1 -0
  441. package/dist/src/core/state.d.ts +505 -0
  442. package/dist/src/core/state.js +1009 -0
  443. package/dist/src/core/state.js.map +1 -0
  444. package/dist/src/core/task-file.d.ts +185 -0
  445. package/dist/src/core/task-file.js +464 -0
  446. package/dist/src/core/task-file.js.map +1 -0
  447. package/dist/src/core/telegram-config.d.ts +93 -0
  448. package/dist/src/core/telegram-config.js +114 -0
  449. package/dist/src/core/telegram-config.js.map +1 -0
  450. package/dist/src/core/token.d.ts +409 -0
  451. package/dist/src/core/token.js +561 -0
  452. package/dist/src/core/token.js.map +1 -0
  453. package/dist/src/core/validate.d.ts +138 -0
  454. package/dist/src/core/validate.js +0 -0
  455. package/dist/src/core/validate.js.map +1 -0
  456. package/dist/src/core/values.d.ts +137 -0
  457. package/dist/src/core/values.js +153 -0
  458. package/dist/src/core/values.js.map +1 -0
  459. package/dist/src/core/vault.d.ts +291 -0
  460. package/dist/src/core/vault.js +612 -0
  461. package/dist/src/core/vault.js.map +1 -0
  462. package/dist/src/core/verified-snapshot.d.ts +204 -0
  463. package/dist/src/core/verified-snapshot.js +506 -0
  464. package/dist/src/core/verified-snapshot.js.map +1 -0
  465. package/dist/src/core/verify.d.ts +336 -0
  466. package/dist/src/core/verify.js +549 -0
  467. package/dist/src/core/verify.js.map +1 -0
  468. package/dist/src/core/version.d.ts +8 -0
  469. package/dist/src/core/version.js +9 -0
  470. package/dist/src/core/version.js.map +1 -0
  471. package/dist/src/core/wysiwys.d.ts +370 -0
  472. package/dist/src/core/wysiwys.js +728 -0
  473. package/dist/src/core/wysiwys.js.map +1 -0
  474. package/dist/src/daemon/advance-child.d.ts +39 -0
  475. package/dist/src/daemon/advance-child.js +78 -0
  476. package/dist/src/daemon/advance-child.js.map +1 -0
  477. package/dist/src/daemon/advance.d.ts +466 -0
  478. package/dist/src/daemon/advance.js +849 -0
  479. package/dist/src/daemon/advance.js.map +1 -0
  480. package/dist/src/daemon/audit.d.ts +87 -0
  481. package/dist/src/daemon/audit.js +90 -0
  482. package/dist/src/daemon/audit.js.map +1 -0
  483. package/dist/src/daemon/daemon.d.ts +1180 -0
  484. package/dist/src/daemon/daemon.js +1988 -0
  485. package/dist/src/daemon/daemon.js.map +1 -0
  486. package/dist/src/daemon/dark-session.d.ts +64 -0
  487. package/dist/src/daemon/dark-session.js +119 -0
  488. package/dist/src/daemon/dark-session.js.map +1 -0
  489. package/dist/src/daemon/draw-child.d.ts +36 -0
  490. package/dist/src/daemon/draw-child.js +132 -0
  491. package/dist/src/daemon/draw-child.js.map +1 -0
  492. package/dist/src/daemon/draw.d.ts +154 -0
  493. package/dist/src/daemon/draw.js +458 -0
  494. package/dist/src/daemon/draw.js.map +1 -0
  495. package/dist/src/daemon/git-evidence.d.ts +173 -0
  496. package/dist/src/daemon/git-evidence.js +345 -0
  497. package/dist/src/daemon/git-evidence.js.map +1 -0
  498. package/dist/src/daemon/projection.d.ts +180 -0
  499. package/dist/src/daemon/projection.js +233 -0
  500. package/dist/src/daemon/projection.js.map +1 -0
  501. package/dist/src/daemon/prune.d.ts +207 -0
  502. package/dist/src/daemon/prune.js +376 -0
  503. package/dist/src/daemon/prune.js.map +1 -0
  504. package/dist/src/mcp/http.d.ts +113 -0
  505. package/dist/src/mcp/http.js +343 -0
  506. package/dist/src/mcp/http.js.map +1 -0
  507. package/dist/src/mcp/server.d.ts +265 -0
  508. package/dist/src/mcp/server.js +602 -0
  509. package/dist/src/mcp/server.js.map +1 -0
  510. package/docs/adapter-api.md +106 -0
  511. package/docs/cli-reference.md +5716 -0
  512. package/docs/codex-enforced-session.md +30 -0
  513. package/package.json +53 -4
  514. package/schema/.gitkeep +0 -0
  515. package/schema/LICENSE +117 -0
  516. package/schema/codex-instance.schema.json +82 -0
  517. package/schema/envelope.schema.json +137 -0
  518. package/schema/event.schema.json +1811 -0
  519. package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
  520. package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
  521. package/schema/fixtures/envelope/invalid/action-missing-idempotency-key.json +15 -0
  522. package/schema/fixtures/envelope/invalid/action-unknown-class-format.json +14 -0
  523. package/schema/fixtures/envelope/invalid/confidence-out-of-range.json +11 -0
  524. package/schema/fixtures/envelope/invalid/est-cost-bare-number.json +14 -0
  525. package/schema/fixtures/envelope/invalid/est-cost-noncanonical-string.json +14 -0
  526. package/schema/fixtures/envelope/invalid/malformed-assignee.json +10 -0
  527. package/schema/fixtures/envelope/invalid/malformed-created-by.json +7 -0
  528. package/schema/fixtures/envelope/invalid/malformed-max-latency.json +11 -0
  529. package/schema/fixtures/envelope/invalid/malformed-payload-hash.json +14 -0
  530. package/schema/fixtures/envelope/invalid/max-cost-bare-number.json +11 -0
  531. package/schema/fixtures/envelope/invalid/missing-origin.json +3 -0
  532. package/schema/fixtures/envelope/invalid/negative-est-cost.json +14 -0
  533. package/schema/fixtures/envelope/invalid/unknown-state.json +7 -0
  534. package/schema/fixtures/envelope/invalid/unknown-top-level-field.json +8 -0
  535. package/schema/fixtures/envelope/valid/action-payload-hash.json +17 -0
  536. package/schema/fixtures/envelope/valid/actions-without-budget.json +18 -0
  537. package/schema/fixtures/envelope/valid/canonical.json +25 -0
  538. package/schema/fixtures/envelope/valid/minimal.json +7 -0
  539. package/schema/fixtures/envelope/valid/multi-action-executed.json +30 -0
  540. package/schema/fixtures/envelope/valid/record-write-stage.json +22 -0
  541. package/schema/fixtures/event/invalid/approval-granted-agent-actor.json +15 -0
  542. package/schema/fixtures/event/invalid/approval-granted-empty-batch-delivery-id.json +17 -0
  543. package/schema/fixtures/event/invalid/approval-granted-fifth-reaction.json +16 -0
  544. package/schema/fixtures/event/invalid/approval-granted-missing-actor.json +14 -0
  545. package/schema/fixtures/event/invalid/approval-requested-missing-action-key.json +14 -0
  546. package/schema/fixtures/event/invalid/approval-withdrawn-agent-policy-drift.json +15 -0
  547. package/schema/fixtures/event/invalid/approval-withdrawn-missing-reason.json +15 -0
  548. package/schema/fixtures/event/invalid/approval-withdrawn-system-actor.json +15 -0
  549. package/schema/fixtures/event/invalid/audit-decision-refused-human-actor.json +17 -0
  550. package/schema/fixtures/event/invalid/audit-decision-refused-missing-code.json +16 -0
  551. package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +15 -0
  552. package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +16 -0
  553. package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +15 -0
  554. package/schema/fixtures/event/invalid/bad-actor-prefix.json +15 -0
  555. package/schema/fixtures/event/invalid/est-cost-bare-number.json +17 -0
  556. package/schema/fixtures/event/invalid/execution-completed-fabricated-exit-code.json +16 -0
  557. package/schema/fixtures/event/invalid/execution-completed-provider-ref-empty-id.json +18 -0
  558. package/schema/fixtures/event/invalid/execution-completed-provider-ref-extra-field.json +19 -0
  559. package/schema/fixtures/event/invalid/execution-completed-provider-ref-id-not-string.json +18 -0
  560. package/schema/fixtures/event/invalid/execution-completed-provider-ref-missing-adapter.json +17 -0
  561. package/schema/fixtures/event/invalid/execution-failed-open-reported-by.json +16 -0
  562. package/schema/fixtures/event/invalid/execution-indeterminate-open-reason.json +14 -0
  563. package/schema/fixtures/event/invalid/execution-reconciled-agent-actor.json +17 -0
  564. package/schema/fixtures/event/invalid/execution-started-negative-env-stripped.json +16 -0
  565. package/schema/fixtures/event/invalid/gate-bypassed-missing-opened-seq.json +15 -0
  566. package/schema/fixtures/event/invalid/gate-closed-non-integer-opened-seq.json +13 -0
  567. package/schema/fixtures/event/invalid/gate-opened-agent-actor.json +16 -0
  568. package/schema/fixtures/event/invalid/gate-organ-attested-absolute-path.json +14 -0
  569. package/schema/fixtures/event/invalid/gate-organ-attested-agent-actor.json +14 -0
  570. package/schema/fixtures/event/invalid/gate-organ-attested-missing-organ-path.json +13 -0
  571. package/schema/fixtures/event/invalid/harness-unknown-kind.json +18 -0
  572. package/schema/fixtures/event/invalid/harness-version-multiline.json +16 -0
  573. package/schema/fixtures/event/invalid/log-checkpoint-agent-actor.json +17 -0
  574. package/schema/fixtures/event/invalid/log-checkpoint-missing-signature.json +16 -0
  575. package/schema/fixtures/event/invalid/log-checkpoint-short-signed-hash.json +17 -0
  576. package/schema/fixtures/event/invalid/log-checkpoint-unknown-signature-alg.json +17 -0
  577. package/schema/fixtures/event/invalid/malformed-ts.json +15 -0
  578. package/schema/fixtures/event/invalid/missing-alg.json +14 -0
  579. package/schema/fixtures/event/invalid/missing-hash.json +14 -0
  580. package/schema/fixtures/event/invalid/non-integer-seq.json +15 -0
  581. package/schema/fixtures/event/invalid/payload-pruned-human-actor.json +14 -0
  582. package/schema/fixtures/event/invalid/payload-pruned-missing-hash.json +14 -0
  583. package/schema/fixtures/event/invalid/policy-declined-agent-actor.json +15 -0
  584. package/schema/fixtures/event/invalid/policy-proposed-missing-diff.json +19 -0
  585. package/schema/fixtures/event/invalid/policy-proposed-system-actor.json +26 -0
  586. package/schema/fixtures/event/invalid/short-hash.json +15 -0
  587. package/schema/fixtures/event/invalid/unknown-alg.json +15 -0
  588. package/schema/fixtures/event/invalid/unknown-event-type.json +15 -0
  589. package/schema/fixtures/event/invalid/unknown-top-level-field.json +16 -0
  590. package/schema/fixtures/event/valid/approval-expired.json +15 -0
  591. package/schema/fixtures/event/valid/approval-granted-batch.json +19 -0
  592. package/schema/fixtures/event/valid/approval-granted-reaction.json +16 -0
  593. package/schema/fixtures/event/valid/approval-granted.json +15 -0
  594. package/schema/fixtures/event/valid/approval-rejected.json +15 -0
  595. package/schema/fixtures/event/valid/approval-requested.json +19 -0
  596. package/schema/fixtures/event/valid/approval-revoked.json +15 -0
  597. package/schema/fixtures/event/valid/approval-withdrawn-policy-drift.json +17 -0
  598. package/schema/fixtures/event/valid/approval-withdrawn.json +16 -0
  599. package/schema/fixtures/event/valid/audit-decision-refused.json +20 -0
  600. package/schema/fixtures/event/valid/audit-reviewed-reaction.json +17 -0
  601. package/schema/fixtures/event/valid/audit-reviewed.json +15 -0
  602. package/schema/fixtures/event/valid/audit-sampled.json +14 -0
  603. package/schema/fixtures/event/valid/budget-exceeded.json +21 -0
  604. package/schema/fixtures/event/valid/envelope-drift.json +16 -0
  605. package/schema/fixtures/event/valid/execution-completed-harness-report.json +16 -0
  606. package/schema/fixtures/event/valid/execution-completed-provider-ref.json +18 -0
  607. package/schema/fixtures/event/valid/execution-completed.json +15 -0
  608. package/schema/fixtures/event/valid/execution-failed-harness-report.json +16 -0
  609. package/schema/fixtures/event/valid/execution-failed.json +15 -0
  610. package/schema/fixtures/event/valid/execution-indeterminate.json +15 -0
  611. package/schema/fixtures/event/valid/execution-reconciled.json +17 -0
  612. package/schema/fixtures/event/valid/execution-started-env-stripped.json +17 -0
  613. package/schema/fixtures/event/valid/execution-started.json +14 -0
  614. package/schema/fixtures/event/valid/gate-bypassed-harness-version.json +18 -0
  615. package/schema/fixtures/event/valid/gate-bypassed.json +19 -0
  616. package/schema/fixtures/event/valid/gate-closed.json +14 -0
  617. package/schema/fixtures/event/valid/gate-opened.json +16 -0
  618. package/schema/fixtures/event/valid/gate-organ-attested.json +14 -0
  619. package/schema/fixtures/event/valid/genesis-null-prev.json +14 -0
  620. package/schema/fixtures/event/valid/log-checkpoint.json +17 -0
  621. package/schema/fixtures/event/valid/payload-pruned-orphan.json +13 -0
  622. package/schema/fixtures/event/valid/payload-pruned.json +17 -0
  623. package/schema/fixtures/event/valid/policy-declined.json +16 -0
  624. package/schema/fixtures/event/valid/policy-proposed.json +35 -0
  625. package/schema/fixtures/event/valid/policy-updated.json +14 -0
  626. package/schema/fixtures/event/valid/reconciliation-required.json +18 -0
  627. package/schema/fixtures/event/valid/reconciliation-satisfied.json +17 -0
  628. package/schema/fixtures/event/valid/route-accepted.json +15 -0
  629. package/schema/fixtures/event/valid/route-proposed.json +16 -0
  630. package/schema/fixtures/event/valid/spec-example.json +15 -0
  631. package/schema/fixtures/event/valid/task-registered-harness-version.json +23 -0
  632. package/schema/fixtures/event/valid/task-registered.json +14 -0
  633. package/schema/fixtures/hash/known-answer-pre-121.json +74 -0
  634. package/schema/fixtures/hash/known-answer.json +74 -0
  635. package/schema/fixtures/policy/invalid/bad-approval-ttl.json +7 -0
  636. package/schema/fixtures/policy/invalid/bad-web-port.json +4 -0
  637. package/schema/fixtures/policy/invalid/checkpoint-key-not-base64.json +6 -0
  638. package/schema/fixtures/policy/invalid/class-rule-missing-autonomy.json +9 -0
  639. package/schema/fixtures/policy/invalid/empty-class-key.json +6 -0
  640. package/schema/fixtures/policy/invalid/live-rate-on-human-only.json +7 -0
  641. package/schema/fixtures/policy/invalid/malformed-class-key.json +6 -0
  642. package/schema/fixtures/policy/invalid/missing-version.json +8 -0
  643. package/schema/fixtures/policy/invalid/negative-limit.json +9 -0
  644. package/schema/fixtures/policy/invalid/non-numeric-limit.json +9 -0
  645. package/schema/fixtures/policy/invalid/non-positive-max-pending.json +9 -0
  646. package/schema/fixtures/policy/invalid/on-expiry-grant.json +8 -0
  647. package/schema/fixtures/policy/invalid/payload-retention-bare-number.json +4 -0
  648. package/schema/fixtures/policy/invalid/payload-retention-compound.json +4 -0
  649. package/schema/fixtures/policy/invalid/payload-retention-fractional.json +4 -0
  650. package/schema/fixtures/policy/invalid/payload-retention-zero.json +4 -0
  651. package/schema/fixtures/policy/invalid/protected-paths-escape.json +4 -0
  652. package/schema/fixtures/policy/invalid/protected-paths-glob.json +4 -0
  653. package/schema/fixtures/policy/invalid/retro-rate-on-human-only.json +7 -0
  654. package/schema/fixtures/policy/invalid/retro-rate-on-manual.json +7 -0
  655. package/schema/fixtures/policy/invalid/retro-rate-zero.json +7 -0
  656. package/schema/fixtures/policy/invalid/sample-rate-too-high.json +5 -0
  657. package/schema/fixtures/policy/invalid/sampling-secret-env-empty.json +7 -0
  658. package/schema/fixtures/policy/invalid/sampling-secret-env-not-string.json +6 -0
  659. package/schema/fixtures/policy/invalid/skew-tolerance-compound.json +6 -0
  660. package/schema/fixtures/policy/invalid/unknown-autonomy.json +7 -0
  661. package/schema/fixtures/policy/invalid/unknown-class-rule-key.json +6 -0
  662. package/schema/fixtures/policy/invalid/unknown-top-level-key.json +7 -0
  663. package/schema/fixtures/policy/invalid/vault-passphrase-env-empty.json +6 -0
  664. package/schema/fixtures/policy/invalid/vault-passphrase-literal.json +6 -0
  665. package/schema/fixtures/policy/invalid/version-not-string.json +4 -0
  666. package/schema/fixtures/policy/valid/canonical.json +47 -0
  667. package/schema/fixtures/policy/valid/checkpoint-keys.json +18 -0
  668. package/schema/fixtures/policy/valid/class-approvers-limits.json +25 -0
  669. package/schema/fixtures/policy/valid/class-retro-rate.json +17 -0
  670. package/schema/fixtures/policy/valid/global-budgets.json +19 -0
  671. package/schema/fixtures/policy/valid/human-only.json +9 -0
  672. package/schema/fixtures/policy/valid/minimal.json +6 -0
  673. package/schema/fixtures/policy/valid/protected-paths.json +10 -0
  674. package/schema/fixtures/policy/valid/record-namespace.json +13 -0
  675. package/schema/fixtures/policy/valid/request-volume-limits.json +26 -0
  676. package/schema/fixtures/policy/valid/retention-and-sampling-secret.json +16 -0
  677. package/schema/fixtures/policy/valid/skew-tolerance.json +15 -0
  678. package/schema/fixtures/policy/valid/vault-passphrase-env.json +14 -0
  679. package/schema/fixtures/policy/valid/wildcards.json +15 -0
  680. package/schema/fixtures/policy-md/invalid/alias-bomb.md +15 -0
  681. package/schema/fixtures/policy-md/invalid/no-fence.md +7 -0
  682. package/schema/fixtures/policy-md/invalid/protected-route-not-a-subclass.md +16 -0
  683. package/schema/fixtures/policy-md/invalid/schema-invalid-autonomy.md +16 -0
  684. package/schema/fixtures/policy-md/invalid/schema-invalid-read-proof.md +17 -0
  685. package/schema/fixtures/policy-md/invalid/two-fences.md +19 -0
  686. package/schema/fixtures/policy-md/invalid/unclosed-fence.md +11 -0
  687. package/schema/fixtures/policy-md/invalid/wrong-info-string.md +11 -0
  688. package/schema/fixtures/policy-md/invalid/yaml-syntax-error.md +13 -0
  689. package/schema/fixtures/policy-md/precedence/both/APPROVAL.md +7 -0
  690. package/schema/fixtures/policy-md/precedence/both/APPROVALS.md +7 -0
  691. package/schema/fixtures/policy-md/precedence/fallback-only/APPROVALS.md +7 -0
  692. package/schema/fixtures/policy-md/valid/canonical.md +50 -0
  693. package/schema/fixtures/policy-md/valid/daemon-read-proof.md +18 -0
  694. package/schema/fixtures/policy-md/valid/minimal.md +3 -0
  695. package/schema/fixtures/policy-md/valid/prose-lookalikes.md +54 -0
  696. package/schema/fixtures/policy-md/valid/routed-protected-paths.md +49 -0
  697. package/schema/fixtures/policy-md/valid/with-values.md +79 -0
  698. package/schema/fixtures/sample-record/invalid/bad-date-time.json +4 -0
  699. package/schema/fixtures/sample-record/invalid/missing-required-field.json +3 -0
  700. package/schema/fixtures/sample-record/invalid/unknown-top-level-field.json +5 -0
  701. package/schema/fixtures/sample-record/invalid/wrong-type.json +4 -0
  702. package/schema/fixtures/sample-record/valid/minimal.json +4 -0
  703. package/schema/fixtures/sample-record/valid/with-note.json +5 -0
  704. package/schema/fixtures/values/invalid/class-shaped.json +9 -0
  705. package/schema/fixtures/values/invalid/duplicate-entry.json +4 -0
  706. package/schema/fixtures/values/invalid/non-string-item.json +4 -0
  707. package/schema/fixtures/values/invalid/over-cap.json +26 -0
  708. package/schema/fixtures/values/invalid/unknown-key.json +5 -0
  709. package/schema/fixtures/values/invalid/version-string.json +1 -0
  710. package/schema/fixtures/values/valid/empty-lists.json +7 -0
  711. package/schema/fixtures/values/valid/full.json +20 -0
  712. package/schema/fixtures/values/valid/minimal.json +1 -0
  713. package/schema/fixtures/values-md/invalid/schema-invalid.md +62 -0
  714. package/schema/fixtures/values-md/invalid/two-blocks.md +69 -0
  715. package/schema/fixtures/values-md/invalid/unterminated.md +61 -0
  716. package/schema/fixtures/values-md/invalid/yaml-error.md +63 -0
  717. package/schema/fixtures/values-md/valid/absent.md +50 -0
  718. package/schema/fixtures/values-md/valid/with-values.md +79 -0
  719. package/schema/policy.schema.json +501 -0
  720. package/schema/sample-record.schema.json +26 -0
  721. package/schema/values.schema.json +55 -0
  722. package/templates/codex/README.md +9 -0
@@ -0,0 +1,738 @@
1
+ /**
2
+ * Human-signed log checkpoints (APRV-220).
3
+ *
4
+ * ## The hole this fills
5
+ *
6
+ * The chain in `.approval/log/events.jsonl` is unkeyed, and
7
+ * `docs/proposals/incremental-prefix-proof.md` §3 states the consequence: a
8
+ * process with write access to that file can truncate it and recompute a chain
9
+ * that is self-consistent from genesis. Nothing INSIDE the file contradicts the
10
+ * forgery, so no walk of it ever will. The conformance suite says the same from
11
+ * the other side — `chain-verification/truncation-unanchored` is a boundary
12
+ * vector, and an implementation that claims to catch an unanchored truncation
13
+ * is claiming more than a hash chain can give.
14
+ *
15
+ * §12 of that proposal named three ways out: external anchoring, a keyed chain,
16
+ * and human-signed checkpoints. APRV-219 built the first. This module builds
17
+ * the third.
18
+ *
19
+ * ## How the two witnesses relate
20
+ *
21
+ * They are independent, and neither weakens the other.
22
+ *
23
+ * The ANCHOR (`cli/log-anchor.ts`) asks "does somebody else hold a copy of
24
+ * these bytes?" and answers from git. It is exactly as fresh as the last push,
25
+ * and on a machine with no remote and no records branch it says nothing at all
26
+ * (a skip, never a pass).
27
+ *
28
+ * A CHECKPOINT asks "did a key that no agent process holds sign this head?" and
29
+ * answers from the log itself plus the policy. It works offline, it covers the
30
+ * window since the last push, and it survives being copied to another machine.
31
+ *
32
+ * Against the forger of §3 they fail in different directions, which is the
33
+ * point of having both: the anchor catches a truncation whose records somebody
34
+ * else already holds, and a checkpoint catches a truncation inside the window
35
+ * nobody has pushed yet — because every checkpoint in the rewritten range names
36
+ * a `(seq, hash)` the rewritten chain does not carry, and the forger cannot
37
+ * produce a signature over the hashes they DID recompute.
38
+ *
39
+ * `approval log verify` runs the two independently and reports both. Neither
40
+ * check may be weakened to make the other pass.
41
+ *
42
+ * ## The key
43
+ *
44
+ * A DEDICATED Ed25519 keypair, not the attestation identity. Attestation
45
+ * (`core/attest.ts`) has no keypair to reuse: human identity at v0.1 is
46
+ * config-declared (`--as human:<id>`), and its whole documented claim is that
47
+ * *someone with local control* signed off, not who. A checkpoint has to claim
48
+ * more than that or it claims nothing, because the party it defends against is
49
+ * a process with local control.
50
+ *
51
+ * - The PRIVATE half never appears in the log, in a policy, or in any file an
52
+ * agent may read: it lives in the credential vault (`core/vault.ts`),
53
+ * encrypted at rest under a passphrase `core/child-env.ts` strips from every
54
+ * spawned child, behind a file whose reading classifies `account.credential`
55
+ * (human-only). This module never reads it from anywhere — the caller passes
56
+ * the bytes, so the custody decision lives in one place, the CLI verb.
57
+ * - The PUBLIC half is listed in the policy, `audit.checkpoint_keys`. The
58
+ * policy is the human's own committed, attested artifact: editing it is a
59
+ * visible diff AND de-attests the policy, so gate operations refuse until a
60
+ * human re-attests. That is a materially harder thing to do quietly than
61
+ * rewriting a log line.
62
+ *
63
+ * A list rather than a scalar, so rotation can RETAIN a retired key. A
64
+ * checkpoint signed by a key the policy no longer lists is a refusal here, on
65
+ * purpose (see {@link CHECKPOINT_REFUSAL_CODES}), which makes dropping a key
66
+ * that signed anything a de-verification rather than a cleanup.
67
+ *
68
+ * ## What is signed, and why not more
69
+ *
70
+ * `"approval.md/log-checkpoint/v1\n" + JCS({alg, hash, seq})`. The prefix is
71
+ * domain separation: a signature made here cannot be lifted into any other use
72
+ * of the same key, and a signature made elsewhere cannot be presented as a
73
+ * checkpoint. The head's `hash` is a 256-bit chain digest, so the message is
74
+ * already specific to one chain at one position and needs no further binding.
75
+ *
76
+ * The signature deliberately does NOT cover the rest of the record. It could
77
+ * not: the record's own `hash` covers its payload, which covers the signature.
78
+ * What a checkpoint asserts is exactly "a key holder saw this head" — every
79
+ * other field of the record is covered by the chain, and by the anchor, and by
80
+ * this module refusing a checkpoint whose signed head is not the head the log
81
+ * actually carries.
82
+ *
83
+ * ## Fail closed on a bad signature, fail open on an absent one
84
+ *
85
+ * An invalid signature, an unknown key, or a signed hash the log contradicts is
86
+ * a refusal. A log with no checkpoints at all is not: a human who has been away
87
+ * is not a forger, and a runtime that refused a log for want of a tap would
88
+ * teach its operator to turn the check off. A configured cadence that has
89
+ * lapsed is a WARNING, at every layer, and there is no path in this module from
90
+ * "due" to "refused".
91
+ *
92
+ * A missing PUBLIC KEY is a skip naming why, never a pass — the same rule the
93
+ * anchor check follows for a missing anchor. Nothing has been verified, and
94
+ * reporting silence as a pass is how a check stops being one.
95
+ */
96
+ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify, } from "node:crypto";
97
+ import { tick } from "./clock.js";
98
+ import { attemptsOf, withHeadRetry } from "./head-retry.js";
99
+ import { canonicalize } from "./jcs.js";
100
+ import { appendEvent, } from "./log.js";
101
+ import { loadPolicy } from "./policy-load.js";
102
+ import { readVerifiedRecords } from "./state.js";
103
+ /** The one signature scheme at v0.1. Recorded on every checkpoint. */
104
+ export const CHECKPOINT_ALG = "ed25519";
105
+ /** The event type a checkpoint is written as. */
106
+ export const CHECKPOINT_EVENT = "log.checkpoint";
107
+ /**
108
+ * Domain separation for the signed message. A constant, and a versioned one:
109
+ * if the signed shape ever changes, the prefix changes with it, so a signature
110
+ * over the old shape can never be read as one over the new.
111
+ */
112
+ export const CHECKPOINT_DOMAIN = "approval.md/log-checkpoint/v1";
113
+ /** The credential name the CLI reads the private half from. */
114
+ export const CHECKPOINT_KEY_CREDENTIAL = "approval.checkpoint.key";
115
+ /** Actors permitted to sign a checkpoint, in code as well as in the schema. */
116
+ const HUMAN_ACTOR = /^human:.+/u;
117
+ // ---------------------------------------------------------------------------
118
+ // The refusal union
119
+ // ---------------------------------------------------------------------------
120
+ /**
121
+ * Every way a checkpoint can refuse a range. A closed union per SPEC.md §11.1
122
+ * invariant 6, frozen the way the others are: callers branch on the string.
123
+ *
124
+ * Five codes, because they are five different facts about how a checkpoint
125
+ * failed and they have five different repairs. Collapsing them would leave the
126
+ * one message a person needs — which record, which key, which seq — inside a
127
+ * free-text blob nothing can branch on.
128
+ *
129
+ * `checkpoint-key-unknown` is a refusal rather than a warning, and that is the
130
+ * load-bearing choice in this union. Softening it would hand a forger the
131
+ * escape hatch: rewrite each checkpoint's `key_sha256` to name a key nobody
132
+ * lists, and every refusal in the range becomes a shrug. The cost is that
133
+ * removing a retired key from `audit.checkpoint_keys` stops the checkpoints it
134
+ * signed from verifying, which is why the policy field is a LIST and why
135
+ * APRV-257's rotation verb refuses to drop a key that signed anything.
136
+ */
137
+ export const CHECKPOINT_REFUSAL_CODES = [
138
+ /**
139
+ * The record names a `key_sha256` that no configured public key hashes to.
140
+ * Either a key was retired out of the policy, or somebody wrote a checkpoint
141
+ * with a key of their own — and this runtime cannot tell which, so it refuses.
142
+ */
143
+ "checkpoint-key-unknown",
144
+ /**
145
+ * The signature does not verify under the key the record names. The bytes
146
+ * signed are not the bytes presented; nothing further is inferred, because
147
+ * distinguishing "wrong key" from "tampered payload" would be an oracle and
148
+ * the repair is identical.
149
+ */
150
+ "checkpoint-signature-invalid",
151
+ /**
152
+ * The signature is good and the log disagrees with it: the record at the
153
+ * signed `seq` carries a hash the signature does not name (or the log carries
154
+ * no record at that seq at all). THIS is the forged-chain catch — a chain
155
+ * recomputed after a checkpoint cannot reproduce a signature over the hashes
156
+ * it replaced.
157
+ */
158
+ "checkpoint-hash-mismatch",
159
+ /**
160
+ * The signed `seq` is not below the checkpoint record's own. A checkpoint
161
+ * signs the past; one naming itself or the future is not a checkpoint, and a
162
+ * runtime that accepted one would accept a record vouching for a head that
163
+ * did not exist when it was written.
164
+ */
165
+ "checkpoint-out-of-order",
166
+ /**
167
+ * The payload is not a checkpoint payload: a missing field, a hash that is
168
+ * not 64 hex, an `alg` this build does not implement. The write boundary
169
+ * refuses these, so reaching one means the record came from elsewhere — and a
170
+ * `log.checkpoint` nobody can read is not a checkpoint that passes.
171
+ */
172
+ "checkpoint-malformed",
173
+ ];
174
+ /** Mint a checkpoint keypair. Ed25519 from `node:crypto`; no dependency added. */
175
+ export function mintCheckpointKeypair() {
176
+ const pair = generateKeyPairSync("ed25519");
177
+ const spki = pair.publicKey.export({ type: "spki", format: "der" });
178
+ return {
179
+ publicKey: spki.toString("base64"),
180
+ privateKey: pair.privateKey.export({ type: "pkcs8", format: "der" }).toString("base64"),
181
+ fingerprint: createHash("sha256").update(spki).digest("hex"),
182
+ };
183
+ }
184
+ /**
185
+ * SHA-256 of a public key's DER SPKI **bytes**, hex.
186
+ *
187
+ * Over the bytes rather than over their base64 spelling, so a key that was
188
+ * re-wrapped, re-encoded, or copied through a text editor still fingerprints to
189
+ * the same value. Returns `null` for anything that is not a public key this
190
+ * build can parse — an unreadable key is not a key, and callers turn that into
191
+ * a skip or a refusal rather than into a match against nothing.
192
+ */
193
+ export function checkpointKeyFingerprint(publicKey) {
194
+ const spki = derOf(publicKey);
195
+ return spki === null ? null : createHash("sha256").update(spki).digest("hex");
196
+ }
197
+ /** The DER SPKI bytes of a base64 public key, or `null` when it will not parse. */
198
+ function derOf(publicKey) {
199
+ try {
200
+ const key = createPublicKey({
201
+ key: Buffer.from(publicKey, "base64"),
202
+ format: "der",
203
+ type: "spki",
204
+ });
205
+ if (key.asymmetricKeyType !== "ed25519")
206
+ return null;
207
+ return key.export({ type: "spki", format: "der" });
208
+ }
209
+ catch {
210
+ return null;
211
+ }
212
+ }
213
+ /**
214
+ * The configured public keys, indexed by fingerprint.
215
+ *
216
+ * Keys that do not parse are DROPPED rather than throwing, and the count of
217
+ * what survived is what a caller reports: a policy listing one good key and one
218
+ * typo must still verify the checkpoints the good key signed, and it must not
219
+ * be able to claim the typo verified anything.
220
+ */
221
+ export function checkpointKeyIndex(publicKeys) {
222
+ const keys = new Map();
223
+ let unreadable = 0;
224
+ for (const key of publicKeys) {
225
+ const fingerprint = checkpointKeyFingerprint(key);
226
+ if (fingerprint === null) {
227
+ unreadable += 1;
228
+ continue;
229
+ }
230
+ keys.set(fingerprint, key);
231
+ }
232
+ return { keys, unreadable };
233
+ }
234
+ /**
235
+ * The bytes a checkpoint signature covers.
236
+ *
237
+ * JCS (RFC 8785) over `{alg, hash, seq}`, behind {@link CHECKPOINT_DOMAIN} and
238
+ * a newline. Canonical, so two runtimes that agree on the head agree on the
239
+ * bytes; domain-separated, so the signature means one thing.
240
+ */
241
+ export function checkpointMessage(head) {
242
+ const body = canonicalize({
243
+ alg: head.alg ?? CHECKPOINT_ALG,
244
+ hash: head.hash,
245
+ seq: head.seq,
246
+ });
247
+ return Buffer.from(`${CHECKPOINT_DOMAIN}\n${body}`, "utf8");
248
+ }
249
+ /** Sign a head with a base64 PKCS#8 private key. `null` when the key is unusable. */
250
+ export function signCheckpoint(head, privateKey) {
251
+ try {
252
+ return sign(null, checkpointMessage(head), {
253
+ key: Buffer.from(privateKey, "base64"),
254
+ format: "der",
255
+ type: "pkcs8",
256
+ }).toString("base64");
257
+ }
258
+ catch {
259
+ return null;
260
+ }
261
+ }
262
+ /** Does `signature` verify over `head` under `publicKey`? Never throws. */
263
+ export function verifyCheckpointSignature(head, signature, publicKey) {
264
+ try {
265
+ return verify(null, checkpointMessage(head), { key: Buffer.from(publicKey, "base64"), format: "der", type: "spki" }, Buffer.from(signature, "base64"));
266
+ }
267
+ catch {
268
+ return false;
269
+ }
270
+ }
271
+ const HEX64 = /^[a-f0-9]{64}$/u;
272
+ const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/u;
273
+ /** Is this record a `log.checkpoint`? Type only; the payload is read separately. */
274
+ export function isCheckpointRecord(record) {
275
+ return record.event === CHECKPOINT_EVENT;
276
+ }
277
+ /**
278
+ * Read a checkpoint record's payload, or `null` when it is not one.
279
+ *
280
+ * Strict, and deliberately so: the write boundary already refuses a malformed
281
+ * checkpoint, so a payload that fails here arrived from somewhere else, and the
282
+ * caller's answer to that is {@link CHECKPOINT_REFUSAL_CODES}'s
283
+ * `checkpoint-malformed` rather than a pass.
284
+ */
285
+ export function readCheckpointPayload(record) {
286
+ const payload = record.payload;
287
+ if (typeof payload !== "object" || payload === null)
288
+ return null;
289
+ const seq = payload["seq"];
290
+ const hash = payload["hash"];
291
+ const alg = payload["alg"];
292
+ const keySha256 = payload["key_sha256"];
293
+ const signature = payload["signature"];
294
+ if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 1)
295
+ return null;
296
+ if (typeof hash !== "string" || !HEX64.test(hash))
297
+ return null;
298
+ if (alg !== CHECKPOINT_ALG)
299
+ return null;
300
+ if (typeof keySha256 !== "string" || !HEX64.test(keySha256))
301
+ return null;
302
+ if (typeof signature !== "string" || signature.length === 0 || !BASE64.test(signature)) {
303
+ return null;
304
+ }
305
+ return { seq, hash, alg, keySha256, signature };
306
+ }
307
+ /**
308
+ * Which keys have signed checkpoints in this log, and at which seqs (APRV-257).
309
+ *
310
+ * The question rotation has to ask before it retires anything. Removing a key
311
+ * from `audit.checkpoint_keys` turns every checkpoint it signed into
312
+ * `checkpoint-key-unknown` — a REFUSAL, by the deliberate choice recorded in
313
+ * {@link CHECKPOINT_REFUSAL_CODES} — so a verb that let an operator drop a key
314
+ * casually would be a verb that broke a log's verification to tidy a list.
315
+ *
316
+ * Keyed by the record's self-reported `key_sha256`, which is the only thing
317
+ * `checkLogCheckpoints` looks a key up by, so the answer is exactly the set of
318
+ * names whose removal would change a verdict. Records with an unreadable
319
+ * payload are skipped: they refuse for a different reason already, and no
320
+ * removal makes that better or worse.
321
+ */
322
+ export function checkpointSignersIn(records) {
323
+ const signers = new Map();
324
+ for (const record of records) {
325
+ if (!isCheckpointRecord(record))
326
+ continue;
327
+ const payload = readCheckpointPayload(record);
328
+ if (payload === null)
329
+ continue;
330
+ const seqs = signers.get(payload.keySha256);
331
+ if (seqs === undefined)
332
+ signers.set(payload.keySha256, [record.seq]);
333
+ else
334
+ seqs.push(record.seq);
335
+ }
336
+ return signers;
337
+ }
338
+ // ---------------------------------------------------------------------------
339
+ // Appending one
340
+ // ---------------------------------------------------------------------------
341
+ /** Why a checkpoint was not appended. Nothing was written in any of these. */
342
+ export const CHECKPOINT_APPEND_REFUSAL_CODES = [
343
+ /** The actor is not `human:`-prefixed. Refused here and in the schema. */
344
+ "actor-not-human",
345
+ /** The private key will not parse, or would not sign. */
346
+ "checkpoint-key-unusable",
347
+ /** The log has no records yet: an empty chain has no head to sign. */
348
+ "log-empty",
349
+ /**
350
+ * A caller named a head this log does not carry (APRV-257).
351
+ *
352
+ * Only {@link appendCheckpointAt} can reach it, and only from the tap: the
353
+ * human is shown a `(seq, hash)`, the head moves while the phone is in a
354
+ * pocket, and the record they sign still names the head they SAW. That is
355
+ * allowed — a checkpoint signs any seq below its own — but only while the
356
+ * log actually carries that hash at that seq. When it does not, the thing in
357
+ * front of the human was derived from a different chain than the one being
358
+ * written to, and signing it would mint a checkpoint that
359
+ * `checkpoint-hash-mismatch` refuses forever after.
360
+ */
361
+ "checkpoint-head-unknown",
362
+ /** The log could not be opened. */
363
+ "log-unreadable",
364
+ /** The log's last line is truncated. Nothing may chain onto it. */
365
+ "log-torn-tail",
366
+ /** The chain does not verify, so no head derived from it may be signed. */
367
+ "log-corrupt",
368
+ /** The append itself was refused; `append` carries the writer's own code. */
369
+ "append-failed",
370
+ ];
371
+ /**
372
+ * Append one `log.checkpoint` signing the log's current head.
373
+ *
374
+ * The head is read, then signed, then written with that head as
375
+ * `expectedHead`, so the record cannot land on a chain that moved underneath it
376
+ * (SPEC.md §11.1: every check-then-append passes through compare-and-append).
377
+ * A concurrent append is `head-moved`, and the repair is to run it again — the
378
+ * signature would otherwise vouch for a head that is no longer this record's
379
+ * predecessor, which is a checkpoint that verifies and means less than it looks
380
+ * like it means.
381
+ *
382
+ * `ts` is stamped from the clock at the write boundary and is never a
383
+ * parameter: `log.checkpoint` is gate-typed under amended SPEC.md §8 (A2), and
384
+ * a caller who could choose the moment could backdate the one record whose
385
+ * whole content is a claim about a moment.
386
+ *
387
+ * The private key arrives as a value. This module never reads it from a vault,
388
+ * a file, or an environment variable, so there is exactly one place in the
389
+ * codebase that decides where a checkpoint key may come from, and it is the CLI
390
+ * verb a human runs.
391
+ */
392
+ export function appendCheckpoint(logPath, privateKey, actor, options = {}) {
393
+ return signAndAppend(logPath, privateKey, actor, null, options);
394
+ }
395
+ /**
396
+ * Append one `log.checkpoint` signing a head the CALLER names (APRV-257).
397
+ *
398
+ * The tap's entry point, and the reason APRV-220's verify rule asks only that a
399
+ * checkpoint signs a seq BELOW its own rather than its immediate predecessor.
400
+ * A human is shown `(seq, hash)` on a phone; by the time they tap, the daemon
401
+ * has appended three records. The honest thing to sign is the head they SAW,
402
+ * because that is what they looked at, and a runtime that quietly re-read the
403
+ * head and signed something else would be putting a human's key over bytes
404
+ * nobody inspected.
405
+ *
406
+ * The named head is checked against the log before anything is signed: it must
407
+ * be a `(seq, hash)` this chain actually carries ({@link
408
+ * CHECKPOINT_APPEND_REFUSAL_CODES}'s `checkpoint-head-unknown`). So a stale
409
+ * prompt from a chain that has since been rewritten cannot be turned into a
410
+ * signature, and the checkpoint that lands is one `checkLogCheckpoints` will
411
+ * accept rather than one it will refuse forever.
412
+ */
413
+ export function appendCheckpointAt(logPath, privateKey, actor, head, options = {}) {
414
+ return signAndAppend(logPath, privateKey, actor, { seq: head.seq, hash: head.hash }, options);
415
+ }
416
+ /**
417
+ * The one body behind both entry points.
418
+ *
419
+ * `want` is `null` for "whatever the head is when this runs" and a `(seq,
420
+ * hash)` for "the head the human was shown". Everything else — the human-only
421
+ * rule, the verified read, the compare-and-append, the runtime-stamped `ts` —
422
+ * is identical, because the two verbs differ in exactly one decision and
423
+ * writing them twice would be two chances to get the rest of it wrong.
424
+ */
425
+ function signAndAppend(logPath, privateKey, actor, want, options) {
426
+ return withHeadRetry(attemptsOf(options.attempts), () => signAndAppendOnce(logPath, privateKey, actor, want, options));
427
+ }
428
+ function signAndAppendOnce(logPath, privateKey, actor, want, options) {
429
+ if (!HUMAN_ACTOR.test(actor)) {
430
+ return {
431
+ ok: false,
432
+ code: "actor-not-human",
433
+ message: `signing a checkpoint requires a human actor matching ^human:.+, got ${JSON.stringify(actor)}; a checkpoint is the one witness an agent process is not supposed to be able to produce, and the log was left unchanged`,
434
+ };
435
+ }
436
+ const read = readVerifiedRecords(logPath, options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir });
437
+ if (!read.ok) {
438
+ return { ok: false, code: read.code, message: read.message };
439
+ }
440
+ if (read.head === null) {
441
+ return {
442
+ ok: false,
443
+ code: "log-empty",
444
+ message: `${logPath} carries no records, and an empty chain has no head to sign; nothing was appended`,
445
+ };
446
+ }
447
+ // The head the record will name: the current one, or the one the human was
448
+ // shown — and in the second case only after this log is asked whether it
449
+ // carries those bytes at that seq. A signature over a head this chain does
450
+ // not have is a checkpoint that refuses for the life of the log.
451
+ let head;
452
+ if (want === null) {
453
+ head = { seq: read.head.seq, hash: read.head.hash };
454
+ }
455
+ else {
456
+ const carried = read.records.find((record) => record.seq === want.seq);
457
+ if (carried === undefined || carried.hash !== want.hash) {
458
+ return {
459
+ ok: false,
460
+ code: "checkpoint-head-unknown",
461
+ message: `this log does not carry ${want.hash} at seq ${String(want.seq)} (${carried === undefined
462
+ ? `it has no record at that seq; its head is seq ${String(read.head.seq)}`
463
+ : `it carries ${carried.hash} there`}). The head you were shown belongs to a different chain than the one being written to, so nothing was signed and nothing was appended`,
464
+ };
465
+ }
466
+ head = { seq: want.seq, hash: want.hash };
467
+ }
468
+ const signature = signCheckpoint(head, privateKey);
469
+ if (signature === null) {
470
+ return {
471
+ ok: false,
472
+ code: "checkpoint-key-unusable",
473
+ message: "the checkpoint signing key could not be read as a base64 PKCS#8 Ed25519 private key; nothing was appended",
474
+ };
475
+ }
476
+ const fingerprint = privateKeyFingerprint(privateKey);
477
+ if (fingerprint === null) {
478
+ return {
479
+ ok: false,
480
+ code: "checkpoint-key-unusable",
481
+ message: "the checkpoint signing key signed but its public half could not be derived, so the record could not name which key to verify it against; nothing was appended",
482
+ };
483
+ }
484
+ const appended = appendEvent(logPath, {
485
+ ts: tick(options),
486
+ event: CHECKPOINT_EVENT,
487
+ actor,
488
+ channel: options.channel ?? "cli",
489
+ payload: {
490
+ seq: head.seq,
491
+ hash: head.hash,
492
+ alg: CHECKPOINT_ALG,
493
+ key_sha256: fingerprint,
494
+ signature,
495
+ },
496
+ }, {
497
+ ...(options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir }),
498
+ ...options.append,
499
+ expectedHead: read.head,
500
+ });
501
+ if (!appended.ok) {
502
+ return {
503
+ ok: false,
504
+ code: "append-failed",
505
+ message: `the checkpoint was not appended: ${appended.error.code}: ${appended.error.message}`,
506
+ append: appended.error,
507
+ };
508
+ }
509
+ // The head this checkpoint SIGNED, which is the head the human was shown and
510
+ // not the head the append landed on. The two are the same only for
511
+ // `appendCheckpoint`; reporting the log's head here would tell the tap's
512
+ // caller that a different `(seq, hash)` was covered than the signature covers.
513
+ return {
514
+ ok: true,
515
+ record: appended.record,
516
+ head: { seq: head.seq, hash: head.hash },
517
+ fingerprint,
518
+ };
519
+ }
520
+ /** The fingerprint of the public half of a private key, or `null`. */
521
+ export function privateKeyFingerprint(privateKey) {
522
+ try {
523
+ const spki = createPublicKey(createPrivateKey({ key: Buffer.from(privateKey, "base64"), format: "der", type: "pkcs8" })).export({ type: "spki", format: "der" });
524
+ return createHash("sha256").update(spki).digest("hex");
525
+ }
526
+ catch {
527
+ return null;
528
+ }
529
+ }
530
+ /**
531
+ * Demand every checkpoint inside the walked range.
532
+ *
533
+ * Every `log.checkpoint` record in `records` must carry a readable payload,
534
+ * name a configured key, verify under it, and name the hash the log actually
535
+ * carries at the seq it signed. The FIRST failure refuses, carrying the seq of
536
+ * the offending record and the checkpoints that validated ahead of it: a person
537
+ * reading a divergence needs to know how far the log was still good.
538
+ *
539
+ * Three things are deliberately not refusals.
540
+ *
541
+ * - **No configured key** is a skip naming why, and naming how many checkpoint
542
+ * records went unchecked. Nothing was verified, and a check that reported
543
+ * that as a pass would have stopped being a check.
544
+ * - **A signed seq below the walked range** is counted and named, not refused.
545
+ * A full walk starts at genesis so this cannot arise there; a caller walking
546
+ * a suffix gets an honest count of what its range could not speak to.
547
+ * - **A lapsed cadence** is a warning. A human who has been away is not a
548
+ * forger, and a runtime that refused a log for want of a tap is a runtime
549
+ * whose operator turns the check off.
550
+ */
551
+ export function checkLogCheckpoints(options) {
552
+ const found = options.records.filter(isCheckpointRecord);
553
+ const { keys, unreadable } = checkpointKeyIndex(options.publicKeys);
554
+ if (keys.size === 0) {
555
+ return {
556
+ status: "skip",
557
+ checkpoints: 0,
558
+ reason: options.publicKeys.length === 0
559
+ ? `${options.keysUnavailable ?? "no checkpoint public key is configured (audit.checkpoint_keys)"}, so the ${String(found.length)} checkpoint record(s) in this range could not be verified against anything`
560
+ : `none of the ${String(options.publicKeys.length)} configured checkpoint key(s) could be read as an Ed25519 public key (${String(unreadable)} unreadable), so the ${String(found.length)} checkpoint record(s) in this range could not be verified`,
561
+ };
562
+ }
563
+ // Hash by seq, once, and only when there is something to look up. A range
564
+ // with many checkpoints would otherwise scan the whole record list per
565
+ // checkpoint; a log with none would build an index of every record it holds
566
+ // to answer no questions, and this check runs on a daemon tick over a log
567
+ // that grows without bound.
568
+ const hashAt = new Map();
569
+ if (found.length > 0) {
570
+ for (const record of options.records)
571
+ hashAt.set(record.seq, record.hash);
572
+ }
573
+ const firstSeq = options.records[0]?.seq ?? 0;
574
+ const verified = [];
575
+ let unchecked = 0;
576
+ for (const record of found) {
577
+ const payload = readCheckpointPayload(record);
578
+ if (payload === null) {
579
+ return refusedAt(record, "checkpoint-malformed", `the log.checkpoint at seq ${String(record.seq)} does not carry a readable checkpoint payload (seq, hash, alg ${CHECKPOINT_ALG}, key_sha256, signature). The write boundary refuses such a record, so this one was written by something else, and a checkpoint nobody can read is not one that passes`, verified);
580
+ }
581
+ if (payload.seq >= record.seq) {
582
+ return refusedAt(record, "checkpoint-out-of-order", `the log.checkpoint at seq ${String(record.seq)} signs seq ${String(payload.seq)}, which is not below its own. A checkpoint signs the past; one naming itself or the future vouches for a head that did not exist when it was written`, verified);
583
+ }
584
+ const publicKey = keys.get(payload.keySha256);
585
+ if (publicKey === undefined) {
586
+ return refusedAt(record, "checkpoint-key-unknown", `the log.checkpoint at seq ${String(record.seq)} names key ${payload.keySha256}, which none of the ${String(keys.size)} configured checkpoint key(s) hashes to. Either that key was retired out of audit.checkpoint_keys — in which case put it back, since removing a key that signed a checkpoint de-verifies the range it signed — or this record was signed with a key the policy does not vouch for`, verified);
587
+ }
588
+ if (!verifyCheckpointSignature({ seq: payload.seq, hash: payload.hash, alg: payload.alg }, payload.signature, publicKey)) {
589
+ return refusedAt(record, "checkpoint-signature-invalid", `the signature on the log.checkpoint at seq ${String(record.seq)} does not verify under the key it names (${payload.keySha256}) over seq ${String(payload.seq)} ${payload.hash}. The bytes signed are not the bytes presented`, verified);
590
+ }
591
+ if (payload.seq < firstSeq) {
592
+ unchecked += 1;
593
+ continue;
594
+ }
595
+ const actual = hashAt.get(payload.seq);
596
+ if (actual !== payload.hash) {
597
+ return refusedAt(record, "checkpoint-hash-mismatch", `the log.checkpoint at seq ${String(record.seq)} carries a valid signature over seq ${String(payload.seq)} ${payload.hash}, and this log ${actual === undefined
598
+ ? "carries no record at that seq"
599
+ : `carries ${actual} there`}. A human's key signed a head this chain does not have: the chain was rewritten after the checkpoint was taken, and whoever rewrote it recomputed the hashes but could not recompute the signature`, verified);
600
+ }
601
+ verified.push({
602
+ at: record.seq,
603
+ seq: payload.seq,
604
+ hash: payload.hash,
605
+ ts: record.ts,
606
+ actor: record.actor,
607
+ keySha256: payload.keySha256,
608
+ });
609
+ }
610
+ return {
611
+ status: "pass",
612
+ checkpoints: verified,
613
+ unchecked,
614
+ keys: keys.size,
615
+ warning: cadenceWarning(verified, options),
616
+ detail: verified.length === 0
617
+ ? `no checkpoint has been signed in this range; ${String(keys.size)} key(s) are configured and nothing contradicts the chain`
618
+ : `${String(verified.length)} checkpoint(s) validate against ${String(keys.size)} configured key(s), the newest signing seq ${String(verified[verified.length - 1].seq)}`,
619
+ };
620
+ }
621
+ function refusedAt(record, code, message, checkpoints) {
622
+ return { status: "refused", code, at: record.seq, message, checkpoints };
623
+ }
624
+ /**
625
+ * The cadence sentence, or `null`.
626
+ *
627
+ * Report-only in every direction, like `audit.skew_tolerance`: it decides what
628
+ * a human is shown about a log that already verified, and no verdict, exit code
629
+ * or authorization reads it. A cadence nobody set says nothing; a cadence set
630
+ * and never met says so from the newest record in the range, since a log whose
631
+ * first checkpoint is still owed is exactly the case an operator most wants
632
+ * named.
633
+ */
634
+ function cadenceWarning(verified, options) {
635
+ const offer = offerFrom(verified, options);
636
+ return offer === null ? null : offer.warning;
637
+ }
638
+ // ---------------------------------------------------------------------------
639
+ // The cadence, as something to ASK rather than only to report (APRV-257)
640
+ // ---------------------------------------------------------------------------
641
+ /** Milliseconds as a rounded hour count, for every cadence sentence. */
642
+ function hours(ms) {
643
+ return `${(ms / 3_600_000).toFixed(1)}h`;
644
+ }
645
+ /**
646
+ * The due-ness rule, over an already-computed set of verified checkpoints.
647
+ *
648
+ * ONE rule, and that is the point of extracting it: the verify verdict's
649
+ * warning, the daemon's `checkpoint-due`, `approval doctor`'s row and the
650
+ * channel prompt all read this, so there is no arrangement in which the daemon
651
+ * says a checkpoint is due and the listener declines to ask for one, or the
652
+ * other way around.
653
+ *
654
+ * Returns `null` — never due — when the cadence is off, when the log is empty,
655
+ * or when nothing has aged past the interval. Report-only in every direction:
656
+ * the return type carries a sentence and a head, and there is no code path in
657
+ * this runtime from an offer to a refusal.
658
+ */
659
+ function offerFrom(verified, options) {
660
+ const every = options.checkpointEveryMs;
661
+ if (every === undefined || every === null || every <= 0)
662
+ return null;
663
+ const now = options.now ?? Date.now();
664
+ const newest = verified[verified.length - 1];
665
+ const since = newest === undefined ? oldestTs(options.records) : Date.parse(newest.ts);
666
+ if (since === null || Number.isNaN(since))
667
+ return null;
668
+ const age = now - since;
669
+ if (age <= every)
670
+ return null;
671
+ // The head to sign is this log's last record. An empty range has none, and a
672
+ // prompt naming nothing is not a prompt.
673
+ const last = options.records[options.records.length - 1];
674
+ if (last === undefined)
675
+ return null;
676
+ return {
677
+ head: { seq: last.seq, hash: last.hash },
678
+ since: newest?.at ?? null,
679
+ ageMs: age,
680
+ everyMs: every,
681
+ warning: newest === undefined
682
+ ? `audit.checkpoint_every is ${hours(every)} and this log has never been checkpointed (its oldest record is ${hours(age)} old). A missing checkpoint is not tampering and nothing is refused; sign one with \`approval log checkpoint\``
683
+ : `audit.checkpoint_every is ${hours(every)} and the newest checkpoint (seq ${String(newest.at)}, signing seq ${String(newest.seq)}) is ${hours(age)} old. A checkpoint that is due is not a checkpoint that is missing on purpose; nothing is refused`,
684
+ };
685
+ }
686
+ /**
687
+ * Is a checkpoint due, and over which head? `null` when it is not.
688
+ *
689
+ * The whole question in one call, over already-verified records: it runs
690
+ * {@link checkLogCheckpoints} and offers only from a PASS. A refused range is
691
+ * not a range to ask for another signature over — the thing to do with a
692
+ * checkpoint that does not verify is look at it, not sign a new one on top —
693
+ * and a skipped one has no key configured, so there is nothing to sign with and
694
+ * nobody to ask.
695
+ */
696
+ export function checkpointDue(options) {
697
+ const check = checkLogCheckpoints(options);
698
+ if (check.status !== "pass")
699
+ return null;
700
+ return offerFrom(check.checkpoints, options);
701
+ }
702
+ /**
703
+ * The checkpoint half of a policy, read the way `skewToleranceMsOf` reads its
704
+ * key — except that this one fails to a SKIP rather than to a default.
705
+ *
706
+ * A policy that cannot be loaded configures no keys, and a caller with no keys
707
+ * skips with a reason. There is no safe default here: falling back to "no keys"
708
+ * and calling it a pass would report an unreadable policy as a verified log,
709
+ * and falling back to a built-in key would be a key nobody chose.
710
+ */
711
+ export function checkpointPolicyOf(policy, schemaDir) {
712
+ const where = policy.file !== undefined ? { file: policy.file } : { dir: policy.dir ?? process.cwd() };
713
+ if (schemaDir !== undefined)
714
+ where.schemaDir = schemaDir;
715
+ const load = loadPolicy(where);
716
+ if (!load.ok) {
717
+ return {
718
+ publicKeys: [],
719
+ checkpointEveryMs: null,
720
+ unloadable: `the policy could not be loaded (${load.code}: ${load.message}) and configures no checkpoint key`,
721
+ };
722
+ }
723
+ const keys = load.policy.audit?.checkpoint_keys;
724
+ return {
725
+ publicKeys: Array.isArray(keys) ? [...keys] : [],
726
+ checkpointEveryMs: load.durations.checkpointEveryMs,
727
+ unloadable: null,
728
+ };
729
+ }
730
+ /** Milliseconds of the oldest record's `ts`, or `null` when there is none. */
731
+ function oldestTs(records) {
732
+ const first = records[0];
733
+ if (first === undefined)
734
+ return null;
735
+ const parsed = Date.parse(first.ts);
736
+ return Number.isNaN(parsed) ? null : parsed;
737
+ }
738
+ //# sourceMappingURL=checkpoint.js.map