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,172 @@
1
+ /**
2
+ * Read scope: which directories an agent may read from (APRV-347).
3
+ *
4
+ * ## The hole this closes
5
+ *
6
+ * Writes and deletes have been path-scoped for a while. `files.delete.scratch`
7
+ * versus `files.delete.out_of_scope` is decided by comparing a resolved target
8
+ * against roots the caller supplied (`ClassifierContext.scratchRoots`), and the
9
+ * file tools carry their target into the payload a grant binds. Reads had none
10
+ * of that: every shell reader classified `read.shell` with no path bound, and
11
+ * `Read`, `Glob` and `Grep` were answered `allow` before classification ever
12
+ * ran. So a policy could say a great deal about what an agent may WRITE and
13
+ * nothing at all about what it may SEE, and an agent working in one directory
14
+ * could read every sibling of it.
15
+ *
16
+ * This module is the pure half of the read-side mirror. It holds the class
17
+ * name, the roots arithmetic, and the one genuinely fiddly question — which
18
+ * words of a read command are paths — and it touches no disk, reads no
19
+ * environment and resolves nothing. The impure half (relative paths resolved
20
+ * against a working directory, symlinks followed, the escape that only the
21
+ * filesystem can see) lives in `src/cli/hook.ts`, exactly where the delete
22
+ * rule's second pass lives, and it can only ever TIGHTEN this file's answer.
23
+ *
24
+ * ## Fail closed, in three places
25
+ *
26
+ * SPEC.md §11.1: ambiguity resolves to the stricter path. Here that is
27
+ *
28
+ * 1. a target this file cannot read as a path (a `$VAR`, a glob, a `~`) is out
29
+ * of scope, because what it expands to is not in the text;
30
+ * 2. a read command naming NO target reads the working directory, so it is
31
+ * checked against the working directory rather than waved through;
32
+ * 3. an empty root list means nothing is in scope — but a caller that passes no
33
+ * roots at all gets today's answer instead (see {@link ClassifierContext}),
34
+ * because a caller that forgot the field must not have every read it makes
35
+ * turned into a decision.
36
+ *
37
+ * ## What a root is
38
+ *
39
+ * The gate root (the directory holding the policy file the runtime resolved),
40
+ * the session scratchpad, and the system temp root. A policy may WIDEN that
41
+ * with `read_scope.roots`; it may not narrow it below the gate root, because a
42
+ * runtime that cannot read its own policy, log and workspace cannot run at all.
43
+ */
44
+ /**
45
+ * The class a read outside every root takes.
46
+ *
47
+ * A sibling of `read.shell` rather than a replacement for it: a read INSIDE the
48
+ * roots is the same ordinary, autonomous act it has always been, and a policy
49
+ * that says nothing about this class gets `defaults.autonomy` for it, which is
50
+ * the fail-closed direction for a name nobody has declared.
51
+ */
52
+ export declare const READ_OUT_OF_SCOPE_CLASS = "read.file.out_of_scope";
53
+ /**
54
+ * The `read_scope` block of a policy (SPEC.md §5, amended APRV-347).
55
+ *
56
+ * Additive and optional, with the same discipline `protected_paths` has: the
57
+ * built-in roots stand whatever this says, so a policy can widen the scope and
58
+ * never shrink it. A relative entry is resolved against the gate root, so a
59
+ * policy stays portable between a checkout and a clone of it.
60
+ */
61
+ export interface ReadScope {
62
+ roots?: string[];
63
+ }
64
+ /**
65
+ * Is `candidate` AT or under `root`, by path segment?
66
+ *
67
+ * At-or-under rather than the delete rule's strictly-under: `ls <gate root>` is
68
+ * a read of the workspace an agent is working in, and a rule that made the root
69
+ * itself out of scope would classify the most ordinary command in the session.
70
+ *
71
+ * Segment matching, never string prefixes: `/dev/muse-other` must not match a
72
+ * root of `/dev/muse`, and `startsWith` says it does.
73
+ */
74
+ export declare function isAtOrUnderReadRoot(candidate: string, root: string): boolean;
75
+ /** Is this path inside ANY of these roots? */
76
+ export declare function isInReadScope(candidate: string, roots: readonly string[]): boolean;
77
+ /**
78
+ * A value whose expansion the classifier cannot see, and therefore may not
79
+ * vouch for. The same test the delete rule applies, and for the same reason:
80
+ * `cat $SOMEWHERE` reads whatever that variable holds.
81
+ */
82
+ export declare function isUnreadableTarget(word: string): boolean;
83
+ /**
84
+ * The effective read roots: the built-ins, plus whatever the policy added.
85
+ *
86
+ * Pure, and every input is the caller's. `gateRoot` is the directory holding
87
+ * the policy file the runtime resolved; `systemRoots` are the scratchpad and
88
+ * temp roots the caller already resolved (`resolveScratchRoots` in the hook);
89
+ * `declared` is `read_scope.roots` verbatim.
90
+ *
91
+ * A declared entry that is relative is joined onto the gate root. A declared
92
+ * entry the caller cannot vouch for — empty, or one this file can see is not a
93
+ * path at all — is DROPPED rather than accepted, because a root is an
94
+ * authorization and a malformed one must not become `/`.
95
+ *
96
+ * The result is de-duplicated and otherwise in the order given, so the first
97
+ * root a path matches is the most specific one a reader would expect.
98
+ */
99
+ export declare function effectiveReadRoots(options: {
100
+ gateRoot: string;
101
+ declared?: readonly string[] | undefined;
102
+ systemRoots?: readonly string[] | undefined;
103
+ }): string[];
104
+ /**
105
+ * How a reader's positionals map to paths.
106
+ *
107
+ * - `all`: every positional is a file or directory (`cat a b`, `ls src`,
108
+ * `diff a b`, `cut -d: -f1 /etc/passwd` — `cut`'s delimiter and field list
109
+ * are flags, so none of its positionals is a pattern).
110
+ * - `after-pattern`: the FIRST positional is a pattern or a script and the rest
111
+ * are paths (`grep needle src`, `sed -n 1,5p file`, `jq .x file.json`) —
112
+ * UNLESS the pattern arrived through a flag (`-e`, `-f`), in which case every
113
+ * positional is a path and the shape collapses to `all`.
114
+ * - `walk`: `find`'s shape — positionals up to the first primary are paths.
115
+ *
116
+ * Binaries absent from this table are absent on purpose, and each omission is a
117
+ * decision not to widen anything:
118
+ *
119
+ * - `echo`, `printf`, `tr`, `test`, `type`, `which`, `pwd`, `true`, `false`,
120
+ * `cd`: their positionals are not files, or name a file without reading its
121
+ * contents. A rule that treated `echo /etc/passwd` as a read of that file
122
+ * would route text through a human.
123
+ * - `basename`, `dirname`, `readlink`, `realpath`: they manipulate or resolve a
124
+ * path and never open it. What leaks is the existence of a name, which is not
125
+ * what this class is about.
126
+ * - `less` and `more` are NOT added to the classifier's reader list by this
127
+ * task. Adding them would take them from `unclassified` (a deny) to
128
+ * `read.shell` (this repository's policy: autonomous), which is a widening,
129
+ * and a task that exists to narrow reads has no business doing that in
130
+ * passing. They are named in the follow-up in `docs/sandboxed-exec.md`.
131
+ */
132
+ export type ReadTargetShape = "all" | "after-pattern" | "walk";
133
+ /** Which readers take paths, and where. Keyed by the binary's basename. */
134
+ export declare const READ_TARGET_SHAPES: Readonly<Record<string, ReadTargetShape>>;
135
+ /**
136
+ * The paths a read command will open, or `null` when this binary is not one
137
+ * whose reads this module scopes.
138
+ *
139
+ * An empty array is a real answer and is NOT the same as `null`: it means this
140
+ * reader opens the working directory (`ls`, `find`, `grep needle` with no file
141
+ * operand), and the caller checks the working directory in its place. `null`
142
+ * means "not a scoped reader", and the caller leaves the segment alone.
143
+ *
144
+ * Treating a non-path positional as a path costs nothing: a bare word resolves
145
+ * against the working directory, which is inside a root in every session this
146
+ * module is meant for. Treating a path as a non-path costs the whole property.
147
+ */
148
+ export declare function readTargetsOf(bin: string, positionals: readonly string[], args?: readonly string[]): string[] | null;
149
+ /**
150
+ * The verdict the PURE half can reach for one target.
151
+ *
152
+ * `out-of-scope` and `in-scope` are final. `needs-disk` is the honest answer
153
+ * for a relative path: its meaning depends on a working directory this file
154
+ * does not have, and the caller with the disk decides it. A caller that cannot
155
+ * do the second pass must treat `needs-disk` as out of scope — which is what
156
+ * `hook classify` and `hook <harness>` both do, through the same function.
157
+ */
158
+ export type ReadTargetVerdict = "in-scope" | "out-of-scope" | "needs-disk";
159
+ /**
160
+ * Read one target against the roots, as far as text alone can settle it.
161
+ *
162
+ * Absolute and inside a root: in scope. Absolute and outside every root: out of
163
+ * scope, decided here, no disk needed. Unreadable (a variable, a glob, a `~`):
164
+ * out of scope, because what it names is not in the text. Anything relative, or
165
+ * carrying a `..`, is `needs-disk`.
166
+ */
167
+ export declare function readTargetVerdict(target: string, roots: readonly string[]): ReadTargetVerdict;
168
+ /**
169
+ * The roots, rendered for a human: `approval policy check`'s line and the
170
+ * hook's verdict note say the same sentence.
171
+ */
172
+ export declare function renderReadRoots(roots: readonly string[]): string;
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Read scope: which directories an agent may read from (APRV-347).
3
+ *
4
+ * ## The hole this closes
5
+ *
6
+ * Writes and deletes have been path-scoped for a while. `files.delete.scratch`
7
+ * versus `files.delete.out_of_scope` is decided by comparing a resolved target
8
+ * against roots the caller supplied (`ClassifierContext.scratchRoots`), and the
9
+ * file tools carry their target into the payload a grant binds. Reads had none
10
+ * of that: every shell reader classified `read.shell` with no path bound, and
11
+ * `Read`, `Glob` and `Grep` were answered `allow` before classification ever
12
+ * ran. So a policy could say a great deal about what an agent may WRITE and
13
+ * nothing at all about what it may SEE, and an agent working in one directory
14
+ * could read every sibling of it.
15
+ *
16
+ * This module is the pure half of the read-side mirror. It holds the class
17
+ * name, the roots arithmetic, and the one genuinely fiddly question — which
18
+ * words of a read command are paths — and it touches no disk, reads no
19
+ * environment and resolves nothing. The impure half (relative paths resolved
20
+ * against a working directory, symlinks followed, the escape that only the
21
+ * filesystem can see) lives in `src/cli/hook.ts`, exactly where the delete
22
+ * rule's second pass lives, and it can only ever TIGHTEN this file's answer.
23
+ *
24
+ * ## Fail closed, in three places
25
+ *
26
+ * SPEC.md §11.1: ambiguity resolves to the stricter path. Here that is
27
+ *
28
+ * 1. a target this file cannot read as a path (a `$VAR`, a glob, a `~`) is out
29
+ * of scope, because what it expands to is not in the text;
30
+ * 2. a read command naming NO target reads the working directory, so it is
31
+ * checked against the working directory rather than waved through;
32
+ * 3. an empty root list means nothing is in scope — but a caller that passes no
33
+ * roots at all gets today's answer instead (see {@link ClassifierContext}),
34
+ * because a caller that forgot the field must not have every read it makes
35
+ * turned into a decision.
36
+ *
37
+ * ## What a root is
38
+ *
39
+ * The gate root (the directory holding the policy file the runtime resolved),
40
+ * the session scratchpad, and the system temp root. A policy may WIDEN that
41
+ * with `read_scope.roots`; it may not narrow it below the gate root, because a
42
+ * runtime that cannot read its own policy, log and workspace cannot run at all.
43
+ */
44
+ /**
45
+ * The class a read outside every root takes.
46
+ *
47
+ * A sibling of `read.shell` rather than a replacement for it: a read INSIDE the
48
+ * roots is the same ordinary, autonomous act it has always been, and a policy
49
+ * that says nothing about this class gets `defaults.autonomy` for it, which is
50
+ * the fail-closed direction for a name nobody has declared.
51
+ */
52
+ export const READ_OUT_OF_SCOPE_CLASS = "read.file.out_of_scope";
53
+ /** Non-empty path segments, `.` dropped. Identical to the classifier's own. */
54
+ function segmentsOf(candidate) {
55
+ return candidate
56
+ .split(/[/\\]+/u)
57
+ .filter((segment) => segment.length > 0 && segment !== ".");
58
+ }
59
+ /**
60
+ * Is `candidate` AT or under `root`, by path segment?
61
+ *
62
+ * At-or-under rather than the delete rule's strictly-under: `ls <gate root>` is
63
+ * a read of the workspace an agent is working in, and a rule that made the root
64
+ * itself out of scope would classify the most ordinary command in the session.
65
+ *
66
+ * Segment matching, never string prefixes: `/dev/muse-other` must not match a
67
+ * root of `/dev/muse`, and `startsWith` says it does.
68
+ */
69
+ export function isAtOrUnderReadRoot(candidate, root) {
70
+ const want = segmentsOf(root);
71
+ const have = segmentsOf(candidate);
72
+ if (want.length === 0)
73
+ return false;
74
+ if (have.length < want.length)
75
+ return false;
76
+ return want.every((segment, index) => segment === have[index]);
77
+ }
78
+ /** Is this path inside ANY of these roots? */
79
+ export function isInReadScope(candidate, roots) {
80
+ return roots.some((root) => isAtOrUnderReadRoot(candidate, root));
81
+ }
82
+ /**
83
+ * A value whose expansion the classifier cannot see, and therefore may not
84
+ * vouch for. The same test the delete rule applies, and for the same reason:
85
+ * `cat $SOMEWHERE` reads whatever that variable holds.
86
+ */
87
+ export function isUnreadableTarget(word) {
88
+ return (word.includes("$") ||
89
+ word.includes("*") ||
90
+ word.includes("?") ||
91
+ word.includes("[") ||
92
+ word.startsWith("~"));
93
+ }
94
+ /**
95
+ * The effective read roots: the built-ins, plus whatever the policy added.
96
+ *
97
+ * Pure, and every input is the caller's. `gateRoot` is the directory holding
98
+ * the policy file the runtime resolved; `systemRoots` are the scratchpad and
99
+ * temp roots the caller already resolved (`resolveScratchRoots` in the hook);
100
+ * `declared` is `read_scope.roots` verbatim.
101
+ *
102
+ * A declared entry that is relative is joined onto the gate root. A declared
103
+ * entry the caller cannot vouch for — empty, or one this file can see is not a
104
+ * path at all — is DROPPED rather than accepted, because a root is an
105
+ * authorization and a malformed one must not become `/`.
106
+ *
107
+ * The result is de-duplicated and otherwise in the order given, so the first
108
+ * root a path matches is the most specific one a reader would expect.
109
+ */
110
+ export function effectiveReadRoots(options) {
111
+ const roots = [];
112
+ const add = (candidate) => {
113
+ if (candidate.length === 0)
114
+ return;
115
+ if (!roots.includes(candidate))
116
+ roots.push(candidate);
117
+ };
118
+ add(options.gateRoot);
119
+ for (const root of options.systemRoots ?? [])
120
+ add(root);
121
+ for (const entry of options.declared ?? []) {
122
+ if (typeof entry !== "string" || entry.length === 0)
123
+ continue;
124
+ if (isUnreadableTarget(entry))
125
+ continue;
126
+ add(entry.startsWith("/") ? entry : `${options.gateRoot}/${entry}`);
127
+ }
128
+ return roots;
129
+ }
130
+ /** Which readers take paths, and where. Keyed by the binary's basename. */
131
+ export const READ_TARGET_SHAPES = {
132
+ cat: "all",
133
+ cksum: "all",
134
+ cut: "all",
135
+ diff: "all",
136
+ du: "all",
137
+ file: "all",
138
+ find: "walk",
139
+ grep: "after-pattern",
140
+ head: "all",
141
+ jq: "after-pattern",
142
+ ls: "all",
143
+ md5sum: "all",
144
+ rg: "after-pattern",
145
+ sed: "after-pattern",
146
+ sha256sum: "all",
147
+ shasum: "all",
148
+ sort: "all",
149
+ stat: "all",
150
+ tail: "all",
151
+ tree: "all",
152
+ uniq: "all",
153
+ wc: "all",
154
+ };
155
+ /**
156
+ * `find` primaries: the first word starting with `-` ends the path list.
157
+ *
158
+ * `find` is the one reader whose arguments are a little language, and its shape
159
+ * is `find [paths…] [expression]`. This one reads the RAW argument list rather
160
+ * than the flag-filtered positionals, because the filter is what tells the
161
+ * paths from the expression: in `find . -name '*.ts'` the pattern `*.ts` is a
162
+ * positional too, and a rule fed the filtered list would read it as a path,
163
+ * find it unreadable, and call an ordinary walk of the workspace out of scope.
164
+ */
165
+ function walkTargets(args) {
166
+ const targets = [];
167
+ for (const word of args) {
168
+ if (word.startsWith("-"))
169
+ break;
170
+ targets.push(word);
171
+ }
172
+ return targets;
173
+ }
174
+ /**
175
+ * Flags that carry the pattern or the script, so every positional is a path.
176
+ *
177
+ * `grep -e needle src`, `sed -f script.sed file`, `rg --regexp needle dir`: the
178
+ * first positional is the FILE, and a rule that skipped it would leave the one
179
+ * target that matters unchecked. Under-detection is the failure mode that
180
+ * matters here — an unchecked read is a read outside the jail — so the shape
181
+ * widens to `all` whenever one of these appears.
182
+ */
183
+ const PATTERN_BEARING_FLAGS = [
184
+ "-e",
185
+ "-f",
186
+ "--regexp",
187
+ "--expression",
188
+ "--file",
189
+ ];
190
+ /** Did the pattern (or script) arrive through a flag rather than a positional? */
191
+ function patternCameFromFlag(args) {
192
+ return args.some((arg) => PATTERN_BEARING_FLAGS.includes(arg) ||
193
+ PATTERN_BEARING_FLAGS.some((flag) => flag.startsWith("--") && arg.startsWith(`${flag}=`)));
194
+ }
195
+ /**
196
+ * The paths a read command will open, or `null` when this binary is not one
197
+ * whose reads this module scopes.
198
+ *
199
+ * An empty array is a real answer and is NOT the same as `null`: it means this
200
+ * reader opens the working directory (`ls`, `find`, `grep needle` with no file
201
+ * operand), and the caller checks the working directory in its place. `null`
202
+ * means "not a scoped reader", and the caller leaves the segment alone.
203
+ *
204
+ * Treating a non-path positional as a path costs nothing: a bare word resolves
205
+ * against the working directory, which is inside a root in every session this
206
+ * module is meant for. Treating a path as a non-path costs the whole property.
207
+ */
208
+ export function readTargetsOf(bin, positionals, args = []) {
209
+ const shape = READ_TARGET_SHAPES[basenameOf(bin)];
210
+ if (shape === undefined)
211
+ return null;
212
+ switch (shape) {
213
+ case "all":
214
+ return [...positionals];
215
+ case "after-pattern":
216
+ return patternCameFromFlag(args) ? [...positionals] : positionals.slice(1);
217
+ case "walk":
218
+ return walkTargets(args.length === 0 ? positionals : args);
219
+ }
220
+ }
221
+ /** The last segment of a command word, so `/usr/bin/cat` reads as `cat`. */
222
+ function basenameOf(bin) {
223
+ const segments = segmentsOf(bin);
224
+ return segments[segments.length - 1] ?? bin;
225
+ }
226
+ /**
227
+ * Read one target against the roots, as far as text alone can settle it.
228
+ *
229
+ * Absolute and inside a root: in scope. Absolute and outside every root: out of
230
+ * scope, decided here, no disk needed. Unreadable (a variable, a glob, a `~`):
231
+ * out of scope, because what it names is not in the text. Anything relative, or
232
+ * carrying a `..`, is `needs-disk`.
233
+ */
234
+ export function readTargetVerdict(target, roots) {
235
+ if (target.length === 0)
236
+ return "needs-disk";
237
+ if (isUnreadableTarget(target))
238
+ return "out-of-scope";
239
+ if (!target.startsWith("/"))
240
+ return "needs-disk";
241
+ if (segmentsOf(target).includes(".."))
242
+ return "needs-disk";
243
+ return isInReadScope(target, roots) ? "in-scope" : "out-of-scope";
244
+ }
245
+ /**
246
+ * The roots, rendered for a human: `approval policy check`'s line and the
247
+ * hook's verdict note say the same sentence.
248
+ */
249
+ export function renderReadRoots(roots) {
250
+ return roots.length === 0 ? "(none)" : roots.join(", ");
251
+ }
252
+ //# sourceMappingURL=read-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-scope.js","sourceRoot":"","sources":["../../../src/core/read-scope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Registration lookups shared by the gate, the daemon, and the doctor.
3
+ *
4
+ * Both exports are pure facts about the log and the Backlog.md layout that
5
+ * more than one layer needs. They lived in `daemon/` until APRV-59's layering
6
+ * guard (`tests/layering.test.ts`) made the rule explicit: `src/cli/` imports
7
+ * from `core/`, never from `daemon/`, so anything the CLI needs from the
8
+ * daemon's projection code moves here instead of widening the exception list.
9
+ */
10
+ import type { EventRecord } from "./log.js";
11
+ /**
12
+ * Where Backlog.md keeps task files, relative to the project directory. The
13
+ * daemon watches it and doctor's `--tasks` defaults to it.
14
+ */
15
+ export declare const DEFAULT_TASKS_DIR = "backlog/tasks";
16
+ /**
17
+ * The latest `task.registered` record for `task`, or `null`.
18
+ *
19
+ * `loose` matches the task id case-insensitively, for the one caller that has
20
+ * no frontmatter to read an id out of and must work from the Backlog.md file
21
+ * name (`task-3 - Slug.md` for a board key written `TASK-3`). It is a matching
22
+ * relaxation only: the record returned, and therefore the id every later step
23
+ * uses, is the log's, never the file name's.
24
+ */
25
+ export declare function latestRegistration(records: EventRecord[], task: string, loose?: boolean): EventRecord | null;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The SQLite index projection (SPEC.md §9.2, `.approval/index.sqlite`).
3
+ *
4
+ * **The database is a cache; the log is the truth.** Everything in this module
5
+ * follows from that one sentence:
6
+ *
7
+ * - The index is *derived*. It is rebuilt from scratch on every call — there is
8
+ * no incremental path, no upsert, no "catch up from seq N". A projection that
9
+ * can drift is a second source of truth, and this project only has one.
10
+ * - Deleting `index.sqlite` loses nothing. Every byte in it is recomputable
11
+ * from `events.jsonl`; the file is a query surface, never a record.
12
+ * - Nothing here writes to the log. {@link reindex} opens `logPath` for reading
13
+ * only: it never appends, never truncates, never repairs a torn tail.
14
+ * - It refuses to index what it cannot vouch for. Chain verification (APRV-7)
15
+ * runs *first*, always. A corrupt log is refused outright; a torn tail is
16
+ * refused unless the caller explicitly opts in, and even then only the intact
17
+ * prefix is indexed and the truncation is recorded in the index metadata. An
18
+ * index that silently contains tampered rows is worse than no index at all.
19
+ *
20
+ * **Determinism.** The same log always produces the same index *content*: rows
21
+ * are inserted in `seq` order inside a single transaction, `payload` is stored
22
+ * as its RFC 8785 canonicalization (so key order cannot vary), and no clock,
23
+ * hostname, or random value is ever written. SQLite's internal file bytes are
24
+ * explicitly *not* claimed to be reproducible — page layout and freelist state
25
+ * are the engine's business. Equality is asserted at the SQL level: identical
26
+ * logs yield identical query results.
27
+ *
28
+ * **Crash safety.** The index is built at a temporary path in the *same*
29
+ * directory and `rename(2)`d over `indexPath` once complete. A reindex killed
30
+ * halfway therefore never leaves a half-built index where a reader expects a
31
+ * whole one: the previous index survives untouched, and the temp file is
32
+ * cleaned up.
33
+ *
34
+ * **Staleness** is the caller's call to make, not this module's. The index
35
+ * records the log head `(seq, hash)` it was built from; {@link indexHead} reads
36
+ * it back so a caller can compare it against a fresh {@link verify} head and
37
+ * decide whether to rebuild.
38
+ *
39
+ * Dependency note: `better-sqlite3`, exact-pinned. `node:sqlite` was ruled out —
40
+ * `engines.node` is `>=20` and `node:sqlite` does not exist before Node 22.5.
41
+ */
42
+ import type { ValidateOptions } from "./validate.js";
43
+ import { type LogHead, type VerifyResult } from "./verify.js";
44
+ /**
45
+ * Version of the index's own table layout. Bumped whenever the SQL schema
46
+ * below changes shape, so a reader can tell an index it understands from one it
47
+ * does not. It is not the log's version: the log is versioned by SPEC.md.
48
+ */
49
+ export declare const INDEX_SCHEMA_VERSION = 1;
50
+ /** Why a reindex was refused. Every failure is one of these, never a throw. */
51
+ export type ReindexErrorCode = "not-clean" | "torn-tail" | "io";
52
+ export interface ReindexError {
53
+ code: ReindexErrorCode;
54
+ message: string;
55
+ /** The verification result behind the refusal, when there is one. */
56
+ verify?: VerifyResult;
57
+ }
58
+ export type ReindexResult = {
59
+ ok: true;
60
+ /** Rows written to `events`. */
61
+ records: number;
62
+ /** The log head the index was built from; `null` for an empty log. */
63
+ head: LogHead | null;
64
+ /** True when only an intact prefix was indexed (forced torn tail). */
65
+ truncated: boolean;
66
+ } | {
67
+ ok: false;
68
+ error: ReindexError;
69
+ };
70
+ /** Options for {@link reindex}. */
71
+ export interface ReindexOptions extends ValidateOptions {
72
+ /**
73
+ * Index the intact prefix of a torn-tail log (records `1..intactThroughSeq`)
74
+ * instead of refusing. The truncation is recorded in `meta`. This never
75
+ * repairs the log — the torn line stays exactly where it is.
76
+ */
77
+ force?: boolean;
78
+ }
79
+ /**
80
+ * Rebuild the SQLite index at `indexPath` from the log at `logPath`.
81
+ *
82
+ * Always verifies the chain first. Returns a structured result rather than
83
+ * throwing; on any refusal the existing index (if any) is left exactly as it
84
+ * was and no new file is created.
85
+ */
86
+ export declare function reindex(logPath: string, indexPath: string, options?: ReindexOptions): ReindexResult;
87
+ /**
88
+ * The provenance recorded in an existing index: the log head it was built from,
89
+ * and whether it covers only an intact prefix.
90
+ *
91
+ * Returns `null` when the file is missing, unreadable, or carries no `meta`
92
+ * row — all of which mean the same thing to a caller: there is no index to
93
+ * trust, rebuild. Compare `head` against a fresh `verify(logPath).head` to
94
+ * detect staleness. Deleting `indexPath` loses nothing.
95
+ */
96
+ export declare function indexHead(indexPath: string): {
97
+ head: LogHead | null;
98
+ truncated: boolean;
99
+ } | null;