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,697 @@
1
+ /**
2
+ * Shell command classification: a pure, deterministic map from a command line
3
+ * to the SPEC.md §7 side-effect classes it would produce (APRV-82).
4
+ *
5
+ * This is the input half of the Claude Code PreToolUse hook. The harness hands
6
+ * us a command string it is about to run; policy speaks in classes; something
7
+ * has to translate. That translation is the reviewable artifact of this task, so
8
+ * it lives in one file, is data-driven, and is exhaustively fixture-tested.
9
+ *
10
+ * Three properties are load-bearing.
11
+ *
12
+ * **Pure.** No filesystem, no clock, no environment, no network. The same string
13
+ * always yields the same answer, which is what makes the fixture table a real
14
+ * specification rather than a sample of observed behaviour.
15
+ *
16
+ * **Fail closed, in three named ways.** A construct whose effect cannot be read
17
+ * off the text is `opaque` (`bash -c`, `eval`, backticks, a tainting command
18
+ * substitution). A binary the table does not know is `unclassified`. A string
19
+ * the tokenizer cannot even split is `unparseable`. All three are refusals, and
20
+ * the hook denies on each: the alternative, guessing a class for a command we do
21
+ * not understand, would let the least legible command lines through the widest
22
+ * gap. Ambiguity inside a construct we DO understand resolves to the stricter
23
+ * class (`git push` with no refspec is `vcs.push.main`, an `rm` path holding an
24
+ * unexpanded `$VAR` is `files.delete.out_of_scope`).
25
+ *
26
+ * **Best effort, and honest about it.** This is a classifier over shell text,
27
+ * not a shell. It is a speed bump against an agent doing the wrong thing by
28
+ * mistake, in the SPEC.md §11 sense; it is not scheming-robust, and nothing here
29
+ * claims otherwise. The hard boundary stays where SPEC.md §10.4 puts it: the
30
+ * credentials in the vault and the token an adapter demands.
31
+ *
32
+ * Self-reported text is never read. The hook passes the command only; the
33
+ * harness's `description` field is authored by the very agent being gated
34
+ * (SPEC.md §11.1: self-reported fields never reduce scrutiny).
35
+ *
36
+ * Two imports, and both are pure in exactly the same way this file is: no disk,
37
+ * no clock, no environment, no dependencies.
38
+ *
39
+ * `core/read-scope.ts` (APRV-347) holds the read-side path arithmetic, so the
40
+ * hook and the policy explainer can ask the same questions this file asks, of
41
+ * the same table, rather than each growing a copy of it.
42
+ *
43
+ * `core/policy-match.ts` (APRV-354) is imported for ONE name, the
44
+ * `harness.launch.` prefix. The classifier emits the family and two enforcement
45
+ * paths refuse a member of it that no policy rule names, so the three have to
46
+ * agree on what the family is; a second spelling of the prefix would close one
47
+ * of those doors and leave the other open. The import is type-safe in the
48
+ * dependency sense as well: `policy-match.ts` itself imports only types.
49
+ */
50
+ /** Why a command could not be classified. Each denies; none is a soft failure. */
51
+ export type ClassifierFailureCode = "unclassified" | "opaque" | "unparseable";
52
+ /** One command in a pipeline or list, with the class it resolved to. */
53
+ export interface ClassifiedSegment {
54
+ /** The segment's source text, as written. */
55
+ text: string;
56
+ /** The dotted side-effect class (SPEC.md §7). */
57
+ class: string;
58
+ /** Which rule decided it, for `hook classify` output and for tests. */
59
+ rule: string;
60
+ /**
61
+ * The protected path that selected the class, present only when one did
62
+ * (APRV-143).
63
+ *
64
+ * The protected classes (`policy.edit`, `policy.core`, `log.mutate`) are the
65
+ * classes a segment can take because of a *value* in it rather than because
66
+ * of its binary, and until this field the value was discarded the moment the
67
+ * rule fired: an approver was told the class and left to guess which of six
68
+ * arguments earned it. It is the word the classifier matched, verbatim, so a
69
+ * channel can name it without a second search of its own.
70
+ */
71
+ path?: string;
72
+ /**
73
+ * The sandbox wrapper this segment runs inside, when the classifier unwrapped
74
+ * one (APRV-193). Absent means "not wrapped, as far as this classifier can
75
+ * tell", which is the direction every caller must fail in: the marker is only
76
+ * ever read to RELAX a requirement, so an early return that omits it costs a
77
+ * refusal and never an authorization.
78
+ *
79
+ * `runtime` is `approval sandbox -- …`, whose profile this runtime writes.
80
+ * `external` is a hand-written `sandbox-exec -f <profile> …`, which is
81
+ * classified honestly (by its inner argv) and trusted for nothing: the
82
+ * profile is the caller's, and a profile a caller wrote can allow anything.
83
+ */
84
+ sandbox?: SandboxWrapper;
85
+ }
86
+ /**
87
+ * Who wrote the profile a wrapped command runs under (APRV-193).
88
+ *
89
+ * The distinction is the whole reason this is not a boolean. Classification
90
+ * treats the two identically — both are unwrapped to the inner argv, because a
91
+ * wrapper is a room and the class belongs to what runs in it. Enforcement does
92
+ * not: `APPROVAL_HOOK_REQUIRE_SANDBOX` is satisfied only by `runtime`, because
93
+ * `sandbox-exec -f /tmp/anything.sb` with a profile whose only line is
94
+ * `(allow default)` denies nothing at all, and a requirement a caller can meet
95
+ * by writing their own permission is not a requirement.
96
+ */
97
+ export type SandboxWrapper = "runtime" | "external";
98
+ export type CommandClassification = {
99
+ ok: true;
100
+ segments: ClassifiedSegment[];
101
+ classes: string[];
102
+ } | {
103
+ ok: false;
104
+ code: ClassifierFailureCode;
105
+ /** The segment (or whole command) that could not be read. */
106
+ segment: string;
107
+ detail: string;
108
+ };
109
+ /**
110
+ * The pass-through pseudo-class for the gate's own CLI.
111
+ *
112
+ * `approval …` is already the enforcement path — gating it with itself would
113
+ * either deadlock (the hook waiting on a decision that `approval grant` cannot
114
+ * deliver) or recurse. The hook allows this class without touching the log; it
115
+ * is never written to any envelope and no policy rule should name it.
116
+ */
117
+ export declare const GATE_SELF_CLASS = "gate.self";
118
+ /**
119
+ * The three classes a protected path can select (APRV-198).
120
+ *
121
+ * One class used to cover the whole protected surface, so a policy could not
122
+ * say "an agent may edit the prose that describes the gate, under sampling,
123
+ * and may never touch the gate itself". These are that sentence, in the order
124
+ * of decreasing consequence:
125
+ *
126
+ * - `log.mutate` — anything aimed at `.approval/log/`. The log is the truth;
127
+ * a write to it is not an edit of the rules, it is an edit of the record of
128
+ * what happened.
129
+ * - `policy.core` — the policy file itself and the rest of the gate's own
130
+ * directory (env, payload store, vault, keys, `QUEUE.md`), plus the harness
131
+ * files that install the hook. An agent that can write these can write
132
+ * itself out of the gate without the gate ever seeing it.
133
+ * - `policy.edit` — the prose and configuration ABOUT the gate: the agent
134
+ * instructions, CI and release configuration, and whatever paths the policy
135
+ * itself protects. Reviewable after the fact, and the only one of the three
136
+ * a policy can sensibly sample.
137
+ */
138
+ export type ProtectedPathClass = "log.mutate" | "policy.core" | "policy.edit";
139
+ /**
140
+ * The `policy.edit` sub-class namespace a `protected_paths` entry may route to
141
+ * (APRV-266).
142
+ *
143
+ * One protected surface with one autonomy was the shape until now, so a policy
144
+ * that wanted its specification sampled at one tenth and its release workflows
145
+ * gated every time had to choose one of those numbers for both. A routed entry
146
+ * says which sub-class a path family takes, and the sub-class is an ordinary
147
+ * §7 class with an ordinary policy line, so each family gets its own autonomy
148
+ * and its own live rate without any new grammar in `classes`.
149
+ *
150
+ * The namespace is closed to ONE extra segment under `policy.edit` and nothing
151
+ * else. A policy may not route a path to `policy.core`, to `log.mutate`, or to
152
+ * any class outside this namespace: those are the gate's own organs and the
153
+ * record of what happened, and §11.1 invariant 9 reserves them — a policy
154
+ * widening its own protected surface is naming prose and configuration, and
155
+ * mints no authority over anything else. `policy.schema.json` enforces the
156
+ * shape; this pattern is the same rule where the matcher can see it.
157
+ */
158
+ export declare const POLICY_EDIT_SUBCLASS: RegExp;
159
+ /**
160
+ * Sub-class names this spec reserves, with the meaning an implementation must
161
+ * give them (APRV-266).
162
+ *
163
+ * Reserved so that two policies written by two people mean the same thing by
164
+ * `policy.edit.ci`, and so a reader of somebody else's policy does not have to
165
+ * infer it. An author is free to mint their own word beside these — the pattern
166
+ * above admits any lowercase word — and a minted name carries only the meaning
167
+ * its own policy line gives it.
168
+ */
169
+ export declare const RESERVED_POLICY_EDIT_SUBCLASSES: Readonly<Record<string, string>>;
170
+ /**
171
+ * One `protected_paths` entry: a bare path, or a path routed to a sub-class.
172
+ *
173
+ * The bare string is the APRV-107 spelling and keeps its meaning exactly —
174
+ * `policy.edit`, as though the object form did not exist. Nothing about a
175
+ * string-only policy changes by a byte, which is the compatibility rule this
176
+ * type exists to state: `approval hook classify` over such a policy answers
177
+ * today's class with today's rule name.
178
+ */
179
+ export type ProtectedPathEntry = string | {
180
+ path: string;
181
+ class: string;
182
+ };
183
+ /**
184
+ * One entry of `policy.protected_paths`, pre-split (APRV-107).
185
+ *
186
+ * `directory` records the trailing `/` that distinguishes `design/` (a subtree)
187
+ * from `design` (a file named `design`).
188
+ */
189
+ interface ProtectedEntry {
190
+ segments: string[];
191
+ directory: boolean;
192
+ /**
193
+ * The class this entry routes to (APRV-266). `null` for a bare string entry,
194
+ * which means `policy.edit` and is matched in the built-in `policy.edit`
195
+ * tier exactly where it always was.
196
+ */
197
+ routed: string | null;
198
+ }
199
+ /**
200
+ * Read one `policy.protected_paths` entry, or `null` when it names nothing.
201
+ *
202
+ * The schema already rejects globs, absolute paths and `..` segments; this
203
+ * repeats the structural half of that check so a caller that skipped
204
+ * validation gets an entry that matches nothing rather than an entry that
205
+ * matches surprisingly. Pure, like everything else here: no resolution against
206
+ * a checkout, no disk.
207
+ *
208
+ * The same defensiveness covers the routed form (APRV-266): an object whose
209
+ * `class` is not a well-formed `policy.edit` sub-class matches NOTHING at all
210
+ * rather than falling back to `policy.edit`. A silent fallback would be the
211
+ * worst of the three available answers — the author would read their file and
212
+ * see a rate that is not the rate in force — and the loader refuses such a
213
+ * policy outright, so this branch is only ever reached by a caller that
214
+ * skipped validation.
215
+ */
216
+ export declare function parseProtectedEntry(entry: ProtectedPathEntry): ProtectedEntry | null;
217
+ /**
218
+ * Which protected class does this path name, if any? (APRV-198.)
219
+ *
220
+ * Deliberately name-based rather than location-based: the hook runs in whatever
221
+ * directory the harness is in, and a classifier that resolved paths against a
222
+ * checkout would answer differently in a worktree than in the primary. A
223
+ * false positive here costs one approval prompt; a false negative costs the
224
+ * property the whole file exists to defend.
225
+ *
226
+ * **The check order IS the precedence.** A path is answered by the strictest
227
+ * surface it names, `log.mutate` first, then `policy.core`, then
228
+ * `policy.edit`: `.approval/log/events.jsonl` is a log write and not merely an
229
+ * approval-home write, and a `policy.protected_paths` entry that happens to
230
+ * name a built-in surface cannot demote it, because the built-ins are matched
231
+ * before the policy's own list is read.
232
+ *
233
+ * `extra` carries `policy.protected_paths` (APRV-107). It is strictly
234
+ * ADDITIVE: the built-in set above is protected whatever a policy says, so a
235
+ * policy can widen the protected surface and can never narrow it, and every
236
+ * path it adds in the APRV-107 bare-string spelling lands on `policy.edit` —
237
+ * the reviewable class — because a policy widening its own surface is naming
238
+ * prose and configuration, not minting authority over the gate's organs. Still
239
+ * pure: the caller loads the policy, this function only matches segments.
240
+ *
241
+ * ## Routed entries (APRV-266)
242
+ *
243
+ * An entry written as `{path, class}` answers with the class it names, and the
244
+ * routed tier sits between the built-in `policy.core` tier and the built-in
245
+ * `policy.edit` tier. That position is the whole of the routing rule:
246
+ *
247
+ * - It is BELOW `log.mutate` and `policy.core`, so a routing can never reach
248
+ * the log or the gate's own organs. `{path: .approval/, class:
249
+ * policy.edit.home}` matches nothing, because tier 2 answered first — which
250
+ * is invariant 9's "no verb minting authority" holding at the one place a
251
+ * policy could otherwise have reached past it. The loader refuses such an
252
+ * entry outright rather than letting it sit inert.
253
+ * - It is ABOVE the built-in `policy.edit` set, so a routing CAN re-label a
254
+ * built-in `policy.edit` path: `{path: .github/workflows/, class:
255
+ * policy.edit.ci}` is exactly the sentence a project wants to write. What
256
+ * stops that from being a demotion is not this function but the load-time
257
+ * floor in `policy-load.ts`, which refuses a policy whose routing would
258
+ * resolve a built-in path below what the `policy.edit` line itself resolves
259
+ * to. The classifier stays pure: it reports the class the policy named and
260
+ * judges no autonomy.
261
+ *
262
+ * Among several routed entries matching one path, the MOST SPECIFIC wins (most
263
+ * segments), and declaration order breaks a tie. Most-specific is what a
264
+ * carve-out means — `design/` routed one way and `design/frozen/` another — and
265
+ * a tie is two entries of equal depth both claiming one path, which is the
266
+ * author's own ambiguity and is resolved the only way a pure function can:
267
+ * by the order they wrote them in.
268
+ *
269
+ * A string-only `extra` cannot reach the routed tier at all, so a policy that
270
+ * has not adopted the object form classifies byte for byte as it did before.
271
+ */
272
+ export declare function protectedPathClass(candidate: string, extra?: readonly ProtectedPathEntry[]): string | null;
273
+ /**
274
+ * The class a path takes from the BUILT-IN set alone, ignoring every policy
275
+ * entry (APRV-266).
276
+ *
277
+ * The load-time routing floor needs this and nothing else: "is the path this
278
+ * entry routes one the runtime protects on its own?" decides whether the floor
279
+ * applies to it, and asking {@link protectedPathClass} with the policy's own
280
+ * entries in hand would answer with the routing under test.
281
+ */
282
+ export declare function builtinProtectedPathClass(candidate: string): ProtectedPathClass | null;
283
+ /**
284
+ * Does this path name something only a human may write?
285
+ *
286
+ * The boolean face of {@link protectedPathClass}, kept because two callers
287
+ * (`core/wysiwys.ts`'s protected-path view and the hook's file-tool gate) ask
288
+ * whether a path is protected at all before they ask which surface it is.
289
+ */
290
+ export declare function isProtectedPath(candidate: string, extra?: readonly ProtectedPathEntry[]): boolean;
291
+ /**
292
+ * One path, in the spelling an organ attestation records (APRV-272).
293
+ *
294
+ * Segment-wise: separators collapse, `./` noise disappears, a trailing slash
295
+ * goes, and the result is joined with `/` whatever the caller's platform uses.
296
+ * Two spellings of one file therefore attest and match as one file, which is
297
+ * the property the guard needs, since git reports `.claude/settings.json` and a
298
+ * human at a terminal may type `./.claude/settings.json`.
299
+ *
300
+ * Pure and disk-free, like everything else in this file: it never resolves,
301
+ * never follows a link, and never asks whether the path exists.
302
+ */
303
+ export declare function normalizePathSpelling(candidate: string): string;
304
+ /**
305
+ * Is this path one of the gate's ORGANS — a `policy.core` surface a human can
306
+ * attest by content (APRV-272)?
307
+ *
308
+ * The organs are the harness files that install the hook: `.claude/settings*`
309
+ * and Cursor's `hooks.json`, `hooks/` and `agents/`. They are `policy.core`
310
+ * because an agent that could write them could write itself out of the gate,
311
+ * and `policy.core` is human-only, so the gate mints no record for them at all
312
+ * — which is exactly why the protected-path guard could never pass a hand-made
313
+ * edit to one, and why {@link normalizePathSpelling}-keyed attestation is the
314
+ * evidence for them.
315
+ *
316
+ * Two `policy.core` surfaces are deliberately NOT organs, and both keep their
317
+ * own rules:
318
+ *
319
+ * - The policy file, which has had content attestation since APRV-15 and whose
320
+ * attestation the gate reads on every operation. An organ record must never
321
+ * be able to stand in for it.
322
+ * - Everything under the approval home (`.approval/`): the payload store, the
323
+ * vault, the keys, the environment map, the queue. Those are the human's own
324
+ * ceremony surface, and the log directory under them is `log.mutate`, which
325
+ * is stricter still.
326
+ *
327
+ * The question is asked of the BUILT-IN set alone. A policy may not route any
328
+ * path to `policy.core` (§11.1 invariant 9 and the `policy.edit.*` namespace
329
+ * close that), so consulting the policy's entries here could only ever widen
330
+ * the set of files a human may attest by a routing the classifier already
331
+ * refuses to honor.
332
+ */
333
+ export declare function isGateOrganPath(candidate: string): boolean;
334
+ /**
335
+ * Environment variables whose NAME says they carry a secret.
336
+ *
337
+ * Prefix-matched, because the classifier reads command text and never an
338
+ * environment: it cannot know which `APPROVAL_*` holds a token, so it treats
339
+ * the family alike and lets the allowlist below carve out the runtime's own
340
+ * non-secret names. Erring wide costs one approval prompt.
341
+ *
342
+ * Exported since APRV-205: `core/child-env.ts` starves a spawned child of the
343
+ * same family, and two copies of this list would be one list that drifts.
344
+ *
345
+ * `AGENTMAIL_` joins the family with the AgentMail adapter (APRV-224). An
346
+ * AgentMail API key is a mailbox in one string, and the deployment the adapter
347
+ * assumes hands the agent a key that cannot send while the sending key waits in
348
+ * the vault (SPEC.md §10.4). A key of either half in a granted child's
349
+ * environment would undo that split, so the prefix is withheld like the rest.
350
+ * The adapter's own declared credentials are vault names (`agentmail.api_key`,
351
+ * `agentmail.inbox_id`), so nothing under this prefix passes through by
352
+ * declaration either.
353
+ */
354
+ export declare const SECRET_ENV_PREFIXES: readonly string[];
355
+ /**
356
+ * The runtime's own variables under those prefixes that hold no secret: an
357
+ * identity, a rendering switch, a path. Listed rather than pattern-matched so
358
+ * that adding one is a deliberate act with a reviewer.
359
+ */
360
+ export declare const NON_SECRET_ENV_NAMES: readonly string[];
361
+ /**
362
+ * Does this bare variable name name credential material?
363
+ *
364
+ * Exported since APRV-205 for the same reason the two lists are: the scrub that
365
+ * builds a granted child's environment asks exactly this question, of a real
366
+ * environment rather than of command text, and it must ask it the same way.
367
+ */
368
+ export declare function isSecretEnvName(name: string): boolean;
369
+ interface LexWord {
370
+ /** Literal text, quotes removed and backslash escapes applied. */
371
+ text: string;
372
+ /** True when any part of the word was quoted. */
373
+ quoted: boolean;
374
+ /** Inner text of each `$(…)` in this word, for recursive classification. */
375
+ substitutions: string[];
376
+ }
377
+ interface LexRedirect {
378
+ /** `>` and `>>` write; `<` reads. */
379
+ op: ">" | ">>" | "<";
380
+ target: LexWord;
381
+ }
382
+ interface LexSegment {
383
+ text: string;
384
+ words: LexWord[];
385
+ redirects: LexRedirect[];
386
+ /** Set when the segment contains a construct whose effect cannot be read. */
387
+ opaque: string | null;
388
+ }
389
+ /**
390
+ * Facts about the machine the command will run on, resolved by the CALLER
391
+ * (APRV-267).
392
+ *
393
+ * The classifier is pure and stays pure. Some rules, though, turn on something
394
+ * no string can carry: whether a path names the agent's own scratch space. So
395
+ * the impure half is hoisted out of this file entirely — the caller resolves
396
+ * the roots, this file only compares path segments against them — and the
397
+ * shape is the one `protectedPaths` already established: an optional argument
398
+ * whose absence yields the strictly NARROWER answer. A caller that forgets it
399
+ * classifies every delete the way this file classified it before the field
400
+ * existed; it never invents an authorization.
401
+ *
402
+ * Every root must be ABSOLUTE and already resolved (symlinks followed) by the
403
+ * caller. This file does not touch the disk and cannot check either property,
404
+ * so a caller handing it a relative or unresolved root gets segment matching
405
+ * against exactly what it passed.
406
+ */
407
+ export interface ClassifierContext {
408
+ /**
409
+ * Roots under which a delete is the agent tidying after itself: the session
410
+ * scratchpad the harness allots, and the system temp directory.
411
+ *
412
+ * `src/cli/hook.ts` resolves these (`resolveScratchRoots`) and tightens the
413
+ * answer afterwards with the checks that need the disk — a symlink escaping
414
+ * the root, a git checkout living inside it. Nothing here is a grant on its
415
+ * own: a path under a root still has to survive that second pass.
416
+ */
417
+ scratchRoots?: readonly string[];
418
+ /**
419
+ * Roots a read may stay inside: the gate root, the session scratchpad and the
420
+ * system temp root, plus whatever `read_scope.roots` added (APRV-347).
421
+ *
422
+ * The polarity is the OPPOSITE of `scratchRoots`, and the difference is worth
423
+ * stating. A scratch root LOOSENS a delete, so forgetting the field is safe
424
+ * by construction. A read root NARROWS a read, so forgetting the field would
425
+ * be safe in a different way — it leaves every read `read.shell`, exactly as
426
+ * this classifier answered before the field existed. An EMPTY array means the
427
+ * same thing as absent, and deliberately: read against no roots at all would
428
+ * make every read in the session a decision, which is a fail-closed answer
429
+ * nobody asked for and the one spelling a caller reaches by accident. A
430
+ * caller that means to scope reads passes roots.
431
+ *
432
+ * As with `scratchRoots`, this file only compares segments. The relative
433
+ * paths, the symlinks and the working directory are the hook's second pass,
434
+ * which can only ever tighten the answer reached here.
435
+ */
436
+ readRoots?: readonly string[];
437
+ /**
438
+ * May a login-shell wrapper be classified by the script it runs (APRV-380)?
439
+ *
440
+ * `true` by default, and `false` for exactly one caller: the classifier
441
+ * itself, recursing into an unwrapped script. That is what keeps the unwrap
442
+ * ONE LEVEL deep — a shell nested inside the script is the shape the
443
+ * {@link OPAQUE_BINS} position still covers, and it stays opaque.
444
+ *
445
+ * A caller that passes `false` gets the pre-APRV-380 answer, which is the
446
+ * strictly stricter one: every wrapper refuses. As with every other field
447
+ * here, forgetting it cannot loosen anything.
448
+ */
449
+ unwrapShell?: boolean;
450
+ }
451
+ /** Everything a refinement needs: the binary and the words that followed it. */
452
+ interface RuleContext {
453
+ bin: string;
454
+ /** Words after the binary, quotes already removed. */
455
+ args: string[];
456
+ /** Words after the binary that are not flags. */
457
+ positionals: string[];
458
+ /** The matched subcommand (first positional), or `null`. */
459
+ sub: string | null;
460
+ /**
461
+ * Did any of those words come out of a command substitution? Its text is
462
+ * gone by the time a rule sees it, so a rule that reads its arguments closely
463
+ * (APRV-114's fetch refinement) needs to know that one of them is a hole.
464
+ */
465
+ substituted: boolean;
466
+ /** What the caller knows about the machine (APRV-267). Never read from here. */
467
+ context: ClassifierContext;
468
+ }
469
+ /**
470
+ * A refinement's answer: a class and the rule id that chose it, or a statement
471
+ * that this invocation cannot be read at all.
472
+ *
473
+ * The opaque arm is APRV-283's. A refinement that returns `null` is answered
474
+ * with the interpreter message (`runs inline source`), which is true of
475
+ * `node -e` and false of `find … -exec`, and a refusal that misdescribes the
476
+ * command it refuses sends the agent looking for a flag it did not pass. A
477
+ * refinement that has its own reason states it.
478
+ */
479
+ type Refinement = {
480
+ class: string;
481
+ rule: string;
482
+ path?: string;
483
+ } | {
484
+ opaque: string;
485
+ };
486
+ /**
487
+ * One row of the classification table.
488
+ *
489
+ * `bins` + `subs` is the match; `class` is the answer. A row with a `refine`
490
+ * looks at the flags before answering, and declares every class it can emit in
491
+ * `emits` so the table stays enumerable (the dogfood test reads that list).
492
+ */
493
+ export interface CommandRule {
494
+ /** Stable identifier, printed by `hook classify` and pinned by the fixtures. */
495
+ id: string;
496
+ bins: readonly string[];
497
+ /** Match only when the first positional is one of these. */
498
+ subs?: readonly string[];
499
+ class: string;
500
+ /** Additional classes a refinement may return. */
501
+ emits?: readonly string[];
502
+ /** Flag-sensitive answer; falls back to `class` when it returns `null`. */
503
+ refine?: (ctx: RuleContext) => Refinement | null;
504
+ }
505
+ /**
506
+ * The suffix that marks a Muse model as training on what it is shown.
507
+ *
508
+ * Exported since APRV-350 so the hook adapter's contributor guard and this
509
+ * classifier's `harness.launch.muse` refinement test the SAME mark. Two
510
+ * spellings of "which models are unsafe" would drift, and the direction they
511
+ * drift in is the one where a launch is refused and a tool call is not.
512
+ */
513
+ export declare const CONTRIBUTOR_SUFFIX = "-contributor";
514
+ /** The rule id for a read whose ABSOLUTE target sits outside every root. */
515
+ export declare const READ_OUT_OF_SCOPE_RULE = "read-out-of-scope";
516
+ /** The rule id for a read target whose expansion the text cannot show. */
517
+ export declare const READ_UNREADABLE_TARGET_RULE = "read-unreadable-path";
518
+ /**
519
+ * The table.
520
+ *
521
+ * Order matters: the first row whose binary and subcommand match decides. Rows
522
+ * are grouped by binary, strictest interpretation first within a binary, and
523
+ * every class named here is one SPEC.md §7 declares (§7's developer-workstation
524
+ * namespaces, plus `read.shell` / `read.vcs.remote` / `read.web` under
525
+ * `read.*`), with one addition: the `log.*` namespace of the two verbs that
526
+ * move the log file, introduced by SPEC §10.1's APRV-125 amendment.
527
+ */
528
+ export declare const COMMAND_RULES: readonly CommandRule[];
529
+ /**
530
+ * The script a LOGIN-SHELL WRAPPER runs, when the segment is exactly one
531
+ * (APRV-380), or `null`.
532
+ *
533
+ * ## Why this is not the second parser the table above refuses
534
+ *
535
+ * {@link OPAQUE_BINS} states the position this narrows: a second parser for the
536
+ * same text is a second answer waiting to disagree with the shell's. The hazard
537
+ * that names is reading shell text a SECOND WAY. This is not that. When the
538
+ * argv is exactly `[shell, -lc, script]` and nothing else, the script is the
539
+ * text a Claude Code `Bash` call hands this classifier directly, and what
540
+ * happens to it here is what happens to that: the same lexer, the same segment
541
+ * rules, the same table. No new parser is written, and the text is not read a
542
+ * second way — it is read the first way, by the only reader there is.
543
+ *
544
+ * ## The line, and it is exact
545
+ *
546
+ * Three words, no more: a known shell, one inline-script flag, one script. Any
547
+ * of these keeps the wrapper opaque, because each is a shape whose effect
548
+ * depends on something the argv alone does not say:
549
+ *
550
+ * - extra words (a script FILE, `--`, an option this rule does not model);
551
+ * - an assignment prefix, which changes the environment the script runs in;
552
+ * - a redirection on the wrapper, which is the outer shell's and not the
553
+ * script's;
554
+ * - a substitution in any of the three words, whose effect happens before the
555
+ * shell even starts;
556
+ * - a nested shell inside the script, refused by {@link ClassifierContext} when
557
+ * the recursion runs (the inner classification unwraps nothing).
558
+ *
559
+ * The BINDING does not move. `cli/hook.ts` binds the outer command and argv
560
+ * exactly as APRV-362 built them; what this changes is only which text is
561
+ * classified, and the classes are additional evidence about bytes that are
562
+ * bound elsewhere and unchanged.
563
+ */
564
+ export declare function loginShellScript(segment: LexSegment): string | null;
565
+ /**
566
+ * The rules whose commands RUN CODE THIS RUNTIME DID NOT AUTHOR (APRV-193).
567
+ *
568
+ * Rule ids rather than classes, because the class does not separate them: `npm
569
+ * test`, `node build.mjs`, `tsc` and `mkdir` all resolve to
570
+ * `files.write.workspace`, and only the first three execute a file an agent may
571
+ * have written a minute ago. That is the whole distinction laundering turns on
572
+ * — the command's NAME stops describing its effect exactly when the effect is
573
+ * in a file the name does not mention — so it is drawn here, once, where a
574
+ * future rule's author will see it.
575
+ *
576
+ * Read by `APPROVAL_HOOK_REQUIRE_SANDBOX` (`src/cli/hook.ts`) and by nothing
577
+ * else. It grants nothing and denies nothing on its own: it says which commands
578
+ * the hook may be asked to require a sandbox for, and the requirement is off
579
+ * unless an operator turns it on.
580
+ */
581
+ export declare const CODE_EXECUTING_RULES: readonly string[];
582
+ /**
583
+ * Every class the table can emit, for docs and for the dogfood test.
584
+ *
585
+ * Fixed, and it does not include the `policy.edit` sub-classes (APRV-266): a
586
+ * routed class is emitted only because a particular policy named it, so the set
587
+ * of them is a property of that file rather than of this table. A reader
588
+ * asking "can the classifier ever emit this class?" of a routed name must ask
589
+ * it WITH the policy in hand — {@link emittableClass} is that question.
590
+ */
591
+ export declare const CLASSIFIER_CLASSES: readonly string[];
592
+ /**
593
+ * The class the DAEMON's own cadence advance is gated as (APRV-382).
594
+ *
595
+ * The sub-class exists because the policy grammar has no actor condition and
596
+ * this repository wanted one: an advance publishes records the log already
597
+ * holds, so the daemon may make it unattended, while the same act from a
598
+ * session in a worktree or a human terminal stays supervised. Two classes are
599
+ * how that is written down, and which of them a cycle asks under is decided by
600
+ * `core/advance-cycle.ts` from the running process, never from an argument.
601
+ *
602
+ * NO COMMAND SPELLS IT, on purpose. `approval log advance` classifies
603
+ * `log.advance` whoever types it, so the looser line is unreachable from a
604
+ * shell an agent can drive: it is reached only from inside the daemon process,
605
+ * which an agent cannot become without a `gate.self` command this policy holds
606
+ * at the manual default.
607
+ */
608
+ export declare const ADVANCE_DAEMON_CLASS = "log.advance.daemon";
609
+ /**
610
+ * Classes this RUNTIME emits for its own gated actions, which no command spells.
611
+ *
612
+ * Separate from {@link CLASSIFIER_CLASSES}, which is the binary table's own set
613
+ * and is what `docs/claude-code-hook.md` documents row by row. A class here is
614
+ * emitted by a runtime cycle that registers and requests it directly — the
615
+ * daemon's advance is the first — so a policy declaring it is declaring a line
616
+ * that CAN fire, and the reachability check `core/policy-expectations.ts` runs
617
+ * at the amendment ceremony must say so. Without this list that ceremony would
618
+ * refuse the line `unreachable`, which is a true statement about the command
619
+ * classifier and a false one about the runtime.
620
+ *
621
+ * Adding a name here is a claim that some path in this codebase asks the gate
622
+ * for that class, and widening it is a reviewable diff.
623
+ */
624
+ export declare const RUNTIME_CLASSES: readonly string[];
625
+ /**
626
+ * Can the classifier emit `actionClass` for a project whose policy carries
627
+ * these `protected_paths`? (APRV-266.)
628
+ *
629
+ * {@link CLASSIFIER_CLASSES} answers for the binary table, which is fixed. A
630
+ * routed class is not in that table and never will be: it exists because one
631
+ * policy wrote it beside one path, and the same name in another project's
632
+ * policy would be a different class over different files. So the reachability
633
+ * question — the one `core/policy-expectations.ts` asks of every class a policy
634
+ * declares, so that a policy line nobody can ever fire is caught at the
635
+ * ceremony rather than believed for a year — takes the policy's own entries.
636
+ *
637
+ * A routed name is reachable exactly when some entry routes to it. A
638
+ * `policy.edit.spec` rule in a policy whose `protected_paths` routes nothing to
639
+ * it is a line that will never fire, and saying so is the whole point.
640
+ *
641
+ * {@link RUNTIME_CLASSES} is the third answer (APRV-382): a class no command
642
+ * spells and a runtime cycle asks for directly is reachable in every project,
643
+ * with no policy entry needed, because the path that emits it is in this
644
+ * codebase rather than in the operator's file.
645
+ */
646
+ export declare function emittableClass(actionClass: string, protectedPaths?: readonly ProtectedPathEntry[]): boolean;
647
+ /**
648
+ * Classify a shell command line into the classes it would produce.
649
+ *
650
+ * Every segment must classify: one unreadable segment refuses the whole
651
+ * command, because a command line's effect is the union of its parts and a
652
+ * partial answer would authorize the parts we happened to understand.
653
+ *
654
+ * `protectedPaths` is `policy.protected_paths` (APRV-107), added to the
655
+ * built-in protected set rather than replacing it. Omitting it classifies
656
+ * against the built-ins alone, which is the strictly narrower answer, so a
657
+ * caller that forgets it under-reports the protected classes rather than inventing an
658
+ * authorization; every enforcement path passes the loaded policy's list.
659
+ *
660
+ * Since APRV-266 an entry may be `{path, class}`, routing that path family to a
661
+ * `policy.edit` sub-class. The classifier stays what it was: the entry's class
662
+ * is DATA it copies out of the policy, matched by the same segment matcher as
663
+ * every other entry, so this resolves no autonomy at all.
664
+ *
665
+ * `context` (APRV-267) carries the machine facts a caller has resolved: today
666
+ * only `scratchRoots`. It behaves exactly as `protectedPaths` does: omitting it
667
+ * yields the strictly narrower answer, because every rule that reads it can only
668
+ * ever LOOSEN a class, and no rule reads it to loosen a protected or credential
669
+ * one.
670
+ */
671
+ export declare function classifyCommand(command: string, protectedPaths?: readonly ProtectedPathEntry[], context?: ClassifierContext): CommandClassification;
672
+ /** One segment's words, as the classifier's own tokenizer read them. */
673
+ export interface CommandSegmentWords {
674
+ /** The segment's source text, as written. */
675
+ text: string;
676
+ /** The binary, `VAR=value` prefixes already skipped, quotes already removed. */
677
+ bin: string;
678
+ /** Every word after the binary, flags included, in order. */
679
+ args: string[];
680
+ }
681
+ /**
682
+ * The words of each segment, from the SAME parse {@link classifyCommand} uses.
683
+ *
684
+ * Exported for the channel-side command breakdown (APRV-144): a prompt that
685
+ * says what a compound command does needs the verb and the arguments of each
686
+ * segment, and a display layer that re-split the string itself would be a
687
+ * second tokenizer, free to disagree with the one that chose the class. This
688
+ * runs {@link lex} — the tokenizer — and applies the same assignment-prefix
689
+ * skip `classifySegment` applies, and stops there: it classifies nothing and
690
+ * decides nothing.
691
+ *
692
+ * `null` when the tokenizer refuses the string, which is the same input
693
+ * `classifyCommand` answers `unparseable` for. Segments carrying no binary (a
694
+ * bare assignment, a lone redirection) are omitted: they have no verb to show.
695
+ */
696
+ export declare function commandSegmentWords(command: string): CommandSegmentWords[] | null;
697
+ export {};