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,566 @@
1
+ /**
2
+ * The protected-path guard (APRV-151): CI asks the log, not the session.
3
+ *
4
+ * ## The hole this closes
5
+ *
6
+ * `approval hook claude-code` classifies a file-tool call before it applies,
7
+ * and when the target is a protected path the answer is `policy.edit` and the
8
+ * phone buzzes. That control is only as good as the wiring: a session whose
9
+ * PreToolUse hook never loaded edits SPEC.md with no prompt, no denial, and —
10
+ * this is the part that hurts — no refused-request record either. Nothing in
11
+ * the log says the edit happened, because the thing that would have written
12
+ * the record is the thing that did not run. Two incidents (2026-08-29 SPEC.md
13
+ * in worktree `aprv-145-land`, 2026-08-30 `.github/workflows/ci.yml` in
14
+ * `agent-a3f5d255372d43ac0`) are the same shape.
15
+ *
16
+ * A detector that asked the session whether it was hooked would be asking the
17
+ * party under oversight to report its own oversight, which SPEC.md §11 rules
18
+ * out on principle. So this one never talks to a session. It takes two commits,
19
+ * asks git which protected paths changed between them, and requires — for each
20
+ * one — exact evidence in the committed hash-chained log that either a human
21
+ * granted that edit or the runtime recorded that policy authorized its
22
+ * execution. A change with no evidence fails the pull request. Session wiring
23
+ * is not an input.
24
+ *
25
+ * ## The unit of judgment: one commit (APRV-375)
26
+ *
27
+ * This file answers about ONE PAIR OF BLOBS, whichever pair the caller hands
28
+ * it, so the caller's choice of pair is where the question is really settled.
29
+ * Both callers choose the same one: for every commit of the range, base is that
30
+ * commit's first parent and head is the commit, because a grant binds one edit
31
+ * and the combined diff of a branch is a change nobody made. `core/commit-guard.ts`
32
+ * enumerates the commits, builds the per-commit inputs, states the argument
33
+ * that nothing goes unjudged, and is shared by the CI script and by
34
+ * `approval doctor`'s dark-session arm A.
35
+ *
36
+ * ## What counts as evidence
37
+ *
38
+ * Evidence is about the CHANGE, not about the path (APRV-202). The guard reads
39
+ * the blob at both commits, reduces the difference to the lines this pull
40
+ * request adds and removes, and requires every one of them to trace back to
41
+ * bound material in an authorization record. A path authorized last Tuesday
42
+ * and edited again today has evidence naming it and none covering today's
43
+ * lines, and that fails. Naming remains necessary; it stopped being sufficient.
44
+ *
45
+ * Four verdicts pass:
46
+ *
47
+ * 1. `attested` — the policy file, and the gate's ORGANS. `approval policy
48
+ * amend --commit` appends `policy.updated` carrying `{policy_path, sha256}`,
49
+ * the SHA-256 of the policy bytes a human attested. So for `APPROVAL.md` the
50
+ * guard does not look for a grant at all: it hashes the file's bytes AT THE
51
+ * HEAD COMMIT and requires that exact digest in the log. This is the
52
+ * strongest match in the system — content-level, not path-level — and it is
53
+ * why amendment PRs pass without a `policy.edit` grant, which they would
54
+ * never have.
55
+ *
56
+ * Since APRV-272 the same verdict covers the gate's organs, the harness
57
+ * files that install the hook (`.claude/settings.json` and kin), on a
58
+ * `gate.organ.attested` record carrying that path and that digest. They need
59
+ * it for a stronger reason than the policy file does: an organ is
60
+ * `policy.core`, `policy.core` is human-only, and the gate mints NOTHING for
61
+ * a human-only class — so `granted-file` and `granted-command` cannot exist
62
+ * for one however correctly a human edited it, and before this the guard
63
+ * could only ever fail such a change (PR #300). The path is part of the
64
+ * match: a digest attested for one organ is not evidence for another, which
65
+ * is why the organ record carries a whole relative path where the policy
66
+ * record carries a basename.
67
+ *
68
+ * Since APRV-338 the verdict ALSO covers an ordinary `policy.edit` path on a
69
+ * `gate.path.signed_off` record carrying that path and the digest at head —
70
+ * and it is reached LAST, after every grant search below has failed. That
71
+ * ordering is the whole design. A sign-off is whole-file evidence: it says a
72
+ * human read this file at these bytes, not that they saw a particular line
73
+ * change. A grant binds the hunk. So a change a grant covers passes on the
74
+ * grant and prints the grant as its reason, and the sign-off answers only
75
+ * the case the pending-sign-off suffix was invented for — text a human has
76
+ * read and agrees with, for which no grant was ever taken. The organ's
77
+ * verdict stays first because an organ can have no grant at all, so for one
78
+ * there is nothing weaker to prefer.
79
+ * 2. `policy-authorized-file` — an exact Edit or Write whose verified
80
+ * `execution.started` is preceded by its unique matching registration and no
81
+ * approval request. The registration, start and recomputed stored payload
82
+ * must agree on task, action, class and hash. The start must precede the
83
+ * change and the recorded class must be the class this path is routed to.
84
+ * This records authorization to execute, not successful completion; the
85
+ * exact hunk checks below establish whether those bytes landed.
86
+ *
87
+ * The payload's `file` is the path the hook bound, and the hook binds an
88
+ * ABSOLUTE one: `fileToolGate` resolves the declared target against the
89
+ * session's `cwd`, so an Edit of SPEC.md in a worktree is recorded as
90
+ * `/Users/carter/dev/approval-md/.claude/worktrees/<name>/SPEC.md`. This
91
+ * tier accepts that shape and the bare repository-relative path, matching
92
+ * an absolute one by its trailing segments (see
93
+ * {@link namesProtectedFile}). Until APRV-337 it accepted only the relative
94
+ * shape, which no hook has ever written, so every unsampled
95
+ * supervised-live edit to SPEC.md failed CI (PR #393). Tail matching is
96
+ * sound HERE because the bytes carry the proof: `before` has to occur in
97
+ * the blob at base, `after` in the blob at head, and the replay has to
98
+ * reach HEAD byte-identical, so a scratch copy holding other bytes covers
99
+ * nothing whatever its path says. Verdict 4 below keeps the stricter
100
+ * cwd-join rule, because a command payload describes no bytes.
101
+ * 3. `granted-file` — a file-tool edit. The hook binds the CHANGE rather than
102
+ * the touch (APRV-124), so the bound material carries `file` plus the exact
103
+ * edit: `{before, after}` for an Edit, `{content}` for a Write. That is
104
+ * HUNK-level evidence, and it is used as such. The granted `after` bytes
105
+ * have to occur verbatim in the blob at head before any added line is
106
+ * credited to them, and the granted `before` bytes have to occur in the blob
107
+ * at base before any removed line is. A granted edit whose after-state is not
108
+ * in head is a grant for something that did not land, and covers nothing.
109
+ * Some payloads carry the `{input}` fallback shape instead, which describes
110
+ * no bytes; those name the path and cover nothing.
111
+ * 4. `granted-command` — a shell edit. The bound material is `{command, cwd}`
112
+ * or `{argv, cwd}`, and the guard re-runs the runtime's own
113
+ * {@link classifyCommand} over it, requiring a segment that classifies as a
114
+ * granting class BECAUSE of a word naming this path. A mention is not a
115
+ * grant: `cat SPEC.md` is `read.shell` and proves nothing, and the first
116
+ * draft of this module, which substring-matched, accepted `hook classify --
117
+ * vi SPEC.md` as evidence for a later SPEC.md edit.
118
+ *
119
+ * A command payload cannot describe hunks: `node scripts/apply.mjs` names
120
+ * the file it will rewrite and says nothing about the bytes. So this kind is
121
+ * attributed rather than covered, and three things all have to hold:
122
+ *
123
+ * - The write lands on THIS checkout's copy of the path. The payload's `cwd`
124
+ * joined with the repository-relative path has to be exactly the word the
125
+ * classifier matched (see {@link commandTargetsPath}). Three of the grants
126
+ * that would otherwise have carried PR #187's SPEC.md change were dry runs
127
+ * into `$SCRATCH/dry/SPEC.md`.
128
+ * - The grant was SPENT: an `execution.started` for its `action_key`, and
129
+ * its `execution.completed` when the log has one. A grant nobody spent
130
+ * authorized a command that never ran.
131
+ * - That run sits within {@link DEFAULT_COMMAND_ATTRIBUTION_MS} of the
132
+ * commit AND does not start after it. A command's effect follows its own
133
+ * `execution.started`, so a run four hours after the commit did not write
134
+ * it — a real batch, on 2026-09-02, that a symmetric window would have
135
+ * credited with PR #187's changes.
136
+ *
137
+ * The finding names the run it attributed the change to, so a reader can
138
+ * check the attribution rather than take it. It stays weaker than
139
+ * `granted-file` because time is a weaker link than bytes: everything the
140
+ * approved run wrote to that path in its window is carried by it.
141
+ *
142
+ * There is deliberately no class-level pass. A `policy.edit` grant that exists
143
+ * in the window but names some other file is not evidence that anybody saw
144
+ * THIS edit, and accepting it would let one approved edit launder every other
145
+ * edit in the same window. Class-level grants appear in the failure detail as
146
+ * diagnosis, never as a verdict.
147
+ *
148
+ * ## Grants go stale
149
+ *
150
+ * Naming the path is necessary and not sufficient, because grants accumulate
151
+ * forever. Run without a recency rule against the real log, this guard passed a
152
+ * SPEC.md edit made on 2026-08-29 on the strength of a `git add SPEC.md`
153
+ * granted on 2026-08-20: once any edit to a path has ever been approved, every
154
+ * later edit to that path would inherit the approval. So evidence must also sit
155
+ * within {@link DEFAULT_LOOKBACK_MS} of the commit that introduced the change,
156
+ * on either side of it. Either side, because both orderings are real: a grant
157
+ * shortly BEFORE the commit is the ordinary case, and a grant shortly after is
158
+ * the grant-follows-write anomaly (APRV-117/150 adjacent) — a defect in its own
159
+ * right, but a complete consent trail all the same, and not this guard's to
160
+ * adjudicate.
161
+ *
162
+ * That bound used to be the guard's weakest joint: a repeat edit to the same
163
+ * path inside the window inherited the earlier grant, and the guard passed
164
+ * PR #187, #196 and #207 on grants that authorized some earlier edit. APRV-202
165
+ * closes it, and the window is now the cheap pre-filter in front of the real
166
+ * question. A grant inside the window still has to cover the lines: an
167
+ * uncovered change fails `uncovered-hunk` however fresh the grants naming its
168
+ * path are. Attestation is exempt from the bound, because it matches CONTENT: bytes that
169
+ * hash to an attested digest are the attested bytes whenever they were signed.
170
+ * And when git cannot date the change at all, no bound is applied rather than a
171
+ * weaker one invented: see `changeTsFor` on {@link GuardInput} for why a bound
172
+ * against the head commit would have been theatre. The finding says which of
173
+ * the two it got, every time.
174
+ *
175
+ * ## Two anchors, because git keeps two dates (APRV-339)
176
+ *
177
+ * A commit carries an author date and a committer date, and they answer
178
+ * different questions. ORDERING — could this record have written these bytes,
179
+ * or does it come after them — is measured against the COMMITTER date, the
180
+ * moment those bytes were committed: an amend or a rebase only ever moves it
181
+ * later, so it cannot turn a genuine earlier start into a post-hoc one.
182
+ * STALENESS — is this evidence about this change or about some edit months ago
183
+ * — is measured against the AUTHOR date, which a rebase does not move, so
184
+ * replaying history does not expire evidence. Measuring both against the author
185
+ * date is what refused PR #393: `git commit --amend` kept the first author date
186
+ * while the bytes were committed 98 seconds later, and the unattended start
187
+ * that wrote them sat between the two and was read as having happened after the
188
+ * change. A caller that has only one date supplies it alone, and it answers
189
+ * both questions, which is where every caller stood before this.
190
+ *
191
+ * ## How a hunk is decided to be covered
192
+ *
193
+ * The unit is a line of text. `added` is the multiset of lines the head blob
194
+ * has and the base blob does not; `removed` is the converse. A line is covered
195
+ * when its exact text appears in the granted material of some in-window grant
196
+ * (in `after`/`content` for an added line, in `before` for a removed one) and
197
+ * that material is anchored to the blob it claims, as described above. Coverage
198
+ * may be assembled from several grants, because one pull request may carry
199
+ * several approved edits to one file; the finding names every contributing
200
+ * grant and puts the strongest and nearest at the head.
201
+ *
202
+ * A bound Edit may describe LESS than a line, because the hook binds exactly
203
+ * what the tool replaced: rewriting part of a long paragraph binds a fragment
204
+ * and covers neither the line it removed nor the line it added. So before the
205
+ * replay below there is a line-local step (APRV-340). A fragment that occurs
206
+ * exactly once in exactly one line of the blob at base has one possible
207
+ * effect — that line, rewritten — and when the line it rewrites is one this
208
+ * change removes and the line it produces is one this change adds, both lines
209
+ * are credited to it. It is a one-step replay that needs no search, and it
210
+ * carries every eligibility condition the replay carries; what it does not
211
+ * carry is the replay's budget, which a 200 KB file with forty naming
212
+ * candidates exhausts before reaching a proof (PR #393).
213
+ *
214
+ * Three properties of that choice are worth stating, because each is a limit:
215
+ *
216
+ * - A blob that differs while its line multiset does not is a REORDERING, and
217
+ * it is reported as one uncovered hunk rather than as no change. A rule that
218
+ * compared multisets alone would pass a rewrite that only moved paragraphs.
219
+ * - Blank and whitespace-only lines neither need coverage nor give it. They
220
+ * carry no content, and treating them as material would let one granted edit
221
+ * containing an empty line cover every blank line added anywhere.
222
+ * - Coverage is by line text, not by position. An added line whose exact text
223
+ * appears in some granted edit counts as covered even if it landed somewhere
224
+ * else in the file. Tightening that to positions would trade a narrow
225
+ * laundering channel (repeating a line the human already approved) for false
226
+ * failures on every rebase and re-indent, and the first is the cheaper loss.
227
+ *
228
+ * ## The evidence surface is not a protected write surface
229
+ *
230
+ * `.approval/` is protected wherever it sits, and the daemon appends to it
231
+ * every time anything is approved — so a records / log-advance pull request
232
+ * changes `.approval/log/events.jsonl`, the payload store beside it, and the
233
+ * regenerated `QUEUE.md`. Requiring a grant for those would require a grant for
234
+ * the evidence, which is circular and would make it impossible to land the very
235
+ * commits this guard reads. {@link EXEMPT_PREFIXES} names that surface, and
236
+ * nothing else under `.approval/` is exempt: the vault, the environment map and
237
+ * anything else that lands there is still a protected write.
238
+ *
239
+ * ## The lag, and the ordering rule it implies
240
+ *
241
+ * The log on `main` trails the primary checkout's live log; advances land
242
+ * periodically as records pull requests. A grant made this morning may not be
243
+ * on `main` yet, and this module can only see the records its caller hands it.
244
+ * That is not a bug to paper over, it is an ordering rule, and every failure
245
+ * states it: **the log advance carrying the grant must be pushed to a records
246
+ * branch or merged to main before or with the protected-path pull request.**
247
+ * Each failure also names the window it searched (the seq and timestamp range
248
+ * of the records it was given) so the reader can tell "the grant is not there"
249
+ * from "the grant is newer than this log".
250
+ *
251
+ * A records branch counts because the caller may read further along the same
252
+ * chain than the head commit does (APRV-260): `scripts/protected-path-guard.mjs`
253
+ * takes the freshest committed copy that carries head's own last record at
254
+ * head's index, so an advance that is pushed but not yet merged is already
255
+ * evidence. Nothing here changes: this module still reads only the verified
256
+ * records it was handed, and the window it reports is the window it searched.
257
+ *
258
+ * ## Fail closed
259
+ *
260
+ * A missing log, a log that does not pass chain verification, and a protected
261
+ * path with no evidence are all failures, each with its own code. Records that
262
+ * have not passed verification are never read for evidence (SPEC.md §11.1
263
+ * invariant 1): the caller hands this module the verified records or none.
264
+ * This module appends nothing, reads no clock, and performs no IO of its own —
265
+ * git plumbing and file reads live in the caller.
266
+ */
267
+ import { type ProtectedPathEntry } from "./command-class.js";
268
+ import type { EventRecord } from "./log.js";
269
+ /**
270
+ * Classes whose grant authorizes a protected-path write.
271
+ *
272
+ * `policy.edit` is what the hook and this repository's policy use.
273
+ * `policy.core` is accepted alongside it because SPEC.md §7's taxonomy admits
274
+ * a stricter sibling and a policy that routed the highest-value edits there
275
+ * should not lose its evidence.
276
+ */
277
+ export declare const GRANTING_CLASSES: readonly string[];
278
+ /**
279
+ * Does a grant of this class authorize a protected-path write? (APRV-266.)
280
+ *
281
+ * The named classes above, plus any `policy.edit` sub-class a policy routes a
282
+ * path to. Both directions of the cross-check matter and both are accepted:
283
+ *
284
+ * - A grant of the ROUTED class is the ordinary case once a policy adopts
285
+ * routing. `policy.edit.spec` is the class the hook asked about and the class
286
+ * the human decided, so it is the class the record carries.
287
+ * - A grant of `policy.edit` ITSELF is accepted for a path now routed, because
288
+ * a routing is a policy edit and the two are not synchronized: a grant taken
289
+ * under yesterday's string-only policy, or under the daemon's own fallback
290
+ * when the policy would not load, names `policy.edit` for a path today's
291
+ * policy routes. Refusing it would make adopting a routing retroactively
292
+ * invalidate evidence that was correct when it was taken.
293
+ *
294
+ * What this does NOT do is loosen the guard: the class only opens the door, and
295
+ * the naming test — the grant's own material has to name THIS path, and since
296
+ * APRV-202 has to cover the actual hunks — is unchanged and is what decides.
297
+ * A sub-class name is not authority over anything; the namespace is closed to
298
+ * `policy.edit.*` in the classifier, so no grant of `log.mutate` or
299
+ * `policy.core` can be manufactured by naming one (SPEC.md §11.1 invariant 9).
300
+ */
301
+ export declare function isGrantingClass(actionClass: string): boolean;
302
+ /**
303
+ * The daemon's own append surface: evidence, not a protected write.
304
+ *
305
+ * Repository-relative, `/`-separated prefixes. A changed path equal to one of
306
+ * these, or under one of the directory ones, is skipped before any evidence is
307
+ * sought. See the module note for why this carve-out is narrow on purpose.
308
+ */
309
+ export declare const EXEMPT_PREFIXES: readonly string[];
310
+ /** Every way this guard can refuse, as stable codes. */
311
+ export declare const GUARD_FAILURE_CODES: readonly [
312
+ /** The log blob does not exist at the head commit at all. */
313
+ "log-missing",
314
+ /** The log exists and does not pass chain verification. */
315
+ "log-unverified",
316
+ /** A protected path changed and nothing in the log is evidence for it. */
317
+ "no-evidence",
318
+ /**
319
+ * Grants DO name this path, inside the window, and some added or removed line
320
+ * of this change traces to none of their bound material (APRV-202).
321
+ *
322
+ * Distinct from `no-evidence` on purpose, because the two ask different
323
+ * things of the reader. `no-evidence` says nobody approved anything about
324
+ * this file and the question is whether the hook fired at all.
325
+ * `uncovered-hunk` says somebody approved something about this file and it
326
+ * was not this, which is the repeat-edit shape: the grant is real, the
327
+ * consent trail for THESE bytes is missing, and the fix is to take the
328
+ * change to the gate rather than to hunt for a lost record.
329
+ */
330
+ "uncovered-hunk",
331
+ /**
332
+ * The blobs at base and head could not be read for this path (git could not
333
+ * show them, or they are binary), so no coverage could be established.
334
+ * Failing is the fail-closed direction: a change the guard cannot read is not
335
+ * a change it has checked.
336
+ */
337
+ "change-unreadable"];
338
+ export type GuardFailureCode = (typeof GUARD_FAILURE_CODES)[number];
339
+ /** How the verified log and bound material authorize this exact path. */
340
+ export type EvidenceKind = "attested" | "policy-authorized-file" | "granted-file" | "granted-command";
341
+ /**
342
+ * How far from the commit that introduced a change a grant may sit and still be
343
+ * evidence for it: seven days, either side. See the module note.
344
+ */
345
+ export declare const DEFAULT_LOOKBACK_MS: number;
346
+ /**
347
+ * How far from the change commit a granted command's RUN may sit and still be
348
+ * the run that produced it: six hours, either side.
349
+ *
350
+ * Deliberately much tighter than {@link DEFAULT_LOOKBACK_MS}, because it is
351
+ * carrying much more weight. A file grant is checked against the bytes, so the
352
+ * recency bound is only a sanity rail around a content match. A command grant
353
+ * has no bytes to check, so time is the whole attribution, and a week of it
354
+ * would re-open exactly the hole this closes: every later edit to a path some
355
+ * approved script once wrote would inherit that script's grant. Six hours is
356
+ * about a working session, which is the unit of "this run produced this
357
+ * commit"; a batch that legitimately takes longer than that is asked for a
358
+ * fresh approval, which costs one tap.
359
+ */
360
+ export declare const DEFAULT_COMMAND_ATTRIBUTION_MS: number;
361
+ /** Fail-closed resource bounds for exact protected-edit reconstruction. */
362
+ export declare const EXACT_REPLAY_MAX_CANDIDATES = 128;
363
+ export declare const EXACT_REPLAY_MAX_STATES = 2048;
364
+ export declare const EXACT_REPLAY_MAX_EXAMINED_BYTES: number;
365
+ export interface GuardFinding {
366
+ /** The changed path, repository-relative. */
367
+ path: string;
368
+ ok: boolean;
369
+ /** Present on a pass. */
370
+ evidence?: EvidenceKind;
371
+ /** The log record that is the evidence, on a pass. */
372
+ seq?: number;
373
+ ts?: string;
374
+ actor?: string;
375
+ /**
376
+ * Every grant that covered part of this change, in report order, strongest
377
+ * and nearest first. `seq` is the head of this list.
378
+ */
379
+ coveredBy?: readonly number[];
380
+ /** Present on a failure. */
381
+ code?: GuardFailureCode;
382
+ /**
383
+ * On `uncovered-hunk`, a sample of the lines that traced to no granted
384
+ * material, `+` for added and `-` for removed.
385
+ */
386
+ uncovered?: readonly string[];
387
+ /** Prose a reader can act on, on either outcome. */
388
+ detail: string;
389
+ }
390
+ /** A protected path's bytes at both ends of the range the guard is checking. */
391
+ export interface ChangeBlobs {
392
+ /** The blob at base, or `null` when this pull request adds the file. */
393
+ base: string | null;
394
+ /** The blob at head, or `null` when this pull request deletes it. */
395
+ head: string | null;
396
+ }
397
+ /**
398
+ * The two dates git carries for the commit that changed a path (APRV-339).
399
+ *
400
+ * They are the same instant on an ordinary commit and they part company on an
401
+ * amended or rebased one, which is why the guard asks its two time questions
402
+ * against different ones. See {@link GuardInput.changeTsFor}.
403
+ */
404
+ export interface ChangeTimestamps {
405
+ /** git's author date (`%aI`): when the change was written. */
406
+ author: string | null;
407
+ /** git's committer date (`%cI`): when those bytes were committed. */
408
+ committer: string | null;
409
+ }
410
+ /** The window of log the guard could see, for the failure messages. */
411
+ export interface LogWindow {
412
+ /** Lowest and highest `seq` in the log at head, or `null` for an empty log. */
413
+ firstSeq: number | null;
414
+ lastSeq: number | null;
415
+ firstTs: string | null;
416
+ lastTs: string | null;
417
+ /** The commit range the caller diffed. */
418
+ base: string;
419
+ head: string;
420
+ }
421
+ export interface GuardInput {
422
+ /** Repository-relative paths that differ between base and head, deletions included. */
423
+ changedPaths: readonly string[];
424
+ /**
425
+ * Records from the log at HEAD that have passed chain verification.
426
+ * `null` means the log could not be verified or could not be read; pair it
427
+ * with `logStatus` so the guard can say which.
428
+ */
429
+ records: readonly EventRecord[] | null;
430
+ logStatus: "ok" | "missing" | "unverified";
431
+ /** Why the log did not verify, when `logStatus` is not `ok`. */
432
+ logDetail?: string;
433
+ /** `policy.protected_paths` from the policy, widening the built-in set. */
434
+ policyProtectedPaths: readonly ProtectedPathEntry[];
435
+ /**
436
+ * SHA-256 of the policy file's bytes at the head commit, or `null` when the
437
+ * head tree carries no policy file. Only used for the `attested` verdict.
438
+ */
439
+ policySha256AtHead: string | null;
440
+ /** The policy file's repository-relative path, e.g. `APPROVAL.md`. */
441
+ policyPath: string;
442
+ /**
443
+ * SHA-256 of one GATE ORGAN's bytes at the head commit, or `null` when the
444
+ * head tree does not carry that path (APRV-272).
445
+ *
446
+ * The per-path counterpart of {@link policySha256AtHead}, computed the same
447
+ * way and from the same place — the blob at the head COMMIT, never the
448
+ * working tree. Only used for the `attested` verdict on an organ.
449
+ *
450
+ * OPTIONAL, and a caller that omits it gets no organ verdict at all rather
451
+ * than a weaker one: an organ change then falls through to the grant search
452
+ * and fails like any other unevidenced protected path. That is the
453
+ * fail-closed direction, and it is what keeps a caller written before this
454
+ * field existed correct rather than newly permissive.
455
+ *
456
+ * A DELETED organ resolves to `null` here, so removing one cannot pass by
457
+ * attestation: there are no bytes at head for a human to have signed. That is
458
+ * deliberate — the repair for a deletion is a grant or a human's own commit
459
+ * outside a pull request, not a record about bytes that no longer exist.
460
+ */
461
+ organSha256AtHead?: (path: string) => string | null;
462
+ /**
463
+ * SHA-256 of ANY guarded path's bytes at the head commit, or `null` when the
464
+ * head tree does not carry it (APRV-338).
465
+ *
466
+ * The same computation {@link organSha256AtHead} performs and a separate
467
+ * field, because the two answer for different surfaces and a caller wired for
468
+ * one must not silently start answering for the other. Only used for the
469
+ * sign-off half of the `attested` verdict, on `policy.edit` and
470
+ * `policy.edit.*` paths.
471
+ *
472
+ * OPTIONAL and fail-closed in the same direction: a caller that omits it gets
473
+ * no sign-off verdict at all, so a change falls through to the ordinary
474
+ * failure rather than passing on a record nothing was checked against. A
475
+ * DELETED path resolves to `null` here, so removing a protected file cannot
476
+ * pass by sign-off — there are no bytes at head for a human to have read.
477
+ */
478
+ pathSha256AtHead?: (path: string) => string | null;
479
+ /**
480
+ * Resolve bound material from the committed payload store, by hash.
481
+ * Returns `null` when the head tree does not carry that payload.
482
+ */
483
+ payloadFor: (hash: string) => unknown | null;
484
+ /**
485
+ * When the newest commit in `base..head` that touched this path landed, as
486
+ * ISO-8601 instants, or `null` when git could not say.
487
+ *
488
+ * Two dates, because git keeps two and they answer different questions
489
+ * (APRV-339). A {@link ChangeTimestamps} pair carries git's author date
490
+ * (`%aI`) and its committer date (`%cI`); a bare string, or a pair with one
491
+ * side missing or unparseable, means the one date the caller has answers
492
+ * both, which is exactly what every caller written before APRV-339 supplies.
493
+ *
494
+ * - The ORDERING question — could this start have written these bytes, or
495
+ * does it come after them — is measured against the COMMITTER date, the
496
+ * moment those bytes were committed. An amend or a rebase only moves it
497
+ * later, so it never turns a genuine earlier start into a post-hoc one,
498
+ * while the author date does exactly that: `git commit --amend` keeps the
499
+ * first author date, so an edit folded into the amend sits AFTER it and was
500
+ * refused as post-hoc (PR #393, commit c03cbb8).
501
+ * - The STALENESS question — is this evidence about this change or about some
502
+ * edit months ago — is measured against the AUTHOR date, which a rebase
503
+ * does not move, so replaying history does not expire evidence.
504
+ *
505
+ * With no anchor — `null`, or values that do not parse — NO recency bound
506
+ * is applied to this path, and the finding says so in its own text rather
507
+ * than reporting a bound it did not enforce.
508
+ *
509
+ * That is stated plainly because it is the accepting direction and it would
510
+ * be easy to dress up. The alternative considered was to bound the grant
511
+ * against the head commit instead, and it was rejected as theatre: every
512
+ * record in the log AT head is already before head by construction, so the
513
+ * rule would pass everything it was asked about while reading like a check.
514
+ * A guard that reports a bound it cannot enforce is worse than one that
515
+ * admits it has none, because only the first kind gets trusted.
516
+ *
517
+ * In practice the anchor is missing only when git cannot date a path it just
518
+ * reported in the diff, which is a broken-git condition rather than an
519
+ * attacker-reachable one; refusing on it would fire only on that breakage.
520
+ * The path-level evidence requirement is unaffected and still holds.
521
+ */
522
+ changeTsFor: (path: string) => string | ChangeTimestamps | null;
523
+ /**
524
+ * The path's bytes at base and at head, or `null` when they could not be
525
+ * read (git could not show them, or the blob is binary).
526
+ *
527
+ * This is what makes the guard's question "was THIS change approved" rather
528
+ * than "was this path approved once" (APRV-202). `null` fails the path with
529
+ * `change-unreadable` rather than falling back to the path-level rule: the
530
+ * fallback is the hole.
531
+ */
532
+ blobsFor: (path: string) => ChangeBlobs | null;
533
+ /** Override {@link DEFAULT_LOOKBACK_MS}. */
534
+ lookbackMs?: number;
535
+ /** Override {@link DEFAULT_COMMAND_ATTRIBUTION_MS}. */
536
+ commandAttributionMs?: number;
537
+ window: LogWindow;
538
+ }
539
+ export interface GuardReport {
540
+ ok: boolean;
541
+ /** Every protected path that changed, in the order git reported them. */
542
+ findings: readonly GuardFinding[];
543
+ /** Changed paths skipped as the daemon's own append surface. */
544
+ exempt: readonly string[];
545
+ window: LogWindow;
546
+ }
547
+ /** Is this changed path the daemon's own evidence surface? */
548
+ export declare function isExemptPath(path: string): boolean;
549
+ /**
550
+ * Is this changed path one whose edit requires a human decision?
551
+ *
552
+ * The guarded set is exactly the hook's ({@link isProtectedPath}, built-ins
553
+ * plus `policy.protected_paths`) minus the evidence surface. Sharing the
554
+ * predicate is the point: a CI guard whose idea of "protected" drifted from the
555
+ * hook's would fail the changes the hook already gated and pass the ones it
556
+ * would have caught.
557
+ */
558
+ export declare function isGuardedPath(path: string, policyProtectedPaths: readonly ProtectedPathEntry[]): boolean;
559
+ /**
560
+ * Evaluate a candidate against the committed log. Pure: no IO, no clock.
561
+ *
562
+ * @see GuardInput for what the caller has to gather.
563
+ */
564
+ export declare function evaluateProtectedPaths(input: GuardInput): GuardReport;
565
+ /** The report as the lines CI prints. Pure. */
566
+ export declare function renderGuardReport(report: GuardReport): string;