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
package/README.md CHANGED
@@ -1,6 +1,942 @@
1
- # approval-md
1
+ # approval.md
2
2
 
3
- Human approval for agent actions: pre-release placeholder for the
4
- approval.md runtime. The full specification is in SPEC.md.
3
+ [![ci](https://github.com/approval-md/approval.md/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/approval-md/approval.md/actions/workflows/ci.yml)
5
4
 
6
- Spec site: https://approval.md
5
+ **A harness-agnostic, open-source framework for approving agent actions with a
6
+ human in the loop.**
7
+
8
+ Your agent is about to send the email, push to main, spend the money, delete
9
+ the folder, or publish the post. A bad diff can be reverted. A sent message
10
+ cannot, and it carries your name.
11
+
12
+ approval.md puts a button between the agent and that action. You write a
13
+ short policy file saying which kinds of action need you. The agent runs freely
14
+ inside those lines. When it reaches one, a message arrives on your phone with
15
+ exactly what is about to happen, and nothing happens until you tap.
16
+
17
+ Two things people use it for first:
18
+
19
+ - **Signing off an email.** The agent drafts, you read the recipients, subject
20
+ and body on your phone, you tap Approve, and the adapter sends it once with a
21
+ credential the agent never held.
22
+ - **Watching a coding agent.** A hook classifies every command Claude Code or
23
+ Cursor runs. Reads and edits go through; `git push origin main`, `npm
24
+ install`, `curl -d`, `rm -rf` come to your phone first, and every decision is
25
+ in a log you can verify.
26
+
27
+ Spec site: https://approval.md · Specification: [SPEC.md](SPEC.md) · Package:
28
+ `approval-md` on npm.
29
+
30
+ ## Five minutes to a working gate
31
+
32
+ **1. Install.** No source checkout is required for the published CLI.
33
+
34
+ ```sh
35
+ npm install -g approval-md
36
+ ```
37
+
38
+ **2. Make a gate.** For the published 0.1.0 package, run `approval init`, edit
39
+ and read `APPROVAL.md`, run `approval setup identity`, optionally run
40
+ `approval setup channel telegram`, then run `approval policy attest --as human:<id>`.
41
+
42
+ The upcoming `quickstart` command combines these steps. It is currently
43
+ available from a source checkout (`npm ci` and `npm run build`), pending a
44
+ package release. Run the three-question ceremony in the project directory.
45
+ It asks who you are, whether decisions appear in this terminal or on Telegram,
46
+ and which five class families always ask. It shows the exact policy and requires
47
+ the typed word `understood` before attesting it.
48
+
49
+ ```sh
50
+ node /path/to/approval.md/cli.js quickstart
51
+ ```
52
+
53
+ Then run the `activate:` command quickstart prints. It includes the absolute
54
+ project directory, so it resolves this instance even if the next shell starts
55
+ somewhere else.
56
+
57
+ ```
58
+ ready: 5 selected class families ask human:yourname on cli; other classified reversible actions use the autonomous default
59
+ ```
60
+
61
+ The gate is operative. `.approval/env` remains inert until you run the explicit
62
+ `eval` line. Protected controls, failed policy loads, irreversible declarations,
63
+ and commands the classifier cannot read still take their stricter paths.
64
+
65
+ **3. Run the local service.** If you chose Telegram, message **@BotFather**
66
+ with `/newbot` before quickstart so you have the token it asks for. After setup
67
+ and human attestation, start the full runtime from this policy project's directory:
68
+
69
+ ```sh
70
+ cd /path/to/your/project
71
+ eval "$(approval env)" # explicitly load this instance's environment
72
+ approval up # daemon and configured channels, one foreground process
73
+ ```
74
+
75
+ For a source checkout, use `node /path/to/approval.md/cli.js env` inside the
76
+ `eval` line and `node /path/to/approval.md/cli.js up` to start the service.
77
+ Leave the service running. Requests use your configured channel; Telegram
78
+ requests reach your phone. `up` does not load `.approval/env` itself. An already
79
+ exported approval variable wins over the environment map, so start with a clean
80
+ shell or unset another instance's approval variables before evaluating it.
81
+
82
+ Use `approval up` for normal operation. `approval channel telegram listen` runs
83
+ only the Telegram component, for focused use or diagnosis. Never run both
84
+ against the same bot, or run two instances polling that bot: Telegram returns
85
+ HTTP 409. Stop the polling runtime before rerunning `approval setup channel
86
+ telegram`, then reload the environment and start `up` again.
87
+
88
+ By default the daemon scans `backlog/tasks/`. If your envelopes live elsewhere,
89
+ use `approval up --tasks /path/to/existing/task-folder`. The scan reads `.md`
90
+ files directly inside that folder, without descending into subdirectories.
91
+ Creating an empty default folder does not monitor envelopes stored elsewhere.
92
+ A missing default folder warns about envelope drift coverage; TTL sweeping,
93
+ queue rendering and configured channels can still run. See [runtime startup
94
+ checks](docs/cli-reference.md#up) for the draw socket and optional web channel.
95
+
96
+ **4. Pick your first experience.**
97
+
98
+ - *A coding agent*: [gate your coding agent](#gate-your-coding-agent) is two
99
+ more steps, a classification you can try immediately and a hook you paste
100
+ into `.claude/settings.json`.
101
+ - *An email*: [hand a grant to a real credential](#hand-a-grant-to-a-real-credential)
102
+ adds an SMTP or AgentMail credential to the vault, and
103
+ [examples/email-demo.md](examples/email-demo.md) walks the whole send.
104
+
105
+ When something does not work, `approval doctor` prints one line per check with
106
+ a `fix:` line under each failure. It is described under [Running the
107
+ checks](#running-the-checks), and it is not a step you need on the way in.
108
+
109
+ ## What it is made of
110
+
111
+ - **A policy file you wrote.** `APPROVAL.md` is markdown at the root of your
112
+ project with one YAML block declaring which classes of side effect an agent
113
+ may take on its own, which need you, and under what budgets. A human signs
114
+ for its exact bytes; an edit makes it inoperative until someone signs again.
115
+ - **A message on your phone.** A request arrives over Telegram carrying what
116
+ the runtime computed, what the agent claimed, and the exact bytes about to
117
+ leave. You tap Approve or Reject. A local web page and the terminal are the
118
+ other two channels.
119
+ - **A single-use execution token.** Minted at one place in the code, only as a
120
+ human decision is recorded, spent once, stored nowhere. Manual and selected
121
+ live executions require it. An attested class rule may instead explicitly
122
+ authorize an irreversible supervised or autonomous execution.
123
+ - **A log nobody can quietly rewrite.** Every proposal, decision and execution
124
+ is an append-only, hash-chained JSONL record. `approval log verify` answers
125
+ for the chain.
126
+
127
+ Not everything is worth a tap. A class declared `supervised` runs at once, and
128
+ a fraction of those runs is sampled for your retrospective review using a
129
+ secret the agent cannot read, so you see one in a hundred `gh pr merge` calls
130
+ rather than all of them.
131
+
132
+ The design rule is **files are the interface, the log is the truth, the
133
+ database is a cache**. Routing, gating, budget math and chain verification are
134
+ deterministic code. Models propose; the runtime decides.
135
+
136
+ ## How the gate holds
137
+
138
+ - **Credentials live in an encrypted vault**, never in the policy file and never
139
+ in the agent's environment. `APPROVAL.md` carries the *name* of an environment
140
+ variable, and there is no `approval vault get`.
141
+ - **Adapters answer only inside a verified execution.** Manual and selected-live
142
+ executions present and consume a valid token. An irreversible supervised or
143
+ autonomous execution must be explicitly enabled by its attested class rule.
144
+ The adapter opens the credential window only after the runtime authorizes the
145
+ declared action, then closes it as soon as the adapter returns.
146
+ - **Tokens are minted at one site**, in the path that records a human decision,
147
+ and the log holds only their SHA-256. A second spend is refused
148
+ `token-consumed`.
149
+ - **The log makes tampering evident.** Each record chains to the previous one.
150
+ Projections rebuild from the log and never write back.
151
+ - **The harness hook covers the direct-shell path.** `approval hook claude-code`
152
+ classifies the commands a coding agent runs on its own and answers allow or
153
+ deny, fail-closed.
154
+ - **The escape hatch is a recorded ceremony.** When the gate itself is broken, a
155
+ human opens a time-boxed window with `approval gate open`: a terminal, a
156
+ required `--reason`, and the typed word `understood`. Every call it lets
157
+ through is logged as `gate.bypassed`, human-only classes stay refused, and
158
+ `approval status` reports unhealthy until it closes
159
+ ([docs/cli-reference.md#gate](docs/cli-reference.md#gate)).
160
+
161
+ This is an oversight layer for broadly cooperative agents, with hard
162
+ enforcement at the adapter boundaries that hold the credentials (SPEC.md
163
+ section 11). Identity in v0.1 is config-declared, so the trust boundary is the
164
+ machine rather than cryptography. ["Can't the agent just go around
165
+ it?"](#cant-the-agent-just-go-around-it) works through each evasion.
166
+
167
+ ## Gate your coding agent
168
+
169
+ `approval run` gates the commands an agent hands to the runtime. It cannot gate
170
+ the ones the harness runs directly, and those are most of them. Two surfaces
171
+ close that gap: a PreToolUse hook for Claude Code and an MCP server for any
172
+ harness that speaks MCP. Both resolve against the same policy and append to the
173
+ same log as the CLI.
174
+
175
+ Codex support is opt-in while native compatibility and everyday activation are
176
+ still being verified. See the bounded [Codex hook operator
177
+ runbook](docs/codex-hook.md) before installing or trusting it.
178
+
179
+ **1. See how a command classifies.** This touches nothing.
180
+
181
+ ```
182
+ $ approval hook classify -- npm install left-pad
183
+ class rule command
184
+ deps.add npm-install-package npm install left-pad
185
+
186
+ classes: deps.add
187
+ ```
188
+
189
+ Every segment of a command line is classified and the command takes the union,
190
+ so `git status && curl -d …` is gated as `network.call`.
191
+
192
+ **2. Install the hook.** It lives in `.claude/settings.json`, and a human
193
+ commits that file: an agent that could write its own hook entry could write
194
+ itself out of it.
195
+
196
+ ```json
197
+ { "hooks": { "PreToolUse": [ {
198
+ "matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
199
+ "hooks": [ { "type": "command", "timeout": 600,
200
+ "command": "approval hook claude-code --dir <primary checkout> --as agent:claude-code --timeout 9m" } ]
201
+ } ] } }
202
+ ```
203
+
204
+ Register the same command under `PostToolUse` as well, without `--timeout`, so
205
+ the runtime learns how each command ended. `--dir` resolves the policy and the
206
+ log together, so a session inside a linked worktree still writes to the one
207
+ log. Keep `--timeout` (how long the hook waits for you) below `timeout` (Claude
208
+ Code's cap on the process).
209
+
210
+ **3. Watch a verdict.** An `autonomous` class allows and logs nothing. A
211
+ `supervised` class allows and records the action for sampling. A `manual` class
212
+ waits for your tap. Anything the classifier cannot read denies. There is no
213
+ "ask" answer: a decision taken outside the log is a decision nothing can audit.
214
+ The deny reason is `<code>: <detail>`, and the codes are frozen
215
+ (`hook-unclassified`, `hook-opaque`, `hook-rejected`, `hook-timeout` and the
216
+ rest in [docs/claude-code-hook.md](docs/claude-code-hook.md)).
217
+
218
+ **4. Know the sharp edges.** The hook never creates a log: pointed at a path
219
+ with no log it denies `hook-log-unreachable` rather than forking a second
220
+ chain. A wait that runs out keeps its question open for a short grace and then
221
+ withdraws it, so nobody is pinged about a question whose asker has left. A hook
222
+ grant mints no token: the harness runs the command itself.
223
+
224
+ **5. Or connect the MCP server.** `approval mcp serve` is a stdio server
225
+ publishing the agent's verbs as tools.
226
+
227
+ ```sh
228
+ claude mcp add approval -- \
229
+ node /path/to/approval-md/dist/src/cli/main.js mcp serve \
230
+ --as agent:claude-code \
231
+ --dir /path/to/project
232
+ ```
233
+
234
+ `register`, `request`, `wait`, `run`, `queue`, `status` and the rest of the
235
+ agent's surface are there. `grant`, `reject`, `revoke`, `policy attest` and
236
+ `vault set` are not: an MCP client is the agent's harness, and a `grant` tool
237
+ on it would hand the agent the overseer's pen. Grant never travels over MCP,
238
+ and neither does the token it mints. The identity is fixed at startup and
239
+ `--as` is removed from every published schema, so a tool call cannot name an
240
+ actor. Walkthrough: [examples/mcp-demo.md](examples/mcp-demo.md).
241
+
242
+ A harness that can run commands needs neither surface: `request`, `wait`, `run`
243
+ is how sessions in this repository take manual-class actions
244
+ ([docs/dogfood-cutover.md](docs/dogfood-cutover.md)). The task-file side of
245
+ that flow, on a Backlog.md board, is
246
+ [examples/backlog-md-project/README.md](examples/backlog-md-project/README.md).
247
+
248
+ ## Put approvals on your phone
249
+
250
+ **1. Setup writes the environment map, not the secrets.** `approval setup
251
+ channel telegram` puts the bot token in the OS keystore (macOS Keychain, or
252
+ `secret-tool` on Linux) and records in `.approval/env` only where it lives. The
253
+ verbs are interactive by refusal: a pipe or `--json` exits 2 and prints the
254
+ non-interactive commands, because a setup a CI job could drive would let a CI
255
+ job declare a human identity. `approval env` is the only command that reads
256
+ that file, and evaluating it is a step a human takes. Stop any `approval up`
257
+ process or standalone listener polling this bot before setup, because setup
258
+ also polls to discover the chat. After setup, from this project's directory run
259
+ `eval "$(approval env)"` and `approval up`. Full walkthrough:
260
+ [examples/telegram-demo.md](examples/telegram-demo.md).
261
+
262
+ **2. A request binds to exact bytes.** The payload lives in a file, the
263
+ envelope declares its `payload_hash`, and `--payload` supplies the bytes at
264
+ request time:
265
+
266
+ ```sh
267
+ approval payload hash payload.json # the binding the envelope declares
268
+ approval register task-demo.md --as agent:drafter
269
+ approval request task-demo --action task-demo:chaser --payload payload.json --as agent:drafter
270
+ ```
271
+
272
+ ```
273
+ registered task-demo at seq 2: 1 action(s)
274
+ requested task-demo task-demo:chaser at seq 3 (manual)
275
+ ```
276
+
277
+ Material that hashes to something else is refused `payload-mismatch`, and
278
+ nothing is stored or appended. Class, cost and reversibility come from the
279
+ registered envelope rather than from flags, so an agent cannot rename its own
280
+ class between registering and asking. An approval is about specific bytes,
281
+ never about a description of them.
282
+
283
+ **3. The runtime delivers it.** `approval up` prints `notified
284
+ task-demo:chaser (message 501)` and your phone has it. That one foreground
285
+ process is the daemon loop (envelope drift, expiry, queue regeneration,
286
+ retrospective sampling) plus every channel the policy configures. A channel
287
+ whose credential is unset is not started and says so; a channel that falls over
288
+ is restarted with backoff while the loop keeps ticking. `approval setup service`
289
+ writes the launchd or systemd user unit that starts it at login, and prints the
290
+ whole unit for you to read first.
291
+
292
+ The message shows the action key, a **COMPUTED** block the runtime derived
293
+ (class, task, binding, budget verdicts, chain head), a **CLAIMED** block naming
294
+ the agent and marked unverified, the **FULL PAYLOAD**, and two buttons. It says
295
+ how long an answer still has: `waiting: requested 4 min ago · expires 13:07
296
+ UTC`, or, for a request a process is blocked on, `requester waits until 13:07
297
+ UTC`. An email-shaped payload is rendered field by field with real line
298
+ breaks, with the canonical JSON and its hash underneath. Detection is
299
+ structural and never reads a self-declared type. Agent-authored text is
300
+ HTML-escaped, so markup stays inert.
301
+
302
+ An optional gloss, a one-line plain-English reading of the payload by a model,
303
+ can sit above the computed block. It is marked unverified, it never changes a
304
+ verdict, and a failed gloss is omitted while delivery continues:
305
+
306
+ ```sh
307
+ approval up --gloss-provider codex --gloss-model gpt-5.4-mini # or the default, claude/haiku
308
+ ```
309
+
310
+ **4. Tap Approve.** The prompt rewrites itself in place. The buttons go away and
311
+ the text becomes the outcome:
312
+
313
+ ```
314
+ ✓ APPROVED
315
+ task-demo:chaser
316
+
317
+ by human:alice at 10:20 UTC (seq 4)
318
+ ```
319
+
320
+ One edit carries the annotation and the disarming together, so there is no
321
+ moment when the message reads "approved" and still offers a tap. Rejections,
322
+ revocations, expiries and withdrawals settle the same way, and a tap on a stale
323
+ button records nothing.
324
+
325
+ **5. The token stays off the chat.** The grant mints a single-use execution
326
+ token. With `defaults.token_delivery: sealed` the requesting process opens it
327
+ itself and no human ever sees it, which is how this repository releases. With
328
+ the default `manual` delivery it is printed once, in a panel, at the surface
329
+ that recorded the decision:
330
+
331
+ ```
332
+ granted task-demo:chaser at seq 4 by human:alice
333
+ ─────────────────────────────────────────────────────────────
334
+ execution token task-demo:chaser
335
+ 516670320878e97dede99cf84bc48025fc80b7cf14bd9e9782bb1cfd0d92a787
336
+ single-use · stored nowhere · copy it now
337
+ ─────────────────────────────────────────────────────────────
338
+ ```
339
+
340
+ For a tap on your phone that panel appears on the terminal running the
341
+ runtime, and its last line reads `not sent to Telegram`: a chat transcript
342
+ lives on servers you do not control, so a credential does not go there. The
343
+ local **web** channel shows the token once in the response page for the grant
344
+ that minted it, served over loopback, gone on reload, because there the browser
345
+ is already the surface you are looking at. In every case the log holds only the
346
+ token's SHA-256. Lose it, revoke the grant, and request again.
347
+
348
+ **6. Spend it.** `approval run <action> --token "$TOKEN" -- <command>` appends
349
+ `execution.started` before spawning the child and `execution.completed` after,
350
+ and exits with the child's own exit code, so it composes with `make`, CI and
351
+ `&&`. Run it before the approval and it refuses `token-required` at exit 5.
352
+ Run it twice and it refuses:
353
+
354
+ ```
355
+ ✗ token-consumed action task-demo:chaser already executed: execution.started at seq 5 spent this token. A token is single-use and the log is the proof.
356
+ ```
357
+
358
+ A request is not owed an answer forever. `approval withdraw` lets the party
359
+ that opened one take it back while it is pending, and `approval wait
360
+ --withdraw-on-timeout` does it when your own wait elapses.
361
+
362
+ **7. Read the whole story.** Two actors, one clean chain:
363
+
364
+ ```
365
+ 1 2026-08-19T19:03:58.381Z policy.updated human:alice -
366
+ 2 2026-08-19T19:03:58.585Z task.registered agent:drafter task-demo
367
+ 3 2026-08-19T19:03:58.767Z approval.requested agent:drafter task-demo
368
+ 4 2026-08-19T19:04:31.192Z approval.granted human:alice task-demo
369
+ 5 2026-08-19T19:04:41.371Z execution.started agent:drafter task-demo
370
+ 6 2026-08-19T19:04:41.499Z execution.completed agent:drafter task-demo
371
+ ```
372
+
373
+ That is `approval log tail`, tab-separated for `cut` when piped, aligned and
374
+ coloured on a terminal. `approval log verify` answers for the chain: `clean: 6
375
+ record(s), head seq 6 843705c6bbea…`.
376
+
377
+ Downstream services can follow the same channel-independent record with
378
+ `approval log follow --from <seq> --cursor-hash <hash> --json`. The sequence is
379
+ exclusive and the hash binds the resume point to the prefix already consumed.
380
+ Each JSON line is emitted only after a complete chain verification. Delivery
381
+ across reconnects is at least once: apply an idempotent effect, then persist the
382
+ event's `seq` and `hash`. See [the CLI reference](docs/cli-reference.md#log-follow)
383
+ for failure behavior, resource costs, and the weaker sequence-only bootstrap.
384
+
385
+ **8. Review what ran without you.** Supervised actions the sampler picks arrive
386
+ on the same chat as review cards, after the fact: what ran, when, and that the
387
+ runtime allowed it unasked. ✅ records that you looked, 🛑 twice records a
388
+ denial and opens a reconciliation obligation, and 👎 😐 👍 ❤️ leave a graded
389
+ reaction. `approval audit list` and `approval audit review` are the same
390
+ backlog at the terminal.
391
+
392
+ ## The other half of the word
393
+
394
+ Everything above is control. The file carries your voice too. Below the policy
395
+ block, `APPROVAL.md` may hold one optional `yaml approval-values` block: what
396
+ you love, like and dislike in the work, what you want from an agent as
397
+ behaviour, and how you read and answer.
398
+
399
+ ```sh
400
+ approval values # the operator's block, or "the operator has declared no values here."
401
+ approval feedback # the reactions and notes humans left on this log's actions
402
+ ```
403
+
404
+ A retrospective review or a grant can carry a graded reaction (`disliked`,
405
+ `indifferent`, `liked`, `loved`; the two extremes need a note), and `approval
406
+ feedback` reads them back to the agent whose work they were about. Both verbs
407
+ print human-authored guidance behind a banner that says so, and neither reaches
408
+ enforcement: no verdict, sample, budget or token moves because of them (SPEC.md
409
+ section 11.1, invariant 10). They mirror `approval journal write`, the agent's
410
+ outlet the gate does not stand in front of. `approval import agents-md` drafts
411
+ the block from a "What I value" heading in an AGENTS.md.
412
+
413
+ ## Define what needs approval
414
+
415
+ A policy is a fenced `yaml approval-policy` block inside a markdown file named
416
+ `APPROVAL.md`. The prose around the block is for you; the runtime parses the
417
+ block and ignores the rest. The thing you sign for is text you read.
418
+
419
+ **1. Name the classes.** A class is a dotted path from the side-effect taxonomy
420
+ of SPEC.md section 7 (`communicate.email.external`, `financial.spend`,
421
+ `public.post`, `data.delete`, `read.*`). Matching is most-specific-first, `*`
422
+ is a single-segment wildcard, a trailing `.*` matches any depth, and at equal
423
+ specificity the strictest rule wins.
424
+
425
+ **2. Pick an autonomy for each.** Six values, strictest first: `human-only` (a
426
+ person performs the action outside agent execution, and every gate verb refuses
427
+ an agent with `class-human-only`), `manual` (a human decides before execution),
428
+ `supervised-live` (a policy-declared fraction blocks on the gate exactly as
429
+ `manual` does, and the rest proceed, so the rule carries a `live_rate`),
430
+ `supervised-retro` (executes immediately, a sampled fraction escalated for
431
+ retrospective review), `supervised` (an alias of `supervised-retro`), and
432
+ `autonomous` (executes freely). A truthful `reversible: false` declaration
433
+ normally engages section 7's manual floor. An operator who deliberately accepts
434
+ irreversible execution for one nonmanual class can add
435
+ `allow_irreversible: true` to that class rule. Every equally most-specific rule
436
+ must opt in, and the edit has no effect until the policy is re-attested.
437
+
438
+ **3. Set the budgets.** Class `limits` and the `budgets` scopes are conjunctive,
439
+ so an action must pass both, and consumption is computed from the log over
440
+ rolling windows rather than from a mutable counter. An action whose class
441
+ matches no rule takes `defaults.autonomy`, and a policy that does not parse
442
+ resolves every class to `manual`: unattested and unparseable are both strict.
443
+
444
+ **4. Widen the protected paths.** `APPROVAL.md`, the agent instruction files,
445
+ `.approval/`, the harness settings and the release configuration are protected
446
+ by the runtime whatever a policy says. `protected_paths` adds repo-relative
447
+ literals (an exact file, `SPEC.md`, or a directory prefix, `design/`), so a
448
+ project can put its own governing documents behind the same gate. The key can
449
+ only widen, and globs are a schema violation.
450
+
451
+ An entry can also be an object, `{path, class}`, routing that path family to a
452
+ named `policy.edit` sub-class with its own autonomy and live rate. Four names
453
+ are reserved: `policy.edit.spec` (the governing specification),
454
+ `policy.edit.harness` (agent instruction files and harness configuration),
455
+ `policy.edit.ci` (continuous-integration and release configuration),
456
+ `policy.edit.design` (design documents and decision records). Any other
457
+ lowercase word may be minted beside them, and nothing outside `policy.edit` may
458
+ be named: a route to `policy.core` or `log.mutate` is refused. A route aimed at
459
+ a built-in protected path must land at least as strictly as the `policy.edit`
460
+ line itself, or the policy is refused at load with `protected-route-floor`.
461
+
462
+ **5. Attest it.** `approval policy attest` is what makes a policy operative. An
463
+ attestation records that a human saw these exact bytes, as their SHA-256. Edit
464
+ `APPROVAL.md` afterwards and every gated operation refuses `hash-mismatch`
465
+ until you attest again. Attestation is human-only, and identity in v0.1 is
466
+ config-declared, so what one proves is that someone with local control signed
467
+ off.
468
+
469
+ **6. Amend it with the verb, not by hand.** Changing a policy is two facts that
470
+ have to land together, the new bytes and a human's attestation of them, and
471
+ `approval policy amend` owns the ceremony (`--dry-run` reports only,
472
+ `--require-load` refuses to attest a policy that does not load, `--commit`
473
+ lands the two files as one commit and opens the pull request). It prints a
474
+ semantic diff (class resolutions, approver changes, defaults, limits) rather
475
+ than a text diff, then a load advisory saying whether the edited policy parses.
476
+ Attesting one that does not parse is allowed, since attestation records bytes
477
+ rather than correctness, and such a policy fails closed to all-manual.
478
+
479
+ ### Why this verb exists: seq 2
480
+
481
+ Read this repository's own log. At **seq 2** a policy amendment was attested at
482
+ 11:56:07. It was **superseded** seven minutes later, at seq 3, because the edit
483
+ broke a pinned assertion and nobody found out until the test suite ran against
484
+ it. The operator attested bytes whose consequences had never been shown to
485
+ them. (This account originally said eleven minutes. The log says seven, and
486
+ the log won.)
487
+
488
+ That is the failure the load advisory is for. Had `approval policy amend`
489
+ existed that morning, the load failure would have been on screen while the
490
+ human was deciding, and `--require-load` would have refused to attest at all.
491
+
492
+ ## Hand a grant to a real credential
493
+
494
+ `echo sent` is a demo. The point of the gate is the send that cannot be undone,
495
+ so the runtime holds a credential the agent never sees. Four commands carry the
496
+ ceremony; the walkthrough against real Telegram and a real mail provider is
497
+ [examples/email-demo.md](examples/email-demo.md).
498
+
499
+ ```sh
500
+ approval setup vault # mint the passphrase, store it, record where
501
+ approval setup adapter email # the five SMTP settings, into the vault
502
+ eval "$(approval env)" # the variable the policy names, in this shell
503
+ approval adapter email task-042:chaser --token "$TOKEN" \
504
+ --payload message.json --as agent:claude-admin
505
+ ```
506
+
507
+ **1. Two stores.** `.approval/env` says where the values that unlock the
508
+ machine come from, and `approval setup vault` writes the passphrase line under
509
+ whatever name `vault.passphrase_env` declares. The SMTP password is an adapter
510
+ credential, so it goes in the vault, where a gated adapter spends it inside a
511
+ verified execution window.
512
+
513
+ **2. Setup fills the vault and proves it.** `approval setup adapter email` reads
514
+ the credential manifest the adapter declares, then probes the server without
515
+ sending anything.
516
+
517
+ **3. A credential's only journey is into an adapter.** `approval vault set`
518
+ stores one credential in `.approval/vault.enc`, encrypted under a passphrase
519
+ the policy names and never carries. The value comes from stdin or `--value-env
520
+ <VAR>`; there is no `--value` flag, because a secret on a command line is a
521
+ secret in the shell history. There is no `approval vault get`; `approval vault
522
+ list` shows the names.
523
+
524
+ **4. The send happens inside the execution window.** `approval adapter email`
525
+ re-hashes `message.json` against the declaration or grant binding, applies the
526
+ attested policy, appends `execution.started`, opens the vault, reads the SMTP
527
+ settings, sends over STARTTLS, closes the window, and appends
528
+ `execution.completed`. Manual and selected-live paths verify and spend the
529
+ grant token; an explicitly opted-in supervised or autonomous path has no grant
530
+ and mints no token. On that no-token path, the vault passphrase must already be
531
+ in the adapter process environment. The `.approval/env` fallback remains
532
+ token-only. The credential exists for one send and appears in no event, output
533
+ or error message.
534
+
535
+ **5. Check two properties in your own mailbox.** The bytes that left are the
536
+ bytes you approved, since the hash the token spend verified is the hash of the
537
+ payload your phone displayed. And the `Message-ID` is derived from the action
538
+ key, the payload hash and the sender, so the header in a mailbox and the
539
+ binding in the chain identify each other months later.
540
+
541
+ ### The same grant over AgentMail
542
+
543
+ `communicate.email.external` has a second adapter. Where the email adapter
544
+ opens an SMTP session, `approval adapter agentmail` calls the AgentMail API, and
545
+ a mail the agent has already composed as a Draft leaves only when a grant says
546
+ so. Walkthrough: [examples/agentmail-demo.md](examples/agentmail-demo.md).
547
+
548
+ ```sh
549
+ approval setup adapter agentmail # inbox id + sending key, into the vault
550
+ approval payload agentmail-draft "$INBOX" "$DRAFT" > payload.json
551
+ approval adapter agentmail task-042:chaser --token "$TOKEN" \
552
+ --payload payload.json --as agent:claude-admin
553
+ ```
554
+
555
+ **Two keys, and the split is the enforcement.** AgentMail API keys carry
556
+ per-permission booleans, and `draft_create`, `draft_update` and `draft_read` are
557
+ separate from `draft_send` and `message_send`. Give the agent a key holding the
558
+ first three and none of the last two, and put a key holding the send permissions
559
+ in the vault. The agent composes all day and cannot send at all: an ungated
560
+ send is refused by AgentMail itself, `agentmail-unauthorized`, before this
561
+ runtime is involved. `AGENTMAIL_` is withheld from every child `approval run`
562
+ spawns, so a key in the agent's environment cannot ride into a command.
563
+
564
+ **A draft is mutable, so the grant binds its bytes.** `approval payload
565
+ agentmail-draft` snapshots the draft's recipients, subject and text at request
566
+ time, and that snapshot is what the hash binds and what your phone displays.
567
+ Before it sends, the adapter re-fetches the draft and compares; a draft edited
568
+ after the grant refuses `agentmail-draft-drifted`, sends nothing, and names
569
+ which fields differ without quoting text nobody approved. That comparison runs
570
+ before the token is spent, so the refusal costs no authority: restore the
571
+ approved text and the same token still sends.
572
+
573
+ ### First-class zzz.bot messages
574
+
575
+ `approval adapter zzz` creates a thread or replies through zzz.bot's versioned
576
+ HTTP API. Put the invited write credential in the vault, then approve the
577
+ complete tagged payload. The environment, destination, body, metadata, tags and
578
+ references all sit inside the payload hash.
579
+
580
+ This adapter is available from a source checkout containing APRV-320 until the
581
+ next approval.md package release. The published npm `approval-md@0.1.0`
582
+ predates it, and this change does not publish a package.
583
+
584
+ ```sh
585
+ approval setup adapter zzz
586
+ approval adapter zzz task-320:announce --token "$TOKEN" \
587
+ --payload zzz-message.json --as agent:codex
588
+ ```
589
+
590
+ Thread payload:
591
+
592
+ ```json
593
+ {"environment":"production","operation":"create_thread",
594
+ "room_id":"<room-id-from-GET-api-v1-rooms>",
595
+ "title":"Release ready","body":"The verified build is ready for review.",
596
+ "tags":["release"],"references":[]}
597
+ ```
598
+
599
+ A reply uses `"operation":"create_reply"` and `"thread_id"` instead of
600
+ `room_id` and `title`. The adapter chooses only fixed production or preview
601
+ origins, rejects redirects, and derives zzz.bot's idempotency key from the
602
+ approval action key and payload hash.
603
+
604
+ Public writes require an invited credential with write scope. Private writes
605
+ also require active room membership and accepted, unexpired approval.md workflow
606
+ evidence. The setup probe performs one authenticated room-list GET. It proves
607
+ that zzz.bot accepts the credential and does not prove those write or private
608
+ room prerequisites. A local non-guest MCP server exposes the same adapter verb,
609
+ but MCP use is voluntary; custody is enforced only when the write credential is
610
+ kept solely in the approval.md vault.
611
+
612
+ ### Build a third-party adapter
613
+
614
+ Adapter authors can import the supported ESM API from `approval-md/adapters`.
615
+ It exposes the shared execution contract, conformance runner, vault credential
616
+ provider, refusal unions, and TypeScript types without making internal package
617
+ paths public. See the [adapter API guide](docs/adapter-api.md).
618
+
619
+ ## The APPROVAL.md dictionary
620
+
621
+ Every key that can appear in the policy block. The schema is closed at every
622
+ level: an unrecognised key fails validation, which fails the policy closed to
623
+ all-manual, because a key the runtime did not understand is a rule its author
624
+ believed was in force. Full semantics: SPEC.md section 5.
625
+
626
+ | key | what it says |
627
+ | --- | --- |
628
+ | `version` | Policy format version, quoted (`"0.1"`). The only required key (§5.1). |
629
+ | `defaults.autonomy` | Autonomy for an action matching no class rule. Five of the six levels are admitted: `supervised-live` is not, since it needs a `live_rate` that `defaults` has nowhere to hold. `human-only` is, and reserves every unnamed class to human hands. No default of its own, and `manual` is the fail-closed choice (§5.2, APRV-185). |
630
+ | `defaults.channel` | Channel name requests surface on by default; expected to name a key of `channels`, which is a runtime cross-check rather than a schema one. No default (§5.1, §10.3). |
631
+ | `defaults.approval_ttl` | How long a pending request stays actionable. Duration string, `24h`. No default; the scaffolded policy writes one (§5.1). |
632
+ | `defaults.token_delivery` | How a minted token reaches the process that will spend it. `manual` (the default, and what an absent key means): printed once on the granting surface and carried by a human. `sealed`: sealed to a per-request X25519 key so `approval wait` can hand it back, which addresses the token and never authorizes it (§10.4, APRV-105). |
633
+ | `defaults.on_expiry` | What happens when the TTL lapses. `reject` is the only value, and absent means `reject` (§5.1). |
634
+ | `payload_retention` | How long payload bytes are kept after their action is terminal. Absent means nothing is ever pruned (§5.2). |
635
+ | `protected_paths` | Repo-relative files and directory prefixes whose edit is classified `policy.edit`. A bare string is the whole entry. Additive only, no globs, and absent means the built-in protected set alone (§5.2, APRV-107). |
636
+ | `protected_paths[].path` | The path half of the object form: the same grammar as the bare string, an exact file (`SPEC.md`) or a directory prefix (`design/`) (§5.2, APRV-266). |
637
+ | `protected_paths[].class` | The class half: one lowercase segment under `policy.edit`. Four reserved names, `policy.edit.spec`, `policy.edit.harness`, `policy.edit.ci` and `policy.edit.design`, plus any word an author mints beside them. Nothing outside `policy.edit` may be named, and a route below the `policy.edit` line is refused `protected-route-floor`. No default: an entry that wants a sub-class states it (§5.2, APRV-266). |
638
+ | `approvers.<name>.channels` | The channels one approver can decide on. At least one: an approver reachable nowhere can never grant. No default (§5.1). |
639
+ | `classes.<pattern>.autonomy` | Required on every class rule, so it has no default. Six levels, strictest first: `human-only`, `manual`, `supervised-live`, `supervised-retro`, `autonomous`, and `supervised`, which is the pre-split spelling and an alias of `supervised-retro` (§5.2, APRV-127, APRV-185). |
640
+ | `classes.<pattern>.live_rate` | The fraction of a `supervised-live` class that blocks on the gate, in (0, 1]. Required there and refused everywhere else, so it has no default: a live mode with no fraction declares a control without saying how much of it runs. Selection is HMAC-SHA-256 over the payload hash under the operator's secret (§5.2, APRV-127). |
641
+ | `classes.<pattern>.retro_rate` | This class's retrospective sampling rate, in (0, 1], overriding `audit.supervised_sample_rate` for it alone. Optional on `supervised`, `supervised-retro` and `supervised-live`, refused on the rest. Absent means the global rate (§5.2, APRV-183). |
642
+ | `classes.<pattern>.allow_irreversible` | Explicit operator permission for a truthful `reversible: false` action to retain this rule's `autonomous` or supervised behavior. Optional boolean; absent or `false` preserves the manual floor. `true` is refused on `manual` and `human-only`, cannot appear in `defaults`, and takes effect only when every equally most-specific matching rule says `true` (§5.2, §7, APRV-317). |
643
+ | `classes.<pattern>.approvers` | Approver ids permitted to decide this class. Absent restricts nobody, since the list is a narrowing and a narrowing nobody wrote narrows nothing; a named list refuses everyone else with `actor-not-approver` (§5.1). |
644
+ | `classes.<pattern>.limits` | Per-class ceilings, every value a positive number: `per_action_usd`, `daily_usd`, and the request-volume counts `max_pending` and `requests_per_hour`. Absent means this class carries no ceiling of its own (§5.1, §5.2). |
645
+ | `budgets.global.daily_usd` | Repo-wide spend ceiling per rolling day, computed from the log. Absent means no spend ceiling (§5.1). |
646
+ | `budgets.global.daily_actions` | Repo-wide count of side-effecting actions per rolling day. Absent means no count ceiling (§5.1). |
647
+ | `budgets.global.max_pending` | Simultaneously pending requests across the scope; excess is refused `queue-full`. Absent means no ceiling (§5.2). |
648
+ | `budgets.<scope>` | Any other named scope, same three keys. Budgets are conjunctive with class limits (§5.2). |
649
+ | `audit.supervised_sample_rate` | The FALLBACK fraction of supervised actions escalated for retrospective review, in [0, 1], for classes declaring no `retro_rate`. Absent means no fallback rate is configured (§5.2, APRV-183). |
650
+ | `audit.sampling_secret_env` | Name of the variable holding the operator's HMAC sampling secret. Unnamed means sampling is off and says so (§5.2, §11). |
651
+ | `audit.skew_tolerance` | How far a gate-typed event's timestamp may step back before verification reports an anomaly. Report-only; default 2 seconds (§8). |
652
+ | `audit.checkpoint_keys` | Public halves of the Ed25519 keys permitted to sign a `log.checkpoint`, base64 DER SPKI. The private halves live in the vault and never in this file. A list, so a retired key stays listed: a checkpoint signed by a key the list does not carry is refused. Absent, empty or unreadable means verification skips the checkpoint check with a reason and never reports it as a pass (§9, APRV-220). |
653
+ | `audit.checkpoint_every` | How long the log may go without a human-signed checkpoint before verification says one is due, and before the listener puts one `CHECKPOINT DUE` prompt on the approver's channel (`approval setup checkpoint` mints the key). Report-only at every layer: a due checkpoint is a warning and never a refusal. Absent means the cadence is off and nothing is ever reported as due (§9, APRV-220, APRV-257). |
654
+ | `daemon.read_proof` | Which prefix proof a long-lived reader runs before reusing a cached prefix: `full` (the default, re-hash the whole prefix on every read) or `incremental` (hash only the appended bytes, re-proving in full on a cadence). One-shot processes, the Claude Code hook and `approval log verify` prove in full regardless (§5.2, APRV-217). |
655
+ | `daemon.full_reproof_every` | Reads one full re-proof may cover under `incremental`, the anchoring read included. Default 50 (§5.2). |
656
+ | `daemon.full_reproof_after` | Wall clock one full re-proof may cover under `incremental`. Duration string, default `60s` (§5.2). |
657
+ | `vault.passphrase_env` | Name of the variable holding the vault passphrase. Absent means `APPROVAL_VAULT_PASSPHRASE` (§5.2, §10.4). |
658
+ | `channels.telegram.token_env` | Name of the variable holding the bot token. Default `APPROVAL_TG_TOKEN` (§5.1). |
659
+ | `channels.telegram.chat_id_env` | Name of the variable holding the approver chat id. Default `APPROVAL_TG_CHAT` (§5.1). |
660
+ | `channels.telegram.delivery` | `paced` (the default) shows one summary line and the oldest pending request, then the next one after a decision, `/skip` or `/next`; `burst` sends every pending request the listener has not sent yet. Neither mode changes what is pending: that is re-derived from the verified log on every cycle (§10.3, APRV-216). |
661
+ | `channels.web.port` | TCP port for the local approval UI, bound on loopback only. No default in the schema; the scaffolded policy names `4680`, and 0 is excluded because the policy must name a port a human can navigate to (§5.1). |
662
+ | `channels.<name>.prompt.rows` | Order only, for `telegram`, `web` and `cli`: the rows named here render in this order ahead of the rest, which keep their default relative order behind them. Never a whitelist, so a field added later cannot be lost to a list written before it existed. Absent means the layout the channel ships (§5.2, §10.3, APRV-218). |
663
+ | `channels.<name>.prompt.always` | Rows this channel renders only when abnormal, or not at all, render on every prompt instead. The anomaly mark stays a statement about the value, so a forced-on row shouts only when the value is in fact the reason to look. Absent means the channel's own visibility rules (§5.2, §10.3, APRV-218). |
664
+ | `channels.<name>.prompt.hide` | Rows this channel never renders. Refused for the rows required for a decision (`action_key`, `class`, `command_breakdown`, `protected_path`, `policy_diff`, `policy_load`) with `prompt-row-required`, and refused for a row `always` also names. Absent means nothing is hidden, and the canonical payload block is out of reach either way (§5.2, §10.3, APRV-218). |
665
+ | `channels.<other>` | An unknown channel name is accepted as an object, so a third-party transport does not fail the whole policy closed (§10.3). A `prompt` block written under such a name is still validated: a layout is checked wherever it appears. |
666
+
667
+ Every key ending in `_env` carries a variable's *name* and never its value:
668
+ agents may read `APPROVAL.md`, so a secret it carried would be a secret they
669
+ hold. Where those values live is recorded in `.approval/env`, which a single
670
+ verb reads, `approval env`, whose output is an export block a human evaluates.
671
+
672
+ ## How this compares
673
+
674
+ Three kinds of thing already exist in this space, and each solves a different
675
+ part of the problem. A hosted daemon and reviewer layer is operated by
676
+ Bountify.ai; it is optional, and nothing in the format depends on it
677
+ ([GOVERNANCE.md](GOVERNANCE.md)).
678
+
679
+ **Harness-native permission prompts** (Claude Code permission rules and hooks,
680
+ Cursor auto-run, Codex CLI approval modes) enforce inside the one harness they
681
+ ship with. That enforcement is real: a Claude Code PreToolUse deny holds even
682
+ under its bypass mode, and Codex backs its gate with an OS-level sandbox, which
683
+ this project does not attempt. What they lack is a durable record and
684
+ portability. None writes an append-only log of what was asked, who decided and
685
+ what ran; the decision reaches a human only as a terminal prompt; and the
686
+ mechanism does not travel to another harness. approval.md's Claude Code hook is
687
+ built on that PreToolUse mechanism and adds the two missing pieces: the
688
+ decision comes from an attested policy file, and it lands in a verifiable log.
689
+
690
+ **AGENTS.md permissions prose** states the policy in English and trusts the
691
+ agent to obey. Nothing parses it, nothing blocks a call against it, and no
692
+ record exists when it is violated. approval.md is the enforcement layer that
693
+ convention is missing, and treats it as an input: the permissions section of
694
+ this repository's own CLAUDE.md is the first import fixture.
695
+
696
+ **Framework interrupts** (LangGraph `interrupt()`, CrewAI human input, AutoGen
697
+ `UserProxyAgent`, the OpenAI Agents SDK's `needsApproval`, Temporal signal
698
+ approvals) give a developer a pause-and-resume primitive and leave policy,
699
+ audit format, the human channel and the credential boundary to them. They also
700
+ require adopting the framework. Temporal's event history is a real append-only
701
+ execution record with crash recovery this project does not claim, though it
702
+ lives in Temporal's storage rather than as policy-attested files in your repo.
703
+
704
+ **Hosted approval platforms** (HumanLayer, gotoHuman, Permit.io's access
705
+ requests) are the closest relatives: multi-channel human routing, review UIs,
706
+ and in Permit.io's case a real authorization engine richer than autonomy
707
+ classes. Their model is a third-party service in the decision path, with the
708
+ audit trail in the platform's backend, and the agent's own process still
709
+ choosing to honor the returned verdict. They bring hosted infrastructure,
710
+ escalation and team routing, and compliance certifications.
711
+
712
+ The difference is the combination: policy as a hash-attested markdown file in
713
+ your repo; an append-only, hash-chained log you verify locally with one
714
+ command; and an execution boundary where the credential is inert until a
715
+ single-use token is minted at the moment a human decides. Every framework
716
+ primitive and hosted API above relies on the agent's process honoring a
717
+ returned decision. Here the thing the agent needs, the credential, answers only
718
+ to the thing it cannot make, the token. The tradeoffs: you run the daemon and
719
+ listener yourself, there is no OS-level sandbox, no compliance certification,
720
+ and the reference phone channel is one app, Telegram.
721
+
722
+ ## Can't the agent just go around it?
723
+
724
+ **Edit the policy?** An attestation records the SHA-256 of the policy's bytes,
725
+ and every gated operation refuses `hash-mismatch` when the live file disagrees
726
+ with it. An unattested policy refuses too, and attesting is human-only. Under
727
+ the harness hook the edit itself is classified `policy.edit` before it happens,
728
+ because `APPROVAL.md` is in the built-in protected set no policy can narrow.
729
+
730
+ **Fabricate or rewrite the log?** Each record chains to the previous one's
731
+ hash, so an edited or reordered record breaks the chain and `approval log
732
+ verify` says so. Appends go through compare-and-append against the head, and
733
+ projections (`QUEUE.md`, the SQLite index) rebuild from the log and never write
734
+ back to it.
735
+
736
+ **Mint its own token, or reuse one?** Tokens are minted at one site, inside the
737
+ path that records a human decision, and the log stores only the hash. No verb
738
+ and no tool returns a token for a grant it did not just record, and a hook
739
+ grant mints none at all. The second spend is refused `token-consumed`, naming
740
+ the seq of the `execution.started` that spent it.
741
+
742
+ **Call the adapter or the credential directly?** Credentials live in
743
+ `.approval/vault.enc` under a passphrase the policy names and never carries,
744
+ and an adapter opens the vault only inside an execution window admitted by the
745
+ attested policy. There is no `approval vault get`. Manual and selected-live
746
+ paths need a grant token. An explicitly opted-in supervised or autonomous path
747
+ needs the passphrase already present in the adapter process environment; it
748
+ cannot use the token-scoped `.approval/env` fallback. This is where the hard
749
+ enforcement lives.
750
+
751
+ **Run the side effect from its own shell?** That is what the PreToolUse hook is
752
+ for: it classifies the command text, resolves the class, and answers allow or
753
+ deny before the harness runs anything, fail-closed and with no "ask". The
754
+ classifier reads shell text without being a shell, so parameter expansion,
755
+ aliases and wrapper binaries are gaps, and each resolves toward denial or the
756
+ stricter class. `bash -c`, `eval`, `sudo`, `xargs` and `node -e` deny as
757
+ `hook-opaque`.
758
+
759
+ **POST straight at the web queue?** It can. `approval channel web` binds
760
+ `127.0.0.1` with no `--host` flag, because with no authentication the loopback
761
+ interface is the entire access control, and every decision the page collects is
762
+ recorded against the human the runtime was started with: it proves "someone
763
+ with access to this machine approved". There is no CSRF token in v0.1,
764
+ deliberately: a CSRF token defends a session, and with no session anything that
765
+ can open a socket to `127.0.0.1:4680` can POST with or without one. The
766
+ same-origin check that rejects a POST naming a non-loopback `Origin` allows one
767
+ carrying neither header, since `curl` sends neither: treat it as a speed bump
768
+ rather than a control. A session, or a wider bind address, would need a real
769
+ anti-CSRF token.
770
+
771
+ **Lie in its self-report?** Self-reported fields never reduce scrutiny (SPEC.md
772
+ section 11.1, invariant 4). They may raise it, inform display, or feed
773
+ retrospective analysis, and they can never downgrade a resolved autonomy level
774
+ or supply `allow_irreversible`. Only an explicit class rule in the attested
775
+ operator policy can retain nonmanual autonomy for a truthful `reversible:
776
+ false` declaration. The prompt keeps computed facts apart from claimed fields.
777
+
778
+ **So what is left?** A fully adversarial agent on a machine it controls.
779
+ Identity in v0.1 is config-declared, so anyone who can set that variable and
780
+ write to the log is inside the trust boundary; cryptographic identity is future
781
+ work ([docs/proposals/hardened-authorization.md](docs/proposals/hardened-authorization.md)).
782
+ What holds regardless of what the harness believes it is running: the vault,
783
+ the adapter boundary, and the single-use token. Section 7 keeps irreversible
784
+ classes at `manual` unless the attested policy explicitly opts a class into the
785
+ exception.
786
+
787
+ ## Running the checks
788
+
789
+ ```
790
+ npm run ci:local # run the CI tier this diff would get, before pushing
791
+ npm run check:changed # classify the working tree, then run that tier
792
+ npm run check:tier -- <path> # classify the given paths and print the tier
793
+ approval doctor # the other check: this machine, not the code
794
+ ```
795
+
796
+ `approval doctor` prints **28 rows** and a tally, in the order their failures
797
+ cascade: build freshness, identity, attestation, the log chain, the channels
798
+ (`telegram`, `web-port`), the payload store, audit sampling, envelope
799
+ integrity, the vault, the environment source map, then the rows that ask git
800
+ and the harness what happened (`log-drift`, `reconciliation`,
801
+ `harness-hook-outcomes`, `harness-hook-wiring`, `keychain-scope`,
802
+ `log-advance-cadence`, `dark-sessions`, `verified-snapshot`, `read-proof`,
803
+ `main-behind-origin`, `harness-version-unverified`, `live-draw`,
804
+ `values-block`, `checkpoint`, `gate-organs`, `sealed-keys`,
805
+ `codex-hook-wiring`). Each failure
806
+ carries a `fix:` line you run yourself. Doctor appends nothing, sends nothing
807
+ and repairs nothing, and no credential value appears in its output. Three
808
+ of the 28 lines from a fresh directory, plus the tally:
809
+
810
+ ```
811
+ ✓ identity APPROVAL_HUMAN=human:alice (config-declared: the trust boundary is this machine, not cryptography)
812
+ ✓ log /your/project/.approval/log/events.jsonl verifies: 1 record(s), head seq 1 0f3c4a19187a…
813
+ ✗ audit-sampling disabled (secret-env-unnamed): APPROVAL.md sets audit.supervised_sample_rate to 0.1 but names no audit.sampling_secret_env. …
814
+ fix: approval policy attest --as human:<id> — after setting audit.supervised_sample_rate and audit.sampling_secret_env in the policy; then export the named variable where the daemon runs
815
+ 9 ok · 18 not applicable · 1 failed
816
+ ```
817
+
818
+ That one failure is expected on the scaffolded policy: it samples supervised
819
+ actions for audit, sampling needs an operator-held secret the policy only
820
+ names, and a control that looks on while the party under oversight could steer
821
+ it is worse than one that is visibly off. Name the secret when you want
822
+ sampling, or delete the `audit` block if one person's gate has no use for it.
823
+
824
+ **18 of the 28 report `not applicable` in a fresh directory**, and each names
825
+ the absence it skipped on: `telegram` (no bot variables), `envelope-integrity`
826
+ (no task folder), `vault` (no vault file), `environment` (no `.approval/env`),
827
+ `read-proof` (no `daemon` block), `live-draw` (no `supervised-live` class),
828
+ `checkpoint` (no `audit.checkpoint_keys`), `harness-hook-outcomes`,
829
+ `harness-hook-wiring`, `codex-hook-wiring`, `harness-version-unverified` and
830
+ `gate-organs` (no harness settings file), `verified-snapshot` (no daemon has
831
+ run), and
832
+ `log-drift`, `log-advance-cadence`, `dark-sessions`, `main-behind-origin` and
833
+ `sealed-keys` (not a git checkout). `sealed-keys` asks git what it tracks:
834
+ `.approval/payloads/` is tracked on purpose, and a sealed-delivery private key
835
+ swept in by a `git add` of that directory would open that action's token for
836
+ everyone holding the log. `gate-organs` is informational wherever it lands: it
837
+ lists the harness files whose current bytes carry no `approval policy attest
838
+ --organ` record, and never moves the exit code.
839
+
840
+ Checks come in three tiers.
841
+
842
+ | Tier | Chosen when every changed path is | What runs |
843
+ | --- | --- | --- |
844
+ | light | `README.md`, `docs/**/*.md`, `examples/**/*.md` | the documentation guard (`tests/docs-guard.test.ts`) |
845
+ | records | `backlog/**`, `MILESTONES.md` | the tests that read records (`milestones-guard`, `backlog-fixtures`, `docs-guard`), on Node 20 |
846
+ | full | anything else, or a mix of the above | the whole suite in three shards plus `npm run lint`, on Node 22; the Node 20 floor runs the same three shards on the merge queue and on pushes to `main` |
847
+
848
+ A denylist forces the full tier regardless of file extension: `APPROVAL.md`,
849
+ `CLAUDE.md`, `.claude/**`, `SPEC.md`, `schema/**`, `**/fixtures/**`,
850
+ `backlog/**`, `scripts/**`, `.github/**`, the packaging files, and `cli.js`.
851
+ `backlog/**` sits on both that denylist and the records list, so a task file
852
+ mixed with any other path takes the full tier. Classification is computed from
853
+ the changed paths by `scripts/classify-tier.mjs`, never asserted by the author
854
+ of the change, and every merge to `main` runs the full suite.
855
+
856
+ ### Before the push: `npm run ci:local`
857
+
858
+ The merge queue is serial, so every red run there costs a slot and another
859
+ wait. `npm run ci:local` asks the same classifier the workflow asks and runs
860
+ the jobs `.github/workflows/ci.yml` declares for that tier: the docs guard for
861
+ light, the record-reading tests for records, the three shards plus lint for
862
+ full, and the protected-path grant cross-check on every tier when a merge base
863
+ is computable. `--base <ref>` picks the base, `--working-tree` and explicit
864
+ paths are the other path sources, `--dry-run` prints the plan, `--json` prints
865
+ it as data, and `--parallel` runs the tier's jobs concurrently. What it cannot
866
+ reproduce it says: the Node 20 legs need Node 20, and CI's runner is
867
+ `ubuntu-latest`. A green run locally is a prediction; the workflow is the
868
+ verdict.
869
+
870
+ `npm run check:changed` answers a different question: it classifies the
871
+ working tree and runs the tier in its own shape, which for full is `npm test`,
872
+ `npm run lint` and `npm run typecheck`. Use it while working, and `ci:local`
873
+ before pushing. `scripts/run-tests.mjs --shard <k>/<n>` takes shard `k` of the
874
+ sorted file list, so the shards of a matrix partition the suite.
875
+
876
+ ## Exit codes
877
+
878
+ An agent branches on the exit code before it reads stdout, so these numbers
879
+ are frozen. Adding one is a spec change; changing a meaning is breaking.
880
+
881
+ | Code | Meaning |
882
+ | --- | --- |
883
+ | 0 | success |
884
+ | 1 | integrity failure (corrupt log) |
885
+ | 2 | usage error |
886
+ | 3 | torn tail |
887
+ | 4 | I/O error |
888
+ | 5 | no valid execution token (approval run only) |
889
+ | 6 | timeout (approval wait only) |
890
+
891
+ Code 1 and code 4 are kept apart deliberately: "I could not read the file" and
892
+ "the file has been tampered with" are different facts, and conflating them
893
+ either cries wolf over a permission bit or lets tampering read as a filesystem
894
+ hiccup. Code 3, a torn tail, is the signature of a crashed write, and nothing
895
+ is repaired automatically: truncating a torn line is a human decision. A gate
896
+ refusal is exit 1 and never 2, since the command was well-formed and the answer
897
+ is no; branch on `error.code` under `--json`.
898
+
899
+ ## Where to look next
900
+
901
+ [SPEC.md](SPEC.md) is the source of truth for every design decision, and this
902
+ README defers to it wherever the two could be read differently.
903
+ [CLAUDE.md](CLAUDE.md) describes how this repository builds itself behind its
904
+ own gate; the 0.1.0 release was published, tagged and pushed through three
905
+ grants from a phone.
906
+
907
+ Every command carries its own instructions. `approval --help` lists them
908
+ grouped by purpose, `approval <command> --help` gives one command's flags,
909
+ refusal codes and JSON shape, and `--help --long` appends that verb's
910
+ reasoning from [docs/cli-reference.md](docs/cli-reference.md). `approval
911
+ instructions` is the agent-facing guide, and `--schemas` prints the verb
912
+ registry as JSON.
913
+
914
+ Every external adapter, harness, updater or gateway this project has weighed
915
+ has an entry in
916
+ [docs/integrations-considered.md](docs/integrations-considered.md).
917
+ [examples/grok-bot-connector/runbook.md](examples/grok-bot-connector/runbook.md)
918
+ puts a Grok Bot agent on the far end of `approval mcp serve --http --guest`
919
+ and rehearses both halves of the story: the agent asking for a branch push and
920
+ an email and a human deciding on a phone, then the agent skipping the gate and
921
+ finding the credential inert.
922
+
923
+ Designs proposed and not yet built live under `docs/proposals/`.
924
+ [docs/proposals/solo-dev-quickstart.md](docs/proposals/solo-dev-quickstart.md)
925
+ and [docs/proposals/no-daemon-mode.md](docs/proposals/no-daemon-mode.md) are
926
+ the next step for the path at the top of this page: a three-question
927
+ `approval quickstart`, one `approval guard -- <command>` verb that replaces
928
+ the register, request, wait, run quartet, and a runtime that lives inside the
929
+ waiting command instead of a daemon.
930
+ [docs/proposals/hardened-authorization.md](docs/proposals/hardened-authorization.md)
931
+ works through what a grant in this log can and cannot prove to a service that
932
+ does not trust the operator, and what a stronger identity tier would have to
933
+ be.
934
+
935
+ ## License and governance
936
+
937
+ Code: Apache 2.0, see [LICENSE](LICENSE) and [NOTICE](NOTICE). Specification
938
+ and schemas: CC0 1.0, so any language can implement the format without asking.
939
+ Who holds the specification and the name, the relationship to Bountify.ai's
940
+ hosted offering, and the plan for neutral governance:
941
+ [GOVERNANCE.md](GOVERNANCE.md). How to contribute, including the DCO sign-off:
942
+ [CONTRIBUTING.md](CONTRIBUTING.md).