approval-md 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (722) hide show
  1. package/LICENSE +176 -0
  2. package/NOTICE +5 -0
  3. package/README.md +940 -4
  4. package/SPEC.md +476 -34
  5. package/cli.js +29 -3
  6. package/dist/src/adapters/agentmail.d.ts +426 -0
  7. package/dist/src/adapters/agentmail.js +1200 -0
  8. package/dist/src/adapters/agentmail.js.map +1 -0
  9. package/dist/src/adapters/conformance.d.ts +149 -0
  10. package/dist/src/adapters/conformance.js +461 -0
  11. package/dist/src/adapters/conformance.js.map +1 -0
  12. package/dist/src/adapters/contract.d.ts +628 -0
  13. package/dist/src/adapters/contract.js +1035 -0
  14. package/dist/src/adapters/contract.js.map +1 -0
  15. package/dist/src/adapters/email.d.ts +324 -0
  16. package/dist/src/adapters/email.js +749 -0
  17. package/dist/src/adapters/email.js.map +1 -0
  18. package/dist/src/adapters/env-passphrase.d.ts +93 -0
  19. package/dist/src/adapters/env-passphrase.js +132 -0
  20. package/dist/src/adapters/env-passphrase.js.map +1 -0
  21. package/dist/src/adapters/public.d.ts +11 -0
  22. package/dist/src/adapters/public.js +11 -0
  23. package/dist/src/adapters/public.js.map +1 -0
  24. package/dist/src/adapters/registry.d.ts +59 -0
  25. package/dist/src/adapters/registry.js +77 -0
  26. package/dist/src/adapters/registry.js.map +1 -0
  27. package/dist/src/adapters/smtp.d.ts +213 -0
  28. package/dist/src/adapters/smtp.js +499 -0
  29. package/dist/src/adapters/smtp.js.map +1 -0
  30. package/dist/src/adapters/vault-provider.d.ts +114 -0
  31. package/dist/src/adapters/vault-provider.js +161 -0
  32. package/dist/src/adapters/vault-provider.js.map +1 -0
  33. package/dist/src/adapters/zzz.d.ts +66 -0
  34. package/dist/src/adapters/zzz.js +299 -0
  35. package/dist/src/adapters/zzz.js.map +1 -0
  36. package/dist/src/channels/batch.d.ts +109 -0
  37. package/dist/src/channels/batch.js +121 -0
  38. package/dist/src/channels/batch.js.map +1 -0
  39. package/dist/src/channels/cli.d.ts +193 -0
  40. package/dist/src/channels/cli.js +468 -0
  41. package/dist/src/channels/cli.js.map +1 -0
  42. package/dist/src/channels/conformance.d.ts +92 -0
  43. package/dist/src/channels/conformance.js +445 -0
  44. package/dist/src/channels/conformance.js.map +1 -0
  45. package/dist/src/channels/contract.d.ts +623 -0
  46. package/dist/src/channels/contract.js +494 -0
  47. package/dist/src/channels/contract.js.map +1 -0
  48. package/dist/src/channels/payload-view.d.ts +35 -0
  49. package/dist/src/channels/payload-view.js +43 -0
  50. package/dist/src/channels/payload-view.js.map +1 -0
  51. package/dist/src/channels/render-queue.d.ts +149 -0
  52. package/dist/src/channels/render-queue.js +564 -0
  53. package/dist/src/channels/render-queue.js.map +1 -0
  54. package/dist/src/channels/tagging.d.ts +196 -0
  55. package/dist/src/channels/tagging.js +723 -0
  56. package/dist/src/channels/tagging.js.map +1 -0
  57. package/dist/src/channels/telegram.d.ts +1832 -0
  58. package/dist/src/channels/telegram.js +3190 -0
  59. package/dist/src/channels/telegram.js.map +1 -0
  60. package/dist/src/channels/web.d.ts +341 -0
  61. package/dist/src/channels/web.js +903 -0
  62. package/dist/src/channels/web.js.map +1 -0
  63. package/dist/src/cli/adapter.d.ts +90 -0
  64. package/dist/src/cli/adapter.js +288 -0
  65. package/dist/src/cli/adapter.js.map +1 -0
  66. package/dist/src/cli/amend.d.ts +59 -0
  67. package/dist/src/cli/amend.js +2171 -0
  68. package/dist/src/cli/amend.js.map +1 -0
  69. package/dist/src/cli/args.d.ts +43 -0
  70. package/dist/src/cli/args.js +86 -0
  71. package/dist/src/cli/args.js.map +1 -0
  72. package/dist/src/cli/attest.d.ts +41 -0
  73. package/dist/src/cli/attest.js +307 -0
  74. package/dist/src/cli/attest.js.map +1 -0
  75. package/dist/src/cli/audit-card.d.ts +62 -0
  76. package/dist/src/cli/audit-card.js +201 -0
  77. package/dist/src/cli/audit-card.js.map +1 -0
  78. package/dist/src/cli/audit.d.ts +59 -0
  79. package/dist/src/cli/audit.js +460 -0
  80. package/dist/src/cli/audit.js.map +1 -0
  81. package/dist/src/cli/channel-telegram.d.ts +806 -0
  82. package/dist/src/cli/channel-telegram.js +2063 -0
  83. package/dist/src/cli/channel-telegram.js.map +1 -0
  84. package/dist/src/cli/channel-web.d.ts +131 -0
  85. package/dist/src/cli/channel-web.js +357 -0
  86. package/dist/src/cli/channel-web.js.map +1 -0
  87. package/dist/src/cli/channel.d.ts +71 -0
  88. package/dist/src/cli/channel.js +438 -0
  89. package/dist/src/cli/channel.js.map +1 -0
  90. package/dist/src/cli/checkpoint-tap.d.ts +169 -0
  91. package/dist/src/cli/checkpoint-tap.js +238 -0
  92. package/dist/src/cli/checkpoint-tap.js.map +1 -0
  93. package/dist/src/cli/codex.d.ts +2 -0
  94. package/dist/src/cli/codex.js +172 -0
  95. package/dist/src/cli/codex.js.map +1 -0
  96. package/dist/src/cli/coverage.d.ts +61 -0
  97. package/dist/src/cli/coverage.js +343 -0
  98. package/dist/src/cli/coverage.js.map +1 -0
  99. package/dist/src/cli/daemon.d.ts +120 -0
  100. package/dist/src/cli/daemon.js +631 -0
  101. package/dist/src/cli/daemon.js.map +1 -0
  102. package/dist/src/cli/doctor.d.ts +129 -0
  103. package/dist/src/cli/doctor.js +2762 -0
  104. package/dist/src/cli/doctor.js.map +1 -0
  105. package/dist/src/cli/env.d.ts +65 -0
  106. package/dist/src/cli/env.js +302 -0
  107. package/dist/src/cli/env.js.map +1 -0
  108. package/dist/src/cli/execute.d.ts +202 -0
  109. package/dist/src/cli/execute.js +1682 -0
  110. package/dist/src/cli/execute.js.map +1 -0
  111. package/dist/src/cli/exit-codes.d.ts +73 -0
  112. package/dist/src/cli/exit-codes.js +82 -0
  113. package/dist/src/cli/exit-codes.js.map +1 -0
  114. package/dist/src/cli/feedback.d.ts +60 -0
  115. package/dist/src/cli/feedback.js +205 -0
  116. package/dist/src/cli/feedback.js.map +1 -0
  117. package/dist/src/cli/gate-window.d.ts +40 -0
  118. package/dist/src/cli/gate-window.js +294 -0
  119. package/dist/src/cli/gate-window.js.map +1 -0
  120. package/dist/src/cli/gate.d.ts +68 -0
  121. package/dist/src/cli/gate.js +557 -0
  122. package/dist/src/cli/gate.js.map +1 -0
  123. package/dist/src/cli/git-scope.d.ts +190 -0
  124. package/dist/src/cli/git-scope.js +295 -0
  125. package/dist/src/cli/git-scope.js.map +1 -0
  126. package/dist/src/cli/gloss-attach.d.ts +85 -0
  127. package/dist/src/cli/gloss-attach.js +107 -0
  128. package/dist/src/cli/gloss-attach.js.map +1 -0
  129. package/dist/src/cli/gloss-codex-child.d.ts +9 -0
  130. package/dist/src/cli/gloss-codex-child.js +149 -0
  131. package/dist/src/cli/gloss-codex-child.js.map +1 -0
  132. package/dist/src/cli/gloss-codex.d.ts +24 -0
  133. package/dist/src/cli/gloss-codex.js +255 -0
  134. package/dist/src/cli/gloss-codex.js.map +1 -0
  135. package/dist/src/cli/gloss-options.d.ts +42 -0
  136. package/dist/src/cli/gloss-options.js +79 -0
  137. package/dist/src/cli/gloss-options.js.map +1 -0
  138. package/dist/src/cli/gloss.d.ts +265 -0
  139. package/dist/src/cli/gloss.js +362 -0
  140. package/dist/src/cli/gloss.js.map +1 -0
  141. package/dist/src/cli/help.d.ts +103 -0
  142. package/dist/src/cli/help.js +2339 -0
  143. package/dist/src/cli/help.js.map +1 -0
  144. package/dist/src/cli/hook-codex.d.ts +78 -0
  145. package/dist/src/cli/hook-codex.js +167 -0
  146. package/dist/src/cli/hook-codex.js.map +1 -0
  147. package/dist/src/cli/hook.d.ts +331 -0
  148. package/dist/src/cli/hook.js +2849 -0
  149. package/dist/src/cli/hook.js.map +1 -0
  150. package/dist/src/cli/import.d.ts +35 -0
  151. package/dist/src/cli/import.js +175 -0
  152. package/dist/src/cli/import.js.map +1 -0
  153. package/dist/src/cli/init.d.ts +84 -0
  154. package/dist/src/cli/init.js +336 -0
  155. package/dist/src/cli/init.js.map +1 -0
  156. package/dist/src/cli/instructions.d.ts +23 -0
  157. package/dist/src/cli/instructions.js +262 -0
  158. package/dist/src/cli/instructions.js.map +1 -0
  159. package/dist/src/cli/journal.d.ts +41 -0
  160. package/dist/src/cli/journal.js +238 -0
  161. package/dist/src/cli/journal.js.map +1 -0
  162. package/dist/src/cli/log-advance.d.ts +287 -0
  163. package/dist/src/cli/log-advance.js +840 -0
  164. package/dist/src/cli/log-advance.js.map +1 -0
  165. package/dist/src/cli/log-anchor.d.ts +176 -0
  166. package/dist/src/cli/log-anchor.js +387 -0
  167. package/dist/src/cli/log-anchor.js.map +1 -0
  168. package/dist/src/cli/log-checkpoint.d.ts +22 -0
  169. package/dist/src/cli/log-checkpoint.js +128 -0
  170. package/dist/src/cli/log-checkpoint.js.map +1 -0
  171. package/dist/src/cli/log-sync.d.ts +243 -0
  172. package/dist/src/cli/log-sync.js +849 -0
  173. package/dist/src/cli/log-sync.js.map +1 -0
  174. package/dist/src/cli/log-verbs.d.ts +16 -0
  175. package/dist/src/cli/log-verbs.js +360 -0
  176. package/dist/src/cli/log-verbs.js.map +1 -0
  177. package/dist/src/cli/long-help.d.ts +70 -0
  178. package/dist/src/cli/long-help.js +148 -0
  179. package/dist/src/cli/long-help.js.map +1 -0
  180. package/dist/src/cli/main.d.ts +77 -0
  181. package/dist/src/cli/main.js +1206 -0
  182. package/dist/src/cli/main.js.map +1 -0
  183. package/dist/src/cli/mcp.d.ts +52 -0
  184. package/dist/src/cli/mcp.js +306 -0
  185. package/dist/src/cli/mcp.js.map +1 -0
  186. package/dist/src/cli/paths.d.ts +56 -0
  187. package/dist/src/cli/paths.js +79 -0
  188. package/dist/src/cli/paths.js.map +1 -0
  189. package/dist/src/cli/payload.d.ts +58 -0
  190. package/dist/src/cli/payload.js +253 -0
  191. package/dist/src/cli/payload.js.map +1 -0
  192. package/dist/src/cli/policy.d.ts +43 -0
  193. package/dist/src/cli/policy.js +229 -0
  194. package/dist/src/cli/policy.js.map +1 -0
  195. package/dist/src/cli/preflight.d.ts +363 -0
  196. package/dist/src/cli/preflight.js +1175 -0
  197. package/dist/src/cli/preflight.js.map +1 -0
  198. package/dist/src/cli/progress.d.ts +78 -0
  199. package/dist/src/cli/progress.js +112 -0
  200. package/dist/src/cli/progress.js.map +1 -0
  201. package/dist/src/cli/prompt.d.ts +209 -0
  202. package/dist/src/cli/prompt.js +312 -0
  203. package/dist/src/cli/prompt.js.map +1 -0
  204. package/dist/src/cli/quickstart.d.ts +46 -0
  205. package/dist/src/cli/quickstart.js +297 -0
  206. package/dist/src/cli/quickstart.js.map +1 -0
  207. package/dist/src/cli/records.d.ts +34 -0
  208. package/dist/src/cli/records.js +66 -0
  209. package/dist/src/cli/records.js.map +1 -0
  210. package/dist/src/cli/render.d.ts +22 -0
  211. package/dist/src/cli/render.js +132 -0
  212. package/dist/src/cli/render.js.map +1 -0
  213. package/dist/src/cli/sandbox.d.ts +51 -0
  214. package/dist/src/cli/sandbox.js +150 -0
  215. package/dist/src/cli/sandbox.js.map +1 -0
  216. package/dist/src/cli/scaffold.d.ts +79 -0
  217. package/dist/src/cli/scaffold.js +137 -0
  218. package/dist/src/cli/scaffold.js.map +1 -0
  219. package/dist/src/cli/setup-adapter.d.ts +137 -0
  220. package/dist/src/cli/setup-adapter.js +509 -0
  221. package/dist/src/cli/setup-adapter.js.map +1 -0
  222. package/dist/src/cli/setup-channel.d.ts +117 -0
  223. package/dist/src/cli/setup-channel.js +635 -0
  224. package/dist/src/cli/setup-channel.js.map +1 -0
  225. package/dist/src/cli/setup-checkpoint.d.ts +57 -0
  226. package/dist/src/cli/setup-checkpoint.js +196 -0
  227. package/dist/src/cli/setup-checkpoint.js.map +1 -0
  228. package/dist/src/cli/setup-common.d.ts +275 -0
  229. package/dist/src/cli/setup-common.js +376 -0
  230. package/dist/src/cli/setup-common.js.map +1 -0
  231. package/dist/src/cli/setup-flow.d.ts +287 -0
  232. package/dist/src/cli/setup-flow.js +476 -0
  233. package/dist/src/cli/setup-flow.js.map +1 -0
  234. package/dist/src/cli/setup-service.d.ts +96 -0
  235. package/dist/src/cli/setup-service.js +308 -0
  236. package/dist/src/cli/setup-service.js.map +1 -0
  237. package/dist/src/cli/setup.d.ts +202 -0
  238. package/dist/src/cli/setup.js +473 -0
  239. package/dist/src/cli/setup.js.map +1 -0
  240. package/dist/src/cli/style.d.ts +320 -0
  241. package/dist/src/cli/style.js +469 -0
  242. package/dist/src/cli/style.js.map +1 -0
  243. package/dist/src/cli/token.d.ts +39 -0
  244. package/dist/src/cli/token.js +274 -0
  245. package/dist/src/cli/token.js.map +1 -0
  246. package/dist/src/cli/up.d.ts +155 -0
  247. package/dist/src/cli/up.js +849 -0
  248. package/dist/src/cli/up.js.map +1 -0
  249. package/dist/src/cli/usage.d.ts +37 -0
  250. package/dist/src/cli/usage.js +91 -0
  251. package/dist/src/cli/usage.js.map +1 -0
  252. package/dist/src/cli/values.d.ts +40 -0
  253. package/dist/src/cli/values.js +189 -0
  254. package/dist/src/cli/values.js.map +1 -0
  255. package/dist/src/cli/vault.d.ts +59 -0
  256. package/dist/src/cli/vault.js +362 -0
  257. package/dist/src/cli/vault.js.map +1 -0
  258. package/dist/src/cli/verb-registry.d.ts +76 -0
  259. package/dist/src/cli/verb-registry.js +2341 -0
  260. package/dist/src/cli/verb-registry.js.map +1 -0
  261. package/dist/src/cli/wordmark.d.ts +31 -0
  262. package/dist/src/cli/wordmark.js +52 -0
  263. package/dist/src/cli/wordmark.js.map +1 -0
  264. package/dist/src/codex/doctor.d.ts +13 -0
  265. package/dist/src/codex/doctor.js +41 -0
  266. package/dist/src/codex/doctor.js.map +1 -0
  267. package/dist/src/codex/manifest.d.ts +49 -0
  268. package/dist/src/codex/manifest.js +103 -0
  269. package/dist/src/codex/manifest.js.map +1 -0
  270. package/dist/src/codex/templates.d.ts +41 -0
  271. package/dist/src/codex/templates.js +319 -0
  272. package/dist/src/codex/templates.js.map +1 -0
  273. package/dist/src/codex/trust.d.ts +19 -0
  274. package/dist/src/codex/trust.js +183 -0
  275. package/dist/src/codex/trust.js.map +1 -0
  276. package/dist/src/codex/workspace-plan.d.ts +131 -0
  277. package/dist/src/codex/workspace-plan.js +561 -0
  278. package/dist/src/codex/workspace-plan.js.map +1 -0
  279. package/dist/src/core/actor.d.ts +2 -0
  280. package/dist/src/core/actor.js +5 -0
  281. package/dist/src/core/actor.js.map +1 -0
  282. package/dist/src/core/advance-cycle.d.ts +170 -0
  283. package/dist/src/core/advance-cycle.js +200 -0
  284. package/dist/src/core/advance-cycle.js.map +1 -0
  285. package/dist/src/core/agents-md.d.ts +276 -0
  286. package/dist/src/core/agents-md.js +747 -0
  287. package/dist/src/core/agents-md.js.map +1 -0
  288. package/dist/src/core/apply-patch.d.ts +49 -0
  289. package/dist/src/core/apply-patch.js +266 -0
  290. package/dist/src/core/apply-patch.js.map +1 -0
  291. package/dist/src/core/attest.d.ts +420 -0
  292. package/dist/src/core/attest.js +589 -0
  293. package/dist/src/core/attest.js.map +1 -0
  294. package/dist/src/core/audit.d.ts +492 -0
  295. package/dist/src/core/audit.js +882 -0
  296. package/dist/src/core/audit.js.map +1 -0
  297. package/dist/src/core/budgets.d.ts +238 -0
  298. package/dist/src/core/budgets.js +449 -0
  299. package/dist/src/core/budgets.js.map +1 -0
  300. package/dist/src/core/checkpoint.d.ts +500 -0
  301. package/dist/src/core/checkpoint.js +738 -0
  302. package/dist/src/core/checkpoint.js.map +1 -0
  303. package/dist/src/core/child-env.d.ts +88 -0
  304. package/dist/src/core/child-env.js +86 -0
  305. package/dist/src/core/child-env.js.map +1 -0
  306. package/dist/src/core/clock.d.ts +52 -0
  307. package/dist/src/core/clock.js +43 -0
  308. package/dist/src/core/clock.js.map +1 -0
  309. package/dist/src/core/command-class.d.ts +543 -0
  310. package/dist/src/core/command-class.js +2356 -0
  311. package/dist/src/core/command-class.js.map +1 -0
  312. package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
  313. package/dist/src/core/coverage-sources/adapter.js +71 -0
  314. package/dist/src/core/coverage-sources/adapter.js.map +1 -0
  315. package/dist/src/core/coverage-sources/gh.d.ts +48 -0
  316. package/dist/src/core/coverage-sources/gh.js +136 -0
  317. package/dist/src/core/coverage-sources/gh.js.map +1 -0
  318. package/dist/src/core/coverage-sources/git.d.ts +101 -0
  319. package/dist/src/core/coverage-sources/git.js +269 -0
  320. package/dist/src/core/coverage-sources/git.js.map +1 -0
  321. package/dist/src/core/coverage.d.ts +217 -0
  322. package/dist/src/core/coverage.js +337 -0
  323. package/dist/src/core/coverage.js.map +1 -0
  324. package/dist/src/core/credential-spec.d.ts +72 -0
  325. package/dist/src/core/credential-spec.js +23 -0
  326. package/dist/src/core/credential-spec.js.map +1 -0
  327. package/dist/src/core/dark-session.d.ts +331 -0
  328. package/dist/src/core/dark-session.js +714 -0
  329. package/dist/src/core/dark-session.js.map +1 -0
  330. package/dist/src/core/decision-refusal.d.ts +185 -0
  331. package/dist/src/core/decision-refusal.js +265 -0
  332. package/dist/src/core/decision-refusal.js.map +1 -0
  333. package/dist/src/core/env-file.d.ts +450 -0
  334. package/dist/src/core/env-file.js +837 -0
  335. package/dist/src/core/env-file.js.map +1 -0
  336. package/dist/src/core/execute.d.ts +858 -0
  337. package/dist/src/core/execute.js +1271 -0
  338. package/dist/src/core/execute.js.map +1 -0
  339. package/dist/src/core/frontmatter.d.ts +78 -0
  340. package/dist/src/core/frontmatter.js +100 -0
  341. package/dist/src/core/frontmatter.js.map +1 -0
  342. package/dist/src/core/gate-window.d.ts +312 -0
  343. package/dist/src/core/gate-window.js +506 -0
  344. package/dist/src/core/gate-window.js.map +1 -0
  345. package/dist/src/core/gate.d.ts +1364 -0
  346. package/dist/src/core/gate.js +3002 -0
  347. package/dist/src/core/gate.js.map +1 -0
  348. package/dist/src/core/git-run.d.ts +73 -0
  349. package/dist/src/core/git-run.js +93 -0
  350. package/dist/src/core/git-run.js.map +1 -0
  351. package/dist/src/core/harness-version.d.ts +157 -0
  352. package/dist/src/core/harness-version.js +211 -0
  353. package/dist/src/core/harness-version.js.map +1 -0
  354. package/dist/src/core/harness-wait.d.ts +55 -0
  355. package/dist/src/core/harness-wait.js +58 -0
  356. package/dist/src/core/harness-wait.js.map +1 -0
  357. package/dist/src/core/head-retry.d.ts +107 -0
  358. package/dist/src/core/head-retry.js +121 -0
  359. package/dist/src/core/head-retry.js.map +1 -0
  360. package/dist/src/core/instance.d.ts +253 -0
  361. package/dist/src/core/instance.js +319 -0
  362. package/dist/src/core/instance.js.map +1 -0
  363. package/dist/src/core/intake-limits.d.ts +247 -0
  364. package/dist/src/core/intake-limits.js +350 -0
  365. package/dist/src/core/intake-limits.js.map +1 -0
  366. package/dist/src/core/jcs.d.ts +52 -0
  367. package/dist/src/core/jcs.js +132 -0
  368. package/dist/src/core/jcs.js.map +1 -0
  369. package/dist/src/core/journal.d.ts +144 -0
  370. package/dist/src/core/journal.js +200 -0
  371. package/dist/src/core/journal.js.map +1 -0
  372. package/dist/src/core/live-draw.d.ts +436 -0
  373. package/dist/src/core/live-draw.js +703 -0
  374. package/dist/src/core/live-draw.js.map +1 -0
  375. package/dist/src/core/log-reconcile.d.ts +89 -0
  376. package/dist/src/core/log-reconcile.js +136 -0
  377. package/dist/src/core/log-reconcile.js.map +1 -0
  378. package/dist/src/core/log-subscribe.d.ts +36 -0
  379. package/dist/src/core/log-subscribe.js +162 -0
  380. package/dist/src/core/log-subscribe.js.map +1 -0
  381. package/dist/src/core/log.d.ts +278 -0
  382. package/dist/src/core/log.js +546 -0
  383. package/dist/src/core/log.js.map +1 -0
  384. package/dist/src/core/loop.d.ts +274 -0
  385. package/dist/src/core/loop.js +487 -0
  386. package/dist/src/core/loop.js.map +1 -0
  387. package/dist/src/core/md-fence.d.ts +41 -0
  388. package/dist/src/core/md-fence.js +74 -0
  389. package/dist/src/core/md-fence.js.map +1 -0
  390. package/dist/src/core/money.d.ts +147 -0
  391. package/dist/src/core/money.js +195 -0
  392. package/dist/src/core/money.js.map +1 -0
  393. package/dist/src/core/payload-census.d.ts +74 -0
  394. package/dist/src/core/payload-census.js +146 -0
  395. package/dist/src/core/payload-census.js.map +1 -0
  396. package/dist/src/core/payload-store.d.ts +175 -0
  397. package/dist/src/core/payload-store.js +340 -0
  398. package/dist/src/core/payload-store.js.map +1 -0
  399. package/dist/src/core/payload.d.ts +71 -0
  400. package/dist/src/core/payload.js +80 -0
  401. package/dist/src/core/payload.js.map +1 -0
  402. package/dist/src/core/policy-diff.d.ts +292 -0
  403. package/dist/src/core/policy-diff.js +588 -0
  404. package/dist/src/core/policy-diff.js.map +1 -0
  405. package/dist/src/core/policy-expectations.d.ts +199 -0
  406. package/dist/src/core/policy-expectations.js +394 -0
  407. package/dist/src/core/policy-expectations.js.map +1 -0
  408. package/dist/src/core/policy-explain.d.ts +150 -0
  409. package/dist/src/core/policy-explain.js +258 -0
  410. package/dist/src/core/policy-explain.js.map +1 -0
  411. package/dist/src/core/policy-load.d.ts +527 -0
  412. package/dist/src/core/policy-load.js +536 -0
  413. package/dist/src/core/policy-load.js.map +1 -0
  414. package/dist/src/core/policy-match.d.ts +281 -0
  415. package/dist/src/core/policy-match.js +478 -0
  416. package/dist/src/core/policy-match.js.map +1 -0
  417. package/dist/src/core/policy-proposal.d.ts +265 -0
  418. package/dist/src/core/policy-proposal.js +458 -0
  419. package/dist/src/core/policy-proposal.js.map +1 -0
  420. package/dist/src/core/prompt-layout.d.ts +221 -0
  421. package/dist/src/core/prompt-layout.js +422 -0
  422. package/dist/src/core/prompt-layout.js.map +1 -0
  423. package/dist/src/core/protected-path-guard.d.ts +453 -0
  424. package/dist/src/core/protected-path-guard.js +1566 -0
  425. package/dist/src/core/protected-path-guard.js.map +1 -0
  426. package/dist/src/core/registration.d.ts +25 -0
  427. package/dist/src/core/registration.js +39 -0
  428. package/dist/src/core/registration.js.map +1 -0
  429. package/dist/src/core/reindex.d.ts +99 -0
  430. package/dist/src/core/reindex.js +336 -0
  431. package/dist/src/core/reindex.js.map +1 -0
  432. package/dist/src/core/sampler.d.ts +313 -0
  433. package/dist/src/core/sampler.js +388 -0
  434. package/dist/src/core/sampler.js.map +1 -0
  435. package/dist/src/core/sandbox.d.ts +290 -0
  436. package/dist/src/core/sandbox.js +424 -0
  437. package/dist/src/core/sandbox.js.map +1 -0
  438. package/dist/src/core/seal.d.ts +165 -0
  439. package/dist/src/core/seal.js +290 -0
  440. package/dist/src/core/seal.js.map +1 -0
  441. package/dist/src/core/state.d.ts +505 -0
  442. package/dist/src/core/state.js +1009 -0
  443. package/dist/src/core/state.js.map +1 -0
  444. package/dist/src/core/task-file.d.ts +185 -0
  445. package/dist/src/core/task-file.js +464 -0
  446. package/dist/src/core/task-file.js.map +1 -0
  447. package/dist/src/core/telegram-config.d.ts +93 -0
  448. package/dist/src/core/telegram-config.js +114 -0
  449. package/dist/src/core/telegram-config.js.map +1 -0
  450. package/dist/src/core/token.d.ts +409 -0
  451. package/dist/src/core/token.js +561 -0
  452. package/dist/src/core/token.js.map +1 -0
  453. package/dist/src/core/validate.d.ts +138 -0
  454. package/dist/src/core/validate.js +0 -0
  455. package/dist/src/core/validate.js.map +1 -0
  456. package/dist/src/core/values.d.ts +137 -0
  457. package/dist/src/core/values.js +153 -0
  458. package/dist/src/core/values.js.map +1 -0
  459. package/dist/src/core/vault.d.ts +291 -0
  460. package/dist/src/core/vault.js +612 -0
  461. package/dist/src/core/vault.js.map +1 -0
  462. package/dist/src/core/verified-snapshot.d.ts +204 -0
  463. package/dist/src/core/verified-snapshot.js +506 -0
  464. package/dist/src/core/verified-snapshot.js.map +1 -0
  465. package/dist/src/core/verify.d.ts +336 -0
  466. package/dist/src/core/verify.js +549 -0
  467. package/dist/src/core/verify.js.map +1 -0
  468. package/dist/src/core/version.d.ts +8 -0
  469. package/dist/src/core/version.js +9 -0
  470. package/dist/src/core/version.js.map +1 -0
  471. package/dist/src/core/wysiwys.d.ts +370 -0
  472. package/dist/src/core/wysiwys.js +728 -0
  473. package/dist/src/core/wysiwys.js.map +1 -0
  474. package/dist/src/daemon/advance-child.d.ts +39 -0
  475. package/dist/src/daemon/advance-child.js +78 -0
  476. package/dist/src/daemon/advance-child.js.map +1 -0
  477. package/dist/src/daemon/advance.d.ts +466 -0
  478. package/dist/src/daemon/advance.js +849 -0
  479. package/dist/src/daemon/advance.js.map +1 -0
  480. package/dist/src/daemon/audit.d.ts +87 -0
  481. package/dist/src/daemon/audit.js +90 -0
  482. package/dist/src/daemon/audit.js.map +1 -0
  483. package/dist/src/daemon/daemon.d.ts +1180 -0
  484. package/dist/src/daemon/daemon.js +1988 -0
  485. package/dist/src/daemon/daemon.js.map +1 -0
  486. package/dist/src/daemon/dark-session.d.ts +64 -0
  487. package/dist/src/daemon/dark-session.js +119 -0
  488. package/dist/src/daemon/dark-session.js.map +1 -0
  489. package/dist/src/daemon/draw-child.d.ts +36 -0
  490. package/dist/src/daemon/draw-child.js +132 -0
  491. package/dist/src/daemon/draw-child.js.map +1 -0
  492. package/dist/src/daemon/draw.d.ts +154 -0
  493. package/dist/src/daemon/draw.js +458 -0
  494. package/dist/src/daemon/draw.js.map +1 -0
  495. package/dist/src/daemon/git-evidence.d.ts +173 -0
  496. package/dist/src/daemon/git-evidence.js +345 -0
  497. package/dist/src/daemon/git-evidence.js.map +1 -0
  498. package/dist/src/daemon/projection.d.ts +180 -0
  499. package/dist/src/daemon/projection.js +233 -0
  500. package/dist/src/daemon/projection.js.map +1 -0
  501. package/dist/src/daemon/prune.d.ts +207 -0
  502. package/dist/src/daemon/prune.js +376 -0
  503. package/dist/src/daemon/prune.js.map +1 -0
  504. package/dist/src/mcp/http.d.ts +113 -0
  505. package/dist/src/mcp/http.js +343 -0
  506. package/dist/src/mcp/http.js.map +1 -0
  507. package/dist/src/mcp/server.d.ts +265 -0
  508. package/dist/src/mcp/server.js +602 -0
  509. package/dist/src/mcp/server.js.map +1 -0
  510. package/docs/adapter-api.md +106 -0
  511. package/docs/cli-reference.md +5716 -0
  512. package/docs/codex-enforced-session.md +30 -0
  513. package/package.json +53 -4
  514. package/schema/.gitkeep +0 -0
  515. package/schema/LICENSE +117 -0
  516. package/schema/codex-instance.schema.json +82 -0
  517. package/schema/envelope.schema.json +137 -0
  518. package/schema/event.schema.json +1811 -0
  519. package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
  520. package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
  521. package/schema/fixtures/envelope/invalid/action-missing-idempotency-key.json +15 -0
  522. package/schema/fixtures/envelope/invalid/action-unknown-class-format.json +14 -0
  523. package/schema/fixtures/envelope/invalid/confidence-out-of-range.json +11 -0
  524. package/schema/fixtures/envelope/invalid/est-cost-bare-number.json +14 -0
  525. package/schema/fixtures/envelope/invalid/est-cost-noncanonical-string.json +14 -0
  526. package/schema/fixtures/envelope/invalid/malformed-assignee.json +10 -0
  527. package/schema/fixtures/envelope/invalid/malformed-created-by.json +7 -0
  528. package/schema/fixtures/envelope/invalid/malformed-max-latency.json +11 -0
  529. package/schema/fixtures/envelope/invalid/malformed-payload-hash.json +14 -0
  530. package/schema/fixtures/envelope/invalid/max-cost-bare-number.json +11 -0
  531. package/schema/fixtures/envelope/invalid/missing-origin.json +3 -0
  532. package/schema/fixtures/envelope/invalid/negative-est-cost.json +14 -0
  533. package/schema/fixtures/envelope/invalid/unknown-state.json +7 -0
  534. package/schema/fixtures/envelope/invalid/unknown-top-level-field.json +8 -0
  535. package/schema/fixtures/envelope/valid/action-payload-hash.json +17 -0
  536. package/schema/fixtures/envelope/valid/actions-without-budget.json +18 -0
  537. package/schema/fixtures/envelope/valid/canonical.json +25 -0
  538. package/schema/fixtures/envelope/valid/minimal.json +7 -0
  539. package/schema/fixtures/envelope/valid/multi-action-executed.json +30 -0
  540. package/schema/fixtures/envelope/valid/record-write-stage.json +22 -0
  541. package/schema/fixtures/event/invalid/approval-granted-agent-actor.json +15 -0
  542. package/schema/fixtures/event/invalid/approval-granted-empty-batch-delivery-id.json +17 -0
  543. package/schema/fixtures/event/invalid/approval-granted-fifth-reaction.json +16 -0
  544. package/schema/fixtures/event/invalid/approval-granted-missing-actor.json +14 -0
  545. package/schema/fixtures/event/invalid/approval-requested-missing-action-key.json +14 -0
  546. package/schema/fixtures/event/invalid/approval-withdrawn-agent-policy-drift.json +15 -0
  547. package/schema/fixtures/event/invalid/approval-withdrawn-missing-reason.json +15 -0
  548. package/schema/fixtures/event/invalid/approval-withdrawn-system-actor.json +15 -0
  549. package/schema/fixtures/event/invalid/audit-decision-refused-human-actor.json +17 -0
  550. package/schema/fixtures/event/invalid/audit-decision-refused-missing-code.json +16 -0
  551. package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +15 -0
  552. package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +16 -0
  553. package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +15 -0
  554. package/schema/fixtures/event/invalid/bad-actor-prefix.json +15 -0
  555. package/schema/fixtures/event/invalid/est-cost-bare-number.json +17 -0
  556. package/schema/fixtures/event/invalid/execution-completed-fabricated-exit-code.json +16 -0
  557. package/schema/fixtures/event/invalid/execution-completed-provider-ref-empty-id.json +18 -0
  558. package/schema/fixtures/event/invalid/execution-completed-provider-ref-extra-field.json +19 -0
  559. package/schema/fixtures/event/invalid/execution-completed-provider-ref-id-not-string.json +18 -0
  560. package/schema/fixtures/event/invalid/execution-completed-provider-ref-missing-adapter.json +17 -0
  561. package/schema/fixtures/event/invalid/execution-failed-open-reported-by.json +16 -0
  562. package/schema/fixtures/event/invalid/execution-indeterminate-open-reason.json +14 -0
  563. package/schema/fixtures/event/invalid/execution-reconciled-agent-actor.json +17 -0
  564. package/schema/fixtures/event/invalid/execution-started-negative-env-stripped.json +16 -0
  565. package/schema/fixtures/event/invalid/gate-bypassed-missing-opened-seq.json +15 -0
  566. package/schema/fixtures/event/invalid/gate-closed-non-integer-opened-seq.json +13 -0
  567. package/schema/fixtures/event/invalid/gate-opened-agent-actor.json +16 -0
  568. package/schema/fixtures/event/invalid/gate-organ-attested-absolute-path.json +14 -0
  569. package/schema/fixtures/event/invalid/gate-organ-attested-agent-actor.json +14 -0
  570. package/schema/fixtures/event/invalid/gate-organ-attested-missing-organ-path.json +13 -0
  571. package/schema/fixtures/event/invalid/harness-unknown-kind.json +18 -0
  572. package/schema/fixtures/event/invalid/harness-version-multiline.json +16 -0
  573. package/schema/fixtures/event/invalid/log-checkpoint-agent-actor.json +17 -0
  574. package/schema/fixtures/event/invalid/log-checkpoint-missing-signature.json +16 -0
  575. package/schema/fixtures/event/invalid/log-checkpoint-short-signed-hash.json +17 -0
  576. package/schema/fixtures/event/invalid/log-checkpoint-unknown-signature-alg.json +17 -0
  577. package/schema/fixtures/event/invalid/malformed-ts.json +15 -0
  578. package/schema/fixtures/event/invalid/missing-alg.json +14 -0
  579. package/schema/fixtures/event/invalid/missing-hash.json +14 -0
  580. package/schema/fixtures/event/invalid/non-integer-seq.json +15 -0
  581. package/schema/fixtures/event/invalid/payload-pruned-human-actor.json +14 -0
  582. package/schema/fixtures/event/invalid/payload-pruned-missing-hash.json +14 -0
  583. package/schema/fixtures/event/invalid/policy-declined-agent-actor.json +15 -0
  584. package/schema/fixtures/event/invalid/policy-proposed-missing-diff.json +19 -0
  585. package/schema/fixtures/event/invalid/policy-proposed-system-actor.json +26 -0
  586. package/schema/fixtures/event/invalid/short-hash.json +15 -0
  587. package/schema/fixtures/event/invalid/unknown-alg.json +15 -0
  588. package/schema/fixtures/event/invalid/unknown-event-type.json +15 -0
  589. package/schema/fixtures/event/invalid/unknown-top-level-field.json +16 -0
  590. package/schema/fixtures/event/valid/approval-expired.json +15 -0
  591. package/schema/fixtures/event/valid/approval-granted-batch.json +19 -0
  592. package/schema/fixtures/event/valid/approval-granted-reaction.json +16 -0
  593. package/schema/fixtures/event/valid/approval-granted.json +15 -0
  594. package/schema/fixtures/event/valid/approval-rejected.json +15 -0
  595. package/schema/fixtures/event/valid/approval-requested.json +19 -0
  596. package/schema/fixtures/event/valid/approval-revoked.json +15 -0
  597. package/schema/fixtures/event/valid/approval-withdrawn-policy-drift.json +17 -0
  598. package/schema/fixtures/event/valid/approval-withdrawn.json +16 -0
  599. package/schema/fixtures/event/valid/audit-decision-refused.json +20 -0
  600. package/schema/fixtures/event/valid/audit-reviewed-reaction.json +17 -0
  601. package/schema/fixtures/event/valid/audit-reviewed.json +15 -0
  602. package/schema/fixtures/event/valid/audit-sampled.json +14 -0
  603. package/schema/fixtures/event/valid/budget-exceeded.json +21 -0
  604. package/schema/fixtures/event/valid/envelope-drift.json +16 -0
  605. package/schema/fixtures/event/valid/execution-completed-harness-report.json +16 -0
  606. package/schema/fixtures/event/valid/execution-completed-provider-ref.json +18 -0
  607. package/schema/fixtures/event/valid/execution-completed.json +15 -0
  608. package/schema/fixtures/event/valid/execution-failed-harness-report.json +16 -0
  609. package/schema/fixtures/event/valid/execution-failed.json +15 -0
  610. package/schema/fixtures/event/valid/execution-indeterminate.json +15 -0
  611. package/schema/fixtures/event/valid/execution-reconciled.json +17 -0
  612. package/schema/fixtures/event/valid/execution-started-env-stripped.json +17 -0
  613. package/schema/fixtures/event/valid/execution-started.json +14 -0
  614. package/schema/fixtures/event/valid/gate-bypassed-harness-version.json +18 -0
  615. package/schema/fixtures/event/valid/gate-bypassed.json +19 -0
  616. package/schema/fixtures/event/valid/gate-closed.json +14 -0
  617. package/schema/fixtures/event/valid/gate-opened.json +16 -0
  618. package/schema/fixtures/event/valid/gate-organ-attested.json +14 -0
  619. package/schema/fixtures/event/valid/genesis-null-prev.json +14 -0
  620. package/schema/fixtures/event/valid/log-checkpoint.json +17 -0
  621. package/schema/fixtures/event/valid/payload-pruned-orphan.json +13 -0
  622. package/schema/fixtures/event/valid/payload-pruned.json +17 -0
  623. package/schema/fixtures/event/valid/policy-declined.json +16 -0
  624. package/schema/fixtures/event/valid/policy-proposed.json +35 -0
  625. package/schema/fixtures/event/valid/policy-updated.json +14 -0
  626. package/schema/fixtures/event/valid/reconciliation-required.json +18 -0
  627. package/schema/fixtures/event/valid/reconciliation-satisfied.json +17 -0
  628. package/schema/fixtures/event/valid/route-accepted.json +15 -0
  629. package/schema/fixtures/event/valid/route-proposed.json +16 -0
  630. package/schema/fixtures/event/valid/spec-example.json +15 -0
  631. package/schema/fixtures/event/valid/task-registered-harness-version.json +23 -0
  632. package/schema/fixtures/event/valid/task-registered.json +14 -0
  633. package/schema/fixtures/hash/known-answer-pre-121.json +74 -0
  634. package/schema/fixtures/hash/known-answer.json +74 -0
  635. package/schema/fixtures/policy/invalid/bad-approval-ttl.json +7 -0
  636. package/schema/fixtures/policy/invalid/bad-web-port.json +4 -0
  637. package/schema/fixtures/policy/invalid/checkpoint-key-not-base64.json +6 -0
  638. package/schema/fixtures/policy/invalid/class-rule-missing-autonomy.json +9 -0
  639. package/schema/fixtures/policy/invalid/empty-class-key.json +6 -0
  640. package/schema/fixtures/policy/invalid/live-rate-on-human-only.json +7 -0
  641. package/schema/fixtures/policy/invalid/malformed-class-key.json +6 -0
  642. package/schema/fixtures/policy/invalid/missing-version.json +8 -0
  643. package/schema/fixtures/policy/invalid/negative-limit.json +9 -0
  644. package/schema/fixtures/policy/invalid/non-numeric-limit.json +9 -0
  645. package/schema/fixtures/policy/invalid/non-positive-max-pending.json +9 -0
  646. package/schema/fixtures/policy/invalid/on-expiry-grant.json +8 -0
  647. package/schema/fixtures/policy/invalid/payload-retention-bare-number.json +4 -0
  648. package/schema/fixtures/policy/invalid/payload-retention-compound.json +4 -0
  649. package/schema/fixtures/policy/invalid/payload-retention-fractional.json +4 -0
  650. package/schema/fixtures/policy/invalid/payload-retention-zero.json +4 -0
  651. package/schema/fixtures/policy/invalid/protected-paths-escape.json +4 -0
  652. package/schema/fixtures/policy/invalid/protected-paths-glob.json +4 -0
  653. package/schema/fixtures/policy/invalid/retro-rate-on-human-only.json +7 -0
  654. package/schema/fixtures/policy/invalid/retro-rate-on-manual.json +7 -0
  655. package/schema/fixtures/policy/invalid/retro-rate-zero.json +7 -0
  656. package/schema/fixtures/policy/invalid/sample-rate-too-high.json +5 -0
  657. package/schema/fixtures/policy/invalid/sampling-secret-env-empty.json +7 -0
  658. package/schema/fixtures/policy/invalid/sampling-secret-env-not-string.json +6 -0
  659. package/schema/fixtures/policy/invalid/skew-tolerance-compound.json +6 -0
  660. package/schema/fixtures/policy/invalid/unknown-autonomy.json +7 -0
  661. package/schema/fixtures/policy/invalid/unknown-class-rule-key.json +6 -0
  662. package/schema/fixtures/policy/invalid/unknown-top-level-key.json +7 -0
  663. package/schema/fixtures/policy/invalid/vault-passphrase-env-empty.json +6 -0
  664. package/schema/fixtures/policy/invalid/vault-passphrase-literal.json +6 -0
  665. package/schema/fixtures/policy/invalid/version-not-string.json +4 -0
  666. package/schema/fixtures/policy/valid/canonical.json +47 -0
  667. package/schema/fixtures/policy/valid/checkpoint-keys.json +18 -0
  668. package/schema/fixtures/policy/valid/class-approvers-limits.json +25 -0
  669. package/schema/fixtures/policy/valid/class-retro-rate.json +17 -0
  670. package/schema/fixtures/policy/valid/global-budgets.json +19 -0
  671. package/schema/fixtures/policy/valid/human-only.json +9 -0
  672. package/schema/fixtures/policy/valid/minimal.json +6 -0
  673. package/schema/fixtures/policy/valid/protected-paths.json +10 -0
  674. package/schema/fixtures/policy/valid/record-namespace.json +13 -0
  675. package/schema/fixtures/policy/valid/request-volume-limits.json +26 -0
  676. package/schema/fixtures/policy/valid/retention-and-sampling-secret.json +16 -0
  677. package/schema/fixtures/policy/valid/skew-tolerance.json +15 -0
  678. package/schema/fixtures/policy/valid/vault-passphrase-env.json +14 -0
  679. package/schema/fixtures/policy/valid/wildcards.json +15 -0
  680. package/schema/fixtures/policy-md/invalid/alias-bomb.md +15 -0
  681. package/schema/fixtures/policy-md/invalid/no-fence.md +7 -0
  682. package/schema/fixtures/policy-md/invalid/protected-route-not-a-subclass.md +16 -0
  683. package/schema/fixtures/policy-md/invalid/schema-invalid-autonomy.md +16 -0
  684. package/schema/fixtures/policy-md/invalid/schema-invalid-read-proof.md +17 -0
  685. package/schema/fixtures/policy-md/invalid/two-fences.md +19 -0
  686. package/schema/fixtures/policy-md/invalid/unclosed-fence.md +11 -0
  687. package/schema/fixtures/policy-md/invalid/wrong-info-string.md +11 -0
  688. package/schema/fixtures/policy-md/invalid/yaml-syntax-error.md +13 -0
  689. package/schema/fixtures/policy-md/precedence/both/APPROVAL.md +7 -0
  690. package/schema/fixtures/policy-md/precedence/both/APPROVALS.md +7 -0
  691. package/schema/fixtures/policy-md/precedence/fallback-only/APPROVALS.md +7 -0
  692. package/schema/fixtures/policy-md/valid/canonical.md +50 -0
  693. package/schema/fixtures/policy-md/valid/daemon-read-proof.md +18 -0
  694. package/schema/fixtures/policy-md/valid/minimal.md +3 -0
  695. package/schema/fixtures/policy-md/valid/prose-lookalikes.md +54 -0
  696. package/schema/fixtures/policy-md/valid/routed-protected-paths.md +49 -0
  697. package/schema/fixtures/policy-md/valid/with-values.md +79 -0
  698. package/schema/fixtures/sample-record/invalid/bad-date-time.json +4 -0
  699. package/schema/fixtures/sample-record/invalid/missing-required-field.json +3 -0
  700. package/schema/fixtures/sample-record/invalid/unknown-top-level-field.json +5 -0
  701. package/schema/fixtures/sample-record/invalid/wrong-type.json +4 -0
  702. package/schema/fixtures/sample-record/valid/minimal.json +4 -0
  703. package/schema/fixtures/sample-record/valid/with-note.json +5 -0
  704. package/schema/fixtures/values/invalid/class-shaped.json +9 -0
  705. package/schema/fixtures/values/invalid/duplicate-entry.json +4 -0
  706. package/schema/fixtures/values/invalid/non-string-item.json +4 -0
  707. package/schema/fixtures/values/invalid/over-cap.json +26 -0
  708. package/schema/fixtures/values/invalid/unknown-key.json +5 -0
  709. package/schema/fixtures/values/invalid/version-string.json +1 -0
  710. package/schema/fixtures/values/valid/empty-lists.json +7 -0
  711. package/schema/fixtures/values/valid/full.json +20 -0
  712. package/schema/fixtures/values/valid/minimal.json +1 -0
  713. package/schema/fixtures/values-md/invalid/schema-invalid.md +62 -0
  714. package/schema/fixtures/values-md/invalid/two-blocks.md +69 -0
  715. package/schema/fixtures/values-md/invalid/unterminated.md +61 -0
  716. package/schema/fixtures/values-md/invalid/yaml-error.md +63 -0
  717. package/schema/fixtures/values-md/valid/absent.md +50 -0
  718. package/schema/fixtures/values-md/valid/with-values.md +79 -0
  719. package/schema/policy.schema.json +501 -0
  720. package/schema/sample-record.schema.json +26 -0
  721. package/schema/values.schema.json +55 -0
  722. package/templates/codex/README.md +9 -0
@@ -0,0 +1,1009 @@
1
+ /**
2
+ * Derived state: the one place the runtime turns the log into answers
3
+ * (SPEC.md §6.3, §7, §8).
4
+ *
5
+ * Everything the gate, the token module, and the executor believe about an
6
+ * action comes from here. The module exists for two reasons, both structural,
7
+ * both from the APRV-20 review:
8
+ *
9
+ * 1. **Verified reads (finding S1).** {@link readVerifiedRecords} is the only
10
+ * sanctioned way for a decision-making module to read the log. It runs the
11
+ * *full* chain verification — hash recompute, schema validation, `prev`/`seq`
12
+ * walk — by calling `core/verify.ts`'s {@link verifyWithRecords}, and refuses
13
+ * a corrupt log outright. Before this, the gate parsed lines as JSON and
14
+ * trusted them: a forged or spliced record could authorize an action, and the
15
+ * corruption would surface only when someone ran `approval log verify`. A
16
+ * permission system that reads its own evidence without checking it is not a
17
+ * permission system.
18
+ *
19
+ * APRV-43 lifted the linear cost that S1 accepted; see "The verified-read
20
+ * cache" below. What it did not lift is the rule: a decision is still made
21
+ * only from records this process verified in full, against bytes it proved
22
+ * unchanged.
23
+ *
24
+ * 2. **One derivation, no cycle (finding S4).** {@link requestState} used to
25
+ * live in `core/gate.ts`, which `core/token.ts` imported — while
26
+ * `core/gate.ts` imported `core/token.ts` to mint at grant. That import cycle
27
+ * made "which module owns approval state?" unanswerable. The derivation lives
28
+ * here now; `gate.ts`, `token.ts`, and `execute.ts` all import it, and the
29
+ * only remaining edge between them is the intended one, gate → token, at the
30
+ * mint seam. `gate.ts` re-exports the moved names so existing importers (the
31
+ * CLI, the tests) are unaffected.
32
+ *
33
+ * ## The verified-read cache (APRV-43), and why it is not a bypass
34
+ *
35
+ * A daemon re-reads the log on every watch event. Re-verifying from genesis
36
+ * every time makes a session quadratic in the log it is watching, so
37
+ * {@link readVerifiedRecords} keeps a process-lifetime, memory-only cache of the
38
+ * last log it verified clean: the prefix bytes' length, a SHA-256 over those
39
+ * bytes, the head line's bytes at their offset, the chain head, and the records
40
+ * the walk produced. On the next read of the same path, a prefix proved
41
+ * byte-identical is not re-walked; only the appended suffix is verified, chained
42
+ * onto the cached head.
43
+ *
44
+ * **Global invariant 1 ("enforcement paths read only verified records") is what
45
+ * this touches, so the argument is written out rather than assumed.**
46
+ *
47
+ * The tempting design is the cheap one: remember the head line and its offset,
48
+ * and on a re-read accept the prefix if the head line is byte-identical where it
49
+ * was. That design is unsound, and the specific attack says why. Append-only
50
+ * growth is a convention the *writer* honors; it is not a property of the file.
51
+ * An attacker with write access can mutate a record strictly before the head
52
+ * without changing its length — swap two characters of a `summary`, flip a digit
53
+ * of `est_cost_usd` — leaving the file size, the head line, and the head line's
54
+ * offset all identical. Nothing in the suffix walk touches those bytes. The
55
+ * forged record would be handed to the gate as verified, and a hash chain would
56
+ * have been defeated by a cache. "Byte-identical head at the same offset" does
57
+ * not imply "unchanged prefix", and no amount of `stat` makes it imply that.
58
+ *
59
+ * So the cache pays for what it claims: it stores a SHA-256 over the entire
60
+ * verified prefix and **re-hashes those bytes on every cached read**. A match
61
+ * proves the prefix on disk is bit-for-bit the prefix this process verified in
62
+ * full, in this process lifetime. Verification is a pure function of (bytes,
63
+ * schema files, options) — `core/verify.ts` says so and holds no state — so
64
+ * identical bytes verify identically, and replaying the walk over them could
65
+ * only reproduce the records already held. The cache therefore never *admits* a
66
+ * record: it declines to recompute a conclusion it has already computed from
67
+ * bytes it has just re-proved. Every record the caller receives was walked
68
+ * through the full check ladder (parse, `alg`, schema, hash recompute, `seq`
69
+ * succession, `prev` link) by this process, over exactly these bytes.
70
+ *
71
+ * **APRV-217 makes the last sentence conditional, and only under a policy that
72
+ * says so.** Under the default proof (`full`) the paragraph above is exactly
73
+ * what happens on every cached read. Under `incremental`, an operator's policy
74
+ * trades "re-hash the whole prefix on every read" for "hash the appended bytes,
75
+ * and re-hash the whole prefix on a cadence" — same guards, same walk, same
76
+ * verdicts, a bounded window in which an in-place rewrite of the prefix would be
77
+ * served from cache. The argument for that trade, and what it costs, is in the
78
+ * "incremental prefix proof" section below and in
79
+ * `docs/proposals/incremental-prefix-proof.md`.
80
+ *
81
+ * That is still a large win, because the two costs are not comparable: hashing
82
+ * bytes is a single linear pass at memory bandwidth, while the walk it replaces
83
+ * is a JSON parse, an Ajv schema validation, a JCS canonicalization, and a
84
+ * SHA-256 *per record*. The cache trades a full re-verification for a hash of
85
+ * the same bytes and a real verification of the new tail.
86
+ *
87
+ * Everything else the cache records is a discard trigger and never a licence:
88
+ *
89
+ * - **Size** shrinking below the cached prefix discards the entry. A shorter
90
+ * file cannot contain the prefix, and a truncated log must be re-read cold so
91
+ * that the head reported is the file's head, never the remembered one.
92
+ * - **mtime** is a staleness hint with no evidentiary weight. It can only cause
93
+ * a discard (a same-size file whose mtime moved is suspicious), never skip a
94
+ * check. Correctness does not depend on its granularity, on the clock being
95
+ * monotonic, or on the filesystem storing it at all: delete the mtime
96
+ * comparison and the cache is exactly as sound.
97
+ * - **The head line's bytes at their recorded offset** are compared before the
98
+ * prefix hash. This is a fast rejection of the ordinary tamper, not the proof;
99
+ * the prefix hash covers those same bytes and is what the soundness argument
100
+ * rests on.
101
+ * - **The schema directory** is part of the key. Records verified against one
102
+ * schema set are not evidence under another.
103
+ *
104
+ * Only a `clean` verdict populates the cache. A torn tail or a corrupt log
105
+ * leaves the previous entry in place unused (it is discarded on the mismatch
106
+ * that revealed the damage), so nothing derived from a broken read can ever be
107
+ * resumed from. And the cache is memory-only and process-lifetime: no file, no
108
+ * shared state between processes, nothing an attacker can pre-seed. A CLI
109
+ * invocation is a fresh process with an empty cache and behaves exactly as it
110
+ * did before this existed.
111
+ *
112
+ * Records handed out from the cache are deep-frozen, because a caller that
113
+ * mutated a returned record would otherwise corrupt the next reader's evidence.
114
+ * Freezing makes the aliasing safe instead of merely unlikely.
115
+ *
116
+ * Determinism: `requestState` and everything downstream of it read no clock, no
117
+ * network, and no cache; `ts` is a parameter everywhere, so a derivation can be
118
+ * replayed from the log exactly as it was made. The cache sits strictly above
119
+ * that line, in the read path, and is observationally invisible: for the same
120
+ * bytes it returns the same records and the same verdict as a cold read, which
121
+ * `tests/state-cache.test.ts` asserts scenario by scenario.
122
+ */
123
+ import { createHash } from "node:crypto";
124
+ import { closeSync, fstatSync, openSync, readFileSync, readSync, statSync } from "node:fs";
125
+ import { resolve } from "node:path";
126
+ import { isPolicySha256, POLICY_HASH_FIELD } from "./attest.js";
127
+ import { onLogAppended } from "./log.js";
128
+ import { normalizeUsd } from "./money.js";
129
+ import { isPayloadHash } from "./payload.js";
130
+ import { publishVerifiedPrefix, snapshotPrefix } from "./verified-snapshot.js";
131
+ import { verifyText, verifyWithRecords, } from "./verify.js";
132
+ /**
133
+ * Whether this process may resume a read behind a published snapshot
134
+ * (APRV-188).
135
+ *
136
+ * Off by default, so the daemon, every CLI verb, the channels and
137
+ * `approval log verify` read exactly as they did before this existed. It is
138
+ * turned on by one caller, `approval hook`, which is the short-lived process
139
+ * whose empty cache pays for the walk.
140
+ *
141
+ * A process-wide switch rather than a per-call option on purpose. A hook's
142
+ * first verified read is followed by several more from inside `core/gate.ts`,
143
+ * which threads no options of its own; if the switch were an option only the
144
+ * first read could carry it, and the value is in the FIRST read of a process,
145
+ * with the rest served from the cache the first one seeded.
146
+ */
147
+ let snapshotReads = false;
148
+ /**
149
+ * The proof this process's cached reads run when a call names none (APRV-217).
150
+ *
151
+ * `null` means {@link FULL_READ_PROOF}, and that is what every process starts
152
+ * with: a CLI verb, a hook, a test, and a daemon before its own startup line.
153
+ *
154
+ * It is a process-wide switch for the reason `snapshotReads` above is one. The
155
+ * daemon's tick reads are only some of the reads its process makes: the queue
156
+ * renderer and the pending-queue builder read the same log through call paths
157
+ * that thread no options of their own, and a per-call option could not reach
158
+ * them. What sets this is `approval daemon run` / `approval up`, once, from the
159
+ * mode they printed on the `started` line. Nothing an agent runs sets it.
160
+ */
161
+ let processReadProof = null;
162
+ /**
163
+ * Set (or clear, with `null`) the default proof for this process's reads.
164
+ *
165
+ * Called by exactly one kind of caller: the two long-lived operator verbs, at
166
+ * startup, after they have resolved the flag against the policy.
167
+ */
168
+ export function useReadProof(proof) {
169
+ processReadProof = proof;
170
+ }
171
+ /** The default proof in force here. Diagnostics and tests. */
172
+ export function readProofInForce() {
173
+ return processReadProof ?? FULL_READ_PROOF;
174
+ }
175
+ /** Opt this process into (or out of) snapshot-resumed reads. */
176
+ export function useVerifiedSnapshots(enabled) {
177
+ snapshotReads = enabled;
178
+ }
179
+ /** Whether snapshot-resumed reads are enabled here. Diagnostics and tests. */
180
+ export function verifiedSnapshotsEnabled() {
181
+ return snapshotReads;
182
+ }
183
+ function refuseRead(code, message) {
184
+ return { ok: false, code, message };
185
+ }
186
+ function errorMessage(cause) {
187
+ return cause instanceof Error ? cause.message : String(cause);
188
+ }
189
+ /** The head of a record list: the last record's `(seq, hash)`, or `null`. */
190
+ export function headOf(records) {
191
+ const last = records[records.length - 1];
192
+ return last === undefined ? null : { seq: last.seq, hash: last.hash };
193
+ }
194
+ // ---------------------------------------------------------------------------
195
+ // The verified-read cache
196
+ //
197
+ // The soundness argument lives in the module header. The code below is the
198
+ // mechanical part: what is remembered, what discards it, and what a surviving
199
+ // entry is allowed to save.
200
+ // ---------------------------------------------------------------------------
201
+ const NEWLINE = 0x0a;
202
+ /** Reads between full re-proofs under `incremental` (SPEC-signed default). */
203
+ export const DEFAULT_FULL_REPROOF_EVERY = 50;
204
+ /** Milliseconds between full re-proofs under `incremental`. */
205
+ export const DEFAULT_FULL_REPROOF_AFTER_MS = 60_000;
206
+ /**
207
+ * What a reader gets when it asks for nothing: today's proof on every read.
208
+ *
209
+ * The default is `full` because it is the behaviour this repository has today
210
+ * and the one an operator has attested to. `incremental` is reached only by a
211
+ * caller that was handed a policy (or a flag) saying so.
212
+ */
213
+ export const FULL_READ_PROOF = Object.freeze({
214
+ mode: "full",
215
+ everyReads: DEFAULT_FULL_REPROOF_EVERY,
216
+ afterMs: DEFAULT_FULL_REPROOF_AFTER_MS,
217
+ });
218
+ /**
219
+ * Bytes fed to SHA-256 by this module since the last reset.
220
+ *
221
+ * The one seam the APRV-217 tests need: "the incremental path hashes only the
222
+ * appended bytes" is a claim about work, and work is invisible in a result by
223
+ * design. A wall-clock assertion would measure the machine instead of the code,
224
+ * so the tests count bytes here. Nothing in the runtime reads it.
225
+ */
226
+ let hashedBytes = 0;
227
+ /** Bytes hashed by the verified-read cache so far. Diagnostics and tests. */
228
+ export function hashedByteCount() {
229
+ return hashedBytes;
230
+ }
231
+ /** Reset {@link hashedByteCount}. Tests only. */
232
+ export function resetHashedByteCount() {
233
+ hashedBytes = 0;
234
+ }
235
+ /**
236
+ * How many distinct logs one process remembers. A process reads one log; a test
237
+ * process reads many, and an unbounded map would hold every record of every
238
+ * scratch log for the life of the run. Eviction is by insertion order and costs
239
+ * only a cold read.
240
+ */
241
+ const MAX_CACHED_LOGS = 8;
242
+ function sha256(bytes) {
243
+ hashedBytes += bytes.length;
244
+ return createHash("sha256").update(bytes).digest("hex");
245
+ }
246
+ /** A SHA-256 state over `bytes`, counted for {@link hashedByteCount}. */
247
+ function hashState(bytes) {
248
+ hashedBytes += bytes.length;
249
+ return createHash("sha256").update(bytes);
250
+ }
251
+ /** Feed `bytes` to `state`, counted. Mutates and returns the state given. */
252
+ function feedHash(state, bytes) {
253
+ hashedBytes += bytes.length;
254
+ state.update(bytes);
255
+ return state;
256
+ }
257
+ /**
258
+ * Is a full re-proof owed for `entry`?
259
+ *
260
+ * Every answer here is "prove more", never "prove less": the `true` branches
261
+ * send the read down the whole-file path, which is today's behaviour exactly.
262
+ */
263
+ function fullReproofDue(entry, proof, now) {
264
+ if (entry.forceFullReproof)
265
+ return true;
266
+ if (entry.hashState === null)
267
+ return true;
268
+ // `everyReads` counts the reads a single full pass may cover, the anchoring
269
+ // read included: at 1 every read re-proves in full, at 50 one pass anchors
270
+ // itself and the 49 reads that follow it.
271
+ if (entry.readsSinceFullReproof + 1 >= proof.everyReads)
272
+ return true;
273
+ return now - entry.lastFullReproofAt >= proof.afterMs;
274
+ }
275
+ /**
276
+ * Read exactly `into.length` bytes at `position`. Returns false for a short
277
+ * read, which is a file that changed under us and therefore a guard failure.
278
+ */
279
+ function readExactly(fd, into, position) {
280
+ let done = 0;
281
+ while (done < into.length) {
282
+ let got;
283
+ try {
284
+ got = readSync(fd, into, done, into.length - done, position + done);
285
+ }
286
+ catch {
287
+ return false;
288
+ }
289
+ if (got <= 0)
290
+ return false;
291
+ done += got;
292
+ }
293
+ return true;
294
+ }
295
+ /**
296
+ * Freeze a record and everything reachable from it.
297
+ *
298
+ * Cached records are handed to every subsequent reader. A caller that mutated
299
+ * one would be rewriting another reader's evidence in place, which is the one
300
+ * way a memory cache could forge a record that no log ever contained. Freezing
301
+ * turns that from a convention into a `TypeError`.
302
+ */
303
+ function deepFreeze(value) {
304
+ if (typeof value !== "object" || value === null || Object.isFrozen(value))
305
+ return;
306
+ Object.freeze(value);
307
+ for (const inner of Object.values(value))
308
+ deepFreeze(inner);
309
+ }
310
+ /**
311
+ * Process-lifetime, memory-only store of last-verified log state.
312
+ *
313
+ * Nothing here is written to disk and nothing is shared between processes. An
314
+ * instance is safe to construct per caller (the daemon may want its own); the
315
+ * default is {@link processReadCache}, which is why a repeat reader gets the
316
+ * acceleration without asking for it and a one-shot CLI process cannot notice
317
+ * it exists.
318
+ */
319
+ export class VerifiedReadCache {
320
+ #entries = new Map();
321
+ #hits = 0;
322
+ #misses = 0;
323
+ /**
324
+ * Misses that were served from a published snapshot instead of a cold walk
325
+ * (APRV-188). Counted alongside the miss it followed rather than instead of
326
+ * it: the process cache genuinely had nothing, and the walk was skipped only
327
+ * because another process's verification was re-proved over these bytes.
328
+ */
329
+ #resumed = 0;
330
+ /**
331
+ * Reads that hashed the whole prefix — a full digest compare, or a cold walk
332
+ * (APRV-217). Equal to `hits + misses` under `full`, which is the point: it
333
+ * is how a tick line says which path its reads took.
334
+ */
335
+ #fullReproofs = 0;
336
+ /** Forget everything. Tests use this to force a genuinely cold read. */
337
+ clear() {
338
+ this.#entries.clear();
339
+ this.#hits = 0;
340
+ this.#misses = 0;
341
+ this.#resumed = 0;
342
+ this.#fullReproofs = 0;
343
+ }
344
+ /** How many logs are remembered. Diagnostics and tests only. */
345
+ get size() {
346
+ return this.#entries.size;
347
+ }
348
+ /**
349
+ * Reads that reused a proved prefix, and reads that verified from genesis.
350
+ *
351
+ * Diagnostics, and the one way a test can tell the two paths apart: reusing a
352
+ * prefix is *designed* to be invisible in the result, so a test that wants to
353
+ * assert "this tamper discarded the cache" has nothing else to look at.
354
+ */
355
+ get stats() {
356
+ return {
357
+ hits: this.#hits,
358
+ misses: this.#misses,
359
+ resumed: this.#resumed,
360
+ fullReproofs: this.#fullReproofs,
361
+ };
362
+ }
363
+ /**
364
+ * Require a full re-proof of `logPath` on this cache's next read (APRV-217).
365
+ *
366
+ * Called by `core/log.ts` after a successful append, through the listener it
367
+ * registers below. Can only add work: the next read hashes the whole prefix
368
+ * exactly as a `full` read does.
369
+ */
370
+ requireFullReproof(logPath) {
371
+ const entry = this.#entries.get(resolve(logPath));
372
+ if (entry !== undefined)
373
+ entry.forceFullReproof = true;
374
+ }
375
+ /**
376
+ * Verify `logPath`, reusing a proved-identical prefix when there is one.
377
+ *
378
+ * The whole file is read once, and every decision is made from that single
379
+ * snapshot: nothing is re-`stat`ed and re-read behind its own conclusion.
380
+ */
381
+ read(logPath, options, snapshot = {}, proof = FULL_READ_PROOF) {
382
+ const key = resolve(logPath);
383
+ const entry = this.#entries.get(key);
384
+ // The incremental path (APRV-217), taken only when every precondition holds
385
+ // and the cadence has not come due. It reads the head line and the appended
386
+ // bytes and nothing else; on ANY guard failure it returns `null` and this
387
+ // read falls back to the whole-file path below, which is a cold walk.
388
+ if (proof.mode === "incremental" &&
389
+ entry !== undefined &&
390
+ entry.schemaKey === (options.schemaDir === undefined ? "" : resolve(options.schemaDir)) &&
391
+ !fullReproofDue(entry, proof, Date.now())) {
392
+ const incremental = this.#readTail(logPath, key, entry, options, snapshot);
393
+ if (incremental !== null)
394
+ return incremental;
395
+ }
396
+ return this.#readWhole(logPath, key, options, snapshot, proof.mode);
397
+ }
398
+ /**
399
+ * Today's read, unchanged: the whole file, the whole prefix hash, the walk.
400
+ *
401
+ * Every `full` read lands here, and so does every `incremental` read whose
402
+ * cadence came due or whose guards rejected. The only APRV-217 addition is
403
+ * the hash state handed to {@link VerifiedReadCache.#remember}, which is
404
+ * built from the same single pass over the bytes rather than a second one.
405
+ */
406
+ #readWhole(logPath, key, options, snapshot, mode) {
407
+ this.#fullReproofs += 1;
408
+ let raw;
409
+ try {
410
+ raw = readFileSync(logPath);
411
+ }
412
+ catch {
413
+ // An absent or unreadable log is not this module's vocabulary: hand it to
414
+ // the cold path, which owns the ENOENT-is-an-empty-log rule and the exact
415
+ // failure messages. A log we cannot read is also a log we must forget.
416
+ this.#entries.delete(key);
417
+ this.#misses += 1;
418
+ return verifyWithRecords(logPath, options);
419
+ }
420
+ let mtimeMs = Number.NaN;
421
+ try {
422
+ mtimeMs = statSync(logPath).mtimeMs;
423
+ }
424
+ catch {
425
+ // No mtime is simply no hint; an entry that wanted one is discarded below.
426
+ }
427
+ const schemaKey = options.schemaDir === undefined ? "" : resolve(options.schemaDir);
428
+ const entry = this.#entries.get(key);
429
+ const cached = entry === undefined ? null : reusablePrefix(entry, raw, schemaKey, mtimeMs);
430
+ // A published snapshot is consulted only where this process has nothing:
431
+ // the in-process proof always wins, because it is the stronger one (these
432
+ // records were walked here). See `core/verified-snapshot.ts` for what the
433
+ // weaker one costs and what it is allowed to skip.
434
+ let prefix = cached;
435
+ /** The digest of the endorsed prefix, already re-proved. */
436
+ let provedDigest = null;
437
+ if (cached === null) {
438
+ this.#entries.delete(key);
439
+ this.#misses += 1;
440
+ if (snapshot.consume === true) {
441
+ const admitted = snapshotPrefix(logPath, raw, options.schemaDir);
442
+ if (admitted.ok) {
443
+ prefix = admitted.prefix;
444
+ this.#resumed += 1;
445
+ // `admitSnapshot` hashed exactly these bytes a moment ago, so when the
446
+ // file has not grown past the endorsed prefix the entry below may
447
+ // carry that digest instead of hashing the same megabytes twice —
448
+ // the APRV-206 argument, with the proof coming from the admission.
449
+ if (raw.length === admitted.prefix.byteLength)
450
+ provedDigest = admitted.digest;
451
+ }
452
+ }
453
+ }
454
+ else {
455
+ this.#hits += 1;
456
+ }
457
+ const text = prefix === null ? raw.toString("utf8") : raw.toString("utf8", prefix.byteLength);
458
+ const verified = verifyText(logPath, text, options, prefix);
459
+ // APRV-206. When the file has not grown since the entry that was just
460
+ // re-proved, the digest of these bytes is the digest that entry holds: the
461
+ // prefix hash covered the whole file, and `reusablePrefix` has just shown
462
+ // the file is those same bytes. Re-deriving it would hash the same megabytes
463
+ // a second time in one read, which on a repeat reader (the listener between
464
+ // taps) is the larger half of the read. Nothing is admitted on trust: this
465
+ // is only reached when the hash comparison above passed.
466
+ const known = entry !== undefined && cached !== null && raw.length === entry.byteLength
467
+ ? entry.prefixHash
468
+ : provedDigest;
469
+ // The digest of these bytes, computed at most ONCE per read and shared by
470
+ // everything that wants it (APRV-211). Only a clean read has anything to
471
+ // remember or to publish, so a torn or corrupt log is never hashed here at
472
+ // all, and `#remember` still owns the rule about which reads qualify.
473
+ const result = verified.result;
474
+ if (result.status === "clean" && result.head !== null) {
475
+ // Under `full` this is today's line, unchanged. Under `incremental` the
476
+ // entry must also carry the un-finalised state at these bytes, so the
477
+ // digest is taken FROM that state: still one pass over the file, never
478
+ // two, and the state is reused outright when the file has not grown.
479
+ let carried;
480
+ let digest;
481
+ if (entry !== undefined && cached !== null && raw.length === entry.byteLength) {
482
+ // The file has not grown since an entry whose bytes were just re-proved:
483
+ // its digest stands, and so does the state it carries. A `full` read
484
+ // carries that state forward untouched rather than dropping it, so a
485
+ // reader that mixes modes (the daemon's tick reads and the queue
486
+ // renderer's, in one process) does not thrash the anchor.
487
+ digest = entry.prefixHash;
488
+ carried =
489
+ entry.hashState ?? (mode === "incremental" ? hashState(raw) : null);
490
+ }
491
+ else if (known !== null) {
492
+ // A snapshot admission proved these bytes and hashed them elsewhere.
493
+ // There is no state to carry without a second pass, so none is kept;
494
+ // the next read anchors one.
495
+ digest = known;
496
+ carried = null;
497
+ }
498
+ else {
499
+ // The one hash of these bytes this read pays, in both modes: `full`
500
+ // spent exactly this before APRV-217, through `sha256(raw)`.
501
+ carried = hashState(raw);
502
+ digest = carried.copy().digest("hex");
503
+ }
504
+ this.#remember(key, raw, schemaKey, mtimeMs, verified, digest, carried);
505
+ // The publisher is handed the digest rather than left to recompute it: it
506
+ // endorses exactly the bytes this read proved, and a second hash of the
507
+ // same megabytes was the larger half of a daemon tick.
508
+ if (snapshot.publish === true) {
509
+ publishVerifiedPrefix(logPath, raw.length, raw.length > 0 && raw[raw.length - 1] === NEWLINE, digest, result.records, result.head, options.schemaDir);
510
+ }
511
+ }
512
+ else {
513
+ this.#remember(key, raw, schemaKey, mtimeMs, verified, known, null);
514
+ }
515
+ return verified;
516
+ }
517
+ /**
518
+ * The incremental read (APRV-217): the head line and the appended bytes.
519
+ *
520
+ * Returns `null` for every guard failure, which sends the caller to the
521
+ * whole-file path — a cold walk, the same fallback a mismatched prefix hash
522
+ * has always taken. Nothing here can produce a verdict the cold walk would
523
+ * not: the tail is handed to the same `verifyText`, chained onto the same
524
+ * cached head, with the same schema options.
525
+ */
526
+ #readTail(logPath, key, entry, options, snapshot) {
527
+ let fd;
528
+ try {
529
+ fd = openSync(logPath, "r");
530
+ }
531
+ catch {
532
+ return null;
533
+ }
534
+ try {
535
+ let size;
536
+ let mtimeMs;
537
+ try {
538
+ const stats = fstatSync(fd);
539
+ if (!stats.isFile())
540
+ return null;
541
+ size = stats.size;
542
+ mtimeMs = stats.mtimeMs;
543
+ }
544
+ catch {
545
+ return null;
546
+ }
547
+ // Guards 2 to 4 of the design's ladder, in the order `reusablePrefix`
548
+ // runs them, over the same facts. Guard 1 (the schema key) was answered
549
+ // by the caller; guard 5 (the full digest) is what the cadence decides,
550
+ // and this path is reached only when it is not due.
551
+ if (size < entry.byteLength)
552
+ return null;
553
+ if (size === entry.byteLength && mtimeMs !== entry.mtimeMs)
554
+ return null;
555
+ const headLineEnd = entry.headLineStart + entry.headLine.length;
556
+ if (headLineEnd + 1 !== entry.byteLength)
557
+ return null;
558
+ const headBytes = Buffer.allocUnsafe(entry.headLine.length + 1);
559
+ if (!readExactly(fd, headBytes, entry.headLineStart))
560
+ return null;
561
+ if (headBytes[headBytes.length - 1] !== NEWLINE)
562
+ return null;
563
+ if (!headBytes.subarray(0, entry.headLine.length).equals(entry.headLine))
564
+ return null;
565
+ const tail = Buffer.allocUnsafe(size - entry.byteLength);
566
+ if (tail.length > 0 && !readExactly(fd, tail, entry.byteLength))
567
+ return null;
568
+ // Step 6: the appended bytes go to a COPY of the state, and the copy
569
+ // becomes the state of the entry this read leaves behind. The digest of
570
+ // the whole file falls out of it, so the snapshot publisher and the entry
571
+ // below are served without hashing a byte twice.
572
+ const state = entry.hashState === null ? null : entry.hashState.copy();
573
+ if (state === null)
574
+ return null;
575
+ if (tail.length > 0)
576
+ feedHash(state, tail);
577
+ const digest = state.copy().digest("hex");
578
+ this.#hits += 1;
579
+ const prefix = {
580
+ byteLength: entry.byteLength,
581
+ lines: entry.lines,
582
+ head: entry.head,
583
+ records: entry.records,
584
+ };
585
+ const verified = verifyText(logPath, tail.toString("utf8"), options, prefix);
586
+ const result = verified.result;
587
+ if (result.status !== "clean" || result.head === null) {
588
+ // The same rule the whole-file path applies: nothing is resumed from a
589
+ // read that found damage, and the entry that led here is dropped.
590
+ this.#entries.delete(key);
591
+ return verified;
592
+ }
593
+ // The head line of the file as it now stands. With no appended bytes it
594
+ // is the one the entry already holds; otherwise it is the last line of
595
+ // the tail, whose first byte follows the newline before it.
596
+ let headLineStart = entry.headLineStart;
597
+ let headLine = entry.headLine;
598
+ if (tail.length > 0) {
599
+ const relative = tail.lastIndexOf(NEWLINE, tail.length - 2) + 1;
600
+ headLineStart = entry.byteLength + relative;
601
+ headLine = Buffer.from(tail.subarray(relative, tail.length - 1));
602
+ }
603
+ const records = [...verified.records];
604
+ for (const record of records)
605
+ deepFreeze(record);
606
+ this.#store(key, {
607
+ schemaKey: entry.schemaKey,
608
+ byteLength: size,
609
+ lines: result.records,
610
+ prefixHash: digest,
611
+ headLineStart,
612
+ headLine,
613
+ head: result.head,
614
+ records,
615
+ mtimeMs,
616
+ hashState: state,
617
+ // The cadence carries forward: this read did not re-prove the prefix,
618
+ // so it does not re-anchor the clock or the count.
619
+ lastFullReproofAt: entry.lastFullReproofAt,
620
+ readsSinceFullReproof: entry.readsSinceFullReproof + 1,
621
+ forceFullReproof: false,
622
+ });
623
+ if (snapshot.publish === true) {
624
+ publishVerifiedPrefix(logPath, size, true, digest, result.records, result.head, options.schemaDir);
625
+ }
626
+ return verified;
627
+ }
628
+ finally {
629
+ try {
630
+ closeSync(fd);
631
+ }
632
+ catch {
633
+ // Nothing actionable: the read is done and the descriptor is the OS's.
634
+ }
635
+ }
636
+ }
637
+ /**
638
+ * Record a clean read. Only `clean` qualifies: a torn or corrupt log has no
639
+ * prefix this module is willing to resume from, and any entry it had was
640
+ * already dropped by the mismatch that exposed the damage.
641
+ */
642
+ #remember(key, raw, schemaKey, mtimeMs, verified,
643
+ /**
644
+ * The digest of exactly these bytes, when the caller already re-proved them
645
+ * against a stored one (APRV-206). `null` means "hash them".
646
+ */
647
+ knownDigest,
648
+ /**
649
+ * The un-finalised SHA-256 state at these bytes (APRV-217), or `null` under
650
+ * `full`, where no state is kept and nothing is spent building one.
651
+ */
652
+ state) {
653
+ const result = verified.result;
654
+ if (result.status !== "clean" || result.head === null) {
655
+ this.#entries.delete(key);
656
+ return;
657
+ }
658
+ // A clean, non-empty log ends with a newline, so the head line runs from
659
+ // just after the previous newline to the last byte.
660
+ const headLineStart = raw.lastIndexOf(NEWLINE, raw.length - 2) + 1;
661
+ const records = [...verified.records];
662
+ for (const record of records)
663
+ deepFreeze(record);
664
+ this.#store(key, {
665
+ schemaKey,
666
+ byteLength: raw.length,
667
+ lines: result.records,
668
+ prefixHash: knownDigest ?? sha256(raw),
669
+ headLineStart,
670
+ headLine: Buffer.from(raw.subarray(headLineStart, raw.length - 1)),
671
+ head: result.head,
672
+ records,
673
+ mtimeMs,
674
+ hashState: state,
675
+ // This read hashed the whole prefix, so it IS the anchor: the cadence
676
+ // starts again here, and any pending force is discharged.
677
+ lastFullReproofAt: Date.now(),
678
+ readsSinceFullReproof: 0,
679
+ forceFullReproof: false,
680
+ });
681
+ }
682
+ /** Put `entry` in, evicting by insertion order. The one writer of the map. */
683
+ #store(key, entry) {
684
+ this.#entries.delete(key);
685
+ if (this.#entries.size >= MAX_CACHED_LOGS) {
686
+ const oldest = this.#entries.keys().next();
687
+ if (!oldest.done)
688
+ this.#entries.delete(oldest.value);
689
+ }
690
+ this.#entries.set(key, entry);
691
+ }
692
+ }
693
+ /**
694
+ * Decide whether `entry` may stand for the first `entry.byteLength` bytes of
695
+ * `raw`, and return the resume point when it may.
696
+ *
697
+ * The checks run cheapest-first, but only the last one is load-bearing: the
698
+ * prefix hash. Everything above it exists to reject an obviously changed file
699
+ * without hashing it, and every check can only *reject*.
700
+ */
701
+ function reusablePrefix(entry, raw, schemaKey, mtimeMs) {
702
+ // Records verified against one schema set are not evidence under another.
703
+ if (entry.schemaKey !== schemaKey)
704
+ return null;
705
+ // The file shrank: it cannot contain the prefix, and a truncated log must be
706
+ // re-read cold so the head reported is the file's, not the one remembered.
707
+ if (raw.length < entry.byteLength)
708
+ return null;
709
+ // Same size, moved mtime: an in-place rewrite. The prefix hash would catch it
710
+ // anyway; this rejects it a pass earlier. mtime never admits anything.
711
+ if (raw.length === entry.byteLength && mtimeMs !== entry.mtimeMs)
712
+ return null;
713
+ // The head line, byte for byte, where it was recorded — and still terminated,
714
+ // and still the end of the prefix.
715
+ const headLineEnd = entry.headLineStart + entry.headLine.length;
716
+ if (headLineEnd + 1 !== entry.byteLength)
717
+ return null;
718
+ if (raw[headLineEnd] !== NEWLINE)
719
+ return null;
720
+ if (!raw.subarray(entry.headLineStart, headLineEnd).equals(entry.headLine))
721
+ return null;
722
+ // The proof: these are the bytes this process verified in full.
723
+ if (sha256(raw.subarray(0, entry.byteLength)) !== entry.prefixHash)
724
+ return null;
725
+ return {
726
+ byteLength: entry.byteLength,
727
+ lines: entry.lines,
728
+ head: entry.head,
729
+ records: entry.records,
730
+ };
731
+ }
732
+ /**
733
+ * The cache {@link readVerifiedRecords} uses when a caller names none.
734
+ *
735
+ * Shared per process, which is what makes a watch loop fast without any wiring:
736
+ * the daemon calls `readVerifiedRecords` exactly as every other consumer does.
737
+ */
738
+ export const processReadCache = new VerifiedReadCache();
739
+ /**
740
+ * Tell the process cache when this process appends (APRV-217).
741
+ *
742
+ * Registered here rather than called from `core/log.ts` because the dependency
743
+ * runs this way: `core/state.ts` already knows about the log, and a value
744
+ * import in the other direction would close a cycle through `core/verify.ts`.
745
+ * The listener can only ADD work — the named log's next read hashes its whole
746
+ * prefix — so a process that never loads this module simply reads as it always
747
+ * did.
748
+ */
749
+ onLogAppended((logPath) => {
750
+ processReadCache.requireFullReproof(logPath);
751
+ });
752
+ /**
753
+ * Read the log and refuse unless the whole chain verifies.
754
+ *
755
+ * An absent log is an empty log (nothing has happened yet), exactly as
756
+ * `appendEvent` and `approval log verify` treat it.
757
+ *
758
+ * The file is opened once as a readability probe *before* verification so that
759
+ * "I could not open this file" stays an I/O fact (`log-unreadable`) and never
760
+ * arrives dressed as corruption — the same split the CLI's exit table draws, and
761
+ * the one thing `verify()` alone cannot express, since from inside the chain
762
+ * walker an unreadable log is indistinguishable from a broken one.
763
+ *
764
+ * Torn-tail behavior is unchanged from the pre-APRV-20 reader: the tear is
765
+ * reported as `log-torn-tail` and nothing is repaired, because truncating a torn
766
+ * line is a human decision.
767
+ *
768
+ * Reads go through the verified-read cache by default (see the module header):
769
+ * a prefix re-proved byte-identical is not re-walked, the appended suffix is
770
+ * verified in full, and any mismatch falls back to genesis. The result is the
771
+ * result of a cold read on the same bytes, always. `cache: null` opts out.
772
+ */
773
+ export function readVerifiedRecords(logPath, options = {}) {
774
+ try {
775
+ closeSync(openSync(logPath, "r"));
776
+ }
777
+ catch (cause) {
778
+ if (cause.code === "ENOENT") {
779
+ return { ok: true, records: [], head: null };
780
+ }
781
+ return refuseRead("log-unreadable", `log ${logPath} could not be read: ${errorMessage(cause)}`);
782
+ }
783
+ const { cache: requested, publishSnapshot: publish, readProof, ...verifyOptions } = options;
784
+ const cache = requested === undefined ? processReadCache : requested;
785
+ // `cache: null` is the explicit cold read an audit asks for, and it stays
786
+ // cold: a caller that opted out of this process's own proved prefix has not
787
+ // opted into another process's.
788
+ const verified = cache === null
789
+ ? verifyWithRecords(logPath, verifyOptions)
790
+ : cache.read(logPath, verifyOptions, {
791
+ consume: snapshotReads,
792
+ ...(publish === undefined ? {} : { publish }),
793
+ }, readProof ?? processReadProof ?? FULL_READ_PROOF);
794
+ switch (verified.result.status) {
795
+ case "clean":
796
+ return { ok: true, records: verified.records, head: verified.result.head };
797
+ case "torn-tail":
798
+ return refuseRead("log-torn-tail", `log ${logPath} ends without a newline: the final record is truncated, the signature of a crashed write. Nothing is repaired here; run \`approval log verify\`.`);
799
+ case "corrupt":
800
+ return refuseRead("log-corrupt", `log ${logPath} does not verify (${verified.result.reason}${verified.result.firstBadSeq === null ? "" : ` at seq ${verified.result.firstBadSeq}`}): ${verified.result.message}. Nothing may be authorized from a log that does not verify; run \`approval log verify\`.`);
801
+ }
802
+ }
803
+ /**
804
+ * Why a requester retracted a pending request (amended SPEC.md §6.3, APRV-106).
805
+ *
806
+ * Closed, and mirrored by `event.schema.json`: a withdrawal that does not say
807
+ * why is a fact audit cannot use. `timeout` is the one the hook writes — the
808
+ * requester stopped waiting, so a decision on this request can no longer be
809
+ * consumed by anyone.
810
+ */
811
+ export const WITHDRAW_REASONS = ["timeout", "cancelled", "superseded"];
812
+ /** Is `value` one of the closed withdrawal reasons? */
813
+ export function isWithdrawReason(value) {
814
+ return typeof value === "string" && WITHDRAW_REASONS.includes(value);
815
+ }
816
+ /** A record's payload as a map, or `{}` when it has none. */
817
+ export function payloadOf(record) {
818
+ const payload = record.payload;
819
+ return typeof payload === "object" && payload !== null ? payload : {};
820
+ }
821
+ function declaredFrom(record) {
822
+ const payload = payloadOf(record);
823
+ const cls = payload["class"];
824
+ const cost = payload["est_cost_usd"];
825
+ const reversible = payload["reversible"];
826
+ const summary = payload["summary"];
827
+ const hash = payload["payload_hash"];
828
+ const execution = payload["execution"];
829
+ const waitUntil = payload["wait_until"];
830
+ const policySha256 = payload[POLICY_HASH_FIELD];
831
+ return {
832
+ class: typeof cls === "string" ? cls : null,
833
+ est_cost_usd: normalizeUsd(cost),
834
+ reversible: typeof reversible === "boolean" ? reversible : null,
835
+ summary: typeof summary === "string" ? summary : null,
836
+ payload_hash: isPayloadHash(hash) ? hash : null,
837
+ // Recognized values only. An unrecognized `execution` reads as `null`,
838
+ // which is the ordinary token-minting path: a claim the runtime does not
839
+ // understand must not change what the runtime does.
840
+ execution: execution === "harness" ? "harness" : null,
841
+ wait_until: typeof waitUntil === "string" && !Number.isNaN(Date.parse(waitUntil)) ? waitUntil : null,
842
+ // A malformed hash reads as `null`, which is the pre-APRV-118 shape: the
843
+ // grant path then has nothing to compare and proceeds under the current
844
+ // policy. Treating an unreadable value as a mismatch would let a corrupt
845
+ // byte void a pending request, and treating it as a match would let a
846
+ // crafted one claim agreement it cannot prove.
847
+ policy_sha256: isPolicySha256(policySha256) ? policySha256 : null,
848
+ };
849
+ }
850
+ /**
851
+ * Derive `actionKey`'s approval state from `records`.
852
+ *
853
+ * Pure: no I/O, no clock. `ts` is the moment the question is being asked and is
854
+ * **required** — lazy expiry is arithmetic on it, and a state function that read
855
+ * the clock could not be replayed.
856
+ *
857
+ * Sequencing rules, all of them deliberate:
858
+ *
859
+ * - An `approval.requested` **resets** the derivation. A key that was rejected
860
+ * or expired may be requested again; the new request starts a fresh cycle and
861
+ * the old decision no longer governs. (An action that has *executed* is a
862
+ * different matter — `core/gate.ts`'s `request` refuses that on idempotency
863
+ * grounds.)
864
+ * - The **first** decision after a request wins. The gate refuses to append a
865
+ * second one, so a log carrying two is a log written by something else; the
866
+ * fail-closed reading is that the earliest human decision stands rather than
867
+ * that a later append can overwrite it.
868
+ * - Execution facts accumulate across the whole log for the key, independent of
869
+ * the request cycle: an action that has executed has executed, and no
870
+ * subsequent request un-executes it.
871
+ * - Withdrawal (APRV-106): an `approval.withdrawn` settles the request like any
872
+ * other terminal event, and is the only one of them that records no decision.
873
+ * It participates in the "first settlement wins" rule above, so a withdrawal
874
+ * appended after a human's answer does not erase the answer; the gate refuses
875
+ * to append one at all in that case.
876
+ * - Expiry: an `approval.expired` record sets `expiredByEvent`. With no such
877
+ * record, `ttlMs !== null` and `ts > requestTs + ttlMs` sets `expiredLazily`.
878
+ * Both yield `state: "expired"`. An unparseable `requestTs` or `ts` also
879
+ * yields `expired`: liveness that cannot be demonstrated is not assumed. (The
880
+ * event schema's `date-time` format makes that unreachable through the real
881
+ * append path; it is a backstop, not a live branch.)
882
+ */
883
+ export function requestState(records, actionKey, ts, ttlMs) {
884
+ let task = null;
885
+ let requestSeq = null;
886
+ let requestTs = null;
887
+ let requestActor = null;
888
+ let decision = null;
889
+ let decisionSeq = null;
890
+ let decisionTs = null;
891
+ let expiredByEvent = false;
892
+ let declared = {
893
+ class: null,
894
+ est_cost_usd: null,
895
+ reversible: null,
896
+ summary: null,
897
+ payload_hash: null,
898
+ execution: null,
899
+ wait_until: null,
900
+ policy_sha256: null,
901
+ };
902
+ const execution = { started: null, completed: null, failed: null };
903
+ const settle = (record, value) => {
904
+ if (requestSeq === null)
905
+ return;
906
+ // Revocation is the one decision that legitimately follows another: a
907
+ // human withdraws a grant they already made, so `approval.revoked`
908
+ // supersedes. Every other decision settles only an undecided request —
909
+ // the gate refuses to append a second one, and a log carrying two was
910
+ // written by something else, where the fail-closed reading is that the
911
+ // earliest human answer stands rather than that a later append overwrites it.
912
+ if (decision !== null && value !== "revoked")
913
+ return;
914
+ decision = value;
915
+ decisionSeq = record.seq;
916
+ decisionTs = record.ts;
917
+ if (value === "expired")
918
+ expiredByEvent = true;
919
+ };
920
+ for (const record of records) {
921
+ if (record.action_key !== actionKey)
922
+ continue;
923
+ switch (record.event) {
924
+ case "approval.requested":
925
+ task = record.task ?? task;
926
+ requestSeq = record.seq;
927
+ requestTs = record.ts;
928
+ requestActor = record.actor;
929
+ decision = null;
930
+ decisionSeq = null;
931
+ decisionTs = null;
932
+ expiredByEvent = false;
933
+ declared = declaredFrom(record);
934
+ break;
935
+ case "approval.granted":
936
+ settle(record, "granted");
937
+ break;
938
+ case "approval.rejected":
939
+ settle(record, "rejected");
940
+ break;
941
+ case "approval.revoked":
942
+ settle(record, "revoked");
943
+ break;
944
+ case "approval.expired":
945
+ settle(record, "expired");
946
+ break;
947
+ case "approval.withdrawn":
948
+ settle(record, "withdrawn");
949
+ break;
950
+ case "execution.started":
951
+ execution.started = record.seq;
952
+ task = record.task ?? task;
953
+ break;
954
+ case "execution.completed":
955
+ execution.completed = record.seq;
956
+ break;
957
+ case "execution.failed":
958
+ execution.failed = record.seq;
959
+ break;
960
+ default:
961
+ break;
962
+ }
963
+ }
964
+ let state;
965
+ let expiredLazily = false;
966
+ if (requestSeq === null) {
967
+ state = "none";
968
+ }
969
+ else if (decision !== null) {
970
+ state = decision;
971
+ }
972
+ else if (ttlMs === null) {
973
+ // No `defaults.approval_ttl` means the policy declares no lapse. A request
974
+ // stays live until a human decides it; inventing a default TTL here would
975
+ // silently reject approvals a policy author never asked to expire.
976
+ state = "requested";
977
+ }
978
+ else {
979
+ const requestedAt = Date.parse(requestTs ?? "");
980
+ const now = Date.parse(ts);
981
+ if (Number.isNaN(requestedAt) || Number.isNaN(now)) {
982
+ state = "expired";
983
+ expiredLazily = true;
984
+ }
985
+ else if (now > requestedAt + ttlMs) {
986
+ state = "expired";
987
+ expiredLazily = true;
988
+ }
989
+ else {
990
+ state = "requested";
991
+ }
992
+ }
993
+ return {
994
+ actionKey,
995
+ state,
996
+ task,
997
+ requestSeq,
998
+ requestTs,
999
+ requestActor,
1000
+ decision,
1001
+ decisionSeq,
1002
+ decisionTs,
1003
+ expiredByEvent,
1004
+ expiredLazily,
1005
+ declared,
1006
+ execution,
1007
+ };
1008
+ }
1009
+ //# sourceMappingURL=state.js.map