approval-md 0.1.0 → 0.3.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 (416) hide show
  1. package/README.md +629 -559
  2. package/SPEC.md +99 -24
  3. package/dist/src/adapters/agentmail.d.ts +426 -0
  4. package/dist/src/adapters/agentmail.js +2 -2
  5. package/dist/src/adapters/conformance.d.ts +149 -0
  6. package/dist/src/adapters/contract.d.ts +628 -0
  7. package/dist/src/adapters/contract.js +110 -16
  8. package/dist/src/adapters/contract.js.map +1 -1
  9. package/dist/src/adapters/email.d.ts +324 -0
  10. package/dist/src/adapters/env-passphrase.d.ts +93 -0
  11. package/dist/src/adapters/public.d.ts +11 -0
  12. package/dist/src/adapters/public.js +11 -0
  13. package/dist/src/adapters/public.js.map +1 -0
  14. package/dist/src/adapters/registry.d.ts +59 -0
  15. package/dist/src/adapters/registry.js +2 -1
  16. package/dist/src/adapters/registry.js.map +1 -1
  17. package/dist/src/adapters/smtp.d.ts +213 -0
  18. package/dist/src/adapters/vault-provider.d.ts +114 -0
  19. package/dist/src/adapters/vault-provider.js +3 -3
  20. package/dist/src/adapters/zzz.d.ts +66 -0
  21. package/dist/src/adapters/zzz.js +299 -0
  22. package/dist/src/adapters/zzz.js.map +1 -0
  23. package/dist/src/channels/batch.d.ts +109 -0
  24. package/dist/src/channels/cli.d.ts +193 -0
  25. package/dist/src/channels/conformance.d.ts +92 -0
  26. package/dist/src/channels/contract.d.ts +656 -0
  27. package/dist/src/channels/contract.js +200 -7
  28. package/dist/src/channels/contract.js.map +1 -1
  29. package/dist/src/channels/payload-view.d.ts +35 -0
  30. package/dist/src/channels/render-queue.d.ts +149 -0
  31. package/dist/src/channels/tagging.d.ts +196 -0
  32. package/dist/src/channels/telegram.d.ts +1944 -0
  33. package/dist/src/channels/telegram.js +218 -23
  34. package/dist/src/channels/telegram.js.map +1 -1
  35. package/dist/src/channels/web.d.ts +350 -0
  36. package/dist/src/channels/web.js +17 -0
  37. package/dist/src/channels/web.js.map +1 -1
  38. package/dist/src/cli/adapter.d.ts +90 -0
  39. package/dist/src/cli/adapter.js +25 -15
  40. package/dist/src/cli/adapter.js.map +1 -1
  41. package/dist/src/cli/amend.d.ts +59 -0
  42. package/dist/src/cli/amend.js +214 -30
  43. package/dist/src/cli/amend.js.map +1 -1
  44. package/dist/src/cli/args.d.ts +43 -0
  45. package/dist/src/cli/attest.d.ts +50 -0
  46. package/dist/src/cli/attest.js +134 -7
  47. package/dist/src/cli/attest.js.map +1 -1
  48. package/dist/src/cli/audit-card.d.ts +62 -0
  49. package/dist/src/cli/audit.d.ts +59 -0
  50. package/dist/src/cli/channel-telegram.d.ts +879 -0
  51. package/dist/src/cli/channel-telegram.js +311 -13
  52. package/dist/src/cli/channel-telegram.js.map +1 -1
  53. package/dist/src/cli/channel-web.d.ts +131 -0
  54. package/dist/src/cli/channel.d.ts +80 -0
  55. package/dist/src/cli/channel.js +9 -0
  56. package/dist/src/cli/channel.js.map +1 -1
  57. package/dist/src/cli/checkpoint-tap.d.ts +169 -0
  58. package/dist/src/cli/codex-bridge.d.ts +819 -0
  59. package/dist/src/cli/codex-bridge.js +1607 -0
  60. package/dist/src/cli/codex-bridge.js.map +1 -0
  61. package/dist/src/cli/codex.d.ts +2 -0
  62. package/dist/src/cli/codex.js +469 -0
  63. package/dist/src/cli/codex.js.map +1 -0
  64. package/dist/src/cli/coverage.d.ts +61 -0
  65. package/dist/src/cli/daemon.d.ts +120 -0
  66. package/dist/src/cli/daemon.js +4 -1
  67. package/dist/src/cli/daemon.js.map +1 -1
  68. package/dist/src/cli/doctor.d.ts +129 -0
  69. package/dist/src/cli/doctor.js +586 -17
  70. package/dist/src/cli/doctor.js.map +1 -1
  71. package/dist/src/cli/env.d.ts +65 -0
  72. package/dist/src/cli/execute.d.ts +202 -0
  73. package/dist/src/cli/execute.js +25 -2
  74. package/dist/src/cli/execute.js.map +1 -1
  75. package/dist/src/cli/exit-codes.d.ts +73 -0
  76. package/dist/src/cli/feedback.d.ts +60 -0
  77. package/dist/src/cli/gate-window.d.ts +40 -0
  78. package/dist/src/cli/gate.d.ts +68 -0
  79. package/dist/src/cli/git-scope.d.ts +190 -0
  80. package/dist/src/cli/gloss-attach.d.ts +85 -0
  81. package/dist/src/cli/gloss-codex-child.d.ts +9 -0
  82. package/dist/src/cli/gloss-codex.d.ts +24 -0
  83. package/dist/src/cli/gloss-options.d.ts +42 -0
  84. package/dist/src/cli/gloss.d.ts +265 -0
  85. package/dist/src/cli/help.d.ts +107 -0
  86. package/dist/src/cli/help.js +320 -93
  87. package/dist/src/cli/help.js.map +1 -1
  88. package/dist/src/cli/hook-codex.d.ts +126 -0
  89. package/dist/src/cli/hook-codex.js +226 -0
  90. package/dist/src/cli/hook-codex.js.map +1 -0
  91. package/dist/src/cli/hook.d.ts +787 -0
  92. package/dist/src/cli/hook.js +1235 -181
  93. package/dist/src/cli/hook.js.map +1 -1
  94. package/dist/src/cli/import.d.ts +35 -0
  95. package/dist/src/cli/import.js +1 -1
  96. package/dist/src/cli/import.js.map +1 -1
  97. package/dist/src/cli/init.d.ts +84 -0
  98. package/dist/src/cli/init.js +2 -2
  99. package/dist/src/cli/init.js.map +1 -1
  100. package/dist/src/cli/instructions.d.ts +23 -0
  101. package/dist/src/cli/journal.d.ts +41 -0
  102. package/dist/src/cli/log-advance.d.ts +287 -0
  103. package/dist/src/cli/log-advance.js +102 -11
  104. package/dist/src/cli/log-advance.js.map +1 -1
  105. package/dist/src/cli/log-anchor.d.ts +176 -0
  106. package/dist/src/cli/log-checkpoint.d.ts +22 -0
  107. package/dist/src/cli/log-sync.d.ts +243 -0
  108. package/dist/src/cli/log-verbs.d.ts +16 -0
  109. package/dist/src/cli/log-verbs.js +7 -1
  110. package/dist/src/cli/log-verbs.js.map +1 -1
  111. package/dist/src/cli/long-help.d.ts +70 -0
  112. package/dist/src/cli/main.d.ts +77 -0
  113. package/dist/src/cli/main.js +159 -7
  114. package/dist/src/cli/main.js.map +1 -1
  115. package/dist/src/cli/mcp.d.ts +52 -0
  116. package/dist/src/cli/paths.d.ts +56 -0
  117. package/dist/src/cli/payload.d.ts +58 -0
  118. package/dist/src/cli/policy-apply.d.ts +195 -0
  119. package/dist/src/cli/policy-apply.js +573 -0
  120. package/dist/src/cli/policy-apply.js.map +1 -0
  121. package/dist/src/cli/policy.d.ts +43 -0
  122. package/dist/src/cli/policy.js +14 -1
  123. package/dist/src/cli/policy.js.map +1 -1
  124. package/dist/src/cli/preflight.d.ts +501 -0
  125. package/dist/src/cli/preflight.js +689 -45
  126. package/dist/src/cli/preflight.js.map +1 -1
  127. package/dist/src/cli/progress.d.ts +78 -0
  128. package/dist/src/cli/prompt.d.ts +209 -0
  129. package/dist/src/cli/quickstart.d.ts +46 -0
  130. package/dist/src/cli/quickstart.js +297 -0
  131. package/dist/src/cli/quickstart.js.map +1 -0
  132. package/dist/src/cli/records.d.ts +34 -0
  133. package/dist/src/cli/render.d.ts +22 -0
  134. package/dist/src/cli/sandbox.d.ts +51 -0
  135. package/dist/src/cli/sandbox.js +17 -1
  136. package/dist/src/cli/sandbox.js.map +1 -1
  137. package/dist/src/cli/scaffold.d.ts +79 -0
  138. package/dist/src/cli/scaffold.js +1 -1
  139. package/dist/src/cli/setup-adapter.d.ts +137 -0
  140. package/dist/src/cli/setup-adapter.js +38 -4
  141. package/dist/src/cli/setup-adapter.js.map +1 -1
  142. package/dist/src/cli/setup-channel.d.ts +126 -0
  143. package/dist/src/cli/setup-channel.js +28 -1
  144. package/dist/src/cli/setup-channel.js.map +1 -1
  145. package/dist/src/cli/setup-checkpoint.d.ts +57 -0
  146. package/dist/src/cli/setup-common.d.ts +277 -0
  147. package/dist/src/cli/setup-common.js +3 -2
  148. package/dist/src/cli/setup-common.js.map +1 -1
  149. package/dist/src/cli/setup-flow.d.ts +287 -0
  150. package/dist/src/cli/setup-service.d.ts +96 -0
  151. package/dist/src/cli/setup.d.ts +204 -0
  152. package/dist/src/cli/setup.js +94 -2
  153. package/dist/src/cli/setup.js.map +1 -1
  154. package/dist/src/cli/style.d.ts +320 -0
  155. package/dist/src/cli/token.d.ts +39 -0
  156. package/dist/src/cli/up.d.ts +155 -0
  157. package/dist/src/cli/up.js +119 -53
  158. package/dist/src/cli/up.js.map +1 -1
  159. package/dist/src/cli/usage.d.ts +37 -0
  160. package/dist/src/cli/values.d.ts +40 -0
  161. package/dist/src/cli/values.js +3 -4
  162. package/dist/src/cli/values.js.map +1 -1
  163. package/dist/src/cli/vault.d.ts +59 -0
  164. package/dist/src/cli/vault.js +2 -2
  165. package/dist/src/cli/vault.js.map +1 -1
  166. package/dist/src/cli/verb-registry.d.ts +76 -0
  167. package/dist/src/cli/verb-registry.js +344 -11
  168. package/dist/src/cli/verb-registry.js.map +1 -1
  169. package/dist/src/cli/wordmark.d.ts +31 -0
  170. package/dist/src/cli/wordmark.js +2 -2
  171. package/dist/src/codex/broker.d.ts +229 -0
  172. package/dist/src/codex/broker.js +548 -0
  173. package/dist/src/codex/broker.js.map +1 -0
  174. package/dist/src/codex/doctor.d.ts +13 -0
  175. package/dist/src/codex/doctor.js +41 -0
  176. package/dist/src/codex/doctor.js.map +1 -0
  177. package/dist/src/codex/manifest.d.ts +49 -0
  178. package/dist/src/codex/manifest.js +103 -0
  179. package/dist/src/codex/manifest.js.map +1 -0
  180. package/dist/src/codex/runner.d.ts +178 -0
  181. package/dist/src/codex/runner.js +231 -0
  182. package/dist/src/codex/runner.js.map +1 -0
  183. package/dist/src/codex/serve.d.ts +56 -0
  184. package/dist/src/codex/serve.js +98 -0
  185. package/dist/src/codex/serve.js.map +1 -0
  186. package/dist/src/codex/templates.d.ts +41 -0
  187. package/dist/src/codex/templates.js +319 -0
  188. package/dist/src/codex/templates.js.map +1 -0
  189. package/dist/src/codex/trust.d.ts +19 -0
  190. package/dist/src/codex/trust.js +183 -0
  191. package/dist/src/codex/trust.js.map +1 -0
  192. package/dist/src/codex/workspace-commit.d.ts +219 -0
  193. package/dist/src/codex/workspace-commit.js +549 -0
  194. package/dist/src/codex/workspace-commit.js.map +1 -0
  195. package/dist/src/codex/workspace-plan.d.ts +131 -0
  196. package/dist/src/codex/workspace-plan.js +561 -0
  197. package/dist/src/codex/workspace-plan.js.map +1 -0
  198. package/dist/src/core/actor.d.ts +2 -0
  199. package/dist/src/core/actor.js +5 -0
  200. package/dist/src/core/actor.js.map +1 -0
  201. package/dist/src/core/advance-cycle.d.ts +221 -0
  202. package/dist/src/core/advance-cycle.js +66 -2
  203. package/dist/src/core/advance-cycle.js.map +1 -1
  204. package/dist/src/core/agents-md.d.ts +278 -0
  205. package/dist/src/core/agents-md.js +33 -31
  206. package/dist/src/core/agents-md.js.map +1 -1
  207. package/dist/src/core/apply-patch.d.ts +49 -0
  208. package/dist/src/core/apply-patch.js +266 -0
  209. package/dist/src/core/apply-patch.js.map +1 -0
  210. package/dist/src/core/attest.d.ts +635 -0
  211. package/dist/src/core/attest.js +326 -4
  212. package/dist/src/core/attest.js.map +1 -1
  213. package/dist/src/core/audit.d.ts +510 -0
  214. package/dist/src/core/audit.js +13 -0
  215. package/dist/src/core/audit.js.map +1 -1
  216. package/dist/src/core/budgets.d.ts +238 -0
  217. package/dist/src/core/channel-owner.d.ts +213 -0
  218. package/dist/src/core/channel-owner.js +358 -0
  219. package/dist/src/core/channel-owner.js.map +1 -0
  220. package/dist/src/core/checkpoint.d.ts +500 -0
  221. package/dist/src/core/child-env.d.ts +88 -0
  222. package/dist/src/core/clock.d.ts +52 -0
  223. package/dist/src/core/command-class.d.ts +697 -0
  224. package/dist/src/core/command-class.js +713 -25
  225. package/dist/src/core/command-class.js.map +1 -1
  226. package/dist/src/core/commit-guard.d.ts +272 -0
  227. package/dist/src/core/commit-guard.js +424 -0
  228. package/dist/src/core/commit-guard.js.map +1 -0
  229. package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
  230. package/dist/src/core/coverage-sources/gh.d.ts +48 -0
  231. package/dist/src/core/coverage-sources/git.d.ts +101 -0
  232. package/dist/src/core/coverage.d.ts +217 -0
  233. package/dist/src/core/credential-spec.d.ts +72 -0
  234. package/dist/src/core/daemon-actor.d.ts +45 -0
  235. package/dist/src/core/daemon-actor.js +54 -0
  236. package/dist/src/core/daemon-actor.js.map +1 -0
  237. package/dist/src/core/dark-session.d.ts +432 -0
  238. package/dist/src/core/dark-session.js +266 -82
  239. package/dist/src/core/dark-session.js.map +1 -1
  240. package/dist/src/core/decision-refusal.d.ts +206 -0
  241. package/dist/src/core/decision-refusal.js +24 -2
  242. package/dist/src/core/decision-refusal.js.map +1 -1
  243. package/dist/src/core/env-file.d.ts +455 -0
  244. package/dist/src/core/env-file.js +60 -1
  245. package/dist/src/core/env-file.js.map +1 -1
  246. package/dist/src/core/execute.d.ts +871 -0
  247. package/dist/src/core/execute.js +59 -8
  248. package/dist/src/core/execute.js.map +1 -1
  249. package/dist/src/core/frontmatter.d.ts +78 -0
  250. package/dist/src/core/gate-window.d.ts +312 -0
  251. package/dist/src/core/gate.d.ts +1449 -0
  252. package/dist/src/core/gate.js +149 -14
  253. package/dist/src/core/gate.js.map +1 -1
  254. package/dist/src/core/gesture-refusal.d.ts +166 -0
  255. package/dist/src/core/gesture-refusal.js +188 -0
  256. package/dist/src/core/gesture-refusal.js.map +1 -0
  257. package/dist/src/core/git-run.d.ts +73 -0
  258. package/dist/src/core/harness-version.d.ts +157 -0
  259. package/dist/src/core/harness-version.js +4 -1
  260. package/dist/src/core/harness-version.js.map +1 -1
  261. package/dist/src/core/harness-wait.d.ts +55 -0
  262. package/dist/src/core/head-retry.d.ts +107 -0
  263. package/dist/src/core/instance.d.ts +310 -0
  264. package/dist/src/core/instance.js +113 -0
  265. package/dist/src/core/instance.js.map +1 -1
  266. package/dist/src/core/intake-limits.d.ts +247 -0
  267. package/dist/src/core/jcs.d.ts +52 -0
  268. package/dist/src/core/journal.d.ts +144 -0
  269. package/dist/src/core/live-draw.d.ts +436 -0
  270. package/dist/src/core/log-reconcile.d.ts +89 -0
  271. package/dist/src/core/log-subscribe.d.ts +36 -0
  272. package/dist/src/core/log-subscribe.js +162 -0
  273. package/dist/src/core/log-subscribe.js.map +1 -0
  274. package/dist/src/core/log.d.ts +316 -0
  275. package/dist/src/core/log.js.map +1 -1
  276. package/dist/src/core/loop.d.ts +274 -0
  277. package/dist/src/core/loop.js +11 -0
  278. package/dist/src/core/loop.js.map +1 -1
  279. package/dist/src/core/md-fence.d.ts +41 -0
  280. package/dist/src/core/money.d.ts +147 -0
  281. package/dist/src/core/payload-census.d.ts +74 -0
  282. package/dist/src/core/payload-store.d.ts +175 -0
  283. package/dist/src/core/payload.d.ts +71 -0
  284. package/dist/src/core/policy-diff.d.ts +292 -0
  285. package/dist/src/core/policy-diff.js +27 -4
  286. package/dist/src/core/policy-diff.js.map +1 -1
  287. package/dist/src/core/policy-expectations.d.ts +199 -0
  288. package/dist/src/core/policy-explain.d.ts +160 -0
  289. package/dist/src/core/policy-explain.js +63 -3
  290. package/dist/src/core/policy-explain.js.map +1 -1
  291. package/dist/src/core/policy-load.d.ts +567 -0
  292. package/dist/src/core/policy-load.js +36 -6
  293. package/dist/src/core/policy-load.js.map +1 -1
  294. package/dist/src/core/policy-match.d.ts +324 -0
  295. package/dist/src/core/policy-match.js +72 -9
  296. package/dist/src/core/policy-match.js.map +1 -1
  297. package/dist/src/core/policy-proposal.d.ts +317 -0
  298. package/dist/src/core/policy-proposal.js +102 -2
  299. package/dist/src/core/policy-proposal.js.map +1 -1
  300. package/dist/src/core/prompt-layout.d.ts +221 -0
  301. package/dist/src/core/protected-path-guard.d.ts +566 -0
  302. package/dist/src/core/protected-path-guard.js +848 -55
  303. package/dist/src/core/protected-path-guard.js.map +1 -1
  304. package/dist/src/core/question-preempted.d.ts +141 -0
  305. package/dist/src/core/question-preempted.js +152 -0
  306. package/dist/src/core/question-preempted.js.map +1 -0
  307. package/dist/src/core/read-scope.d.ts +172 -0
  308. package/dist/src/core/read-scope.js +252 -0
  309. package/dist/src/core/read-scope.js.map +1 -0
  310. package/dist/src/core/registration.d.ts +25 -0
  311. package/dist/src/core/reindex.d.ts +99 -0
  312. package/dist/src/core/sampler.d.ts +313 -0
  313. package/dist/src/core/sandbox.d.ts +371 -0
  314. package/dist/src/core/sandbox.js +190 -1
  315. package/dist/src/core/sandbox.js.map +1 -1
  316. package/dist/src/core/seal.d.ts +165 -0
  317. package/dist/src/core/sender-identity.d.ts +476 -0
  318. package/dist/src/core/sender-identity.js +572 -0
  319. package/dist/src/core/sender-identity.js.map +1 -0
  320. package/dist/src/core/shlex.d.ts +102 -0
  321. package/dist/src/core/shlex.js +159 -0
  322. package/dist/src/core/shlex.js.map +1 -0
  323. package/dist/src/core/state.d.ts +505 -0
  324. package/dist/src/core/task-file.d.ts +185 -0
  325. package/dist/src/core/telegram-config.d.ts +93 -0
  326. package/dist/src/core/token.d.ts +409 -0
  327. package/dist/src/core/token.js +21 -38
  328. package/dist/src/core/token.js.map +1 -1
  329. package/dist/src/core/validate.d.ts +138 -0
  330. package/dist/src/core/values.d.ts +147 -0
  331. package/dist/src/core/values.js +36 -1
  332. package/dist/src/core/values.js.map +1 -1
  333. package/dist/src/core/vault.d.ts +291 -0
  334. package/dist/src/core/verified-snapshot.d.ts +204 -0
  335. package/dist/src/core/verify.d.ts +336 -0
  336. package/dist/src/core/version.d.ts +8 -0
  337. package/dist/src/core/wysiwys.d.ts +370 -0
  338. package/dist/src/daemon/advance-child.d.ts +39 -0
  339. package/dist/src/daemon/advance.d.ts +476 -0
  340. package/dist/src/daemon/advance.js +25 -4
  341. package/dist/src/daemon/advance.js.map +1 -1
  342. package/dist/src/daemon/audit.d.ts +87 -0
  343. package/dist/src/daemon/daemon.d.ts +1180 -0
  344. package/dist/src/daemon/daemon.js +9 -0
  345. package/dist/src/daemon/daemon.js.map +1 -1
  346. package/dist/src/daemon/dark-session.d.ts +64 -0
  347. package/dist/src/daemon/draw-child.d.ts +36 -0
  348. package/dist/src/daemon/draw.d.ts +154 -0
  349. package/dist/src/daemon/git-evidence.d.ts +173 -0
  350. package/dist/src/daemon/git-evidence.js +1 -1
  351. package/dist/src/daemon/projection.d.ts +180 -0
  352. package/dist/src/daemon/prune.d.ts +207 -0
  353. package/dist/src/mcp/http.d.ts +113 -0
  354. package/dist/src/mcp/server.d.ts +265 -0
  355. package/dist/src/mcp/server.js +17 -1
  356. package/dist/src/mcp/server.js.map +1 -1
  357. package/docs/adapter-api.md +106 -0
  358. package/docs/cli-reference.md +1316 -63
  359. package/docs/codex-enforced-session.md +103 -0
  360. package/docs/codex-workspace-broker.md +118 -0
  361. package/package.json +14 -2
  362. package/schema/codex-instance.schema.json +82 -0
  363. package/schema/event.schema.json +539 -9
  364. package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
  365. package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
  366. package/schema/fixtures/event/invalid/approval-granted-sender-hashed-false.json +20 -0
  367. package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
  368. package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
  369. package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
  370. package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
  371. package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
  372. package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
  373. package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
  374. package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
  375. package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
  376. package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
  377. package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
  378. package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
  379. package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
  380. package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
  381. package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
  382. package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
  383. package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
  384. package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
  385. package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
  386. package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
  387. package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
  388. package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
  389. package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
  390. package/schema/fixtures/policy/valid/canonical.json +1 -1
  391. package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
  392. package/schema/fixtures/policy-md/valid/canonical.md +1 -1
  393. package/schema/fixtures/policy-md/valid/with-values.md +5 -7
  394. package/schema/fixtures/values/invalid/class-shaped.json +1 -1
  395. package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
  396. package/schema/fixtures/values/invalid/non-string-item.json +1 -1
  397. package/schema/fixtures/values/invalid/over-cap.json +1 -1
  398. package/schema/fixtures/values/invalid/unknown-key.json +1 -1
  399. package/schema/fixtures/values/invalid/version-float.json +1 -0
  400. package/schema/fixtures/values/invalid/version-integer.json +1 -0
  401. package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
  402. package/schema/fixtures/values/valid/empty-lists.json +2 -3
  403. package/schema/fixtures/values/valid/full.json +5 -7
  404. package/schema/fixtures/values/valid/minimal.json +1 -1
  405. package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
  406. package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
  407. package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
  408. package/schema/fixtures/values-md/invalid/version-1.md +69 -0
  409. package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
  410. package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
  411. package/schema/fixtures/values-md/valid/absent.md +1 -1
  412. package/schema/fixtures/values-md/valid/with-values.md +5 -7
  413. package/schema/policy.schema.json +75 -3
  414. package/schema/values.schema.json +7 -11
  415. package/templates/codex/README.md +9 -0
  416. package/schema/fixtures/values/invalid/version-string.json +0 -1
@@ -0,0 +1,787 @@
1
+ /**
2
+ * `approval hook` — harness adapters that put the gate in front of the commands
3
+ * an agent's harness runs directly (APRV-82 Claude Code, APRV-133 Cursor).
4
+ *
5
+ * The problem it closes. Until this verb, the runtime gated what went through
6
+ * `approval run`. Everything the harness executed on its own — `git push`, `gh
7
+ * pr create`, `npm install`, `curl` — bypassed APPROVAL.md entirely, so the
8
+ * enforcement of those classes was the prose in CLAUDE.md and an agent's
9
+ * willingness to read it. That is exactly the AGENTS.md failure SPEC.md §2
10
+ * critiques, reproduced inside the repository that critiques it.
11
+ *
12
+ * As everywhere else in this CLI, **no logic lives here.** Classification is
13
+ * `core/command-class.ts` (pure, fixture-tested); registration, policy
14
+ * resolution and intake are `core/gate.ts`; the decision is derived from the
15
+ * verified log by `core/state.ts`. This file reads one JSON object from stdin,
16
+ * calls those, and prints one JSON object back.
17
+ *
18
+ * Four choices are load-bearing enough to state plainly.
19
+ *
20
+ * **It exits 0 with a verdict, or 2 with nothing.** Claude Code reads a hook's
21
+ * stdout as a decision only on exit 0; a hook that exits 2 is a *block* with the
22
+ * stderr text as the reason, and any other non-zero code is a non-blocking
23
+ * error. So every classified or decided outcome — allow and deny alike — is an
24
+ * exit 0 with `hookSpecificOutput` on stdout, and the only exit 2 is a
25
+ * misconfigured hook (an unknown flag, a bad identity), where blocking is the
26
+ * correct failure mode. No new exit code is added to the frozen table.
27
+ *
28
+ * **Never `ask`.** The permission decision vocabulary includes `ask`, which
29
+ * hands the question to the harness's own prompt. Using it would answer an
30
+ * approval question outside the log: no request, no record, no audit trail, and
31
+ * a human deciding in a UI the policy never named. The hook allows or denies,
32
+ * and every deny carries a machine-readable code.
33
+ *
34
+ * **Fail closed on every axis.** An unreadable policy, an unreachable log, a
35
+ * command the classifier cannot read, a wait that times out: all deny. A hook
36
+ * that fell back to allow when it could not reach the gate would be worst
37
+ * precisely when it mattered. Since APRV-139 that includes an unattested
38
+ * policy: a verdict nobody is asked about is checked against the verified log
39
+ * first, exactly as `core/execute.ts` checks one (see `unattendedGuard`).
40
+ *
41
+ * **The harness executes, not the runtime.** The hook decides *before* the tool
42
+ * runs and never spawns anything, so it never writes an `execution.completed`
43
+ * or `execution.failed`: the runtime does not run the command and never learns
44
+ * how it went. It does write one `execution.started`, and only where a verdict
45
+ * of `allow` rests on a human's grant — that record is the *consumption* of the
46
+ * grant (APRV-117), which a harness request needs because it mints no token
47
+ * that could be spent instead. `core/gate.ts`'s `consumeHarnessGrant` is where
48
+ * that lives and why. What the log records is otherwise the approval lifecycle:
49
+ * `task.registered`, `approval.requested`, and the human's decision.
50
+ *
51
+ * **A decision outlives the invocation that asked for it (APRV-117).** Requests
52
+ * are matched by the payload hash of `{command, cwd}`, so the answer to "may I
53
+ * run these bytes, here" belongs to the bytes rather than to one tool-use id.
54
+ * A retry while the question is pending adopts it instead of asking twice; a
55
+ * retry after a grant lands proceeds on it, once, inside the TTL. That is why
56
+ * the wait no longer ends in an immediate withdrawal: a late tap authorizes
57
+ * something. It ends in one once the RETRY GRACE has run out (APRV-287): past
58
+ * that window nothing is coming back to adopt the question, and a request left
59
+ * standing is one more dead message a restarted listener re-delivers.
60
+ *
61
+ * **An allow follows its record, and says which window it sits in (APRV-200).**
62
+ * The harness executes and never sees this process's return value, so what
63
+ * authorizes the tool call is the record and not the verdict. Every allow that
64
+ * rests on a grant therefore spends it, RE-READS the verified log to establish
65
+ * that the `execution.started` is in the chain, and only then prints — a
66
+ * `hook-grant-unverified` deny where it cannot. The record itself carries
67
+ * `grant_origin`: `direct` where the tool call that spent the grant is the tool
68
+ * call that asked for it, `carried` where a later one spent it under the
69
+ * carryover above. Only `direct` states an ordering this runtime observed;
70
+ * `carried` is the window in which a grant can be a ratification of a write the
71
+ * harness already applied, and naming it is what makes that visible to an
72
+ * auditor holding the log alone. See `docs/claude-code-hook.md`.
73
+ */
74
+ import { type ClassifiedSegment, type CommandClassification, type ProtectedPathEntry } from "../core/command-class.js";
75
+ import { type GateOptions } from "../core/gate.js";
76
+ import { type HarnessKind } from "../core/harness-version.js";
77
+ import { type HarnessLoopState } from "../core/loop.js";
78
+ import type { EventRecord } from "../core/log.js";
79
+ import type { Streams } from "./main.js";
80
+ import { type Style } from "./style.js";
81
+ /**
82
+ * How much of the command line goes in the (claimed) summary field.
83
+ *
84
+ * A HEADLINE, and only that (APRV-124). What the approver is bound to is the
85
+ * payload, which carries the whole command (or the whole change) and is never
86
+ * shortened; this is the one-line label above it. Exported because the tests
87
+ * pin the distinction.
88
+ */
89
+ export declare const SUMMARY_LIMIT = 160;
90
+ /**
91
+ * The closed set of hook denial codes, frozen in the sense
92
+ * `GATE_REFUSAL_CODES` is: the reason string a human reads and an agent
93
+ * branches on starts with one of these.
94
+ *
95
+ * `hook-gate-refused` is a family: the emitted code is
96
+ * `hook-gate-refused:<gate refusal code>`, so the gate's own frozen vocabulary
97
+ * reaches the caller unflattened.
98
+ */
99
+ export declare const HOOK_DENY_CODES: readonly [
100
+ /** No rule covers some segment of the command line. */
101
+ "hook-unclassified",
102
+ /**
103
+ * Some class of the command resolves to `human-only` (APRV-185, amended
104
+ * SPEC.md §5.2): the policy reserves it to human hands, so the command is
105
+ * denied outright and no gate lifecycle is opened for it.
106
+ *
107
+ * This union's spelling of the gate's `class-human-only`, which the detail
108
+ * names in full. It wears the `hook-` prefix every other member wears rather
109
+ * than borrowing the gate's bare code, because a caller branching on this
110
+ * vocabulary branches on one shape; `hook-gate-refused:<c>` is the form
111
+ * reserved for a code the gate itself produced, and the gate is not asked
112
+ * here.
113
+ *
114
+ * Distinct from `hook-unclassified`, and the repairs are opposites. That one
115
+ * says the policy has nothing to say about this command, so the fix is to
116
+ * declare a class for it. This one says the policy has spoken as clearly as
117
+ * it can, and the fix is for a person to run the command themselves. Distinct
118
+ * from `hook-rejected` for the reason the gate's code is distinct from a
119
+ * rejection: nobody decided anything, so there is nothing to ask again.
120
+ */
121
+ "hook-class-human-only",
122
+ /**
123
+ * A `harness.launch.*` class that no rule of this policy names (APRV-354).
124
+ *
125
+ * SPEC.md §7 says the family is never inferred autonomous; this is the
126
+ * stronger reading the family needs, which is that it is never inferred at
127
+ * all. A launch resolves only under a rule an operator wrote, and a policy
128
+ * that names neither `harness.launch.*` nor the specific member refuses.
129
+ *
130
+ * It exists because of the window the softer reading opens. Before the family
131
+ * existed, `codex …` and `muse …` were `hook-unclassified`: refused outright.
132
+ * Letting the new class fall to `defaults.autonomy` would have made every
133
+ * harness launch grantable by one approval in every project whose defaults
134
+ * are manual, the moment they upgraded — a capability arriving by upgrade
135
+ * rather than by decision. What that approval would cover is a whole second
136
+ * agent whose own actions this gate never sees.
137
+ *
138
+ * Distinct from `hook-unclassified`, which says the CLASSIFIER has nothing to
139
+ * say about the command; here the classifier was clear and the POLICY is
140
+ * silent. Distinct from `hook-class-human-only`, which is a policy that has
141
+ * spoken and reserved the class: the repair there is for a person to run the
142
+ * command, and the repair here is to write a line.
143
+ */
144
+ "hook-harness-launch-unruled",
145
+ /** A construct whose effect cannot be read off the text (`bash -c`, `eval`). */
146
+ "hook-opaque",
147
+ /** The command line could not be tokenized at all. */
148
+ "hook-unparseable",
149
+ /** A human rejected the request. */
150
+ "hook-rejected",
151
+ /** A previously granted request was withdrawn. */
152
+ "hook-revoked",
153
+ /** The request's TTL lapsed before a decision. */
154
+ "hook-expired",
155
+ /**
156
+ * The request was withdrawn before a decision landed (APRV-106). Since
157
+ * APRV-117 the timeout no longer produces this: what does is a session that
158
+ * ended mid-wait (signal or failure) and an operator's `approval withdraw`.
159
+ * Terminal, and not a refusal by anyone.
160
+ */
161
+ "hook-withdrawn",
162
+ /**
163
+ * The wait elapsed with the request still undecided. The request stays open
164
+ * for the RETRY GRACE (APRV-117, bounded by APRV-287): a decision inside that
165
+ * window authorizes a retry of the identical command in the identical
166
+ * directory, once. Past the grace the hook withdraws it (reason `timeout`),
167
+ * because a question nothing will adopt is a message on a phone that decides
168
+ * nothing.
169
+ */
170
+ "hook-timeout",
171
+ /** The gate refused intake; the gate's own code follows a colon. */
172
+ "hook-gate-refused",
173
+ /**
174
+ * The grant was spent and the VERIFIED log does not show it (APRV-200).
175
+ *
176
+ * Distinct from `hook-gate-refused:append-failed`, which says the write was
177
+ * refused and nothing landed. This one says the write reported success and the
178
+ * chain cannot be seen to carry it, which is a different fact with a different
179
+ * repair: nothing here is retried, the log is checked (`approval log verify`).
180
+ *
181
+ * On this surface the record IS the authorization — the harness executes and
182
+ * never sees the gate's return value — so a verdict is not printed until the
183
+ * verified chain carries the execution the harness is about to perform. The
184
+ * grant is spent by the time this fires, which is the fail-closed direction:
185
+ * one more prompt on the retry, and nothing authorized meanwhile.
186
+ */
187
+ "hook-grant-unverified",
188
+ /**
189
+ * `APPROVAL_HOOK_REQUIRE_SANDBOX=1` is set and this command runs code the
190
+ * runtime did not author, unwrapped (APRV-193).
191
+ *
192
+ * The one deny in this union that names a spelling that would work rather
193
+ * than a decision or a fault: re-run it as `approval sandbox -- <cmd>` and it
194
+ * proceeds, classified exactly as it is now, with no way out to the network.
195
+ *
196
+ * It exists because the hook DECIDES and the harness EXECUTES. A verdict
197
+ * cannot rewrite a command into a wrapper, so the only way for this runtime
198
+ * to insist on the room is to refuse the spelling that does not ask for it.
199
+ * Off by default, and turning it on can only ever refuse more — which is why
200
+ * an environment variable is an acceptable home for it, and why nothing in
201
+ * the other direction is readable from one.
202
+ */
203
+ "hook-sandbox-required",
204
+ /** The policy could not be loaded, so no class can be resolved. */
205
+ "hook-policy-unavailable",
206
+ /**
207
+ * No log exists where the hook was pointed. The hook is a WRITER to an
208
+ * existing log, never an initializer: creating one where it happens to stand
209
+ * (an agent worktree, say) forks a chain off the real log's tail, and git
210
+ * merges do not reconcile hash chains (APRV-101).
211
+ */
212
+ "hook-log-unreachable",
213
+ /**
214
+ * The harness does not tell this hook where the call will run, so no verdict
215
+ * over the visible bytes can bind the action (APRV-311, native evidence in
216
+ * APRV-310 v6/v7).
217
+ *
218
+ * Native Codex 0.152.1 honours a per-call Bash working directory that appears
219
+ * in no field of the event: `tool_input` carries `command` alone, and the
220
+ * event cwd and the hook process cwd both stay at the session root. A
221
+ * decision over `{command, session root}` would therefore authorize different
222
+ * bytes from the `{command, effective directory}` the harness executes, and a
223
+ * relative path in an approved command can name a protected organ in a
224
+ * directory the classifier never saw.
225
+ *
226
+ * Distinct from `hook-io`, which this used to borrow, and the distinction is
227
+ * the repair. `hook-io` says THIS event was malformed and a well-formed one
228
+ * would be answered; this says every event of this shape is refused on this
229
+ * harness version, and the fix is a harness contract that exposes the
230
+ * effective execution directory, not a retry, a policy edit, or an open
231
+ * window. Nothing appends on this path and no gate lifecycle opens.
232
+ */
233
+ "hook-unsupported-execution-context",
234
+ /**
235
+ * The session names a Contributor-tier model, so every tool call is refused
236
+ * (APRV-350).
237
+ *
238
+ * Meta sells a Contributor variant of the Muse Spark family that "trades a
239
+ * lower price for permission to train on your prompts and completions". A
240
+ * session on one discloses every byte it reads, so the refusal is above the
241
+ * policy: no class resolution and no grant widens it, and an absent or
242
+ * unrecognised `model` is refused for the same reason an unparseable event is.
243
+ *
244
+ * Distinct from `hook-class-human-only`, which says a HUMAN must do this
245
+ * action; this says nothing may do it in this session, and the repair is to
246
+ * change the model in Muse's picker rather than to ask anybody. Distinct from
247
+ * `hook-io` because the event was perfectly well formed.
248
+ *
249
+ * What it cannot do is stated wherever it is documented: it stops tool calls,
250
+ * and it cannot recall a prompt the model has already been sent.
251
+ */
252
+ "hook-muse-contributor-model",
253
+ /** Malformed hook input, or a log/filesystem fact that stopped the check. */
254
+ "hook-io"];
255
+ export type HookDenyCode = (typeof HOOK_DENY_CODES)[number];
256
+ /** Where the hook reads policy from and appends to, resolved together. */
257
+ export interface HookScope {
258
+ logPath: string;
259
+ /** The directory `logPath` sits under, named in the unreachable-log detail. */
260
+ root: string;
261
+ options: GateOptions;
262
+ }
263
+ /**
264
+ * Policy and log, resolved from the same root (APRV-101).
265
+ *
266
+ * Before this, `--dir` scoped only the policy and the log was resolved from the
267
+ * process cwd, so a hook invoked with `--dir <primary>` from an agent worktree
268
+ * read the primary's policy and wrote the worktree's copy of the log: a
269
+ * dead-end chain that forks from the real one. Explicit flags still win
270
+ * (`--policy` for the policy, `--log` for the log); otherwise both follow
271
+ * `--dir`, and with no flags at all both follow the primary checkout.
272
+ */
273
+ export declare function hookScope(flags: Record<string, string | boolean>, cwd: string): HookScope;
274
+ /**
275
+ * Which harness JSON envelope to print. Never `ask`.
276
+ *
277
+ * One definition since APRV-227, in `core/harness-version.ts`: the set of
278
+ * harnesses this runtime speaks a protocol for is the same set it knows a
279
+ * binary name for, and two copies of it would be two lists to drift.
280
+ */
281
+ interface HarnessAdapter {
282
+ kind: HarnessKind;
283
+ originApp: string;
284
+ defaultActor: string;
285
+ shellTool: string;
286
+ fileTools: readonly string[];
287
+ /**
288
+ * Tools that READ a named path (APRV-347).
289
+ *
290
+ * Parallel to `fileTools` and answered by a parallel gate. The two lists
291
+ * differ in what an empty entry means: a file tool with no path is a tool
292
+ * call this runtime does not understand, while a read tool with no path is
293
+ * the ordinary spelling of "read the workspace" and keeps the
294
+ * not-a-gated-tool `allow` it has always had.
295
+ */
296
+ readTools: readonly string[];
297
+ /** Include the native tool name in the bytes a grant binds. */
298
+ bindToolName?: boolean;
299
+ /**
300
+ * Read `toolName`/`toolInput`/`sessionId` as well as the snake_case
301
+ * spellings (APRV-243).
302
+ *
303
+ * Grok Build's PreToolUse envelope is Claude Code's with camelCase keys.
304
+ * Opt-in per adapter rather than tolerated everywhere: a Claude Code event
305
+ * that arrived with the wrong spelling is a malformed event, and the strict
306
+ * answer to a malformed event is the deny that `parseHookInput` already
307
+ * produces.
308
+ */
309
+ camelCaseEnvelope?: boolean;
310
+ /**
311
+ * Tools the harness fires for its OWN bookkeeping, answered and never gated
312
+ * (APRV-350).
313
+ *
314
+ * Muse Code fires `PreToolUse` and `PostToolUse` for `submit_reminder_decision`
315
+ * continuously: 100 of the 139 events in the live capture were that one tool.
316
+ * It records a self-assessment and touches nothing, so gating it would put a
317
+ * hundred questions a turn on an approver's phone to authorize the harness
318
+ * thinking. It is listed rather than inferred, because a tool this runtime
319
+ * does not recognise must keep falling through to the ordinary path.
320
+ */
321
+ passThroughTools?: readonly string[];
322
+ /**
323
+ * The `tool_input` key carrying the PER-CALL working directory, when the
324
+ * harness sends one (APRV-350).
325
+ *
326
+ * Muse's `bash` tool carries `workdir`, and it is the directory the command
327
+ * will actually run in, which is the fact the classifier needs. The top-level
328
+ * `cwd` is the session's root and can differ. Codex has neither, which is why
329
+ * its shell arm refuses outright; Claude Code has only the top-level one.
330
+ */
331
+ shellCwdKey?: string;
332
+ /**
333
+ * Refuse every tool call when the envelope names a Contributor-tier model
334
+ * (APRV-350).
335
+ *
336
+ * Meta sells a Contributor variant that "trades a lower price for permission
337
+ * to train on your prompts and completions". A session on one is a session
338
+ * whose every read is disclosed, so the adapter refuses regardless of what
339
+ * the policy would otherwise allow. See {@link contributorModelRefusal}.
340
+ */
341
+ contributorModelGuard?: boolean;
342
+ }
343
+ /**
344
+ * Every harness this runtime speaks a hook protocol for, by kind (APRV-358).
345
+ *
346
+ * The table is `Record<HarnessKind, HarnessAdapter>` rather than a list of
347
+ * consts and a switch, and the type is the point: a kind added to
348
+ * `HARNESS_KINDS` with no adapter beside it fails to compile, so the two lists
349
+ * cannot drift by forgetting. The subcommand dispatch below reads this map, so
350
+ * `approval hook <kind>` is answerable for exactly the kinds named here.
351
+ *
352
+ * The kinds that are enumerated OUTSIDE this module — the schema's
353
+ * `payload.harness` enum, the verb registry's `hook` subcommands, the MCP
354
+ * exclusions, the help — are pinned set-equal to `HARNESS_KINDS` by
355
+ * `tests/harness-enum.test.ts`, which exists because `grok` shipped an adapter
356
+ * in APRV-243 and reached none of them. A Grok session's manual-class
357
+ * registration was refused at the write boundary for eleven days and nothing
358
+ * failed.
359
+ */
360
+ export declare const HARNESS_ADAPTERS: Readonly<Record<HarnessKind, HarnessAdapter>>;
361
+ /** The machine-readable code a Contributor-tier session is refused with. */
362
+ export declare const MUSE_CONTRIBUTOR_REFUSAL = "hook-muse-contributor-model";
363
+ export interface HookInput {
364
+ sessionId: string;
365
+ /** Whether the event supplied the session id, distinct from the strict unknown bucket. */
366
+ sessionIdPresent: boolean;
367
+ cwd: string;
368
+ toolName: string;
369
+ toolInput: Record<string, unknown>;
370
+ toolUseId: string | null;
371
+ /**
372
+ * `hook_event_name`, verbatim, or `null` when the event carries none
373
+ * (APRV-145).
374
+ *
375
+ * Read at last. Until this, nothing in this module looked at it and
376
+ * `runHarnessHook` assumed a pre-execution event unconditionally, so an
377
+ * operator who registered this same command for the post-execution event would
378
+ * have gated every command a second time and doubled every prompt on the
379
+ * approver's phone.
380
+ */
381
+ hookEventName: string | null;
382
+ /**
383
+ * The model the session reports running, or `null` (APRV-350).
384
+ *
385
+ * Muse sends it on every event. Read for one purpose only, the contributor
386
+ * guard, and that guard can only ever refuse: a self-reported field raises
387
+ * scrutiny and never lowers it (SPEC §11.1). It is never logged, because it
388
+ * is untrusted third-party text and §11.1 invariant 3 has no provenance
389
+ * exception.
390
+ */
391
+ model: string | null;
392
+ /**
393
+ * `tool_response`, when the event carries one as an object.
394
+ *
395
+ * Present only on a post-execution event; the pre-execution path never reads
396
+ * it, because the tool has not run. Its SHAPE is all that is ever read (see
397
+ * {@link readReportedOutcome}) — never the text inside it.
398
+ */
399
+ toolResponse: Record<string, unknown> | null;
400
+ /** `tool_response` verbatim, including strings, for harness-specific readers. */
401
+ toolResponseRaw: unknown;
402
+ /**
403
+ * `is_interrupt`, the post-execution events' own word for "a person stopped
404
+ * this" (APRV-303).
405
+ *
406
+ * `PostToolUseFailure` carries it beside `error`; `PostToolUse` carries the
407
+ * same fact as `tool_response.interrupted`. Read only to make an outcome
408
+ * UNREADABLE, never to establish one, so nothing about it can lower scrutiny.
409
+ */
410
+ interrupted: boolean;
411
+ /**
412
+ * `version`, when the harness states its own (APRV-227).
413
+ *
414
+ * Claude Code's event may carry it; Cursor's does not, and neither did any
415
+ * Claude Code release before it. So this is a preference and never a
416
+ * requirement: `core/harness-version.ts` falls back to `<binary> --version`
417
+ * and then to absence, and a hook that can establish nothing records nothing.
418
+ *
419
+ * SELF-REPORTED, and read at all only because it cannot buy the reporter
420
+ * anything. Nothing in this module branches on it; it reaches exactly one
421
+ * payload field whose one reader is a doctor row that can only ADD a red
422
+ * line, so §11.1 invariant 4 holds by construction rather than by care. A
423
+ * harness that states a false version defeats a check that would have asked a
424
+ * human to look, and gains no verdict it did not already have.
425
+ */
426
+ harnessVersion: string | null;
427
+ }
428
+ /** A classification plus a human-readable note for every segment refined. */
429
+ export interface RefinedClassification {
430
+ result: CommandClassification;
431
+ /** One line per downgraded segment; empty when nothing was refined. */
432
+ notes: string[];
433
+ }
434
+ /**
435
+ * Downgrade local rewrites of unpublished history to `vcs.commit.branch`.
436
+ *
437
+ * IMPURE by design and by contract: it runs git in `cwd`. Both callers pass the
438
+ * same directory the hook itself resolves from, so what `hook classify` prints
439
+ * is what `hook claude-code` decides.
440
+ */
441
+ export declare function refineRewrite(result: CommandClassification, cwd: string): RefinedClassification;
442
+ /**
443
+ * Is a candidate deep enough, once resolved, to stand as a scratch root?
444
+ *
445
+ * The depth floor is the anti-poisoning guard (SPEC.md §11.1: self-reported
446
+ * fields never reduce scrutiny). A `TMPDIR` naming `/` resolves and exists, and
447
+ * a root of `/` would turn every absolute delete into a scratch delete, so a
448
+ * resolved root is refused below {@link MIN_ROOT_SEGMENTS}.
449
+ *
450
+ * The well-known system temp roots are the one exception, and they are one on
451
+ * every platform: on Linux `os.tmpdir()` is `/tmp`, a single segment, and
452
+ * refusing it would mean `files.delete.scratch` could never fire there, while
453
+ * on macOS the same directory resolves through the `/tmp` symlink to
454
+ * `/private/tmp` and clears the floor by accident of layout. The exemption is
455
+ * keyed on the RESOLVED value being one of the three compiled-in names, so
456
+ * nothing a caller reports widens it: a poisoned `TMPDIR` still has to resolve
457
+ * to `/tmp`, `/private/tmp` or `/var/tmp` to get in, and those are roots
458
+ * already. `/` is not among them, and every other one-segment directory
459
+ * (`/etc`, `/home`, `/usr`) stays refused.
460
+ */
461
+ export declare function scratchRootDepthAccepted(resolved: string): boolean;
462
+ /**
463
+ * The scratch roots this process may vouch for, resolved and guarded.
464
+ *
465
+ * `cwd` is the directory the hook itself resolved from; a candidate containing
466
+ * it is discarded, because a root that swallowed the checkout would make every
467
+ * delete in the repository a scratch delete.
468
+ */
469
+ export declare function resolveScratchRoots(cwd: string, env?: NodeJS.ProcessEnv): string[];
470
+ /**
471
+ * Tighten `files.delete.scratch` back to `files.delete.out_of_scope` wherever
472
+ * the disk disagrees with the text.
473
+ *
474
+ * IMPURE by design and by contract, exactly as {@link refineRewrite} is: it
475
+ * stats paths. It only ever moves a segment toward the stricter class, so a
476
+ * caller that skipped it would never be MORE permissive than one that runs it,
477
+ * which is what lets `hook classify` and `hook claude-code` share it without
478
+ * either becoming the authority.
479
+ */
480
+ export declare function refineScratchDelete(result: CommandClassification, roots: readonly string[]): RefinedClassification;
481
+ /**
482
+ * The read roots this process may vouch for, resolved.
483
+ *
484
+ * The gate root is the directory the hook resolved its POLICY from, never the
485
+ * harness-supplied `cwd`: a scope the subject of the gate could choose is not a
486
+ * scope (SPEC.md §11.1, self-reported fields never reduce scrutiny). The
487
+ * scratchpad and temp roots are the ones `resolveScratchRoots` already computes
488
+ * and already guards, so the two rules cannot disagree about where the agent's
489
+ * own scratch is. `declared` is `read_scope.roots` out of the loaded policy,
490
+ * which may only widen this set.
491
+ */
492
+ export declare function resolveReadRoots(cwd: string, gateRoot: string, declared?: readonly string[]): string[];
493
+ /**
494
+ * Tighten a `read.shell` segment to `read.file.out_of_scope` wherever the disk
495
+ * disagrees with the text.
496
+ *
497
+ * IMPURE by design and by contract. `roots` empty means the caller asked for no
498
+ * read scoping at all, and every segment is returned untouched — the same
499
+ * "absent yields today's answer" the classifier context promises.
500
+ */
501
+ export declare function refineReadScope(result: CommandClassification, roots: readonly string[], cwd: string): RefinedClassification;
502
+ /**
503
+ * The classifier, its context, and all three impure refinements, in the one
504
+ * order every caller must use.
505
+ *
506
+ * `hook classify` printing a different class from the one `hook claude-code`
507
+ * decides would make the explainer a different program (APRV-108's note), and
508
+ * that stays true now there are three refinements in the chain.
509
+ *
510
+ * `readRoots` is the one argument whose ABSENCE is the loose answer rather than
511
+ * the strict one (APRV-347), so it is passed explicitly at every call site: an
512
+ * empty list means "do not scope reads", which is what every caller outside a
513
+ * resolved gate scope wants and what this classifier did before the field
514
+ * existed.
515
+ */
516
+ export declare function classifyForHook(command: string, protectedPaths: readonly ProtectedPathEntry[], cwd: string, readRoots?: readonly string[]): RefinedClassification;
517
+ /**
518
+ * What the classifier made of a command (APRV-91 #9).
519
+ *
520
+ * Human output is an aligned three-column table under a `key` header row; the
521
+ * command text and the rule name are copyable and stay undressed. `--json`
522
+ * emits the classification object unchanged, and asks for the style FIRST so
523
+ * that the `json` veto on colour is the answer this process memoizes.
524
+ */
525
+ export declare function renderClassification(result: CommandClassification, json: boolean, st?: Style): string;
526
+ interface HookRun {
527
+ logPath: string;
528
+ options: GateOptions;
529
+ actor: string;
530
+ timeoutMs: number;
531
+ intervalMs: number;
532
+ /**
533
+ * How long a request outlives the wait before this hook takes it back
534
+ * (APRV-287, `--retry-grace`).
535
+ *
536
+ * `core/harness-wait.ts` holds the default and the reasoning. Zero withdraws
537
+ * at the moment the wait expires, which is what the tests drive.
538
+ */
539
+ graceMs: number;
540
+ /** `defaults.approval_ttl`, or `null` when the policy declares none. */
541
+ ttlMs: number | null;
542
+ harness: HarnessKind;
543
+ originApp: string;
544
+ /** Exact native command bytes required in a Codex allow's identity update. */
545
+ codexCommand?: string;
546
+ /**
547
+ * The version the hook event stated, or `null` (APRV-227).
548
+ *
549
+ * Carried rather than resolved here: resolving it means a `spawnSync` of
550
+ * `<binary> --version`, and a hook process exists per gated tool call. The
551
+ * resolution happens at the one place that is about to WRITE a record and
552
+ * nowhere else, so the pass-through verdict and the autonomous verdict pay
553
+ * nothing for it. See {@link registrationProvenance}.
554
+ */
555
+ eventVersion: string | null;
556
+ /**
557
+ * The channel names this policy configures, sorted (APRV-281).
558
+ *
559
+ * Read off the policy the caller already loaded, and used for ONE thing: the
560
+ * line this hook prints when it appends a request, so the agent and the
561
+ * operator watching its error stream are told where the question went. It
562
+ * resolves nothing and reaches no verdict. An empty list is a fact worth
563
+ * printing rather than a default to fill in: a request under a policy that
564
+ * configures no channel is a question nothing is delivering.
565
+ */
566
+ channels: readonly string[];
567
+ }
568
+ /**
569
+ * The gated half: find what is already open for these bytes, request whatever
570
+ * is not, wait for the decisions, spend the grants. Returns the exit code of
571
+ * whatever verdict it printed.
572
+ *
573
+ * ## Requests are keyed by bytes, not by invocation (APRV-117)
574
+ *
575
+ * The action key is still `hook:<session>:<tool-use id>:<class>` and is still
576
+ * unique per invocation — what changed is that intake LOOKS for an earlier
577
+ * request about the same `{command, cwd}` before opening a new one, matching on
578
+ * the `payload_hash` recorded on `approval.requested`. Three outcomes per class,
579
+ * decided by `core/gate.ts`'s `findHarnessCarry`:
580
+ *
581
+ * - nothing to carry: register and request, exactly as before;
582
+ * - a pending request: **adopt** it — wait out the remainder of this
583
+ * invocation's window on somebody else's key, opening nothing. The approver's
584
+ * phone never shows two prompts for one command, because there is only ever
585
+ * one question;
586
+ * - an unspent grant inside the TTL: **carry** it — no wait, no prompt, and
587
+ * the grant is spent (once) before the allow is printed.
588
+ *
589
+ * ## Why the wait no longer ends in a withdrawal (APRV-106, revised)
590
+ *
591
+ * APRV-106 retracted the request when the wait elapsed, because a retried tool
592
+ * call was a new request with a new key and a late tap therefore authorized
593
+ * nothing: the human spent attention on a question whose asker had left. The
594
+ * carryover above removes the premise. A late tap now authorizes the retry, so
595
+ * the request stays open for the policy's TTL and the timeout says so.
596
+ *
597
+ * What still withdraws is every path where nothing can adopt the question: a
598
+ * SIGTERM or SIGINT (the session is going away), a thrown failure, and an intake
599
+ * refusal partway through a multi-class command (the command cannot proceed on
600
+ * any retry, so the classes already opened are noise in a human's queue). The
601
+ * signal handlers are installed for the duration of the wait ONLY, and removed
602
+ * in `finally`: a hook process is short-lived and borrowing the harness's
603
+ * signal disposition for longer than the loop would be a side effect nobody
604
+ * asked for.
605
+ */
606
+ /**
607
+ * What the gate decided about one harness tool call, before anything is printed
608
+ * (APRV-361).
609
+ *
610
+ * {@link gateHarnessCall} produces it and {@link gateAndWait} renders it in the
611
+ * harness's own dialect. The split exists because a second caller answers in a
612
+ * protocol rather than on stdout: `cli/codex-bridge.ts` replies
613
+ * `{id, result: {decision}}` over the app-server's JSON-RPC connection, and it
614
+ * has to reach that decision through the SAME classify, register, request and
615
+ * wait this function runs. Two implementations of that sequence would be two
616
+ * gates, and the second one would be the one nobody reviewed.
617
+ *
618
+ * `code` and `detail` are kept apart rather than pre-joined, because the bridge
619
+ * records the code as a code (§11.1 invariant 7) where the hook prints the pair
620
+ * as one reason string.
621
+ */
622
+ export type HarnessVerdict = {
623
+ permission: "allow";
624
+ reason: string;
625
+ } | {
626
+ permission: "deny";
627
+ code: string;
628
+ detail: string;
629
+ };
630
+ export declare function gateHarnessCall(streams: Streams, run: HookRun, classes: string[],
631
+ /**
632
+ * The bytes the grant binds to: `{command, cwd}` for a Bash call, the change
633
+ * itself for a file tool (APRV-124). Whatever this is, it is what reaches the
634
+ * approver's FULL PAYLOAD block, complete — the summary below is a headline
635
+ * and is the only thing here that may be shortened.
636
+ */
637
+ payload: unknown, headline: string,
638
+ /**
639
+ * The task id this invocation acts under, minted once by the caller
640
+ * (APRV-139) so the loop-escalation check and the registration it may lead to
641
+ * name the same task. Deriving it twice would mint two ids whenever
642
+ * `tool_use_id` is absent and the random fallback runs.
643
+ */
644
+ task: string,
645
+ /** The history-rewrite refinement's own words, or `""` (APRV-108). */
646
+ note?: string,
647
+ /**
648
+ * The harness streak that floors the SIDE-EFFECTING classes of this
649
+ * invocation to `manual` (APRV-145, narrowed by APRV-297), or `null` where
650
+ * policy alone sent it here.
651
+ *
652
+ * Passed into `request` as a boolean rather than acted on here, so the floored
653
+ * action takes the identical path a manual class takes — same records, same
654
+ * order, same wait — and nothing below knows how it got there. What the STATE
655
+ * adds (APRV-280) is the deny text: an agent whose commands are all suddenly
656
+ * on the phone is owed the reason and the way out in the same breath, and
657
+ * before APRV-280 the nine-minute wait ended in a bare `hook-timeout` that
658
+ * said neither.
659
+ *
660
+ * Since APRV-297 the caller passes `null` for a command whose classes are all
661
+ * reads, and {@link floorApplies} below carves the read classes out of a mixed
662
+ * one, so a floor never puts a question about looking on a human's phone.
663
+ */
664
+ floor?: HarnessLoopState | null): HarnessVerdict;
665
+ /**
666
+ * Every line the counterpart can print, closed and machine-readable (§11.1
667
+ * invariant 7).
668
+ *
669
+ * A post-execution hook cannot deny anything — the tool has already run — so
670
+ * none of these is a verdict, and every one of them prints an EMPTY STDOUT: a
671
+ * decision object on that stream would be a second answer about a command the
672
+ * harness already ran. The line goes to stderr instead.
673
+ *
674
+ * ## The exit code decides whether anybody reads that line (APRV-303)
675
+ *
676
+ * Claude Code's hooks reference states it plainly: stderr from a hook that
677
+ * exits 0 "goes to the debug log only, never the transcript, and Claude never
678
+ * sees it", and a post-execution hook that exits 2 has its stderr shown, since
679
+ * there is nothing left to block. So a refusal reported at exit 0 is a refusal
680
+ * nobody receives, which is how 22052 unreported starts accumulated on this
681
+ * project's own log without a single visible complaint.
682
+ *
683
+ * Therefore: {@link POST_TOOL_REPORTED} exits 0, because a counterpart that
684
+ * landed is not news; every other code exits {@link POST_TOOL_SURFACE_EXIT},
685
+ * because every other code means the outcome of a tool call was not recorded
686
+ * and somebody has to know. Neither exit is a verdict, and neither blocks
687
+ * anything.
688
+ */
689
+ export declare const POST_TOOL_CODES: readonly [
690
+ /** One or more counterparts were appended. */
691
+ "post-tool-reported",
692
+ /** The event names no tool-use id, so no task id can be reconstructed. */
693
+ "post-tool-unidentified",
694
+ /** The tool is not one this hook gates, so no start exists to close. */
695
+ "post-tool-not-gated",
696
+ /**
697
+ * The outcome could not be read from the event by the pinned set of readings,
698
+ * so NOTHING was appended. Recording a failure nobody observed trips an
699
+ * escalation on noise, and recording a completion nobody observed clears one
700
+ * on nothing.
701
+ */
702
+ "post-tool-unreadable-outcome",
703
+ /** No log where the hook was pointed; the hook is a writer, never an initializer. */
704
+ "post-tool-log-unreachable",
705
+ /** The gate refused the append; its own frozen code follows a colon. */
706
+ "post-tool-gate-refused",
707
+ /** Malformed input, or a filesystem fact that stopped the report. */
708
+ "post-tool-io"];
709
+ export type PostToolCode = (typeof POST_TOOL_CODES)[number];
710
+ /** The environment variable that turns the sandbox requirement on (APRV-193). */
711
+ export declare const REQUIRE_SANDBOX_ENV = "APPROVAL_HOOK_REQUIRE_SANDBOX";
712
+ /**
713
+ * Must this command have been written `approval sandbox -- …`? (APRV-193.)
714
+ *
715
+ * Returns the deny detail, or `null` to proceed. Four conditions, and every one
716
+ * of them is a narrowing, so the answer is `null` for everything the operator
717
+ * did not deliberately ask about:
718
+ *
719
+ * 1. the operator set `APPROVAL_HOOK_REQUIRE_SANDBOX=1`;
720
+ * 2. some segment runs code this runtime did not author
721
+ * (`CODE_EXECUTING_RULES`: `npm test`, `node x.mjs`, `tsc`, `make`…);
722
+ * 3. that segment is not already inside the runtime's own wrapper. A
723
+ * hand-written `sandbox-exec -f mine.sb` does NOT satisfy it, because a
724
+ * profile a caller wrote can allow everything, and a requirement met by
725
+ * writing your own permission is not a requirement;
726
+ * 4. no class of the command is manual. A manual command is going to a human,
727
+ * and a human's grant over these exact bytes is the authority to reach the
728
+ * world — the same line `approval run` draws at the token.
729
+ *
730
+ * The environment variable is read in the strict direction only: setting it can
731
+ * refuse commands that would otherwise run, and nothing an agent can set makes
732
+ * this function return `null` where it would otherwise deny (SPEC.md §11.1
733
+ * invariant 4).
734
+ */
735
+ export declare function sandboxRequirement(segments: readonly ClassifiedSegment[] | undefined, autonomies: readonly string[], env?: NodeJS.ProcessEnv): string | null;
736
+ /**
737
+ * What {@link decideHarnessCall} needs to reach a verdict (APRV-361).
738
+ *
739
+ * Everything here is established by the CALLER: which harness asked, where the
740
+ * policy and the log are, who is acting, and how long this decision may take.
741
+ * Nothing in it is authored by the party under oversight beyond `input`, which
742
+ * is the harness's own event and is treated as such throughout.
743
+ */
744
+ export interface DecideInput {
745
+ streams: Streams;
746
+ input: HookInput;
747
+ adapter: HarnessAdapter;
748
+ /** The directory a relative path in the call resolves against. */
749
+ cwd: string;
750
+ logPath: string;
751
+ /** The scope root, named in the unreachable-log detail. */
752
+ root: string;
753
+ options: GateOptions;
754
+ actor: string;
755
+ timeoutMs: number;
756
+ intervalMs: number;
757
+ graceMs: number;
758
+ /** Exact native command bytes a Codex allow must carry back, where there are any. */
759
+ codexCommand?: string | undefined;
760
+ /**
761
+ * The verified records an open-window lookup already read, or `null`.
762
+ *
763
+ * Passed rather than re-read so the floor and the unattended guard are
764
+ * decided from the same read the window was. A caller that performed no
765
+ * lookup passes `null`, and both of them read the log themselves.
766
+ */
767
+ windowRecords: EventRecord[] | null;
768
+ }
769
+ /**
770
+ * Classify, resolve, gate and wait: one harness tool call, from the event to a
771
+ * verdict (APRV-361).
772
+ *
773
+ * Extracted from the hook's own verb so a SECOND caller can reach a decision
774
+ * through exactly this sequence. `cli/codex-bridge.ts` answers Codex's
775
+ * app-server approval requests over JSON-RPC rather than on stdout, and the
776
+ * thing it must not do is re-implement any of what is below: the human-only
777
+ * refusal, the unruled `harness.launch.*` refusal, the sandbox requirement, the
778
+ * loop floor, the unattended guard, the autonomous charge, and the register,
779
+ * request and wait that follow. Two implementations of that sequence would be
780
+ * two gates, and the second one would be the one nobody reviewed.
781
+ *
782
+ * It returns a verdict and prints none. `streams.err` still carries the
783
+ * progress and withdrawal lines, which are a report rather than a decision.
784
+ */
785
+ export declare function decideHarnessCall(decide: DecideInput): HarnessVerdict;
786
+ export declare function commandHook(argv: string[], streams: Streams, cwd: string, readStdin?: () => string): number;
787
+ export {};