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,1271 @@
1
+ /**
2
+ * Execution: the events that say a side effect actually happened (SPEC.md §8,
3
+ * §10.1, and the human-settled execution points of 2026-08-06).
4
+ *
5
+ * `core/gate.ts` decides whether an action *may* run and appends no
6
+ * `execution.*` event. `core/token.ts` spends a manual action's token. This
7
+ * module is the single door between those two facts and the world: it is where
8
+ * `execution.started` is appended before a command is spawned, and where
9
+ * `execution.completed` / `execution.failed` are appended after it exits.
10
+ *
11
+ * ## The five properties this module exists to hold
12
+ *
13
+ * 1. **Nothing starts without authorization.** On the manual path a valid token
14
+ * is REQUIRED; absent it, {@link startExecution} refuses `token-required` and
15
+ * appends nothing at all. On the supervised/autonomous paths no token exists
16
+ * (amended SPEC.md §6.3 gives them no grant), so authorization is proven
17
+ * differently and in this order: the action must be declared in a
18
+ * `task.registered` record, the policy must be attested, loop safety must not
19
+ * have escalated the task, no execution may already have started for the key,
20
+ * the executor's recomputed `payload_hash` must equal the one the declaration
21
+ * bound to (APRV-140), and the budget must pass. Only then is
22
+ * `execution.started` appended.
23
+ * 2. **`started` precedes the side effect.** The CLI's `approval run` appends
24
+ * the start event *before* it spawns the child, never after. A log that
25
+ * records an execution only once it succeeded is a log that cannot tell you
26
+ * about the one that did not.
27
+ * 3. **A crash therefore leaves a dangling execution, and that is correct.**
28
+ * Between `started` and its outcome the log honestly says "this began and we
29
+ * do not know how it ended". {@link danglingExecutions} surfaces that state
30
+ * distinctly — `approval status` reports it, `approval queue` does not,
31
+ * because a dangling execution is not a pending decision. It is one of five
32
+ * custody states {@link executionCustody} distinguishes (APRV-120), and the
33
+ * other four matter for the same reason: a harness execution is terminal by
34
+ * design rather than debris, and an attempt whose outcome is unknown is
35
+ * neither a failure nor a thing to retry.
36
+ * 4. **Nothing auto-repairs.** No function here closes a dangling execution as a
37
+ * side effect of anything else. A second `approval run` for the same key does
38
+ * not "recover" the first; it refuses (`token-consumed` on the manual path,
39
+ * `already-executed` off it). Recovery is a human calling
40
+ * {@link resolveExecution} with the outcome they actually observed and a
41
+ * mandatory note saying how they know — the same append path, no fabricated
42
+ * exit code, `attested_by_human: true` so no reader mistakes it for a
43
+ * machine's report. ({@link finishExecution} is the mechanical sibling, used
44
+ * by `approval run`, which watched the child exit.) An automatic
45
+ * reconciliation would have to *guess* whether the email went out, and a
46
+ * guess written into an append-only log is indistinguishable from a fact.
47
+ * The same rule governs an INDETERMINATE outcome, more strictly:
48
+ * {@link reconcileExecution} is human-only, appends beside the record rather
49
+ * than over it, and nothing anywhere converts one into completed or failed.
50
+ * 5. **The budgets contract is honored at the documented charge point.**
51
+ * `core/budgets.ts` charges the manual path at `approval.granted` and the
52
+ * supervised/autonomous paths at `execution.started`. This module is that
53
+ * second charge point: it evaluates budgets at the start timestamp, appends
54
+ * `budget.exceeded` and refuses when they fail, and records
55
+ * `payload.class` + `payload.est_cost_usd` on every start event it writes.
56
+ * The manual path is charged at grant and is deliberately NOT charged again
57
+ * here — `consumeToken` writes that start event, and the evaluator already
58
+ * ignores a start whose window holds a matching grant.
59
+ *
60
+ * ## Loop safety (SPEC.md §10.2), from this side
61
+ *
62
+ * Three consecutive `execution.failed` events for one task escalate it to
63
+ * manual. `core/loop.ts` computes that; this module enforces it on the
64
+ * execution side: an escalated task's supervised/autonomous action refuses with
65
+ * `loop-escalated`, which is not a ban but a redirection — request the action,
66
+ * have a human grant it, and run it with the token. `core/gate.ts` enforces the
67
+ * matching half at intake so the redirection is visible one step earlier.
68
+ *
69
+ * ## Time (amended SPEC.md §8, A2)
70
+ *
71
+ * `execution.*` events are gate-typed, so their timestamps are assigned by the
72
+ * runtime at the write boundary: no public function here takes a `ts`, each
73
+ * reads {@link ExecuteOptions.clock} once, and the party whose budget window
74
+ * and TTL are being judged does not author the clock. Replay is preserved by
75
+ * injection — a test hands in a fixed clock, production hands in nothing.
76
+ */
77
+ import { existsSync } from "node:fs";
78
+ import { join } from "node:path";
79
+ import { isPrincipalActor } from "./actor.js";
80
+ import { attestationRefusal, checkAttestation } from "./attest.js";
81
+ import { evaluateBudgetsWithTask } from "./budgets.js";
82
+ import { tick } from "./clock.js";
83
+ import { attemptsOf, withHeadRetry } from "./head-retry.js";
84
+ import { appendEvent, } from "./log.js";
85
+ import { isLoopEscalated } from "./loop.js";
86
+ import { usdOrZero } from "./money.js";
87
+ import { isPayloadHash } from "./payload.js";
88
+ import { loadPolicy, POLICY_FILENAMES } from "./policy-load.js";
89
+ import { humanOnlyRefusal, resolve } from "./policy-match.js";
90
+ import { readVerifiedRecords } from "./state.js";
91
+ import { forgetPrivateKey, keyStoreDirFor } from "./seal.js";
92
+ import { consumeToken, deliveredToken, verifyTokenSpend, } from "./token.js";
93
+ export { isLoopEscalated, loopEscalation, LOOP_ESCALATION_THRESHOLD, } from "./loop.js";
94
+ /**
95
+ * The closed set of execution refusal codes. Frozen public API in the same sense
96
+ * the gate's and the token module's are: an agent branches on these to decide
97
+ * whether to fix itself, ask a human, or stop.
98
+ *
99
+ * The five token codes are re-exposed verbatim rather than collapsed into one:
100
+ * `approval run` on the manual path is a token spend, and "you presented no
101
+ * token" (`token-required`), "you presented the wrong one" (`token-mismatch`),
102
+ * and "it was already spent" (`token-consumed`) call for three different
103
+ * responses.
104
+ */
105
+ export const EXECUTE_REFUSAL_CODES = [
106
+ /** Execution preflight requires a person or agent principal, never runtime identity. */
107
+ "actor-invalid",
108
+ /** No `task.registered` record declares this action key (SPEC.md §7). */
109
+ "action-not-registered",
110
+ /**
111
+ * The action's class resolves to `human-only` (APRV-185, amended SPEC.md
112
+ * §5.2): the policy reserves it to human hands, and a person performs it
113
+ * outside agent execution entirely.
114
+ *
115
+ * Refused on BOTH paths, before either is chosen, which is what separates it
116
+ * from `token-required`. That code is a redirection — get a token and come
117
+ * back — and this one is not: there is no token to get and no grant that
118
+ * could mint one, because `core/gate.ts` refuses the request that would open
119
+ * one under the same code. Nothing is appended on either path, and no retry
120
+ * of any shape changes the answer.
121
+ *
122
+ * Surfaced verbatim from `core/token.ts` as well, for a manual-path spend of
123
+ * a token whose class a policy amendment raised after the grant, so an
124
+ * executor meets one spelling of one fact.
125
+ */
126
+ "class-human-only",
127
+ /** The class resolves manual and no token was presented. Nothing appended. */
128
+ "token-required",
129
+ /** Loop safety escalated the task to manual (SPEC.md §10.2). */
130
+ "loop-escalated",
131
+ /** Policy is unattested or its bytes changed (`core/attest.ts`). */
132
+ "policy-not-attested",
133
+ /** The attested policy changed after supervised-live intake decided to proceed. */
134
+ "policy-drift",
135
+ /** An `execution.started` already exists for this key (idempotency). */
136
+ "already-executed",
137
+ /** Budgets refused the start; a `budget.exceeded` event WAS appended. */
138
+ "budget-exceeded",
139
+ /** `finishExecution` found no unfinished `execution.started`. */
140
+ "not-started",
141
+ /** `finishExecution` found the started execution already closed. */
142
+ "already-finished",
143
+ /** No grant governs this manual action key. */
144
+ "not-granted",
145
+ /** A grant exists, but the presented token is not its preimage. */
146
+ "token-mismatch",
147
+ /** The token was already spent. */
148
+ "token-consumed",
149
+ /** The parent request's TTL lapsed. */
150
+ "token-expired",
151
+ /** A human withdrew the grant. */
152
+ "token-revoked",
153
+ /**
154
+ * The grant was harness-executed and minted no token (APRV-106). Surfaced
155
+ * verbatim from `core/token.ts` so the executor's vocabulary stays that
156
+ * module's vocabulary: an agent that reads this has not lost a token, it is
157
+ * holding a grant that authorized a process which runs the command itself.
158
+ */
159
+ "harness-executed",
160
+ /**
161
+ * The payload presented does not hash to the bytes the grant approved
162
+ * (amended SPEC.md §10, A1). Nothing was appended and the token is still live.
163
+ */
164
+ "payload-mismatch",
165
+ /**
166
+ * `resolveExecution` was called without the mandatory human observation, or
167
+ * by an actor that is not a `human:`. Recorded here rather than reusing
168
+ * `not-started` because the log is unchanged for a different reason: the
169
+ * caller, not the state.
170
+ */
171
+ "actor-not-human",
172
+ /**
173
+ * The key's latest `execution.started` is a DELEGATED record (APRV-117,
174
+ * APRV-120): it carries `payload.execution: "harness"`, so the harness ran the
175
+ * command and this runtime never observed an exit status. The record is
176
+ * complete as written and terminal by design, and no outcome may be placed
177
+ * over it.
178
+ *
179
+ * Distinct from the two refusals it sits between, and the distinctions are the
180
+ * point. `not-started` says nothing began; `already-finished` says something
181
+ * began and an outcome already exists. This one says the thing that began is
182
+ * not this runtime's to close: a `completed` or `failed` written here would
183
+ * report an exit code nobody watched, and an `execution.completed` would
184
+ * additionally clear the task's loop-escalation streak (SPEC.md §10.2) on the
185
+ * strength of it. {@link executionCustody} reports these as `delegated` and
186
+ * {@link danglingExecutions} deliberately leaves them out, so this code is the
187
+ * enforcement half of a custody state the projections already draw.
188
+ */
189
+ "execution-delegated",
190
+ /**
191
+ * The key's execution ended in an unknown outcome (APRV-120) and has not been
192
+ * reconciled. INDETERMINATE IS A CUSTODY STATE: the token stays spent, the
193
+ * idempotency key stays burned, and a re-run is refused here rather than
194
+ * anywhere else, because "we do not know whether this happened" is a
195
+ * different fact from "this already happened" and calls for a different
196
+ * repair — a person establishing which it was, with `execution reconcile`.
197
+ */
198
+ "execution-indeterminate",
199
+ /**
200
+ * `reconcileExecution` found no unreconciled `execution.indeterminate` for
201
+ * the key. There is nothing whose outcome is in doubt, so there is nothing to
202
+ * resolve; a dangling execution is closed with `execution resolve` instead.
203
+ */
204
+ "not-indeterminate",
205
+ /**
206
+ * The indeterminate outcome already carries a resolution. A second one would
207
+ * be a second answer to a question a person already answered, and the first
208
+ * record is never rewritten.
209
+ */
210
+ "already-reconciled",
211
+ /**
212
+ * `execution resolve --dangling` was asked to attest with no terminal to
213
+ * attest at, and without `--yes` (APRV-264). The bulk form writes
214
+ * `attested_by_human: true` on every record it appends, and a confirmation a
215
+ * pipe could answer is not an attestation. Distinct from `actor-not-human`,
216
+ * which is about WHO is attesting: this one is about whether anybody was
217
+ * actually asked.
218
+ */
219
+ "dangling-stdin-not-tty",
220
+ /**
221
+ * The bulk confirmation was declined, or withdrawn at the prompt (APRV-264).
222
+ * Nothing was appended and the dangling executions stand exactly as they
223
+ * were. Its own code because "the operator said no" and "the operator could
224
+ * not be asked" are different facts about the same prompt.
225
+ */
226
+ "dangling-declined",
227
+ /** The log could not be read, or holds a line that is not a record. */
228
+ "log-unreadable",
229
+ /** The log's final line is unterminated (a crashed write). */
230
+ "log-torn-tail",
231
+ /** The chain does not verify; nothing may execute on an untrustworthy log. */
232
+ "log-corrupt",
233
+ /**
234
+ * The append itself failed; `append` carries the underlying error. Its `code`
235
+ * is `head-moved` when a record landed between this module's read and its
236
+ * append, so the idempotency and budget checks that authorized the write were
237
+ * made against an older log. Nothing was written. Since APRV-236 this code
238
+ * reaches a caller of {@link startExecution} only after the bounded
239
+ * read-check-append retry is spent (`core/head-retry.ts`), and its message
240
+ * says how many attempts were made; a single lost race is re-derived rather
241
+ * than reported.
242
+ */
243
+ "append-failed",
244
+ ];
245
+ function refuse(code, message, extra = {}) {
246
+ return { ok: false, code, message, ...extra };
247
+ }
248
+ /** Narrow a verified-read refusal onto this module's codes, unchanged. */
249
+ function fromReadRefusal(refusal) {
250
+ return refuse(refusal.code, refusal.message);
251
+ }
252
+ /**
253
+ * Narrow a token refusal onto this module's codes.
254
+ *
255
+ * The names are identical on purpose — `core/token.ts` chose them so the CLI
256
+ * could map both modules onto the frozen exit table with one function.
257
+ */
258
+ function fromTokenRefusal(refusal) {
259
+ const extra = {};
260
+ if (refusal.seq !== undefined)
261
+ extra.seq = refusal.seq;
262
+ if (refusal.append !== undefined)
263
+ extra.append = refusal.append;
264
+ return refuse(refusal.code, refusal.message, extra);
265
+ }
266
+ function payloadOf(record) {
267
+ const payload = record.payload;
268
+ return typeof payload === "object" && payload !== null ? payload : {};
269
+ }
270
+ function loadOptionsOf(options) {
271
+ const policy = options.policy ?? {};
272
+ const load = {};
273
+ if (policy.file !== undefined)
274
+ load.file = policy.file;
275
+ else
276
+ load.dir = policy.dir ?? process.cwd();
277
+ if (options.schemaDir !== undefined)
278
+ load.schemaDir = options.schemaDir;
279
+ return load;
280
+ }
281
+ /**
282
+ * The policy file whose bytes are attested — discovered exactly as
283
+ * `core/gate.ts` discovers it, so the attested file and the enforced file are
284
+ * the same file. A missing policy returns the first candidate anyway, so
285
+ * `checkAttestation` reports `unreadable` and the start is refused: a missing
286
+ * policy is never a pass.
287
+ */
288
+ function policyPathOf(options) {
289
+ const policy = options.policy ?? {};
290
+ if (policy.file !== undefined)
291
+ return policy.file;
292
+ const dir = policy.dir ?? process.cwd();
293
+ for (const filename of POLICY_FILENAMES) {
294
+ const candidate = join(dir, filename);
295
+ if (existsSync(candidate))
296
+ return candidate;
297
+ }
298
+ return join(dir, POLICY_FILENAMES[0] ?? "APPROVAL.md");
299
+ }
300
+ function appendOptionsOf(options) {
301
+ const append = { ...options.append };
302
+ if (options.schemaDir !== undefined)
303
+ append.schemaDir = options.schemaDir;
304
+ return append;
305
+ }
306
+ /**
307
+ * Append one event under the compare-and-append precondition (APRV-20).
308
+ *
309
+ * `expectedHead` is the head observed at the read that authorized this write:
310
+ * the already-executed check, the loop-safety check, and the budget evaluation
311
+ * were all made against a log ending exactly there.
312
+ */
313
+ function append(logPath, input, options, expectedHead) {
314
+ const result = appendEvent(logPath, input, { ...appendOptionsOf(options), expectedHead });
315
+ if (result.ok)
316
+ return { ok: true, record: result.record };
317
+ return refuse("append-failed", `${input.event} could not be appended: ${result.error.message}`, { append: result.error });
318
+ }
319
+ /**
320
+ * Find the declaration for `actionKey` across every `task.registered` record.
321
+ *
322
+ * The log — not the task file, which may have been edited since — is the
323
+ * authority, exactly as it is for `approval request`. The search is by action
324
+ * key alone because an execution names a key, not a task: SPEC.md §7 makes the
325
+ * `idempotency_key` the identity of a side effect, and an undeclared key is the
326
+ * one thing that must never execute.
327
+ *
328
+ * A key must be declared by exactly one task. If a log somehow carries the same
329
+ * key under two tasks, this returns the last, but callers on an enforcement path
330
+ * MUST first fail closed via {@link declaringTasks}: the collision is refused at
331
+ * registration (`core/gate.ts`, APRV-138), so a log that still holds one is
332
+ * untrustworthy and nothing may execute from the guess.
333
+ */
334
+ export function findDeclaration(records, actionKey) {
335
+ let found = null;
336
+ for (const { task, key, item } of declaredActions(records)) {
337
+ if (key !== actionKey)
338
+ continue;
339
+ const declaration = declarationOf(task, item);
340
+ if (declaration === null)
341
+ continue;
342
+ found = declaration;
343
+ }
344
+ return found;
345
+ }
346
+ function* declaredActions(records) {
347
+ for (const record of records) {
348
+ if (record.event !== "task.registered")
349
+ continue;
350
+ const task = record.task;
351
+ if (typeof task !== "string" || task.length === 0)
352
+ continue;
353
+ const actions = payloadOf(record)["actions"];
354
+ if (!Array.isArray(actions))
355
+ continue;
356
+ for (const entry of actions) {
357
+ if (typeof entry !== "object" || entry === null)
358
+ continue;
359
+ const item = entry;
360
+ yield { task, key: item["idempotency_key"], item };
361
+ }
362
+ }
363
+ }
364
+ /** The {@link Declaration} an entry states, or `null` when it declares no class. */
365
+ function declarationOf(task, item) {
366
+ const cls = item["class"];
367
+ if (typeof cls !== "string")
368
+ return null;
369
+ const cost = item["est_cost_usd"];
370
+ const reversible = item["reversible"];
371
+ const summary = item["summary"];
372
+ const binding = item["payload_hash"];
373
+ return {
374
+ task,
375
+ class: cls,
376
+ est_cost_usd: usdOrZero(cost),
377
+ reversible: typeof reversible === "boolean" ? reversible : null,
378
+ summary: typeof summary === "string" ? summary : null,
379
+ payload_hash: isPayloadHash(binding) ? binding : null,
380
+ };
381
+ }
382
+ export function indexDeclarations(records) {
383
+ const tasksByKey = new Map();
384
+ const declarations = new Map();
385
+ const requested = new Set();
386
+ for (const { task, key, item } of declaredActions(records)) {
387
+ if (typeof key !== "string")
388
+ continue;
389
+ const tasks = tasksByKey.get(key);
390
+ if (tasks === undefined)
391
+ tasksByKey.set(key, [task]);
392
+ else if (!tasks.includes(task))
393
+ tasks.push(task);
394
+ const declaration = declarationOf(task, item);
395
+ if (declaration !== null)
396
+ declarations.set(key, declaration);
397
+ }
398
+ for (const record of records) {
399
+ if (record.event !== "approval.requested")
400
+ continue;
401
+ const key = record.action_key;
402
+ if (typeof key === "string")
403
+ requested.add(key);
404
+ }
405
+ return { declaringTasks: tasksByKey, declarations, requested };
406
+ }
407
+ /**
408
+ * The distinct tasks that declare `actionKey`. More than one is a cross-task
409
+ * collision (APRV-138): the registration boundary refuses these, so a log that
410
+ * still holds one cannot be trusted to say which declaration governs. Every
411
+ * enforcement caller of {@link findDeclaration} guards on this and fails closed
412
+ * rather than executing the last-registered (possibly weaker) declaration.
413
+ */
414
+ export function declaringTasks(records, actionKey) {
415
+ const tasks = new Set();
416
+ for (const { task, key } of declaredActions(records)) {
417
+ if (key === actionKey)
418
+ tasks.add(task);
419
+ }
420
+ return [...tasks];
421
+ }
422
+ /**
423
+ * Has a human ever been asked about this action key (amended SPEC.md §6.3,
424
+ * APRV-127)?
425
+ *
426
+ * True as soon as the log holds one `approval.requested` for the key, and it
427
+ * stays true: a rejected, expired or withdrawn cycle is still a cycle, and an
428
+ * action that could shed its gate by being refused would be an action that
429
+ * profits from a "no".
430
+ *
431
+ * Pure, and derived from the log alone — never from a payload field a requester
432
+ * wrote about itself. Two callers: {@link startExecution}, which requires the
433
+ * token of any action that went through the gate, and `core/audit.ts`, which
434
+ * leaves such actions out of the retrospective pool because a human already
435
+ * looked.
436
+ */
437
+ export function hasApprovalCycle(records, actionKey) {
438
+ return records.some((record) => record.event === "approval.requested" && record.action_key === actionKey);
439
+ }
440
+ /**
441
+ * Check whether an adapter execution may proceed to credential resolution.
442
+ *
443
+ * This is a preflight, never an execution permit. A successful result appends
444
+ * nothing, consumes nothing and leaves delivered-token key material in place.
445
+ * {@link startExecution} must still run after adapter precheck and repeats every
446
+ * check against a fresh verified read before compare-and-append. A budget
447
+ * refusal retains `startExecution`'s required `budget.exceeded` audit record.
448
+ */
449
+ export function checkExecutionEligibility(logPath, actionKey, options, actor) {
450
+ return withHeadRetry(attemptsOf(options.retryOnHeadMoved), () => attemptStart(logPath, actionKey, options, actor, true));
451
+ }
452
+ /**
453
+ * Begin an execution: the single entry point for appending `execution.started`.
454
+ *
455
+ * Check order, and why it is this order:
456
+ *
457
+ * 1. **The log reads**, so a torn or unreadable log stops everything before a
458
+ * policy question is asked.
459
+ * 2. **The declaration.** An action key no `task.registered` record declares is
460
+ * `action-not-registered` — SPEC.md §7's "an action's class MUST be declared
461
+ * before an execution token can be requested for it", enforced at the last
462
+ * possible moment as well as the first.
463
+ * 3. **Policy resolution**, including SPEC.md §7's irreversibility floor (the
464
+ * declared `reversible: false` forces `manual`, which forces a token). A
465
+ * failed policy load resolves everything to `manual` — `policy-match.ts`'s
466
+ * contract, not softened here — so an unparseable policy makes every action
467
+ * require a human's token.
468
+ * 4. **Manual path: the token, or nothing.** No token → `token-required`, and
469
+ * the log is untouched. With one, `consumeToken` verifies it and appends the
470
+ * start event; a class that resolves manual but was never granted refuses
471
+ * `not-granted` from that layer. Attestation is not re-checked here: the
472
+ * grant that minted the token could only have happened under an attested
473
+ * policy, and re-checking would refuse an execution a human already
474
+ * authorized because a file changed afterwards.
475
+ * 5. **Non-manual path**, in order: attestation → loop escalation → idempotency
476
+ * → content binding → budgets → append. Attestation first because an
477
+ * unverified policy cannot answer the autonomy question it was just asked to
478
+ * answer; the binding (APRV-140) after the free checks and before the
479
+ * charging one; budgets last because a budget refusal *writes*
480
+ * (`budget.exceeded`), and the cheaper refusals must leave the log
481
+ * untouched.
482
+ *
483
+ * `actor` is not pre-validated: the event schema is the authority on actor
484
+ * shape, and a malformed one is refused at the write boundary as
485
+ * `append-failed`, with the schema's own error attached. One rule about actors,
486
+ * enforced in one place.
487
+ */
488
+ export function startExecution(logPath, actionKey, options, actor) {
489
+ return withHeadRetry(attemptsOf(options.retryOnHeadMoved), () => attemptStart(logPath, actionKey, options, actor, false));
490
+ }
491
+ /**
492
+ * One whole start, from the clock read to the append (APRV-236 put it under the
493
+ * bounded head-moved retry; see `core/head-retry.ts`).
494
+ *
495
+ * `approval run` is the verb a session drives, and a start that lost the append
496
+ * race used to hand the session a refusal about someone else's write. The
497
+ * re-entry re-derives the lot: the fresh verified read, the declaration and its
498
+ * cross-task collision check, custody, the policy resolution and the human-only
499
+ * test, the approval-cycle test, attestation, loop escalation, the single-use
500
+ * scan, the content binding and the budgets. So a key another writer started in
501
+ * the window is refused `already-executed`, a ceiling it exhausted is
502
+ * `budget-exceeded`, and a task it escalated is `loop-escalated`.
503
+ *
504
+ * The manual path's append happens inside `core/token.ts`'s `consumeToken`,
505
+ * which keeps no retry of its own: the retried cycle re-enters it whole, so its
506
+ * own read, its digest comparison and its single-use scan are re-run rather than
507
+ * skipped, and a token another process spent in the window is refused
508
+ * `token-consumed`. Double-spend stays exactly as pinned.
509
+ */
510
+ function attemptStart(logPath, actionKey, options, actor, preflight) {
511
+ const ts = tick(options);
512
+ if (preflight && !isPrincipalActor(actor)) {
513
+ return refuse("actor-invalid", `execution preflight requires a human: or agent: actor, got ${JSON.stringify(actor)}`);
514
+ }
515
+ const read = readVerifiedRecords(logPath, options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir });
516
+ if (!read.ok)
517
+ return fromReadRefusal(read);
518
+ const records = read.records;
519
+ const declaring = declaringTasks(records, actionKey);
520
+ if (declaring.length > 1) {
521
+ return refuse("action-not-registered", `action key ${JSON.stringify(actionKey)} is declared by more than one task (${declaring.join(", ")}); the runtime will not guess which governs and refuses rather than execute the later declaration. Registration refuses such collisions (APRV-138); a log that holds one is untrustworthy.`);
522
+ }
523
+ const declared = findDeclaration(records, actionKey);
524
+ if (declared === null) {
525
+ return refuse("action-not-registered", `no task.registered record declares an action with idempotency_key ${JSON.stringify(actionKey)}; SPEC.md §7 requires a class to be declared before the action can execute. Run \`approval register <task-file>\` first.`);
526
+ }
527
+ // Custody before authorization (APRV-120). A key whose execution ended in an
528
+ // unknown outcome is BURNED, and it is burned on both paths: the manual one
529
+ // would otherwise say `token-consumed`, which is true and unhelpful, and the
530
+ // non-manual one `already-executed`, which is the assertion nobody can make.
531
+ // Checked before the policy is even read, because the fact is about the key
532
+ // rather than about its autonomy, and a blind retry is what the state exists
533
+ // to refuse.
534
+ const custody = executionCustody(records).find((entry) => entry.actionKey === actionKey);
535
+ if (custody?.state === "indeterminate") {
536
+ return refuse("execution-indeterminate", `action ${actionKey}'s execution ended in an unknown outcome (execution.indeterminate at seq ${String(custody.indeterminateSeq)}${custody.reason === null ? "" : `, ${custody.reason}`}): the side effect was attempted and nobody knows whether it committed. Running it again would be a blind double-execution, so it is refused, and the token and the idempotency key stay spent. Establish what actually happened and record it with \`approval execution reconcile\`; if it did not happen, declare a fresh action and request that.`, { seq: custody.indeterminateSeq ?? custody.seq });
537
+ }
538
+ const load = loadPolicy(loadOptionsOf(options));
539
+ const resolution = resolve(load, declared.class, declared.reversible === null ? {} : { reversible: declared.reversible });
540
+ // APRV-185, amended SPEC.md §5.2, and the first question asked of the
541
+ // resolution: a class reserved to human hands has no execution path here at
542
+ // all. Above the manual/supervised fork deliberately — the fork is a question
543
+ // about HOW this action is authorized, and this one says nothing authorizes it
544
+ // in this process. Above the `gatedByCycle` test for the same reason: an
545
+ // approval cycle opened before a policy amendment raised the class does not
546
+ // survive the amendment as a licence to run.
547
+ if (resolution.autonomy === "human-only") {
548
+ return refuse("class-human-only", humanOnlyRefusal(declared.class, `action ${actionKey} may not be executed and no execution.started was written`));
549
+ }
550
+ // APRV-127. An action that went through the gate is spent through the gate,
551
+ // whatever its class resolves to now.
552
+ //
553
+ // The case this exists for is a `supervised-live` action the live draw
554
+ // selected: `core/gate.ts` sent it down the manual path and recorded an
555
+ // `approval.requested`, but its CLASS still resolves `supervised`, so without
556
+ // this line `approval run` would take the unsupervised branch and start it
557
+ // without spending the token a human minted. The rule is stated as a property
558
+ // of the log rather than of the class deliberately — this process cannot
559
+ // recompute the live draw (the secret is the operator's, and deliberately not
560
+ // an agent's to read), so it asks the one question it can answer from the
561
+ // records it already holds: was a human asked about this action?
562
+ //
563
+ // It is also strictly scrutiny-raising in every other case it can fire. A
564
+ // class relaxed from `manual` to `supervised` after a request was opened would
565
+ // otherwise let the pending question be bypassed by simply running the action;
566
+ // now the token is still required, and `core/token.ts` answers `not-granted`
567
+ // until a human decides. Nothing here can make an ungated action gated: an
568
+ // action with no `approval.requested` never reaches this branch.
569
+ const gatedByCycle = hasApprovalCycle(records, actionKey);
570
+ if (resolution.autonomy === "manual" || gatedByCycle) {
571
+ // APRV-105. With no token in hand, look for one delivered to this machine:
572
+ // the grant sealed it to the ephemeral public key this action's request
573
+ // published, and the private half is in the key store beside the log. Under
574
+ // the default `token_delivery: manual` nothing was sealed and this returns
575
+ // null, so the refusal below is exactly the one it always was.
576
+ //
577
+ // An explicitly passed token always wins. A caller that names a token is
578
+ // making a claim this runtime then checks against the grant's digest, and
579
+ // silently substituting a different one would answer a question nobody
580
+ // asked.
581
+ const keyDir = options.keyStoreDir ?? keyStoreDirFor(logPath);
582
+ const token = options.token !== undefined && options.token.length > 0
583
+ ? options.token
584
+ : (deliveredToken(records, actionKey, keyDir) ?? undefined);
585
+ if (token === undefined || token.length === 0) {
586
+ return refuse("token-required", resolution.autonomy === "manual"
587
+ ? `action ${actionKey} resolves to manual (${resolution.provenance}${resolution.floorApplied ? ", irreversibility floor" : ""}) and cannot execute without the single-use token minted at grant. Request the action, have a human grant it, and pass the token that grant printed.`
588
+ : `action ${actionKey} resolves to ${resolution.autonomy} but the log already carries an approval.requested for it, so a human was asked about this action and it executes on their answer. This is what a supervised-live draw looks like from the executor's side: the gate selected this action into the live fraction and it now follows the manual path. Wait for the decision and pass the token the grant printed.`);
589
+ }
590
+ const tokenOptions = {
591
+ ...(options.policy?.file === undefined ? {} : { policyFile: options.policy.file }),
592
+ ...(options.policy?.file === undefined
593
+ ? { policyDir: options.policy?.dir ?? process.cwd() }
594
+ : {}),
595
+ ...(options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir }),
596
+ ...(options.append === undefined ? {} : { append: options.append }),
597
+ ...(options.presentedPayloadHash === undefined
598
+ ? {}
599
+ : { presentedPayloadHash: options.presentedPayloadHash }),
600
+ // APRV-205: the manual path's `execution.started` is appended by
601
+ // `consumeToken`, so the count travels with the spend.
602
+ ...(options.envStripped === undefined ? {} : { envStripped: options.envStripped }),
603
+ // APRV-193: and the room it ran in, for the same reason — the manual
604
+ // path's start event is written by the spend, so both fields travel with it.
605
+ ...(options.sandbox === undefined ? {} : { sandbox: options.sandbox }),
606
+ // One moment for the whole operation: the timestamp already read above is
607
+ // the one the spend records, so `startExecution` and the `execution.started`
608
+ // it produces cannot disagree about when this happened.
609
+ clock: () => ts,
610
+ };
611
+ if (preflight) {
612
+ const verified = verifyTokenSpend(records, actionKey, token, ts, load, options.presentedPayloadHash);
613
+ if (!verified.ok)
614
+ return fromTokenRefusal(verified);
615
+ return { ok: true, mode: "token", autonomy: "manual", token };
616
+ }
617
+ const consumed = consumeToken(logPath, actionKey, token, actor, tokenOptions);
618
+ if (!consumed.ok)
619
+ return fromTokenRefusal(consumed);
620
+ // APRV-105. The token is spent, so its delivery address is finished. Done
621
+ // AFTER the append rather than before: an unlink before a failed spend would
622
+ // destroy the only local copy of a token that is still live.
623
+ forgetPrivateKey(keyDir, actionKey);
624
+ const payload = payloadOf(consumed.record);
625
+ const cost = payload["est_cost_usd"];
626
+ return {
627
+ ok: true,
628
+ record: consumed.record,
629
+ autonomy: "manual",
630
+ task: declared.task,
631
+ class: typeof payload["class"] === "string" ? payload["class"] : declared.class,
632
+ est_cost_usd: usdOrZero(cost),
633
+ tokenSha256: consumed.tokenSha256,
634
+ };
635
+ }
636
+ // --- supervised / autonomous: no grant exists, so no token exists ---------
637
+ const attestationStatus = checkAttestation(records, policyPathOf(options));
638
+ const attestation = attestationRefusal(attestationStatus);
639
+ if (attestation !== null) {
640
+ return refuse("policy-not-attested", attestation.message, { detail: attestation.detail });
641
+ }
642
+ if (options.expectedPolicySha256 !== undefined &&
643
+ attestationStatus.status === "attested" &&
644
+ attestationStatus.sha256 !== options.expectedPolicySha256) {
645
+ return refuse("policy-drift", `the attested policy changed after supervised-live intake decided action ${actionKey} could proceed (intake ${options.expectedPolicySha256}, current ${attestationStatus.sha256}). No credential or side effect may rely on an unrecorded draw under different rules; retry so intake can draw under the current policy.`);
646
+ }
647
+ if (isLoopEscalated(records, declared.task)) {
648
+ return refuse("loop-escalated", `task ${declared.task} has three consecutive execution.failed events and is escalated to manual (SPEC.md §10.2), so its ${resolution.autonomy} actions may not start unsupervised. This is a redirection, not a ban: request ${actionKey}, have a human grant it, and run it with the token. The escalation clears only when an execution.completed for the task lands.`);
649
+ }
650
+ for (const record of records) {
651
+ if (record.action_key !== actionKey)
652
+ continue;
653
+ if (record.event !== "execution.started")
654
+ continue;
655
+ return refuse("already-executed", `action ${actionKey} already started at seq ${record.seq}; an idempotency key is single-use and nothing here reconciles or reruns it. If that execution is dangling, close it with the outcome you observed.`, { seq: record.seq });
656
+ }
657
+ // Content binding off the manual path (amended SPEC.md §6.2/§10.4, APRV-140).
658
+ //
659
+ // Until this, a supervised or autonomous action executed whatever bytes the
660
+ // executor happened to hold: no grant exists on this path, so nothing was
661
+ // compared, and `approval run <key> -- <anything>` under an autonomous class
662
+ // was unauthenticated arbitrary execution (the residual APRV-138 left open).
663
+ // The declaration is what authorizes here, so the declaration is what the
664
+ // executor is checked against: it states its bytes, and they must be the ones
665
+ // the registered action named.
666
+ //
667
+ // A declaration carrying NO binding is refused rather than waved through, for
668
+ // the reason `core/token.ts` refuses an unbound grant: an action that can
669
+ // execute without stating its bytes makes the binding optional in practice,
670
+ // and ambiguity resolves to the stricter path. The repair is to declare
671
+ // `payload_hash` on the action and register the task again.
672
+ //
673
+ // Checked before budgets, because a budget refusal WRITES and this one must
674
+ // leave the log exactly as it found it.
675
+ const presented = options.presentedPayloadHash;
676
+ if (declared.payload_hash === null) {
677
+ return refuse("payload-mismatch", `action ${actionKey} resolves to ${resolution.autonomy} and its registered declaration carries no payload_hash. Off the manual path there is no grant, so the declaration is the only statement of what was authorized: amended SPEC.md §6.2 (APRV-140) makes the hash MUST for every action that executes, and an execution that cannot be checked against anything is not an authorized execution. Declare payload_hash (SHA-256 over the RFC 8785 canonical serialization of the concrete payload) on the action and register the task again.`);
678
+ }
679
+ if (!isPayloadHash(presented) || presented !== declared.payload_hash) {
680
+ return refuse("payload-mismatch", presented === undefined
681
+ ? `action ${actionKey} is declared with payload_hash ${declared.payload_hash} and this executor presented none. Amended SPEC.md §10.4: an executor MUST recompute the hash of the payload it is about to execute; a start that cannot state its bytes cannot be shown to be executing the declared ones. Nothing was appended.`
682
+ : `the payload presented for ${actionKey} is not the one declared: the registration binds to ${declared.payload_hash}, this executor presented ${JSON.stringify(presented)}. A declaration authorizes specific bytes; changing them requires registering the action again. Nothing was appended.`);
683
+ }
684
+ const budget = evaluateBudgetsWithTask(records, {
685
+ classLimits: resolution.limits,
686
+ classPattern: resolution.matched === null ? null : resolution.matched.pattern,
687
+ globalBudgets: load.ok ? load.policy.budgets ?? null : null,
688
+ }, { class: declared.class, est_cost_usd: declared.est_cost_usd }, ts,
689
+ // S2: the registered envelope's own `budget.max_cost_usd`. This is the
690
+ // supervised/autonomous charge point, so it is where the task cap binds for
691
+ // actions that never pass through a grant.
692
+ declared.task);
693
+ if (!budget.pass) {
694
+ const failed = budget.verdicts.filter((entry) => !entry.pass);
695
+ const logged = append(logPath, {
696
+ ts,
697
+ event: "budget.exceeded",
698
+ actor,
699
+ task: declared.task,
700
+ action_key: actionKey,
701
+ payload: {
702
+ class: declared.class,
703
+ est_cost_usd: declared.est_cost_usd,
704
+ stage: "execution",
705
+ verdicts: budget.verdicts,
706
+ },
707
+ }, options, read.head);
708
+ const message = `budget refused the execution: ${failed
709
+ .map((entry) => `${entry.limit} (${entry.scope})`)
710
+ .join(", ")}`;
711
+ if (!logged.ok && logged.append?.code === "head-moved")
712
+ return logged;
713
+ return logged.ok
714
+ ? refuse("budget-exceeded", message, { verdicts: failed, record: logged.record })
715
+ : refuse("budget-exceeded", `${message}; the budget.exceeded event could not be appended: ${logged.message}`, { verdicts: failed });
716
+ }
717
+ if (preflight) {
718
+ return { ok: true, mode: "policy", autonomy: resolution.autonomy };
719
+ }
720
+ const appended = append(logPath, {
721
+ ts,
722
+ event: "execution.started",
723
+ actor,
724
+ task: declared.task,
725
+ action_key: actionKey,
726
+ // The budgets contract: class and est_cost_usd on every start event. This
727
+ // is the charge point for supervised/autonomous actions.
728
+ //
729
+ // APRV-140 adds the third field: the hash of the bytes that are about to
730
+ // run, recomputed by the executor and checked against the declaration
731
+ // just above. It is what makes the log say WHAT ran rather than only that
732
+ // something did — for `approval run` it is `runPayloadHash(argv, cwd)`,
733
+ // which an operator holding the command can reproduce exactly. The argv
734
+ // itself is deliberately NOT recorded: a command line carries whatever an
735
+ // agent put on it, secrets included, and §11.1's third invariant says the
736
+ // log holds hashes of such material rather than the material.
737
+ payload: {
738
+ class: declared.class,
739
+ est_cost_usd: declared.est_cost_usd,
740
+ payload_hash: declared.payload_hash,
741
+ // APRV-205: how many credential-bearing variables the child was starved
742
+ // of. Additive and optional — an execution that spawns nothing (an
743
+ // adapter's `act`, which runs in this process) records no count at all,
744
+ // because "none withheld" and "no child" are different facts.
745
+ ...(options.envStripped === undefined ? {} : { env_stripped: options.envStripped }),
746
+ // APRV-193: the room the child ran in. `egress-denied` is the default
747
+ // for this path — nobody was asked about this action, so the code it
748
+ // runs executes into a room with no doors. Optional and additive for
749
+ // the same reason the count above is.
750
+ ...(options.sandbox === undefined ? {} : { sandbox: options.sandbox }),
751
+ },
752
+ }, options, read.head);
753
+ if (!appended.ok)
754
+ return appended;
755
+ return {
756
+ ok: true,
757
+ record: appended.record,
758
+ autonomy: resolution.autonomy,
759
+ task: declared.task,
760
+ class: declared.class,
761
+ est_cost_usd: declared.est_cost_usd,
762
+ };
763
+ }
764
+ /**
765
+ * The bounds SPEC.md §8 and `schema/event.schema.json` place on a reference.
766
+ *
767
+ * Printable ASCII with no spaces, and short. An identifier is short; the bound
768
+ * is what keeps the field from becoming somewhere to put a message. Stated here
769
+ * as well as in the schema so that the write path can DECLINE to record a
770
+ * reference that would not validate, rather than hand the schema a record it
771
+ * will reject and leave a completed side effect with no outcome in the log.
772
+ */
773
+ export const PROVIDER_REF_ADAPTER_MAX = 64;
774
+ export const PROVIDER_REF_ID_MAX = 256;
775
+ const PROVIDER_REF_SHAPE = /^[\x21-\x7e]+$/u;
776
+ /** Does `value` fit what the schema will accept for a reference member? */
777
+ export function providerRefMemberOk(value, max) {
778
+ return value.length > 0 && value.length <= max && PROVIDER_REF_SHAPE.test(value);
779
+ }
780
+ /** Is `ref` recordable, in full? A half-recordable reference is not recorded. */
781
+ export function providerRefRecordable(ref) {
782
+ return (providerRefMemberOk(ref.adapter, PROVIDER_REF_ADAPTER_MAX) &&
783
+ providerRefMemberOk(ref.id, PROVIDER_REF_ID_MAX));
784
+ }
785
+ /**
786
+ * Close an execution with the outcome that actually happened.
787
+ *
788
+ * Exit `0` appends `execution.completed`; anything else appends
789
+ * `execution.failed`. Both carry `payload.exit_code` — the number, unmapped and
790
+ * uninterpreted, so a reader can tell exit 1 from exit 127 from a signal death
791
+ * (which `approval run` records as `128 + signal`, the shell convention).
792
+ * Neither event consumes budget: the commitment was charged at authorization
793
+ * time and charging it again would double-count (`core/budgets.ts`).
794
+ *
795
+ * Refuses `not-started` when the key has no `execution.started`,
796
+ * `already-finished` when the most recent start already has an outcome after
797
+ * it, and `execution-delegated` when that start was the harness's rather than
798
+ * this runtime's (APRV-146). All three leave the log untouched.
799
+ *
800
+ * **This is the human recovery path for a dangling execution**, and it is
801
+ * deliberately the only one. Nothing in this codebase closes a dangling
802
+ * execution automatically: an operator who knows the email went out records
803
+ * `0`, an operator who knows it did not records the failure, and either way the
804
+ * log holds an observation rather than a runtime's guess.
805
+ */
806
+ export function finishExecution(logPath, actionKey, exitCode, actor, options = {}) {
807
+ const open = openExecution(logPath, actionKey, options);
808
+ if (!open.ok)
809
+ return open;
810
+ // APRV-261. The read is done and the append has not started: the one instant
811
+ // in which a test can move the head under this attempt on purpose. See
812
+ // `FinishOptions.afterRead` for why it can only ever make the append fail.
813
+ options.afterRead?.();
814
+ const event = exitCode === 0 ? "execution.completed" : "execution.failed";
815
+ // APRV-211. A non-zero exit with no reason is not a report: the daemon's
816
+ // advance recorded `exit_code: 1` and the operator's only surfaces — the
817
+ // daemon's event stream, which is gone the moment nobody is tailing it, and
818
+ // the `log-advance-cadence` doctor row, which reads the log — could say
819
+ // nothing about WHY. So the executor's own words travel with the outcome.
820
+ // Recorded ONLY on failure, and only when the caller states them: the
821
+ // completed case has nothing to explain, and a reason nobody supplied would
822
+ // be a runtime's guess in an append-only log.
823
+ const reason = event === "execution.failed" && options.reason !== undefined
824
+ ? { code: options.reason.code, message: options.reason.message }
825
+ : // APRV-234. The mirror of it: a completion the executor wants on the
826
+ // record (the advance rebuilt the day's branch on a moved trunk).
827
+ // Same closed shape, same one-way street — a report, never read back.
828
+ event === "execution.completed" && options.note !== undefined
829
+ ? { code: options.note.code, message: options.note.message }
830
+ : {};
831
+ // APRV-251. The provider's own identifier for the effect, on a completion and
832
+ // nowhere else: a failed execution produced no effect for a provider to file.
833
+ // Recorded only when the caller states one, and only when it fits what the
834
+ // schema admits — handing the write boundary a record it will reject would
835
+ // leave a side effect that happened with no outcome in the log, which is a
836
+ // worse outcome than a completion carrying no reference.
837
+ const providerRef = event === "execution.completed" &&
838
+ options.providerRef !== undefined &&
839
+ providerRefRecordable(options.providerRef)
840
+ ? {
841
+ provider_ref: {
842
+ adapter: options.providerRef.adapter,
843
+ id: options.providerRef.id,
844
+ },
845
+ }
846
+ : {};
847
+ const appended = append(logPath, {
848
+ ts: tick(options),
849
+ event,
850
+ actor,
851
+ task: open.task,
852
+ action_key: actionKey,
853
+ payload: { exit_code: exitCode, ...reason, ...providerRef },
854
+ }, options,
855
+ // The head read above, when the not-started / already-finished checks ran.
856
+ open.head);
857
+ if (!appended.ok)
858
+ return appended;
859
+ return { ok: true, record: appended.record, event, exitCode, task: open.task };
860
+ }
861
+ /**
862
+ * The one dangling execution for `actionKey`, or a refusal explaining why there
863
+ * is none to close.
864
+ *
865
+ * Shared by {@link finishExecution}, {@link resolveExecution} and
866
+ * {@link indeterminateExecution} so the three verbs cannot drift about what
867
+ * "still open" means. Returns the head observed at the read, which the caller
868
+ * passes as `expectedHead`: the not-started, delegated and already-finished
869
+ * checks were made against a log ending exactly there.
870
+ *
871
+ * A DELEGATED start is refused `execution-delegated` (APRV-146). The three verbs
872
+ * that share this function all write an outcome, and a harness start has none to
873
+ * write: the record says so on its face (`payload.execution: "harness"`), and
874
+ * {@link executionCustody} has reported it terminal by design since APRV-120.
875
+ * Enforcing it in this one place is what keeps the three verbs from disagreeing
876
+ * about it, which is the reason this function exists.
877
+ *
878
+ * EXCEPT BY THE MARKED COUNTERPART (APRV-145). One surface may close a delegated
879
+ * start, and it is not one of these three: `core/gate.ts`'s
880
+ * `finishHarnessExecution`, which appends the outcome a harness REPORTED, marked
881
+ * `execution: "harness"` with a closed `reported_by` code naming which untrusted
882
+ * reporter asserted it. The refusal here is unchanged and deliberately so: these
883
+ * three write an outcome the runtime observed or a person did, and neither exists
884
+ * for a harness start. The counterpart writes a third thing and says so on the
885
+ * record, which is what makes it a carve-out rather than a hole. Amended
886
+ * SPEC.md §10.2 states the rule; the reconciliation recorded on APRV-145 is that
887
+ * these three keep refusing exactly as APRV-146 merged them.
888
+ *
889
+ * An `execution.indeterminate` closes a cycle here like any other outcome
890
+ * (APRV-120). It is not an invitation to try again: a second outcome for a key
891
+ * whose effect may already have happened is exactly the blind double-execution
892
+ * the state exists to refuse, and the repair is `execution reconcile`, which
893
+ * appends beside the record rather than closing it a second time.
894
+ */
895
+ function openExecution(logPath, actionKey, options) {
896
+ const read = readVerifiedRecords(logPath, options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir });
897
+ if (!read.ok)
898
+ return fromReadRefusal(read);
899
+ let started = null;
900
+ let finished = null;
901
+ for (const record of read.records) {
902
+ if (record.action_key !== actionKey)
903
+ continue;
904
+ if (record.event === "execution.started") {
905
+ started = record;
906
+ finished = null;
907
+ continue;
908
+ }
909
+ if (record.event === "execution.completed" ||
910
+ record.event === "execution.failed" ||
911
+ record.event === "execution.indeterminate") {
912
+ if (started !== null)
913
+ finished = record;
914
+ }
915
+ }
916
+ if (started === null) {
917
+ return refuse("not-started", `action ${actionKey} has no execution.started record; an outcome cannot be recorded for an execution that never began`);
918
+ }
919
+ // APRV-146. A delegated start is terminal by design, so there is no open
920
+ // execution here to close. Checked BEFORE the already-finished branch because
921
+ // the fact is about this record's custody rather than about what else the log
922
+ // holds: a harness execution was never going to gain an outcome, whether or
923
+ // not something has since written one over it.
924
+ if (isDelegatedStart(started)) {
925
+ return refuse("execution-delegated", `action ${actionKey}'s execution.started at seq ${started.seq} carries execution: "harness" (APRV-117): the harness ran the command and this runtime never observed an exit status, so the record is complete as written and terminal by design. No outcome may be written over it — a completed or failed recorded here would report an exit code nobody watched, and an execution.completed would additionally clear the task's loop-escalation streak (SPEC.md §10.2). \`approval status\` lists such a record as delegated rather than dangling for the same reason.`, { seq: started.seq });
926
+ }
927
+ if (finished !== null) {
928
+ return refuse("already-finished", finished.event === "execution.indeterminate"
929
+ ? `action ${actionKey} ended in an unknown outcome (execution.indeterminate at seq ${finished.seq}); its side effect was attempted and nobody knows whether it committed, so no outcome may be written over it. Establish what happened and record it with \`approval execution reconcile\`, which appends beside that record and never rewrites it.`
930
+ : `action ${actionKey} was already closed by ${finished.event} at seq ${finished.seq}; an execution has exactly one outcome`, { seq: finished.seq });
931
+ }
932
+ const task = started.task;
933
+ if (typeof task !== "string" || task.length === 0) {
934
+ // Unreachable through the real append path: event.schema.json requires
935
+ // `task` on every execution event. Kept as a fail-closed backstop.
936
+ return refuse("not-started", `the execution.started record for ${actionKey} at seq ${started.seq} names no task; the outcome event requires one`, { seq: started.seq });
937
+ }
938
+ return { ok: true, task, startedSeq: started.seq, head: read.head };
939
+ }
940
+ /** Actors permitted to resolve. A fact nobody observed is not an observation. */
941
+ const HUMAN_ACTOR = /^human:.+/u;
942
+ /**
943
+ * Close a dangling execution with what a human actually observed.
944
+ *
945
+ * {@link finishExecution} is the mechanical path: `approval run` knows the
946
+ * child's exit code because it waited for it. This is the path for the case
947
+ * that code cannot cover — the runtime died between `execution.started` and its
948
+ * outcome, so the log honestly says "this began and we do not know how it
949
+ * ended", and only a person who went and looked can say more.
950
+ *
951
+ * Five properties, all deliberate:
952
+ *
953
+ * 1. **The note is mandatory and non-empty.** The whole value of this event is
954
+ * the observation behind it; an unexplained human-attested outcome is
955
+ * indistinguishable from a guess, and a guess written into an append-only
956
+ * log is indistinguishable from a fact. The CLI refuses an empty note as a
957
+ * usage error before reaching here, and this refuses it again.
958
+ * 2. **Human-only.** An agent closing its own dangling execution is the agent
959
+ * reporting on itself, which is the one thing the log exists not to accept.
960
+ * 3. **`exit_code: null`.** Not `0`, not `127`: nobody ran anything and there
961
+ * is no code to report. A fabricated exit code would read exactly like an
962
+ * observed one, and `payload.attested_by_human: true` marks the difference
963
+ * for every reader and every projection.
964
+ * 4. **A harness execution is out of reach** (APRV-146). A delegated start is
965
+ * refused `execution-delegated` here as it is in {@link finishExecution}: the
966
+ * record is terminal by design, and a person attesting an outcome for a
967
+ * command this runtime never watched would be attesting to the one thing the
968
+ * log already says nobody observed.
969
+ * 5. **No attestation requirement.** Resolve records a fact a human observed;
970
+ * it exercises no policy authority — it authorizes nothing, spends no
971
+ * budget, mints no token — so it does not require an attested policy. A
972
+ * dangling execution left unclosable because a policy file was edited would
973
+ * be a repair blocked by an unrelated fact.
974
+ */
975
+ export function resolveExecution(logPath, actionKey, outcome, note, actor, options = {}) {
976
+ if (!HUMAN_ACTOR.test(actor)) {
977
+ return refuse("actor-not-human", `resolve is human-only: it records what a person observed about an execution nobody watched finish, and an agent-attested outcome would be the executing party reporting on itself. The actor must match human:<id>, got ${JSON.stringify(actor)}.`);
978
+ }
979
+ if (note.trim().length === 0) {
980
+ return refuse("actor-not-human", `resolve requires a non-empty --note: the event's value is the observation behind it, and an unexplained human-attested outcome cannot be told apart from a guess`);
981
+ }
982
+ const open = openExecution(logPath, actionKey, options);
983
+ if (!open.ok)
984
+ return open;
985
+ const event = outcome === "completed" ? "execution.completed" : "execution.failed";
986
+ const appended = append(logPath, {
987
+ ts: tick(options),
988
+ event,
989
+ actor,
990
+ task: open.task,
991
+ action_key: actionKey,
992
+ payload: { note, attested_by_human: true, exit_code: null },
993
+ }, options, open.head);
994
+ if (!appended.ok)
995
+ return appended;
996
+ return { ok: true, record: appended.record, event, outcome, task: open.task };
997
+ }
998
+ /**
999
+ * Close an execution as INDETERMINATE: the side effect was attempted and
1000
+ * nobody knows whether it committed (APRV-120).
1001
+ *
1002
+ * `execution.failed` used to carry this case, and conflating the two is what
1003
+ * made a retry look safe. An adapter that times out mid-send reads, in a log
1004
+ * that only knows `failed`, exactly like one that never opened a socket; a
1005
+ * caller reading the second reasonably tries again, and against the first that
1006
+ * is a double send. So the runtime writes down which it is, and the difference
1007
+ * is positional rather than a judgment: the adapter contract records
1008
+ * `execution.failed` for everything that goes wrong BEFORE `act` is entered
1009
+ * (provably not committed) and this for anything after (provably nothing).
1010
+ *
1011
+ * Three properties, all deliberate:
1012
+ *
1013
+ * 1. **The consumption is burned.** The token was spent at
1014
+ * `execution.started` and stays spent; the idempotency key stays used; the
1015
+ * budget stays charged. Refunding an attempt whose outcome is unknown would
1016
+ * be the runtime deciding the effect did not happen, which is the one thing
1017
+ * nobody here knows.
1018
+ * 2. **No exception text.** `reason` is a closed code and nothing else is
1019
+ * recorded. An error message is where a credential rides into the log with
1020
+ * a plausible excuse, and §11.1's third invariant does not have an
1021
+ * exception for diagnostics. The caller still receives the message, redacted,
1022
+ * from the adapter contract.
1023
+ * 3. **Nothing auto-resolves.** No function here, and nothing in the daemon,
1024
+ * ever converts this into completed or failed. Only
1025
+ * {@link reconcileExecution} does, on a person's evidence.
1026
+ */
1027
+ export function indeterminateExecution(logPath, actionKey, reason, actor, options = {}) {
1028
+ const open = openExecution(logPath, actionKey, options);
1029
+ if (!open.ok)
1030
+ return open;
1031
+ const appended = append(logPath, {
1032
+ ts: tick(options),
1033
+ event: "execution.indeterminate",
1034
+ actor,
1035
+ task: open.task,
1036
+ action_key: actionKey,
1037
+ // `exit_code: null` for the reason `resolve` writes it: nobody watched a
1038
+ // process exit, and a fabricated number would read like a measured one.
1039
+ payload: { reason, exit_code: null },
1040
+ }, options,
1041
+ // The head read above, when the not-started / already-finished checks ran.
1042
+ open.head);
1043
+ if (!appended.ok)
1044
+ return appended;
1045
+ return { ok: true, record: appended.record, reason, task: open.task };
1046
+ }
1047
+ /**
1048
+ * Record what a person established about an indeterminate execution.
1049
+ *
1050
+ * The counterpart of {@link resolveExecution}, and deliberately a separate verb
1051
+ * with separate refusals: `resolve` closes an execution nobody watched finish,
1052
+ * and this resolves one whose effect may or may not have landed. The questions
1053
+ * are different ("what did the runtime do?" against "did the far side commit?"),
1054
+ * the evidence is different (this repo's log against the relying party's), and
1055
+ * an operator who reached for the wrong one should be told so rather than
1056
+ * quietly write the wrong record.
1057
+ *
1058
+ * Four properties:
1059
+ *
1060
+ * 1. **The original is never rewritten.** This appends a record that NAMES the
1061
+ * indeterminate one by seq. The observation "we did not know" survives its
1062
+ * own resolution, which is the whole reason the log is append-only, and an
1063
+ * auditor can see both the doubt and its answer.
1064
+ * 2. **Human-only, and never the daemon.** An agent reconciling its own unknown
1065
+ * outcome is the executing party reporting on itself; a daemon doing it on a
1066
+ * schedule is a guess with a cron entry. The mandatory note is the evidence,
1067
+ * in the reconciler's own words.
1068
+ * 3. **The two resolutions are distinct in the log.** `executed` and
1069
+ * `not-executed` are separate closed values, not two readings of one
1070
+ * sentence, because everything downstream of the record turns on which.
1071
+ * 4. **The key stays burned either way.** Resolving `not-executed` re-opens the
1072
+ * possibility of the EFFECT, not of this action: an `idempotency_key` is the
1073
+ * global identity of one side effect (§6.2) and a used one is used. The
1074
+ * repair is to declare a fresh action and request it, which is a new
1075
+ * question with a new answer, and the reconciliation is what makes asking it
1076
+ * honest.
1077
+ */
1078
+ export function reconcileExecution(logPath, actionKey, resolution, note, actor, options = {}) {
1079
+ if (!HUMAN_ACTOR.test(actor)) {
1080
+ return refuse("actor-not-human", `reconcile is human-only: it records what a person established about an execution whose outcome the runtime could not observe, and an agent-attested resolution would be the executing party reporting on itself. The actor must match human:<id>, got ${JSON.stringify(actor)}.`);
1081
+ }
1082
+ if (note.trim().length === 0) {
1083
+ return refuse("actor-not-human", `reconcile requires a non-empty note: the record's value is the evidence behind it, and an unexplained resolution of an unknown outcome cannot be told apart from a guess`);
1084
+ }
1085
+ const read = readVerifiedRecords(logPath, options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir });
1086
+ if (!read.ok)
1087
+ return fromReadRefusal(read);
1088
+ const cycle = executionCustody(read.records).find((entry) => entry.actionKey === actionKey);
1089
+ if (cycle === undefined || cycle.indeterminateSeq === null) {
1090
+ return refuse("not-indeterminate", `action ${actionKey} has no execution.indeterminate record, so there is no unknown outcome to resolve. A started execution with no outcome at all is a dangling execution and is closed with \`approval execution resolve\`.`);
1091
+ }
1092
+ if (cycle.state === "reconciled") {
1093
+ return refuse("already-reconciled", `action ${actionKey}'s indeterminate outcome at seq ${cycle.indeterminateSeq} was already reconciled at seq ${String(cycle.closedSeq)}; a second resolution would be a second answer to a question a person already answered, and neither record is rewritten`, { seq: cycle.closedSeq ?? cycle.indeterminateSeq });
1094
+ }
1095
+ const task = cycle.task;
1096
+ if (task === null || task.length === 0) {
1097
+ // Unreachable through the real append path: event.schema.json requires
1098
+ // `task` on every execution event. Kept as a fail-closed backstop.
1099
+ return refuse("not-indeterminate", `the execution.started record for ${actionKey} at seq ${cycle.seq} names no task; the reconciliation event requires one`, { seq: cycle.seq });
1100
+ }
1101
+ const appended = append(logPath, {
1102
+ ts: tick(options),
1103
+ event: "execution.reconciled",
1104
+ actor,
1105
+ task,
1106
+ action_key: actionKey,
1107
+ payload: {
1108
+ indeterminate_seq: cycle.indeterminateSeq,
1109
+ resolution,
1110
+ note,
1111
+ attested_by_human: true,
1112
+ },
1113
+ }, options,
1114
+ // Compare-and-append: the "is there an unreconciled indeterminate here"
1115
+ // check above was made against the log ending exactly at this head, so two
1116
+ // reconcilers of one record cannot both land.
1117
+ read.head);
1118
+ if (!appended.ok)
1119
+ return appended;
1120
+ return {
1121
+ ok: true,
1122
+ record: appended.record,
1123
+ resolution,
1124
+ task,
1125
+ indeterminateSeq: cycle.indeterminateSeq,
1126
+ };
1127
+ }
1128
+ // ---------------------------------------------------------------------------
1129
+ // custody
1130
+ // ---------------------------------------------------------------------------
1131
+ /**
1132
+ * What the log knows about one started execution (APRV-120).
1133
+ *
1134
+ * The word is custody rather than status because the question is not "did it
1135
+ * work" but "who is holding this, and what may still be done with it". Five
1136
+ * states, and the two that are easy to confuse are the reason the vocabulary
1137
+ * exists:
1138
+ *
1139
+ * - `settled` — an `execution.completed` or `execution.failed` closed it. The
1140
+ * runtime watched the outcome and wrote down what it saw.
1141
+ * - `open` — a start with no outcome, written by a runtime that MEANT to watch
1142
+ * one. This is the dangling execution: a crash between `execution.started`
1143
+ * and its outcome, repairable by a person with `execution resolve`. It is
1144
+ * debris, and `approval status` says so.
1145
+ * - `delegated` — a start carrying `payload.execution: "harness"` (APRV-117,
1146
+ * APRV-141). **Terminal by design, and never debris.** The harness runs the
1147
+ * command and this runtime never observes an exit status, so no outcome event
1148
+ * will ever follow; the record is complete as written. Reporting these as
1149
+ * dangling — which is what happened before this state existed, to every
1150
+ * harness execution in the reference repository's own log — trains operators
1151
+ * to ignore the one list that is supposed to mean something.
1152
+ * - `indeterminate` — an `execution.indeterminate`: the side effect was
1153
+ * attempted and nobody knows whether it committed. The token is spent and the
1154
+ * key is burned, and a re-run is refused, because a retry against an unknown
1155
+ * outcome is a blind double-execution.
1156
+ * - `reconciled` — a person established which it was, and said so in an
1157
+ * `execution.reconciled` that sits beside the indeterminate record rather
1158
+ * than over it.
1159
+ */
1160
+ export const CUSTODY_STATES = [
1161
+ "settled",
1162
+ "open",
1163
+ "delegated",
1164
+ "indeterminate",
1165
+ "reconciled",
1166
+ ];
1167
+ /** Where an indeterminate outcome's unknowing began. Closed (schema §8). */
1168
+ export const INDETERMINATE_REASONS = ["act-threw"];
1169
+ /** What a reconciliation established. Closed, and the two are distinct. */
1170
+ export const RECONCILE_RESOLUTIONS = ["executed", "not-executed"];
1171
+ export function isIndeterminateReason(value) {
1172
+ return typeof value === "string" && INDETERMINATE_REASONS.includes(value);
1173
+ }
1174
+ export function isReconcileResolution(value) {
1175
+ return typeof value === "string" && RECONCILE_RESOLUTIONS.includes(value);
1176
+ }
1177
+ /** Does this `execution.started` record say the harness ran the command? */
1178
+ function isDelegatedStart(record) {
1179
+ return payloadOf(record)["execution"] === "harness";
1180
+ }
1181
+ /**
1182
+ * The custody state of every started execution, in log order.
1183
+ *
1184
+ * Pure: no I/O, no clock. Per action key, only the **latest cycle** counts — a
1185
+ * start followed by an outcome is closed, and a later start reopens the key.
1186
+ * (The gate refuses a second start for a key anyway; this function does not
1187
+ * assume that, because a projection that only works on well-formed logs is a
1188
+ * projection that goes quiet exactly when something has gone wrong.)
1189
+ */
1190
+ export function executionCustody(records) {
1191
+ const cycles = new Map();
1192
+ for (const record of records) {
1193
+ const actionKey = record.action_key;
1194
+ if (typeof actionKey !== "string" || actionKey.length === 0)
1195
+ continue;
1196
+ if (record.event === "execution.started") {
1197
+ cycles.set(actionKey, {
1198
+ actionKey,
1199
+ task: record.task ?? null,
1200
+ // The marker is read once, here: a harness start is complete as
1201
+ // written, and every later reader asks this projection rather than
1202
+ // re-deriving the rule from a payload field.
1203
+ state: isDelegatedStart(record) ? "delegated" : "open",
1204
+ ts: record.ts,
1205
+ seq: record.seq,
1206
+ actor: record.actor,
1207
+ closedSeq: null,
1208
+ indeterminateSeq: null,
1209
+ reason: null,
1210
+ resolution: null,
1211
+ });
1212
+ continue;
1213
+ }
1214
+ const cycle = cycles.get(actionKey);
1215
+ if (cycle === undefined)
1216
+ continue;
1217
+ if (record.event === "execution.completed" || record.event === "execution.failed") {
1218
+ cycle.state = "settled";
1219
+ cycle.closedSeq = record.seq;
1220
+ continue;
1221
+ }
1222
+ if (record.event === "execution.indeterminate") {
1223
+ const reason = payloadOf(record)["reason"];
1224
+ cycle.state = "indeterminate";
1225
+ cycle.closedSeq = record.seq;
1226
+ cycle.indeterminateSeq = record.seq;
1227
+ cycle.reason = isIndeterminateReason(reason) ? reason : null;
1228
+ continue;
1229
+ }
1230
+ if (record.event === "execution.reconciled") {
1231
+ const resolution = payloadOf(record)["resolution"];
1232
+ cycle.state = "reconciled";
1233
+ cycle.closedSeq = record.seq;
1234
+ cycle.resolution = isReconcileResolution(resolution) ? resolution : null;
1235
+ }
1236
+ }
1237
+ return [...cycles.values()].sort((a, b) => a.seq - b.seq);
1238
+ }
1239
+ /**
1240
+ * Executions that started, were meant to be watched, and never finished.
1241
+ *
1242
+ * This is the state a crash between `execution.started` and its outcome leaves
1243
+ * behind, and it is reported as itself: not as completed, not as failed, not as
1244
+ * clean. `approval status` lists it; `approval queue` does not, because nobody
1245
+ * is being asked to decide anything.
1246
+ *
1247
+ * A `delegated` start is NOT here (APRV-120). The harness ran the command and
1248
+ * this runtime never sees an exit status, so its record was never going to gain
1249
+ * an outcome; listing it as debris says something false about a log that is
1250
+ * exactly right.
1251
+ */
1252
+ export function danglingExecutions(records) {
1253
+ return executionCustody(records)
1254
+ .filter((cycle) => cycle.state === "open")
1255
+ .map(({ actionKey, task, ts, seq, actor }) => ({ actionKey, task, ts, seq, actor }));
1256
+ }
1257
+ /**
1258
+ * Executions whose side effect was attempted and whose outcome nobody knows,
1259
+ * and which no one has reconciled yet.
1260
+ *
1261
+ * Distinct from {@link danglingExecutions} in what it asks of a person. A
1262
+ * dangling execution needs someone to look at what the runtime did; an
1263
+ * indeterminate one needs someone to establish, from the relying party's own
1264
+ * evidence, whether the effect happened at all. Both are debris and both make
1265
+ * `approval status` unhealthy; only one of them is repaired with
1266
+ * `execution resolve`.
1267
+ */
1268
+ export function indeterminateExecutions(records) {
1269
+ return executionCustody(records).filter((cycle) => cycle.state === "indeterminate");
1270
+ }
1271
+ //# sourceMappingURL=execute.js.map