approval-md 0.0.1 → 0.1.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 (522) hide show
  1. package/LICENSE +176 -0
  2. package/NOTICE +5 -0
  3. package/README.md +909 -4
  4. package/SPEC.md +445 -32
  5. package/cli.js +29 -3
  6. package/dist/src/adapters/agentmail.js +1200 -0
  7. package/dist/src/adapters/agentmail.js.map +1 -0
  8. package/dist/src/adapters/conformance.js +461 -0
  9. package/dist/src/adapters/conformance.js.map +1 -0
  10. package/dist/src/adapters/contract.js +941 -0
  11. package/dist/src/adapters/contract.js.map +1 -0
  12. package/dist/src/adapters/email.js +749 -0
  13. package/dist/src/adapters/email.js.map +1 -0
  14. package/dist/src/adapters/env-passphrase.js +132 -0
  15. package/dist/src/adapters/env-passphrase.js.map +1 -0
  16. package/dist/src/adapters/registry.js +76 -0
  17. package/dist/src/adapters/registry.js.map +1 -0
  18. package/dist/src/adapters/smtp.js +499 -0
  19. package/dist/src/adapters/smtp.js.map +1 -0
  20. package/dist/src/adapters/vault-provider.js +161 -0
  21. package/dist/src/adapters/vault-provider.js.map +1 -0
  22. package/dist/src/channels/batch.js +121 -0
  23. package/dist/src/channels/batch.js.map +1 -0
  24. package/dist/src/channels/cli.js +468 -0
  25. package/dist/src/channels/cli.js.map +1 -0
  26. package/dist/src/channels/conformance.js +445 -0
  27. package/dist/src/channels/conformance.js.map +1 -0
  28. package/dist/src/channels/contract.js +494 -0
  29. package/dist/src/channels/contract.js.map +1 -0
  30. package/dist/src/channels/payload-view.js +43 -0
  31. package/dist/src/channels/payload-view.js.map +1 -0
  32. package/dist/src/channels/render-queue.js +564 -0
  33. package/dist/src/channels/render-queue.js.map +1 -0
  34. package/dist/src/channels/tagging.js +723 -0
  35. package/dist/src/channels/tagging.js.map +1 -0
  36. package/dist/src/channels/telegram.js +3190 -0
  37. package/dist/src/channels/telegram.js.map +1 -0
  38. package/dist/src/channels/web.js +903 -0
  39. package/dist/src/channels/web.js.map +1 -0
  40. package/dist/src/cli/adapter.js +278 -0
  41. package/dist/src/cli/adapter.js.map +1 -0
  42. package/dist/src/cli/amend.js +2171 -0
  43. package/dist/src/cli/amend.js.map +1 -0
  44. package/dist/src/cli/args.js +86 -0
  45. package/dist/src/cli/args.js.map +1 -0
  46. package/dist/src/cli/attest.js +307 -0
  47. package/dist/src/cli/attest.js.map +1 -0
  48. package/dist/src/cli/audit-card.js +201 -0
  49. package/dist/src/cli/audit-card.js.map +1 -0
  50. package/dist/src/cli/audit.js +460 -0
  51. package/dist/src/cli/audit.js.map +1 -0
  52. package/dist/src/cli/channel-telegram.js +2063 -0
  53. package/dist/src/cli/channel-telegram.js.map +1 -0
  54. package/dist/src/cli/channel-web.js +357 -0
  55. package/dist/src/cli/channel-web.js.map +1 -0
  56. package/dist/src/cli/channel.js +438 -0
  57. package/dist/src/cli/channel.js.map +1 -0
  58. package/dist/src/cli/checkpoint-tap.js +238 -0
  59. package/dist/src/cli/checkpoint-tap.js.map +1 -0
  60. package/dist/src/cli/coverage.js +343 -0
  61. package/dist/src/cli/coverage.js.map +1 -0
  62. package/dist/src/cli/daemon.js +631 -0
  63. package/dist/src/cli/daemon.js.map +1 -0
  64. package/dist/src/cli/doctor.js +2648 -0
  65. package/dist/src/cli/doctor.js.map +1 -0
  66. package/dist/src/cli/env.js +302 -0
  67. package/dist/src/cli/env.js.map +1 -0
  68. package/dist/src/cli/execute.js +1682 -0
  69. package/dist/src/cli/execute.js.map +1 -0
  70. package/dist/src/cli/exit-codes.js +82 -0
  71. package/dist/src/cli/exit-codes.js.map +1 -0
  72. package/dist/src/cli/feedback.js +205 -0
  73. package/dist/src/cli/feedback.js.map +1 -0
  74. package/dist/src/cli/gate-window.js +294 -0
  75. package/dist/src/cli/gate-window.js.map +1 -0
  76. package/dist/src/cli/gate.js +557 -0
  77. package/dist/src/cli/gate.js.map +1 -0
  78. package/dist/src/cli/git-scope.js +295 -0
  79. package/dist/src/cli/git-scope.js.map +1 -0
  80. package/dist/src/cli/gloss-attach.js +107 -0
  81. package/dist/src/cli/gloss-attach.js.map +1 -0
  82. package/dist/src/cli/gloss-codex-child.js +149 -0
  83. package/dist/src/cli/gloss-codex-child.js.map +1 -0
  84. package/dist/src/cli/gloss-codex.js +255 -0
  85. package/dist/src/cli/gloss-codex.js.map +1 -0
  86. package/dist/src/cli/gloss-options.js +79 -0
  87. package/dist/src/cli/gloss-options.js.map +1 -0
  88. package/dist/src/cli/gloss.js +362 -0
  89. package/dist/src/cli/gloss.js.map +1 -0
  90. package/dist/src/cli/help.js +2217 -0
  91. package/dist/src/cli/help.js.map +1 -0
  92. package/dist/src/cli/hook.js +2743 -0
  93. package/dist/src/cli/hook.js.map +1 -0
  94. package/dist/src/cli/import.js +175 -0
  95. package/dist/src/cli/import.js.map +1 -0
  96. package/dist/src/cli/init.js +336 -0
  97. package/dist/src/cli/init.js.map +1 -0
  98. package/dist/src/cli/instructions.js +262 -0
  99. package/dist/src/cli/instructions.js.map +1 -0
  100. package/dist/src/cli/journal.js +238 -0
  101. package/dist/src/cli/journal.js.map +1 -0
  102. package/dist/src/cli/log-advance.js +749 -0
  103. package/dist/src/cli/log-advance.js.map +1 -0
  104. package/dist/src/cli/log-anchor.js +387 -0
  105. package/dist/src/cli/log-anchor.js.map +1 -0
  106. package/dist/src/cli/log-checkpoint.js +128 -0
  107. package/dist/src/cli/log-checkpoint.js.map +1 -0
  108. package/dist/src/cli/log-sync.js +849 -0
  109. package/dist/src/cli/log-sync.js.map +1 -0
  110. package/dist/src/cli/log-verbs.js +354 -0
  111. package/dist/src/cli/log-verbs.js.map +1 -0
  112. package/dist/src/cli/long-help.js +148 -0
  113. package/dist/src/cli/long-help.js.map +1 -0
  114. package/dist/src/cli/main.js +1056 -0
  115. package/dist/src/cli/main.js.map +1 -0
  116. package/dist/src/cli/mcp.js +306 -0
  117. package/dist/src/cli/mcp.js.map +1 -0
  118. package/dist/src/cli/paths.js +79 -0
  119. package/dist/src/cli/paths.js.map +1 -0
  120. package/dist/src/cli/payload.js +253 -0
  121. package/dist/src/cli/payload.js.map +1 -0
  122. package/dist/src/cli/policy.js +229 -0
  123. package/dist/src/cli/policy.js.map +1 -0
  124. package/dist/src/cli/preflight.js +888 -0
  125. package/dist/src/cli/preflight.js.map +1 -0
  126. package/dist/src/cli/progress.js +112 -0
  127. package/dist/src/cli/progress.js.map +1 -0
  128. package/dist/src/cli/prompt.js +312 -0
  129. package/dist/src/cli/prompt.js.map +1 -0
  130. package/dist/src/cli/records.js +66 -0
  131. package/dist/src/cli/records.js.map +1 -0
  132. package/dist/src/cli/render.js +132 -0
  133. package/dist/src/cli/render.js.map +1 -0
  134. package/dist/src/cli/sandbox.js +150 -0
  135. package/dist/src/cli/sandbox.js.map +1 -0
  136. package/dist/src/cli/scaffold.js +137 -0
  137. package/dist/src/cli/scaffold.js.map +1 -0
  138. package/dist/src/cli/setup-adapter.js +475 -0
  139. package/dist/src/cli/setup-adapter.js.map +1 -0
  140. package/dist/src/cli/setup-channel.js +635 -0
  141. package/dist/src/cli/setup-channel.js.map +1 -0
  142. package/dist/src/cli/setup-checkpoint.js +196 -0
  143. package/dist/src/cli/setup-checkpoint.js.map +1 -0
  144. package/dist/src/cli/setup-common.js +376 -0
  145. package/dist/src/cli/setup-common.js.map +1 -0
  146. package/dist/src/cli/setup-flow.js +476 -0
  147. package/dist/src/cli/setup-flow.js.map +1 -0
  148. package/dist/src/cli/setup-service.js +308 -0
  149. package/dist/src/cli/setup-service.js.map +1 -0
  150. package/dist/src/cli/setup.js +473 -0
  151. package/dist/src/cli/setup.js.map +1 -0
  152. package/dist/src/cli/style.js +469 -0
  153. package/dist/src/cli/style.js.map +1 -0
  154. package/dist/src/cli/token.js +274 -0
  155. package/dist/src/cli/token.js.map +1 -0
  156. package/dist/src/cli/up.js +847 -0
  157. package/dist/src/cli/up.js.map +1 -0
  158. package/dist/src/cli/usage.js +91 -0
  159. package/dist/src/cli/usage.js.map +1 -0
  160. package/dist/src/cli/values.js +189 -0
  161. package/dist/src/cli/values.js.map +1 -0
  162. package/dist/src/cli/vault.js +362 -0
  163. package/dist/src/cli/vault.js.map +1 -0
  164. package/dist/src/cli/verb-registry.js +2173 -0
  165. package/dist/src/cli/verb-registry.js.map +1 -0
  166. package/dist/src/cli/wordmark.js +52 -0
  167. package/dist/src/cli/wordmark.js.map +1 -0
  168. package/dist/src/core/advance-cycle.js +200 -0
  169. package/dist/src/core/advance-cycle.js.map +1 -0
  170. package/dist/src/core/agents-md.js +747 -0
  171. package/dist/src/core/agents-md.js.map +1 -0
  172. package/dist/src/core/attest.js +577 -0
  173. package/dist/src/core/attest.js.map +1 -0
  174. package/dist/src/core/audit.js +882 -0
  175. package/dist/src/core/audit.js.map +1 -0
  176. package/dist/src/core/budgets.js +449 -0
  177. package/dist/src/core/budgets.js.map +1 -0
  178. package/dist/src/core/checkpoint.js +738 -0
  179. package/dist/src/core/checkpoint.js.map +1 -0
  180. package/dist/src/core/child-env.js +86 -0
  181. package/dist/src/core/child-env.js.map +1 -0
  182. package/dist/src/core/clock.js +43 -0
  183. package/dist/src/core/clock.js.map +1 -0
  184. package/dist/src/core/command-class.js +2321 -0
  185. package/dist/src/core/command-class.js.map +1 -0
  186. package/dist/src/core/coverage-sources/adapter.js +71 -0
  187. package/dist/src/core/coverage-sources/adapter.js.map +1 -0
  188. package/dist/src/core/coverage-sources/gh.js +136 -0
  189. package/dist/src/core/coverage-sources/gh.js.map +1 -0
  190. package/dist/src/core/coverage-sources/git.js +269 -0
  191. package/dist/src/core/coverage-sources/git.js.map +1 -0
  192. package/dist/src/core/coverage.js +337 -0
  193. package/dist/src/core/coverage.js.map +1 -0
  194. package/dist/src/core/credential-spec.js +23 -0
  195. package/dist/src/core/credential-spec.js.map +1 -0
  196. package/dist/src/core/dark-session.js +714 -0
  197. package/dist/src/core/dark-session.js.map +1 -0
  198. package/dist/src/core/decision-refusal.js +265 -0
  199. package/dist/src/core/decision-refusal.js.map +1 -0
  200. package/dist/src/core/env-file.js +837 -0
  201. package/dist/src/core/env-file.js.map +1 -0
  202. package/dist/src/core/execute.js +1233 -0
  203. package/dist/src/core/execute.js.map +1 -0
  204. package/dist/src/core/frontmatter.js +100 -0
  205. package/dist/src/core/frontmatter.js.map +1 -0
  206. package/dist/src/core/gate-window.js +506 -0
  207. package/dist/src/core/gate-window.js.map +1 -0
  208. package/dist/src/core/gate.js +2947 -0
  209. package/dist/src/core/gate.js.map +1 -0
  210. package/dist/src/core/git-run.js +93 -0
  211. package/dist/src/core/git-run.js.map +1 -0
  212. package/dist/src/core/harness-version.js +210 -0
  213. package/dist/src/core/harness-version.js.map +1 -0
  214. package/dist/src/core/harness-wait.js +58 -0
  215. package/dist/src/core/harness-wait.js.map +1 -0
  216. package/dist/src/core/head-retry.js +121 -0
  217. package/dist/src/core/head-retry.js.map +1 -0
  218. package/dist/src/core/instance.js +319 -0
  219. package/dist/src/core/instance.js.map +1 -0
  220. package/dist/src/core/intake-limits.js +350 -0
  221. package/dist/src/core/intake-limits.js.map +1 -0
  222. package/dist/src/core/jcs.js +132 -0
  223. package/dist/src/core/jcs.js.map +1 -0
  224. package/dist/src/core/journal.js +200 -0
  225. package/dist/src/core/journal.js.map +1 -0
  226. package/dist/src/core/live-draw.js +703 -0
  227. package/dist/src/core/live-draw.js.map +1 -0
  228. package/dist/src/core/log-reconcile.js +136 -0
  229. package/dist/src/core/log-reconcile.js.map +1 -0
  230. package/dist/src/core/log.js +546 -0
  231. package/dist/src/core/log.js.map +1 -0
  232. package/dist/src/core/loop.js +476 -0
  233. package/dist/src/core/loop.js.map +1 -0
  234. package/dist/src/core/md-fence.js +74 -0
  235. package/dist/src/core/md-fence.js.map +1 -0
  236. package/dist/src/core/money.js +195 -0
  237. package/dist/src/core/money.js.map +1 -0
  238. package/dist/src/core/payload-census.js +146 -0
  239. package/dist/src/core/payload-census.js.map +1 -0
  240. package/dist/src/core/payload-store.js +340 -0
  241. package/dist/src/core/payload-store.js.map +1 -0
  242. package/dist/src/core/payload.js +80 -0
  243. package/dist/src/core/payload.js.map +1 -0
  244. package/dist/src/core/policy-diff.js +565 -0
  245. package/dist/src/core/policy-diff.js.map +1 -0
  246. package/dist/src/core/policy-expectations.js +394 -0
  247. package/dist/src/core/policy-expectations.js.map +1 -0
  248. package/dist/src/core/policy-explain.js +230 -0
  249. package/dist/src/core/policy-explain.js.map +1 -0
  250. package/dist/src/core/policy-load.js +524 -0
  251. package/dist/src/core/policy-load.js.map +1 -0
  252. package/dist/src/core/policy-match.js +467 -0
  253. package/dist/src/core/policy-match.js.map +1 -0
  254. package/dist/src/core/policy-proposal.js +458 -0
  255. package/dist/src/core/policy-proposal.js.map +1 -0
  256. package/dist/src/core/prompt-layout.js +422 -0
  257. package/dist/src/core/prompt-layout.js.map +1 -0
  258. package/dist/src/core/protected-path-guard.js +1087 -0
  259. package/dist/src/core/protected-path-guard.js.map +1 -0
  260. package/dist/src/core/registration.js +39 -0
  261. package/dist/src/core/registration.js.map +1 -0
  262. package/dist/src/core/reindex.js +336 -0
  263. package/dist/src/core/reindex.js.map +1 -0
  264. package/dist/src/core/sampler.js +388 -0
  265. package/dist/src/core/sampler.js.map +1 -0
  266. package/dist/src/core/sandbox.js +424 -0
  267. package/dist/src/core/sandbox.js.map +1 -0
  268. package/dist/src/core/seal.js +290 -0
  269. package/dist/src/core/seal.js.map +1 -0
  270. package/dist/src/core/state.js +1009 -0
  271. package/dist/src/core/state.js.map +1 -0
  272. package/dist/src/core/task-file.js +464 -0
  273. package/dist/src/core/task-file.js.map +1 -0
  274. package/dist/src/core/telegram-config.js +114 -0
  275. package/dist/src/core/telegram-config.js.map +1 -0
  276. package/dist/src/core/token.js +578 -0
  277. package/dist/src/core/token.js.map +1 -0
  278. package/dist/src/core/validate.js +0 -0
  279. package/dist/src/core/validate.js.map +1 -0
  280. package/dist/src/core/values.js +153 -0
  281. package/dist/src/core/values.js.map +1 -0
  282. package/dist/src/core/vault.js +612 -0
  283. package/dist/src/core/vault.js.map +1 -0
  284. package/dist/src/core/verified-snapshot.js +506 -0
  285. package/dist/src/core/verified-snapshot.js.map +1 -0
  286. package/dist/src/core/verify.js +549 -0
  287. package/dist/src/core/verify.js.map +1 -0
  288. package/dist/src/core/version.js +9 -0
  289. package/dist/src/core/version.js.map +1 -0
  290. package/dist/src/core/wysiwys.js +728 -0
  291. package/dist/src/core/wysiwys.js.map +1 -0
  292. package/dist/src/daemon/advance-child.js +78 -0
  293. package/dist/src/daemon/advance-child.js.map +1 -0
  294. package/dist/src/daemon/advance.js +849 -0
  295. package/dist/src/daemon/advance.js.map +1 -0
  296. package/dist/src/daemon/audit.js +90 -0
  297. package/dist/src/daemon/audit.js.map +1 -0
  298. package/dist/src/daemon/daemon.js +1988 -0
  299. package/dist/src/daemon/daemon.js.map +1 -0
  300. package/dist/src/daemon/dark-session.js +119 -0
  301. package/dist/src/daemon/dark-session.js.map +1 -0
  302. package/dist/src/daemon/draw-child.js +132 -0
  303. package/dist/src/daemon/draw-child.js.map +1 -0
  304. package/dist/src/daemon/draw.js +458 -0
  305. package/dist/src/daemon/draw.js.map +1 -0
  306. package/dist/src/daemon/git-evidence.js +345 -0
  307. package/dist/src/daemon/git-evidence.js.map +1 -0
  308. package/dist/src/daemon/projection.js +233 -0
  309. package/dist/src/daemon/projection.js.map +1 -0
  310. package/dist/src/daemon/prune.js +376 -0
  311. package/dist/src/daemon/prune.js.map +1 -0
  312. package/dist/src/mcp/http.js +343 -0
  313. package/dist/src/mcp/http.js.map +1 -0
  314. package/dist/src/mcp/server.js +594 -0
  315. package/dist/src/mcp/server.js.map +1 -0
  316. package/docs/cli-reference.md +5363 -0
  317. package/package.json +43 -4
  318. package/schema/.gitkeep +0 -0
  319. package/schema/LICENSE +117 -0
  320. package/schema/envelope.schema.json +137 -0
  321. package/schema/event.schema.json +1810 -0
  322. package/schema/fixtures/envelope/invalid/action-missing-idempotency-key.json +15 -0
  323. package/schema/fixtures/envelope/invalid/action-unknown-class-format.json +14 -0
  324. package/schema/fixtures/envelope/invalid/confidence-out-of-range.json +11 -0
  325. package/schema/fixtures/envelope/invalid/est-cost-bare-number.json +14 -0
  326. package/schema/fixtures/envelope/invalid/est-cost-noncanonical-string.json +14 -0
  327. package/schema/fixtures/envelope/invalid/malformed-assignee.json +10 -0
  328. package/schema/fixtures/envelope/invalid/malformed-created-by.json +7 -0
  329. package/schema/fixtures/envelope/invalid/malformed-max-latency.json +11 -0
  330. package/schema/fixtures/envelope/invalid/malformed-payload-hash.json +14 -0
  331. package/schema/fixtures/envelope/invalid/max-cost-bare-number.json +11 -0
  332. package/schema/fixtures/envelope/invalid/missing-origin.json +3 -0
  333. package/schema/fixtures/envelope/invalid/negative-est-cost.json +14 -0
  334. package/schema/fixtures/envelope/invalid/unknown-state.json +7 -0
  335. package/schema/fixtures/envelope/invalid/unknown-top-level-field.json +8 -0
  336. package/schema/fixtures/envelope/valid/action-payload-hash.json +17 -0
  337. package/schema/fixtures/envelope/valid/actions-without-budget.json +18 -0
  338. package/schema/fixtures/envelope/valid/canonical.json +25 -0
  339. package/schema/fixtures/envelope/valid/minimal.json +7 -0
  340. package/schema/fixtures/envelope/valid/multi-action-executed.json +30 -0
  341. package/schema/fixtures/envelope/valid/record-write-stage.json +22 -0
  342. package/schema/fixtures/event/invalid/approval-granted-agent-actor.json +15 -0
  343. package/schema/fixtures/event/invalid/approval-granted-empty-batch-delivery-id.json +17 -0
  344. package/schema/fixtures/event/invalid/approval-granted-fifth-reaction.json +16 -0
  345. package/schema/fixtures/event/invalid/approval-granted-missing-actor.json +14 -0
  346. package/schema/fixtures/event/invalid/approval-requested-missing-action-key.json +14 -0
  347. package/schema/fixtures/event/invalid/approval-withdrawn-agent-policy-drift.json +15 -0
  348. package/schema/fixtures/event/invalid/approval-withdrawn-missing-reason.json +15 -0
  349. package/schema/fixtures/event/invalid/approval-withdrawn-system-actor.json +15 -0
  350. package/schema/fixtures/event/invalid/audit-decision-refused-human-actor.json +17 -0
  351. package/schema/fixtures/event/invalid/audit-decision-refused-missing-code.json +16 -0
  352. package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +15 -0
  353. package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +16 -0
  354. package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +15 -0
  355. package/schema/fixtures/event/invalid/bad-actor-prefix.json +15 -0
  356. package/schema/fixtures/event/invalid/est-cost-bare-number.json +17 -0
  357. package/schema/fixtures/event/invalid/execution-completed-fabricated-exit-code.json +16 -0
  358. package/schema/fixtures/event/invalid/execution-completed-provider-ref-empty-id.json +18 -0
  359. package/schema/fixtures/event/invalid/execution-completed-provider-ref-extra-field.json +19 -0
  360. package/schema/fixtures/event/invalid/execution-completed-provider-ref-id-not-string.json +18 -0
  361. package/schema/fixtures/event/invalid/execution-completed-provider-ref-missing-adapter.json +17 -0
  362. package/schema/fixtures/event/invalid/execution-failed-open-reported-by.json +16 -0
  363. package/schema/fixtures/event/invalid/execution-indeterminate-open-reason.json +14 -0
  364. package/schema/fixtures/event/invalid/execution-reconciled-agent-actor.json +17 -0
  365. package/schema/fixtures/event/invalid/execution-started-negative-env-stripped.json +16 -0
  366. package/schema/fixtures/event/invalid/gate-bypassed-missing-opened-seq.json +15 -0
  367. package/schema/fixtures/event/invalid/gate-closed-non-integer-opened-seq.json +13 -0
  368. package/schema/fixtures/event/invalid/gate-opened-agent-actor.json +16 -0
  369. package/schema/fixtures/event/invalid/gate-organ-attested-absolute-path.json +14 -0
  370. package/schema/fixtures/event/invalid/gate-organ-attested-agent-actor.json +14 -0
  371. package/schema/fixtures/event/invalid/gate-organ-attested-missing-organ-path.json +13 -0
  372. package/schema/fixtures/event/invalid/harness-unknown-kind.json +18 -0
  373. package/schema/fixtures/event/invalid/harness-version-multiline.json +16 -0
  374. package/schema/fixtures/event/invalid/log-checkpoint-agent-actor.json +17 -0
  375. package/schema/fixtures/event/invalid/log-checkpoint-missing-signature.json +16 -0
  376. package/schema/fixtures/event/invalid/log-checkpoint-short-signed-hash.json +17 -0
  377. package/schema/fixtures/event/invalid/log-checkpoint-unknown-signature-alg.json +17 -0
  378. package/schema/fixtures/event/invalid/malformed-ts.json +15 -0
  379. package/schema/fixtures/event/invalid/missing-alg.json +14 -0
  380. package/schema/fixtures/event/invalid/missing-hash.json +14 -0
  381. package/schema/fixtures/event/invalid/non-integer-seq.json +15 -0
  382. package/schema/fixtures/event/invalid/payload-pruned-human-actor.json +14 -0
  383. package/schema/fixtures/event/invalid/payload-pruned-missing-hash.json +14 -0
  384. package/schema/fixtures/event/invalid/policy-declined-agent-actor.json +15 -0
  385. package/schema/fixtures/event/invalid/policy-proposed-missing-diff.json +19 -0
  386. package/schema/fixtures/event/invalid/policy-proposed-system-actor.json +26 -0
  387. package/schema/fixtures/event/invalid/short-hash.json +15 -0
  388. package/schema/fixtures/event/invalid/unknown-alg.json +15 -0
  389. package/schema/fixtures/event/invalid/unknown-event-type.json +15 -0
  390. package/schema/fixtures/event/invalid/unknown-top-level-field.json +16 -0
  391. package/schema/fixtures/event/valid/approval-expired.json +15 -0
  392. package/schema/fixtures/event/valid/approval-granted-batch.json +19 -0
  393. package/schema/fixtures/event/valid/approval-granted-reaction.json +16 -0
  394. package/schema/fixtures/event/valid/approval-granted.json +15 -0
  395. package/schema/fixtures/event/valid/approval-rejected.json +15 -0
  396. package/schema/fixtures/event/valid/approval-requested.json +19 -0
  397. package/schema/fixtures/event/valid/approval-revoked.json +15 -0
  398. package/schema/fixtures/event/valid/approval-withdrawn-policy-drift.json +17 -0
  399. package/schema/fixtures/event/valid/approval-withdrawn.json +16 -0
  400. package/schema/fixtures/event/valid/audit-decision-refused.json +20 -0
  401. package/schema/fixtures/event/valid/audit-reviewed-reaction.json +17 -0
  402. package/schema/fixtures/event/valid/audit-reviewed.json +15 -0
  403. package/schema/fixtures/event/valid/audit-sampled.json +14 -0
  404. package/schema/fixtures/event/valid/budget-exceeded.json +21 -0
  405. package/schema/fixtures/event/valid/envelope-drift.json +16 -0
  406. package/schema/fixtures/event/valid/execution-completed-harness-report.json +16 -0
  407. package/schema/fixtures/event/valid/execution-completed-provider-ref.json +18 -0
  408. package/schema/fixtures/event/valid/execution-completed.json +15 -0
  409. package/schema/fixtures/event/valid/execution-failed-harness-report.json +16 -0
  410. package/schema/fixtures/event/valid/execution-failed.json +15 -0
  411. package/schema/fixtures/event/valid/execution-indeterminate.json +15 -0
  412. package/schema/fixtures/event/valid/execution-reconciled.json +17 -0
  413. package/schema/fixtures/event/valid/execution-started-env-stripped.json +17 -0
  414. package/schema/fixtures/event/valid/execution-started.json +14 -0
  415. package/schema/fixtures/event/valid/gate-bypassed-harness-version.json +18 -0
  416. package/schema/fixtures/event/valid/gate-bypassed.json +19 -0
  417. package/schema/fixtures/event/valid/gate-closed.json +14 -0
  418. package/schema/fixtures/event/valid/gate-opened.json +16 -0
  419. package/schema/fixtures/event/valid/gate-organ-attested.json +14 -0
  420. package/schema/fixtures/event/valid/genesis-null-prev.json +14 -0
  421. package/schema/fixtures/event/valid/log-checkpoint.json +17 -0
  422. package/schema/fixtures/event/valid/payload-pruned-orphan.json +13 -0
  423. package/schema/fixtures/event/valid/payload-pruned.json +17 -0
  424. package/schema/fixtures/event/valid/policy-declined.json +16 -0
  425. package/schema/fixtures/event/valid/policy-proposed.json +35 -0
  426. package/schema/fixtures/event/valid/policy-updated.json +14 -0
  427. package/schema/fixtures/event/valid/reconciliation-required.json +18 -0
  428. package/schema/fixtures/event/valid/reconciliation-satisfied.json +17 -0
  429. package/schema/fixtures/event/valid/route-accepted.json +15 -0
  430. package/schema/fixtures/event/valid/route-proposed.json +16 -0
  431. package/schema/fixtures/event/valid/spec-example.json +15 -0
  432. package/schema/fixtures/event/valid/task-registered-harness-version.json +23 -0
  433. package/schema/fixtures/event/valid/task-registered.json +14 -0
  434. package/schema/fixtures/hash/known-answer-pre-121.json +74 -0
  435. package/schema/fixtures/hash/known-answer.json +74 -0
  436. package/schema/fixtures/policy/invalid/bad-approval-ttl.json +7 -0
  437. package/schema/fixtures/policy/invalid/bad-web-port.json +4 -0
  438. package/schema/fixtures/policy/invalid/checkpoint-key-not-base64.json +6 -0
  439. package/schema/fixtures/policy/invalid/class-rule-missing-autonomy.json +9 -0
  440. package/schema/fixtures/policy/invalid/empty-class-key.json +6 -0
  441. package/schema/fixtures/policy/invalid/live-rate-on-human-only.json +7 -0
  442. package/schema/fixtures/policy/invalid/malformed-class-key.json +6 -0
  443. package/schema/fixtures/policy/invalid/missing-version.json +8 -0
  444. package/schema/fixtures/policy/invalid/negative-limit.json +9 -0
  445. package/schema/fixtures/policy/invalid/non-numeric-limit.json +9 -0
  446. package/schema/fixtures/policy/invalid/non-positive-max-pending.json +9 -0
  447. package/schema/fixtures/policy/invalid/on-expiry-grant.json +8 -0
  448. package/schema/fixtures/policy/invalid/payload-retention-bare-number.json +4 -0
  449. package/schema/fixtures/policy/invalid/payload-retention-compound.json +4 -0
  450. package/schema/fixtures/policy/invalid/payload-retention-fractional.json +4 -0
  451. package/schema/fixtures/policy/invalid/payload-retention-zero.json +4 -0
  452. package/schema/fixtures/policy/invalid/protected-paths-escape.json +4 -0
  453. package/schema/fixtures/policy/invalid/protected-paths-glob.json +4 -0
  454. package/schema/fixtures/policy/invalid/retro-rate-on-human-only.json +7 -0
  455. package/schema/fixtures/policy/invalid/retro-rate-on-manual.json +7 -0
  456. package/schema/fixtures/policy/invalid/retro-rate-zero.json +7 -0
  457. package/schema/fixtures/policy/invalid/sample-rate-too-high.json +5 -0
  458. package/schema/fixtures/policy/invalid/sampling-secret-env-empty.json +7 -0
  459. package/schema/fixtures/policy/invalid/sampling-secret-env-not-string.json +6 -0
  460. package/schema/fixtures/policy/invalid/skew-tolerance-compound.json +6 -0
  461. package/schema/fixtures/policy/invalid/unknown-autonomy.json +7 -0
  462. package/schema/fixtures/policy/invalid/unknown-class-rule-key.json +6 -0
  463. package/schema/fixtures/policy/invalid/unknown-top-level-key.json +7 -0
  464. package/schema/fixtures/policy/invalid/vault-passphrase-env-empty.json +6 -0
  465. package/schema/fixtures/policy/invalid/vault-passphrase-literal.json +6 -0
  466. package/schema/fixtures/policy/invalid/version-not-string.json +4 -0
  467. package/schema/fixtures/policy/valid/canonical.json +47 -0
  468. package/schema/fixtures/policy/valid/checkpoint-keys.json +18 -0
  469. package/schema/fixtures/policy/valid/class-approvers-limits.json +25 -0
  470. package/schema/fixtures/policy/valid/class-retro-rate.json +17 -0
  471. package/schema/fixtures/policy/valid/global-budgets.json +19 -0
  472. package/schema/fixtures/policy/valid/human-only.json +9 -0
  473. package/schema/fixtures/policy/valid/minimal.json +6 -0
  474. package/schema/fixtures/policy/valid/protected-paths.json +10 -0
  475. package/schema/fixtures/policy/valid/record-namespace.json +13 -0
  476. package/schema/fixtures/policy/valid/request-volume-limits.json +26 -0
  477. package/schema/fixtures/policy/valid/retention-and-sampling-secret.json +16 -0
  478. package/schema/fixtures/policy/valid/skew-tolerance.json +15 -0
  479. package/schema/fixtures/policy/valid/vault-passphrase-env.json +14 -0
  480. package/schema/fixtures/policy/valid/wildcards.json +15 -0
  481. package/schema/fixtures/policy-md/invalid/alias-bomb.md +15 -0
  482. package/schema/fixtures/policy-md/invalid/no-fence.md +7 -0
  483. package/schema/fixtures/policy-md/invalid/protected-route-not-a-subclass.md +16 -0
  484. package/schema/fixtures/policy-md/invalid/schema-invalid-autonomy.md +16 -0
  485. package/schema/fixtures/policy-md/invalid/schema-invalid-read-proof.md +17 -0
  486. package/schema/fixtures/policy-md/invalid/two-fences.md +19 -0
  487. package/schema/fixtures/policy-md/invalid/unclosed-fence.md +11 -0
  488. package/schema/fixtures/policy-md/invalid/wrong-info-string.md +11 -0
  489. package/schema/fixtures/policy-md/invalid/yaml-syntax-error.md +13 -0
  490. package/schema/fixtures/policy-md/precedence/both/APPROVAL.md +7 -0
  491. package/schema/fixtures/policy-md/precedence/both/APPROVALS.md +7 -0
  492. package/schema/fixtures/policy-md/precedence/fallback-only/APPROVALS.md +7 -0
  493. package/schema/fixtures/policy-md/valid/canonical.md +50 -0
  494. package/schema/fixtures/policy-md/valid/daemon-read-proof.md +18 -0
  495. package/schema/fixtures/policy-md/valid/minimal.md +3 -0
  496. package/schema/fixtures/policy-md/valid/prose-lookalikes.md +54 -0
  497. package/schema/fixtures/policy-md/valid/routed-protected-paths.md +49 -0
  498. package/schema/fixtures/policy-md/valid/with-values.md +79 -0
  499. package/schema/fixtures/sample-record/invalid/bad-date-time.json +4 -0
  500. package/schema/fixtures/sample-record/invalid/missing-required-field.json +3 -0
  501. package/schema/fixtures/sample-record/invalid/unknown-top-level-field.json +5 -0
  502. package/schema/fixtures/sample-record/invalid/wrong-type.json +4 -0
  503. package/schema/fixtures/sample-record/valid/minimal.json +4 -0
  504. package/schema/fixtures/sample-record/valid/with-note.json +5 -0
  505. package/schema/fixtures/values/invalid/class-shaped.json +9 -0
  506. package/schema/fixtures/values/invalid/duplicate-entry.json +4 -0
  507. package/schema/fixtures/values/invalid/non-string-item.json +4 -0
  508. package/schema/fixtures/values/invalid/over-cap.json +26 -0
  509. package/schema/fixtures/values/invalid/unknown-key.json +5 -0
  510. package/schema/fixtures/values/invalid/version-string.json +1 -0
  511. package/schema/fixtures/values/valid/empty-lists.json +7 -0
  512. package/schema/fixtures/values/valid/full.json +20 -0
  513. package/schema/fixtures/values/valid/minimal.json +1 -0
  514. package/schema/fixtures/values-md/invalid/schema-invalid.md +62 -0
  515. package/schema/fixtures/values-md/invalid/two-blocks.md +69 -0
  516. package/schema/fixtures/values-md/invalid/unterminated.md +61 -0
  517. package/schema/fixtures/values-md/invalid/yaml-error.md +63 -0
  518. package/schema/fixtures/values-md/valid/absent.md +50 -0
  519. package/schema/fixtures/values-md/valid/with-values.md +79 -0
  520. package/schema/policy.schema.json +481 -0
  521. package/schema/sample-record.schema.json +26 -0
  522. package/schema/values.schema.json +55 -0
@@ -0,0 +1,201 @@
1
+ /**
2
+ * The retrospective review card, built from the log (SPEC.md §5.2, §9.1, §10.3;
3
+ * APRV-299).
4
+ *
5
+ * The daemon draws `audit.sampled` records and, until this task, the only place
6
+ * to answer one was `approval audit list` / `approval audit review` in a
7
+ * terminal. The supervised bargain is "execute now, a fraction is reviewed
8
+ * after", and a review nobody is shown is a review that does not happen: on
9
+ * 2026-09-07 sixty of them were sitting in `QUEUE.md` while the approver's
10
+ * phone showed nothing.
11
+ *
12
+ * This module turns each open sample into a {@link ReviewCard} the Telegram
13
+ * channel renders. Three properties are the whole of its contract:
14
+ *
15
+ * - **Everything is derived, nothing is self-reported.** The class comes from
16
+ * the `task.registered` declaration the log already holds, the command
17
+ * breakdown is recomputed by the classifier's own tokenizer over the bytes
18
+ * the payload store holds under the hash the `execution.started` recorded,
19
+ * the rate is the one the `audit.sampled` record states, and the claimed
20
+ * summary carries the actor that registered the action. No payload key an
21
+ * authoring party wrote chooses a row (SPEC.md §11.1 invariant 4).
22
+ * - **It reads only verified records** (invariant 1). A log that does not
23
+ * verify produces a refusal and no cards: a card is a statement to a human
24
+ * about what the log says, and one derived from a log the runtime disowns
25
+ * would be worse than no card at all.
26
+ * - **It decides nothing and appends nothing.** No policy is resolved, no
27
+ * reaction is read, and no enforcement path consults anything here
28
+ * (invariant 10). The card is a rendering; the human's answer goes back
29
+ * through the human-only `reviewSample`.
30
+ */
31
+ import { claimed, computed } from "../channels/contract.js";
32
+ import { openSamples } from "../core/audit.js";
33
+ import { indexDeclarations } from "../core/execute.js";
34
+ import { loadPayload, payloadStoreDirFor } from "../core/payload-store.js";
35
+ import { payloadOf, readVerifiedRecords } from "../core/state.js";
36
+ import { commandBreakdown, commandPayloadView } from "../core/wysiwys.js";
37
+ function stringOrNull(value) {
38
+ return typeof value === "string" && value.length > 0 ? value : null;
39
+ }
40
+ /**
41
+ * The actor that declared an action, per key: the `task.registered` record's
42
+ * own, and failing that the `execution.started` that ran it.
43
+ *
44
+ * The author of a card's CLAIMED region, and it is read off who APPENDED a
45
+ * record rather than out of a payload field, for the reason `humanFeedback`
46
+ * gives about the same question: an actor is a property of the writer, and a
47
+ * self-reported one would let the party under oversight choose whose claim the
48
+ * approver is reading (SPEC.md §11.1 invariant 4).
49
+ */
50
+ function authorsByKey(records) {
51
+ const registered = new Map();
52
+ const executed = new Map();
53
+ for (const record of records) {
54
+ const actor = typeof record.actor === "string" && record.actor.length > 0 ? record.actor : null;
55
+ if (actor === null)
56
+ continue;
57
+ if (record.event === "task.registered") {
58
+ const actions = payloadOf(record)["actions"];
59
+ if (!Array.isArray(actions))
60
+ continue;
61
+ for (const entry of actions) {
62
+ if (typeof entry !== "object" || entry === null)
63
+ continue;
64
+ const key = entry["idempotency_key"];
65
+ if (typeof key === "string" && key.length > 0)
66
+ registered.set(key, actor);
67
+ }
68
+ continue;
69
+ }
70
+ if (record.event !== "execution.started")
71
+ continue;
72
+ const key = record.action_key;
73
+ if (typeof key === "string" && key.length > 0 && !executed.has(key))
74
+ executed.set(key, actor);
75
+ }
76
+ for (const [key, actor] of executed) {
77
+ if (!registered.has(key))
78
+ registered.set(key, actor);
79
+ }
80
+ return registered;
81
+ }
82
+ /**
83
+ * The bytes the sampled execution bound to, when anybody holds them.
84
+ *
85
+ * `null` whenever they cannot be produced honestly: no recorded hash, no
86
+ * override and no store, a store with no file, or a stored file that does not
87
+ * hash to what the log recorded. The last case is the one worth naming — a
88
+ * tampered `<hash>.json` yields NO breakdown rather than a breakdown of bytes
89
+ * that are not the ones that ran, because a line describing the wrong command
90
+ * is worse on a review card than an absent line.
91
+ */
92
+ function materialFor(options, logPath, actionKey, boundHash) {
93
+ if (boundHash === null)
94
+ return null;
95
+ const supplied = options.payload?.(actionKey, boundHash);
96
+ if (supplied !== undefined)
97
+ return supplied;
98
+ const storeDir = options.payloadStoreDir === undefined ? payloadStoreDirFor(logPath) : options.payloadStoreDir;
99
+ if (storeDir === null)
100
+ return null;
101
+ const loaded = loadPayload(storeDir, boundHash);
102
+ return loaded.ok ? loaded.value : null;
103
+ }
104
+ /**
105
+ * What the runtime did at the time, in one computed line.
106
+ *
107
+ * The three facts a reviewer needs before they can say whether it should have
108
+ * happened: that NOBODY was asked, which autonomy said so, and the rate the
109
+ * draw was made at — all three read off the `audit.sampled` record the daemon
110
+ * wrote, which states the number the verdict was compared against (APRV-183).
111
+ * The outcome is appended when the log carries one; "not recorded" is the
112
+ * honest answer for an execution whose end nothing wrote, and it is never
113
+ * guessed at from the absence.
114
+ */
115
+ function verdictLine(sample, outcome) {
116
+ const autonomy = stringOrNull(sample["autonomy"]) ?? "supervised";
117
+ const rate = sample["rate"];
118
+ const drawn = typeof rate === "number" ? `, drawn for review at rate ${String(rate)}` : ", drawn for review";
119
+ return `allowed without asking (autonomy ${autonomy})${drawn} · outcome ${outcome}`;
120
+ }
121
+ /** How the log says the sampled execution ended, or that it does not say. */
122
+ function outcomeOf(records, actionKey, startSeq) {
123
+ for (let index = records.length - 1; index >= 0; index -= 1) {
124
+ const record = records[index];
125
+ if (record.seq <= startSeq || record.action_key !== actionKey)
126
+ continue;
127
+ if (record.event === "execution.completed")
128
+ return "completed";
129
+ if (record.event === "execution.failed")
130
+ return "failed";
131
+ }
132
+ return "not recorded";
133
+ }
134
+ /**
135
+ * One card for one open sample, or `null` when the log cannot describe it.
136
+ *
137
+ * A sample naming no action key, or an action key no `task.registered` record
138
+ * declares, produces no card. That is the same refusal to guess `core/audit.ts`
139
+ * makes when it selects candidates: a class nobody declared is a class the card
140
+ * would have to invent, and a review card asserting an invented class is a
141
+ * statement to a human that nothing in the log supports. Such a sample stays in
142
+ * the backlog and is still answerable with `approval audit review`.
143
+ */
144
+ function cardFor(records, subject, authors, declarations, logPath, options) {
145
+ const actionKey = subject.actionKey;
146
+ if (actionKey === null)
147
+ return null;
148
+ const declared = declarations.get(actionKey);
149
+ if (declared === undefined)
150
+ return null;
151
+ const sampleRecord = records.find((record) => record.seq === subject.seq);
152
+ const sample = sampleRecord === undefined ? {} : payloadOf(sampleRecord);
153
+ const start = subject.subjectSeq === null
154
+ ? undefined
155
+ : records.find((record) => record.seq === subject.subjectSeq && record.event === "execution.started");
156
+ const startPayload = start === undefined ? {} : payloadOf(start);
157
+ const ranAt = start?.ts ?? stringOrNull(sample["subject_ts"]) ?? subject.ts;
158
+ const material = materialFor(options, logPath, actionKey, stringOrNull(startPayload["payload_hash"]) ?? declared.payload_hash);
159
+ const command = material === null ? null : commandPayloadView(material);
160
+ const breakdown = command === null ? null : commandBreakdown(command.command);
161
+ const author = authors.get(actionKey) ?? "the requesting party";
162
+ const fields = {
163
+ action_key: computed(actionKey, "log"),
164
+ class: computed(declared.class, "log"),
165
+ task: computed(subject.task ?? declared.task, "log"),
166
+ summary: claimed(declared.summary, author),
167
+ ...(breakdown === null ? {} : { command_breakdown: computed(breakdown, "classifier") }),
168
+ };
169
+ return {
170
+ sampleSeq: subject.seq,
171
+ fields,
172
+ ranAtTs: ranAt,
173
+ ranAt: computed(start === undefined
174
+ ? `${ranAt} (the sample's record of when it started)`
175
+ : `${ranAt} (execution.started at seq ${String(start.seq)})`, "log"),
176
+ verdict: computed(verdictLine(sample, start === undefined ? "not recorded" : outcomeOf(records, actionKey, start.seq)), "log"),
177
+ };
178
+ }
179
+ /**
180
+ * Every `audit.sampled` with no later `audit.reviewed`, as review cards, oldest
181
+ * first.
182
+ *
183
+ * The same projection `approval audit list` and `.approval/QUEUE.md` read
184
+ * (`openSamples`), so the phone, the file and the terminal cannot disagree
185
+ * about what is outstanding. Reads a verified log and writes nothing anywhere.
186
+ */
187
+ export function openReviewCards(logPath, options = {}) {
188
+ const read = readVerifiedRecords(logPath);
189
+ if (!read.ok)
190
+ return { ok: false, code: read.code, message: read.message };
191
+ const index = indexDeclarations(read.records);
192
+ const authors = authorsByKey(read.records);
193
+ const cards = [];
194
+ for (const subject of openSamples(read.records)) {
195
+ const card = cardFor(read.records, subject, authors, index.declarations, logPath, options);
196
+ if (card !== null)
197
+ cards.push(card);
198
+ }
199
+ return { ok: true, cards };
200
+ }
201
+ //# sourceMappingURL=audit-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-card.js","sourceRoot":"","sources":["../../../src/cli/audit-card.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,460 @@
1
+ /**
2
+ * `approval audit` — the retrospective-review verbs (SPEC.md §5.2, §9.1).
3
+ *
4
+ * Two subcommands, and neither of them samples:
5
+ *
6
+ * - `audit list` reads the open sampled-audit backlog and writes nothing;
7
+ * - `audit review <seq|action-key>` appends `audit.reviewed` and is HUMAN-ONLY.
8
+ *
9
+ * There is deliberately no `approval audit sample`. Sampling is the runtime's
10
+ * decision, made by the daemon from an operator-held secret, and a verb that let
11
+ * a caller sample (or decline to sample) an action of its choosing would hand
12
+ * the party under oversight the control that exists to oversee it. The daemon
13
+ * samples on its tick; that is the only path.
14
+ *
15
+ * The CLI holds no logic, as everywhere else here: eligibility, matching, and
16
+ * the human-only rule live in `core/audit.ts`, and this file is argument
17
+ * parsing, output formatting, and the mapping from a core result to an exit code.
18
+ */
19
+ import { isAbsolute, resolve as resolvePathSegments } from "node:path";
20
+ import { openObligations, openSamples, parseSubjectRef, reconciliationObligations, isReaction, REACTIONS, reviewSample, sampledSubjects, satisfyObligation, } from "../core/audit.js";
21
+ import { resolveHumanActor, HUMAN_ACTOR_ENV } from "../core/attest.js";
22
+ import { loadPolicy } from "../core/policy-load.js";
23
+ import { classSampling, resolveSampler } from "../core/sampler.js";
24
+ import { readVerifiedRecords } from "../core/state.js";
25
+ import { boolFlag, parseFlags, stringFlag } from "./args.js";
26
+ import { EXIT_INTEGRITY, EXIT_IO, EXIT_OK, EXIT_TORN_TAIL, EXIT_USAGE, } from "./exit-codes.js";
27
+ import { AUDIT_HELP, AUDIT_LIST_HELP, AUDIT_OBLIGATIONS_HELP, AUDIT_RECONCILE_HELP, AUDIT_REVIEW_HELP, } from "./help.js";
28
+ import { DEFAULT_LOG_PATH, preflightLog, resolvePath } from "./paths.js";
29
+ import { refusal as renderRefusal, style } from "./style.js";
30
+ import { usageErrorText } from "./usage.js";
31
+ const COMMON_FLAGS = {
32
+ "--log": "string",
33
+ "--json": "boolean",
34
+ "--help": "boolean",
35
+ "-h": "boolean",
36
+ };
37
+ function absolute(value, cwd) {
38
+ return isAbsolute(value) ? value : resolvePathSegments(cwd, value);
39
+ }
40
+ function usageError(streams, json, message, helpText) {
41
+ if (json)
42
+ streams.err(`${JSON.stringify({ error: { code: "usage", message } })}\n`);
43
+ else
44
+ streams.err(usageErrorText(message, helpText));
45
+ return EXIT_USAGE;
46
+ }
47
+ function ioError(streams, json, message) {
48
+ if (json)
49
+ streams.err(`${JSON.stringify({ error: { code: "io", message } })}\n`);
50
+ else
51
+ streams.err(`approval: ${message}\n`);
52
+ return EXIT_IO;
53
+ }
54
+ function emitJson(streams, value) {
55
+ streams.out(`${JSON.stringify(value)}\n`);
56
+ }
57
+ /**
58
+ * Map an audit refusal onto the frozen exit table, by the same split every
59
+ * other verb draws: filesystem facts are 4, a crashed write is 3, and every
60
+ * decision the runtime itself made is 1.
61
+ */
62
+ function refusalExitCode(refusal) {
63
+ switch (refusal.code) {
64
+ case "log-unreadable":
65
+ return EXIT_IO;
66
+ case "log-torn-tail":
67
+ return EXIT_TORN_TAIL;
68
+ case "append-failed":
69
+ switch (refusal.append?.code) {
70
+ case "corrupt-tail":
71
+ return EXIT_TORN_TAIL;
72
+ case "io":
73
+ case "lock-timeout":
74
+ return EXIT_IO;
75
+ default:
76
+ return EXIT_INTEGRITY;
77
+ }
78
+ default:
79
+ return EXIT_INTEGRITY;
80
+ }
81
+ }
82
+ function emitRefusal(streams, json, refusal) {
83
+ if (json) {
84
+ const error = { code: refusal.code, message: refusal.message };
85
+ if (refusal.seq !== undefined)
86
+ error["seq"] = refusal.seq;
87
+ streams.err(`${JSON.stringify({ ok: false, error })}\n`);
88
+ }
89
+ else {
90
+ // APRV-102: the one refusal shape, and no `fix:` — an audit refusal names a
91
+ // state of the log or of the backlog, not a command that repairs it.
92
+ streams.err(`${renderRefusal(style({ json }), refusal.code, refusal.message)}\n`);
93
+ }
94
+ return refusalExitCode(refusal);
95
+ }
96
+ function front(argv, spec, helpText, streams, cwd) {
97
+ const json = argv.includes("--json");
98
+ const parsed = parseFlags(argv, spec);
99
+ if (!parsed.ok) {
100
+ return { kind: "handled", code: usageError(streams, json, parsed.message, helpText) };
101
+ }
102
+ if (boolFlag(parsed.flags, "--help") || boolFlag(parsed.flags, "-h")) {
103
+ streams.out(`${helpText}\n`);
104
+ return { kind: "handled", code: EXIT_OK };
105
+ }
106
+ return {
107
+ kind: "run",
108
+ flags: parsed.flags,
109
+ positionals: parsed.positionals,
110
+ json,
111
+ logPath: resolvePath(stringFlag(parsed.flags, "--log"), DEFAULT_LOG_PATH, cwd),
112
+ };
113
+ }
114
+ // ===========================================================================
115
+ // approval audit list
116
+ // ===========================================================================
117
+ /**
118
+ * The open sampled-audit backlog: `audit.sampled` events with no later
119
+ * `audit.reviewed`, oldest first.
120
+ *
121
+ * The same set `.approval/QUEUE.md` renders and the same set the daemon counts,
122
+ * from the same projection, so the file, the daemon's `audit_backlog`, and this
123
+ * verb cannot disagree. Reads a verified log and writes nothing anywhere.
124
+ */
125
+ export function commandAuditList(argv, streams, cwd) {
126
+ const outcome = front(argv, { ...COMMON_FLAGS, "--all": "boolean", "--policy": "string", "--dir": "string" }, AUDIT_LIST_HELP, streams, cwd);
127
+ if (outcome.kind === "handled")
128
+ return outcome.code;
129
+ const { flags, positionals, json, logPath } = outcome;
130
+ const extra = positionals[0];
131
+ if (extra !== undefined) {
132
+ return usageError(streams, json, `unexpected argument ${JSON.stringify(extra)}`, AUDIT_LIST_HELP);
133
+ }
134
+ const check = preflightLog(logPath);
135
+ if (!check.ok)
136
+ return ioError(streams, json, check.message);
137
+ const read = readVerifiedRecords(logPath);
138
+ if (!read.ok) {
139
+ return emitRefusal(streams, json, { ok: false, code: read.code, message: read.message });
140
+ }
141
+ const all = boolFlag(flags, "--all");
142
+ const samples = all ? sampledSubjects(read.records) : openSamples(read.records);
143
+ const rows = samples.map((subject) => ({
144
+ seq: subject.seq,
145
+ ts: subject.ts,
146
+ action_key: subject.actionKey,
147
+ task: subject.task,
148
+ subject_seq: subject.subjectSeq,
149
+ reviewed_seq: subject.reviewedSeq,
150
+ }));
151
+ // The sampler's state is reported beside the backlog it explains: an empty
152
+ // backlog means one thing when sampling is running and quite another when no
153
+ // secret is configured, and an operator reading "empty" deserves to know
154
+ // which. See core/sampler.ts on why a missing secret disables sampling.
155
+ const policyFlag = stringFlag(flags, "--policy");
156
+ const dirFlag = stringFlag(flags, "--dir");
157
+ const samplingLoad = loadPolicy(policyFlag !== null
158
+ ? { file: absolute(policyFlag, cwd) }
159
+ : { dir: dirFlag === null ? cwd : absolute(dirFlag, cwd) });
160
+ const sampler = resolveSampler(samplingLoad);
161
+ // APRV-183. `rate` is the GLOBAL fallback and `classes` is the coverage: with
162
+ // a per-class `retro_rate` in the grammar, one number can no longer describe
163
+ // what is sampled, and a backlog explained by one number would be explained
164
+ // wrongly. Additive to the existing object, so every reader of `enabled`,
165
+ // `rate`, `secret_env` and `reason` keeps reading what it read.
166
+ const classes = classSampling(samplingLoad, sampler).map((entry) => ({
167
+ pattern: entry.pattern,
168
+ autonomy: entry.autonomy,
169
+ rate: entry.rate,
170
+ source: entry.source,
171
+ enabled: entry.enabled,
172
+ reason: entry.reason,
173
+ }));
174
+ const sampling = sampler.enabled
175
+ ? {
176
+ enabled: true,
177
+ rate: sampler.rate,
178
+ secret_env: sampler.secretEnv,
179
+ reason: null,
180
+ classes,
181
+ }
182
+ : {
183
+ enabled: false,
184
+ rate: sampler.rate,
185
+ secret_env: sampler.secretEnv,
186
+ reason: sampler.reason,
187
+ classes,
188
+ };
189
+ if (json) {
190
+ emitJson(streams, {
191
+ ok: true,
192
+ sampling,
193
+ open: rows.filter((row) => row.reviewed_seq === null).length,
194
+ samples: rows,
195
+ });
196
+ }
197
+ else {
198
+ streams.out(sampler.enabled
199
+ ? `sampling: on, rate ${String(sampler.rate)}, secret from ${sampler.secretEnv}\n`
200
+ : `sampling: OFF (${sampler.reason}) — ${sampler.message}\n`);
201
+ for (const entry of classes) {
202
+ streams.out(entry.enabled
203
+ ? ` ${entry.pattern}: rate ${String(entry.rate)} (${entry.source})\n`
204
+ : ` ${entry.pattern}: not sampled (${entry.reason ?? "rate-absent"})\n`);
205
+ }
206
+ if (rows.length === 0) {
207
+ streams.out(all
208
+ ? "no audit.sampled records in this log\n"
209
+ : "sampled-audit backlog: empty (no audit.sampled event is waiting for a review)\n");
210
+ }
211
+ for (const row of rows) {
212
+ streams.out(`${String(row.seq)}\t${row.ts}\t${row.action_key ?? "-"}\t${row.reviewed_seq === null ? "open" : `reviewed at seq ${String(row.reviewed_seq)}`}\n`);
213
+ }
214
+ }
215
+ return EXIT_OK;
216
+ }
217
+ // ===========================================================================
218
+ // approval audit review
219
+ // ===========================================================================
220
+ /**
221
+ * `approval audit review <seq|action-key> [--note "<text>"]`
222
+ *
223
+ * HUMAN-ONLY, enforced in `core/audit.ts` and checked here first so a malformed
224
+ * invocation never reaches the log. `--note` is optional: the event's content is
225
+ * that a person looked, and a note is the reviewer's own record of what they
226
+ * concluded.
227
+ *
228
+ * No attestation is required, for the reason `execution resolve` states: review
229
+ * records an observation, exercises no policy authority, authorizes nothing, and
230
+ * spends no budget. A review blocked because a policy file was edited afterwards
231
+ * would be a supervision backlog held open by an unrelated fact.
232
+ */
233
+ export function commandAuditReview(argv, streams, cwd) {
234
+ const outcome = front(argv, {
235
+ ...COMMON_FLAGS,
236
+ "--note": "string",
237
+ "--deny": "boolean",
238
+ "--reaction": "string",
239
+ "--as": "string",
240
+ "--policy": "string",
241
+ "--dir": "string",
242
+ }, AUDIT_REVIEW_HELP, streams, cwd);
243
+ if (outcome.kind === "handled")
244
+ return outcome.code;
245
+ const { flags, positionals, json, logPath } = outcome;
246
+ const subject = positionals[0];
247
+ if (subject === undefined) {
248
+ return usageError(streams, json, "missing <seq|action-key> argument", AUDIT_REVIEW_HELP);
249
+ }
250
+ const extra = positionals[1];
251
+ if (extra !== undefined) {
252
+ return usageError(streams, json, `unexpected argument ${JSON.stringify(extra)}`, AUDIT_REVIEW_HELP);
253
+ }
254
+ const asFlag = stringFlag(flags, "--as");
255
+ const actor = resolveHumanActor(asFlag === null ? {} : { actor: asFlag });
256
+ if (actor === null) {
257
+ return usageError(streams, json, asFlag === null
258
+ ? `no human identity: set ${HUMAN_ACTOR_ENV}=human:<id> or pass --as human:<id>`
259
+ : `--as expects a human identity matching human:<id>, got ${JSON.stringify(asFlag)}; a review records that a PERSON looked, and an agent: or system: actor cannot perform it`, AUDIT_REVIEW_HELP);
260
+ }
261
+ // Closed vocabulary, refused at exit 2 rather than defaulted or passed
262
+ // through, exactly as `withdraw --reason` is. A misspelling that silently
263
+ // became `indifferent` would put a word in the reviewer's mouth in an
264
+ // append-only log; one that reached the core would be answered as a refusal
265
+ // when what happened is that a person typed "love" for "loved".
266
+ const reactionFlag = stringFlag(flags, "--reaction");
267
+ if (reactionFlag !== null && !isReaction(reactionFlag)) {
268
+ return usageError(streams, json, `--reaction expects one of ${REACTIONS.join(" | ")}, got ${JSON.stringify(reactionFlag)}`, AUDIT_REVIEW_HELP);
269
+ }
270
+ const check = preflightLog(logPath);
271
+ if (!check.ok)
272
+ return ioError(streams, json, check.message);
273
+ const policyFlag = stringFlag(flags, "--policy");
274
+ const dirFlag = stringFlag(flags, "--dir");
275
+ const deny = boolFlag(flags, "--deny");
276
+ const result = reviewSample(logPath, parseSubjectRef(subject), actor, stringFlag(flags, "--note"), {
277
+ policy: policyFlag !== null
278
+ ? { file: absolute(policyFlag, cwd) }
279
+ : { dir: dirFlag === null ? cwd : absolute(dirFlag, cwd) },
280
+ // Explicit rather than defaulted through: the ABSENCE of --deny is "ok",
281
+ // and it must be this file that says so, once, where a reader can see it.
282
+ verdict: deny ? "denied" : "ok",
283
+ // Absent means absent (APRV-239). No default is substituted here or
284
+ // downstream: `indifferent` is a thing a person had to actually say.
285
+ ...(reactionFlag === null ? {} : { reaction: reactionFlag }),
286
+ });
287
+ if (!result.ok)
288
+ return emitRefusal(streams, json, result);
289
+ const obligation = result.obligation;
290
+ if (json) {
291
+ emitJson(streams, {
292
+ ok: true,
293
+ seq: result.record.seq,
294
+ sample_seq: result.subject.seq,
295
+ action_key: result.subject.actionKey,
296
+ task: result.subject.task,
297
+ verdict: deny ? "denied" : "ok",
298
+ // Always present, `null` when the reviewer gave none: a consumer reading
299
+ // this shape must be able to tell "no reaction" from "the key is missing
300
+ // because this build predates the field".
301
+ reaction: reactionFlag,
302
+ obligation_seq: obligation === null ? null : obligation.seq,
303
+ actor,
304
+ });
305
+ }
306
+ else {
307
+ streams.out(`reviewed sample at seq ${String(result.subject.seq)} (action ${result.subject.actionKey ?? "-"}) at seq ${String(result.record.seq)} by ${actor}${deny ? " — DENIED" : ""}${reactionFlag === null ? "" : ` — reaction: ${reactionFlag} (guidance, not policy)`}\n`);
308
+ if (obligation !== null) {
309
+ const shape = String(obligation.payload?.["obligation"] ?? "");
310
+ streams.out(shape === "gated-revert"
311
+ ? `reconciliation obligation at seq ${String(obligation.seq)}: GATED REVERT. The action was declared reversible, so undo it through the gate and close this with \`approval audit reconcile ${String(obligation.seq)} --revert <action-key> --note "…"\`.\n`
312
+ : `reconciliation obligation at seq ${String(obligation.seq)}: POLICY FINDING. Nothing can be reverted, so what is owed is a review of the class that permitted this. Close it with \`approval audit reconcile ${String(obligation.seq)} --note "…"\` once that review has happened.\n`);
313
+ }
314
+ }
315
+ return EXIT_OK;
316
+ }
317
+ // ===========================================================================
318
+ // approval audit obligations
319
+ // ===========================================================================
320
+ /**
321
+ * The open reconciliation backlog: `reconciliation.required` records with no
322
+ * later `reconciliation.satisfied`.
323
+ *
324
+ * Reads a verified log and writes nothing. The same projection `status` and
325
+ * `doctor` read, so the three can never disagree about what is outstanding.
326
+ */
327
+ export function commandAuditObligations(argv, streams, cwd) {
328
+ const outcome = front(argv, { ...COMMON_FLAGS, "--all": "boolean" }, AUDIT_OBLIGATIONS_HELP, streams, cwd);
329
+ if (outcome.kind === "handled")
330
+ return outcome.code;
331
+ const { flags, positionals, json, logPath } = outcome;
332
+ const extra = positionals[0];
333
+ if (extra !== undefined) {
334
+ return usageError(streams, json, `unexpected argument ${JSON.stringify(extra)}`, AUDIT_OBLIGATIONS_HELP);
335
+ }
336
+ const check = preflightLog(logPath);
337
+ if (!check.ok)
338
+ return ioError(streams, json, check.message);
339
+ const read = readVerifiedRecords(logPath);
340
+ if (!read.ok) {
341
+ return emitRefusal(streams, json, { ok: false, code: read.code, message: read.message });
342
+ }
343
+ const all = boolFlag(flags, "--all");
344
+ const items = all ? reconciliationObligations(read.records) : openObligations(read.records);
345
+ const rows = items.map((item) => ({
346
+ seq: item.seq,
347
+ ts: item.ts,
348
+ action_key: item.actionKey,
349
+ task: item.task,
350
+ class: item.class,
351
+ review_seq: item.reviewSeq,
352
+ obligation: item.obligation,
353
+ reversible: item.reversible,
354
+ satisfied_seq: item.satisfiedSeq,
355
+ }));
356
+ if (json) {
357
+ emitJson(streams, {
358
+ ok: true,
359
+ open: rows.filter((row) => row.satisfied_seq === null).length,
360
+ obligations: rows,
361
+ });
362
+ return EXIT_OK;
363
+ }
364
+ if (rows.length === 0) {
365
+ streams.out(all
366
+ ? "no reconciliation.required records in this log\n"
367
+ : "reconciliation backlog: empty (no retrospective denial is waiting to be reconciled)\n");
368
+ return EXIT_OK;
369
+ }
370
+ for (const row of rows) {
371
+ streams.out(`${String(row.seq)}\t${row.ts}\t${row.action_key}\t${row.class}\t${row.obligation}\t${row.satisfied_seq === null ? "OPEN" : `satisfied at seq ${String(row.satisfied_seq)}`}\n`);
372
+ }
373
+ return EXIT_OK;
374
+ }
375
+ // ===========================================================================
376
+ // approval audit reconcile
377
+ // ===========================================================================
378
+ /**
379
+ * `approval audit reconcile <obligation-seq> --note "<text>" [--revert <key>]`
380
+ *
381
+ * HUMAN-ONLY, enforced in `core/audit.ts` and again by the event schema, and
382
+ * checked here first so a malformed invocation never reaches the log.
383
+ */
384
+ export function commandAuditReconcile(argv, streams, cwd) {
385
+ const outcome = front(argv, { ...COMMON_FLAGS, "--note": "string", "--revert": "string", "--as": "string" }, AUDIT_RECONCILE_HELP, streams, cwd);
386
+ if (outcome.kind === "handled")
387
+ return outcome.code;
388
+ const { flags, positionals, json, logPath } = outcome;
389
+ const subject = positionals[0];
390
+ if (subject === undefined) {
391
+ return usageError(streams, json, "missing <obligation-seq> argument", AUDIT_RECONCILE_HELP);
392
+ }
393
+ const extra = positionals[1];
394
+ if (extra !== undefined) {
395
+ return usageError(streams, json, `unexpected argument ${JSON.stringify(extra)}`, AUDIT_RECONCILE_HELP);
396
+ }
397
+ if (!/^[1-9][0-9]*$/u.test(subject)) {
398
+ return usageError(streams, json, `<obligation-seq> is the SEQ of the reconciliation.required record, a positive integer, got ${JSON.stringify(subject)}; run \`approval audit obligations\` for the open ones`, AUDIT_RECONCILE_HELP);
399
+ }
400
+ const asFlag = stringFlag(flags, "--as");
401
+ const actor = resolveHumanActor(asFlag === null ? {} : { actor: asFlag });
402
+ if (actor === null) {
403
+ return usageError(streams, json, asFlag === null
404
+ ? `no human identity: set ${HUMAN_ACTOR_ENV}=human:<id> or pass --as human:<id>`
405
+ : `--as expects a human identity matching human:<id>, got ${JSON.stringify(asFlag)}; satisfying an obligation records that a PERSON discharged it, and an agent: or system: actor cannot perform it`, AUDIT_RECONCILE_HELP);
406
+ }
407
+ const note = stringFlag(flags, "--note");
408
+ if (note === null || note.trim().length === 0) {
409
+ return usageError(streams, json, `--note is required: this record asserts that an obligation was discharged, and a discharge nobody described is one no auditor can check`, AUDIT_RECONCILE_HELP);
410
+ }
411
+ const check = preflightLog(logPath);
412
+ if (!check.ok)
413
+ return ioError(streams, json, check.message);
414
+ const revert = stringFlag(flags, "--revert");
415
+ const result = satisfyObligation(logPath, Number(subject), actor, {
416
+ note,
417
+ ...(revert === null ? {} : { revertActionKey: revert }),
418
+ });
419
+ if (!result.ok)
420
+ return emitRefusal(streams, json, result);
421
+ if (json) {
422
+ emitJson(streams, {
423
+ ok: true,
424
+ seq: result.record.seq,
425
+ obligation_seq: result.obligation.seq,
426
+ action_key: result.obligation.actionKey,
427
+ task: result.obligation.task,
428
+ class: result.obligation.class,
429
+ obligation: result.obligation.obligation,
430
+ actor,
431
+ });
432
+ }
433
+ else {
434
+ streams.out(`reconciled the obligation at seq ${String(result.obligation.seq)} (${result.obligation.obligation} for ${result.obligation.actionKey}) at seq ${String(result.record.seq)} by ${actor}\n`);
435
+ }
436
+ return EXIT_OK;
437
+ }
438
+ /** `approval audit <subcommand>` — `list`, `review`, `obligations`, `reconcile`. */
439
+ export function commandAudit(argv, streams, cwd) {
440
+ const sub = argv[0];
441
+ const rest = argv.slice(1);
442
+ const json = argv.includes("--json");
443
+ if (sub === undefined) {
444
+ return usageError(streams, json, "missing subcommand for `approval audit`", AUDIT_HELP);
445
+ }
446
+ if (sub === "--help" || sub === "-h" || sub === "help") {
447
+ streams.out(`${AUDIT_HELP}\n`);
448
+ return EXIT_OK;
449
+ }
450
+ if (sub === "list")
451
+ return commandAuditList(rest, streams, cwd);
452
+ if (sub === "review")
453
+ return commandAuditReview(rest, streams, cwd);
454
+ if (sub === "obligations")
455
+ return commandAuditObligations(rest, streams, cwd);
456
+ if (sub === "reconcile")
457
+ return commandAuditReconcile(rest, streams, cwd);
458
+ return usageError(streams, json, `unknown subcommand ${JSON.stringify(sub)} for \`approval audit\``, AUDIT_HELP);
459
+ }
460
+ //# sourceMappingURL=audit.js.map