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,903 @@
1
+ /**
2
+ * The local web queue channel (SPEC.md §5.1 `channels.web.port`, §9, §10.3,
3
+ * §10.4, §11 — APRV-25).
4
+ *
5
+ * The third reference channel, and the only *pull* one. `cli` asks a question
6
+ * and waits for an answer; `telegram` pushes a message at a human who is
7
+ * elsewhere. This one serves a page: the queue sits at
8
+ * `http://127.0.0.1:4680/` and a human looks at it when they choose to. That
9
+ * difference is the design, not an accident of the transport — see "notify is a
10
+ * no-op" below.
11
+ *
12
+ * Everything the contract says about a channel still holds:
13
+ *
14
+ * - **It decides nothing.** A submitted form becomes a {@link ChannelDecision}
15
+ * handed to the handler the runtime registered, and that handler calls
16
+ * `recordChannelDecision` / `recordBatchDecisions`, which call the human-only
17
+ * `decide()` in `core/gate.ts`. TTL lapse, budget re-check, attestation,
18
+ * idempotency and compare-and-append apply to a button on a web page exactly
19
+ * as they apply to `approval grant`.
20
+ * - **It writes no log line and holds no decision state.** The only memory here
21
+ * is the map from action key to delivery id, which is delivery bookkeeping.
22
+ * It dies with the process; the approvals live in the log.
23
+ * - **It renders the computed/claimed split** (§9) and the full payload for
24
+ * manual actions (§10.4), and reports what it rendered through
25
+ * {@link WebChannel.lastRendered} so `channels/conformance.ts` can check it.
26
+ *
27
+ * ## 127.0.0.1 ONLY, and there is deliberately no --host
28
+ *
29
+ * {@link WebChannel.start} calls `listen(port, "127.0.0.1")` with the host
30
+ * **hard-coded**. There is no option, no flag and no environment variable that
31
+ * widens it, and adding one would be a SPEC.md amendment rather than a feature:
32
+ * this server has **no authentication at all** (see below), so the loopback
33
+ * interface is the entire access-control mechanism. A `--host 0.0.0.0` would
34
+ * turn "anyone with local access can approve" into "anyone on the coffee shop
35
+ * wifi can approve", silently, from a flag that reads like a convenience. The
36
+ * bound address is asserted by `tests/channels-web.test.ts`.
37
+ *
38
+ * ## No auth in v0.1 — the trust boundary, stated twice
39
+ *
40
+ * SPEC.md §11: *"Human identity in v0.1 is config-declared (an environment
41
+ * variable or flag); the trust boundary is the local machine, and anyone who
42
+ * can set that configuration and write to the log is inside it."*
43
+ *
44
+ * This channel authenticates nobody. Every decision it collects is recorded
45
+ * against the human actor the **runtime** was started with (`--as` /
46
+ * `APPROVAL_HUMAN`), so the guarantee is "someone with access to this machine
47
+ * approved", never "that specific person approved". Because the page *is* the
48
+ * decision surface, that caveat is printed on the page itself
49
+ * ({@link TRUST_BANNER}) as well as in `--help` and here: a human reading a
50
+ * queue in a browser tab should not have to have read the source to know what
51
+ * their click proves.
52
+ *
53
+ * ## CSRF — inside the stated boundary, with cheap hardening. FLAGGED.
54
+ *
55
+ * There is no CSRF token in v0.1, and the reasoning is worth writing down
56
+ * because it is a deliberate omission rather than an oversight. A CSRF token
57
+ * defends a *session* — it stops a page on another origin from acting as an
58
+ * authenticated user. Here there is no session and no authentication: anything
59
+ * that can open a socket to 127.0.0.1:4680 can already POST directly, token or
60
+ * no token, because the trust boundary is the whole local machine. A token
61
+ * would defend against exactly one attacker: a web page, in a browser on this
62
+ * machine, that the human visited, that guessed the port. That attacker is
63
+ * inside the §11 boundary as written — and is also the most realistic one, so:
64
+ *
65
+ * - A same-origin **soft check** rejects (403) any POST whose `Origin` (or
66
+ * failing that, `Referer`) names something other than this server's own
67
+ * loopback origins. It is best effort: a request with neither header is
68
+ * allowed, because `curl`, `fetch` in a test, and a form POST from an older
69
+ * browser all send neither, and refusing them would break the documented
70
+ * scripting path to buy nothing (an attacker can omit a header too).
71
+ * - **Flagged for human review**: if v0.2 puts this server behind anything
72
+ * resembling a session, or widens the bind address, this stance must be
73
+ * revisited and a real anti-CSRF token added. The soft check is a speed bump,
74
+ * not a control.
75
+ *
76
+ * ## The token IS shown on the page — FLAGGED, and it differs from telegram
77
+ *
78
+ * `channels/telegram.ts` refuses to put an execution token in the chat, and
79
+ * prints it on the listener's stdout instead: a chat transcript lives on
80
+ * someone else's servers and is readable by anyone later added to the chat.
81
+ * This channel does the opposite and shows the raw token **once**, in the
82
+ * response page for the grant that minted it. The asymmetry is the point:
83
+ *
84
+ * - the page is served over loopback to the human who is deciding, right now;
85
+ * - the response is generated per request and persisted nowhere — no file, no
86
+ * log line, no `lastRendered()` entry (the notice is rendered and dropped);
87
+ * - the alternative is worse. On telegram the approver and the terminal are
88
+ * often the same person one room apart; here the browser *is* the surface the
89
+ * human is looking at, and sending them to hunt for a token on a daemon's
90
+ * stdout would push them toward copying tokens out of log files.
91
+ *
92
+ * The token is never written to the log (which holds only its SHA-256), never
93
+ * put in a URL (a query string lands in history), and never repeated: reload
94
+ * the response and it is gone. The channel itself does not even hold it — the
95
+ * runtime returns the notice text through {@link WebChannelOptions.decisionNotice}
96
+ * at render time, so a channel that logged its own outcomes would log nothing
97
+ * sensitive. **Flagged for human review** all the same: it is the one place in
98
+ * this codebase where a credential is rendered into a document a browser
99
+ * handles, and browsers cache, autofill and sync more than one expects.
100
+ *
101
+ * ## notify() is a no-op — pull, not push
102
+ *
103
+ * The queue *is* the notification surface. `notify()` records the request as
104
+ * deliverable (so a decision can be routed back to it), returns a synthetic
105
+ * delivery id, and sends nothing anywhere: there is nowhere to send it, since a
106
+ * web page is fetched rather than delivered. Everything the human sees is
107
+ * produced by `GET /`, which re-renders from the runtime's live queue
108
+ * ({@link WebChannelOptions.refresh}) whenever one is supplied. This is why
109
+ * `lastRendered()` reflects the most recent *render* — a GET when there has
110
+ * been one, the last `notify()` otherwise — and why both go through the same
111
+ * {@link renderWebRequest}: the conformance suite must be inspecting the thing a
112
+ * browser was actually served.
113
+ *
114
+ * ## Zero JavaScript required
115
+ *
116
+ * Every flow — grant, reject with a note, select-and-batch — is plain
117
+ * `<form method="post" enctype="application/x-www-form-urlencoded">`. The batch
118
+ * checkboxes live outside the batch form and are attached to it with the HTML5
119
+ * `form=` attribute (nested forms are illegal), so one page can carry per-request
120
+ * forms *and* a batch gesture with no script at all. A four-line inline script
121
+ * offers "select all" as a convenience; disable scripting and every flow still
122
+ * works, which is checked by the tests driving the server with `fetch` alone.
123
+ */
124
+ import { createServer } from "node:http";
125
+ import { isPromptRow, WEB_PROMPT_LAYOUT } from "../core/prompt-layout.js";
126
+ import { assembleBatch } from "./batch.js";
127
+ import { payloadRegionText } from "./payload-view.js";
128
+ // ---------------------------------------------------------------------------
129
+ // Constants
130
+ // ---------------------------------------------------------------------------
131
+ /** SPEC.md §5.1 `channels.web.port`. Used when policy and flags say nothing. */
132
+ export const WEB_DEFAULT_PORT = 4680;
133
+ /**
134
+ * The only address this server ever binds. Hard-coded; see the module header.
135
+ *
136
+ * Exported so the tests can assert the bound address against a constant rather
137
+ * than a literal they could drift from — not so a caller can substitute one.
138
+ */
139
+ export const WEB_LOOPBACK_HOST = "127.0.0.1";
140
+ /** The largest form body accepted. A queue page's POST is a few hundred bytes. */
141
+ export const WEB_MAX_BODY_BYTES = 256 * 1024;
142
+ /** The §11 caveat, rendered at the top of every page. */
143
+ export const TRUST_BANNER = "TRUST BOUNDARY — this page has NO AUTHENTICATION. It is served on 127.0.0.1 only, " +
144
+ "and anyone with access to this machine can decide here as the configured actor. " +
145
+ "A decision recorded from this page proves that someone with local control answered, " +
146
+ "not who. (SPEC.md §11: identity in v0.1 is config-declared.)";
147
+ /** The full-payload delimiters (SPEC.md §10.4). Pinned by tests. */
148
+ export const PAYLOAD_BEGIN = "--- BEGIN FULL PAYLOAD";
149
+ export const PAYLOAD_END = "--- END FULL PAYLOAD ---";
150
+ /** Section headings. The computed/claimed split a reader must not have to infer. */
151
+ export const COMPUTED_HEADING = "COMPUTED — derived by the runtime from the verified log, the attested policy, the budget evaluator, the payload bytes and the clock";
152
+ export const CLAIMED_HEADING_PREFIX = "CLAIMED — authored by";
153
+ export const CLAIMED_HEADING_SUFFIX = "NOT verified by the runtime";
154
+ /** Badge markers, mirroring `channels/cli.ts` so the two channels read alike. */
155
+ export const COMPUTED_MARKER = "[computed]";
156
+ export const CLAIMED_MARKER = "[claimed]";
157
+ // ---------------------------------------------------------------------------
158
+ // Escaping
159
+ // ---------------------------------------------------------------------------
160
+ /**
161
+ * Escape every character that could change the structure of the document.
162
+ *
163
+ * A superset of `channels/telegram.ts`'s three: this channel interpolates into
164
+ * HTML **attribute** positions (checkbox values carry action keys, form ids
165
+ * carry them too), where a bare quote is as dangerous as a bare `<`. Applied
166
+ * uniformly to every interpolated value without exception — the claimed fields
167
+ * and the payload are authored by the party under oversight, and they are the
168
+ * entire injection surface of this page.
169
+ */
170
+ export function escapeHtml(text) {
171
+ return text
172
+ .replace(/&/gu, "&amp;")
173
+ .replace(/</gu, "&lt;")
174
+ .replace(/>/gu, "&gt;")
175
+ .replace(/"/gu, "&quot;")
176
+ .replace(/'/gu, "&#39;");
177
+ }
178
+ /** A DOM id that cannot escape its attribute, derived from an action key. */
179
+ function slug(actionKey, index) {
180
+ return `r${index}-${actionKey.replace(/[^A-Za-z0-9_-]/gu, "-")}`;
181
+ }
182
+ // ---------------------------------------------------------------------------
183
+ // Value formatting (mirrors channels/cli.ts, deliberately)
184
+ // ---------------------------------------------------------------------------
185
+ function formatDuration(ms) {
186
+ if (ms <= 0)
187
+ return "EXPIRED";
188
+ const seconds = Math.max(0, Math.round(ms / 1000));
189
+ const minutes = Math.floor(seconds / 60);
190
+ const hours = Math.floor(minutes / 60);
191
+ if (hours > 0)
192
+ return `${hours}h ${minutes % 60}m left`;
193
+ if (minutes > 0)
194
+ return `${minutes}m ${seconds % 60}s left`;
195
+ return `${seconds}s left`;
196
+ }
197
+ function formatBudgets(verdicts) {
198
+ if (verdicts.length === 0)
199
+ return "none configured";
200
+ return verdicts
201
+ .map((verdict) => `${verdict.limit} ${verdict.pass ? "pass" : "FAIL"} (consumed ${verdict.consumed}, requested ${verdict.requested}, remaining ${verdict.remaining}, ${verdict.window})`)
202
+ .join("; ");
203
+ }
204
+ /**
205
+ * One field's value as display text. Deliberately total: an unrecognized shape
206
+ * is JSON-stringified rather than dropped, because a field the runtime tagged
207
+ * and the channel silently omitted is a field the approver did not see.
208
+ */
209
+ export function formatValue(field, value) {
210
+ if (value === null || value === undefined)
211
+ return "(none)";
212
+ if (field === "ttl_remaining_ms" && typeof value === "number")
213
+ return formatDuration(value);
214
+ if (field === "budgets" && Array.isArray(value))
215
+ return formatBudgets(value);
216
+ if (field === "attestation" && typeof value === "object") {
217
+ const status = value.status;
218
+ const seq = value.seq;
219
+ if (status === "attested")
220
+ return `attested (policy.updated seq ${String(seq)})`;
221
+ if (status === "not-attested") {
222
+ return "NOT ATTESTED — no human has signed off on this policy file";
223
+ }
224
+ if (status === "hash-mismatch") {
225
+ return `HASH MISMATCH — the policy file changed since attestation seq ${String(seq)}`;
226
+ }
227
+ return `UNREADABLE — ${String(value.message)}`;
228
+ }
229
+ if (field === "chain" && typeof value === "object") {
230
+ const chain = value;
231
+ return `seq ${String(chain.seq)} hash ${String(chain.hash)} (log head seq ${String(chain.head_seq)})`;
232
+ }
233
+ if (field === "est_cost_usd" && typeof value === "number")
234
+ return `$${value.toFixed(2)}`;
235
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
236
+ return String(value);
237
+ }
238
+ return JSON.stringify(value) ?? String(value);
239
+ }
240
+ /** The parenthetical: which derivation, or which author. */
241
+ function attribution(field) {
242
+ return field.kind === "computed" ? field.source : field.author;
243
+ }
244
+ /**
245
+ * Presentation order, and which rows render (APRV-218).
246
+ *
247
+ * The default is {@link WEB_PROMPT_LAYOUT}: computed identity and authority
248
+ * first, claimed persuasion last, every row on, because a page scrolls.
249
+ * `channels.web.prompt` in APPROVAL.md replaces it.
250
+ *
251
+ * Members absent from a request are skipped; members the layout does not NAME
252
+ * are appended, so a widened {@link ChannelRequest} cannot silently lose a
253
+ * field to a policy written before that field existed.
254
+ */
255
+ function orderedFields(request, layout) {
256
+ const members = Object.keys(request);
257
+ const ordered = layout.order.filter((name) => layout.visibility[name] !== "off" && members.includes(name));
258
+ for (const name of members) {
259
+ if (!ordered.includes(name) && !isPromptRow(name))
260
+ ordered.push(name);
261
+ }
262
+ return ordered;
263
+ }
264
+ /** Build the split. Pure: no I/O, no clock, no HTML. */
265
+ export function renderWebRequest(request, layout = WEB_PROMPT_LAYOUT) {
266
+ const members = request;
267
+ const computedLines = [];
268
+ const claimedLines = [];
269
+ for (const name of orderedFields(request, layout)) {
270
+ const field = members[name];
271
+ if (field === undefined)
272
+ continue;
273
+ // `fullPayload` has a region of its own (§10.4), rendered verbatim inside
274
+ // delimiters below. Emitting it as a field line too would print the payload
275
+ // twice — once as bytes and once as a JSON blob squeezed into a summary
276
+ // line — and the squeezed copy is where an agent's markup would end up
277
+ // sitting in the computed block, which is precisely the authority §9
278
+ // forbids lending it.
279
+ if (name === "fullPayload")
280
+ continue;
281
+ const line = {
282
+ field: name,
283
+ kind: field.kind,
284
+ text: formatValue(name, field.value),
285
+ origin: attribution(field),
286
+ };
287
+ if (field.kind === "computed")
288
+ computedLines.push(line);
289
+ else
290
+ claimedLines.push(line);
291
+ }
292
+ const rendering = request.fullPayload.value;
293
+ const payloadText = rendering === null
294
+ ? null
295
+ : [
296
+ `${PAYLOAD_BEGIN} (bound sha256 ${rendering.hash}) ---`,
297
+ payloadRegionText(rendering, request.class.value),
298
+ PAYLOAD_END,
299
+ ...(rendering.truncated
300
+ ? ["(TRUNCATED — this is not the whole payload; do not grant on it)"]
301
+ : []),
302
+ ].join("\n");
303
+ return {
304
+ actionKey: request.action_key.value,
305
+ computed: computedLines,
306
+ claimed: claimedLines,
307
+ author: request.summary.kind === "claimed" ? request.summary.author : "the requesting party",
308
+ payloadText,
309
+ };
310
+ }
311
+ /** The {@link RenderedRequest} report for the conformance suite. */
312
+ function reportOf(rendering, batchDeliveryId) {
313
+ const fields = [...rendering.computed, ...rendering.claimed].map((line) => ({
314
+ field: line.field,
315
+ kind: line.kind,
316
+ text: line.text,
317
+ }));
318
+ return {
319
+ action_key: rendering.actionKey,
320
+ fields,
321
+ fullPayloadText: rendering.payloadText,
322
+ ...(batchDeliveryId === undefined ? {} : { batchDeliveryId }),
323
+ };
324
+ }
325
+ // ---------------------------------------------------------------------------
326
+ // The page
327
+ // ---------------------------------------------------------------------------
328
+ /**
329
+ * The stylesheet, inline — a queue page that needed a second request to make
330
+ * the computed/claimed split visible would show an unstyled, indistinguishable
331
+ * page for the duration of that request.
332
+ *
333
+ * Colour is never the only carrier: every claimed line also has the textual
334
+ * `[claimed]` marker and sits under its own heading, so the split survives a
335
+ * monochrome screen, a screen reader and a "reader mode".
336
+ */
337
+ const STYLE = `
338
+ :root { color-scheme: light dark; }
339
+ body { font: 15px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; margin: 0; padding: 1.5rem; }
340
+ h1 { font-size: 1.1rem; margin: 0 0 .5rem; }
341
+ h2 { font-size: 1rem; margin: 0 0 .25rem; }
342
+ h3 { font-size: .85rem; margin: 1rem 0 .25rem; letter-spacing: .02em; }
343
+ .banner { border: 2px solid #b45309; background: #fffbeb; color: #7c2d12; padding: .75rem; margin-bottom: 1rem; }
344
+ .error { border: 2px solid #b91c1c; background: #fef2f2; color: #7f1d1d; padding: .75rem; margin-bottom: 1rem; }
345
+ .notice { border: 2px dashed #15803d; background: #f0fdf4; color: #14532d; padding: .75rem; margin: .75rem 0; }
346
+ article { border: 1px solid #9ca3af; padding: 1rem; margin-bottom: 1.5rem; }
347
+ .computed { border-left: 4px solid #1d4ed8; padding-left: .75rem; }
348
+ .claimed { border-left: 4px dashed #b45309; padding-left: .75rem; background: #fff7ed; }
349
+ .line { display: block; white-space: pre-wrap; }
350
+ .marker { font-weight: 700; }
351
+ .name { display: inline-block; min-width: 11rem; }
352
+ .origin { opacity: .75; }
353
+ pre.payload { white-space: pre-wrap; word-break: break-all; border: 1px solid #374151; background: #f9fafb; color: #111827; padding: .75rem; }
354
+ form { display: inline-block; margin-right: 1rem; }
355
+ input[type=text] { font: inherit; min-width: 18rem; }
356
+ button { font: inherit; padding: .25rem .75rem; }
357
+ code { word-break: break-all; }
358
+ @media (prefers-color-scheme: dark) {
359
+ body { background: #0b0f14; color: #e5e7eb; }
360
+ .banner { background: #3f2d10; color: #fde68a; }
361
+ .error { background: #3f1214; color: #fecaca; }
362
+ .notice { background: #052e16; color: #bbf7d0; }
363
+ .claimed { background: #221708; }
364
+ pre.payload { background: #111827; color: #e5e7eb; border-color: #6b7280; }
365
+ }
366
+ `;
367
+ /** The optional convenience script. Every flow works without it — see the header. */
368
+ const SELECT_ALL_SCRIPT = `
369
+ document.querySelectorAll('[data-select-all]').forEach(function (box) {
370
+ box.addEventListener('change', function () {
371
+ document.querySelectorAll('input[name="select"]').forEach(function (item) {
372
+ item.checked = box.checked;
373
+ });
374
+ });
375
+ });
376
+ `;
377
+ function lineHtml(line) {
378
+ const marker = line.kind === "computed" ? COMPUTED_MARKER : CLAIMED_MARKER;
379
+ return (`<span class="line"><span class="marker">${escapeHtml(marker)}</span> ` +
380
+ `<span class="name">${escapeHtml(line.field)}</span> ` +
381
+ `${escapeHtml(line.text)} <span class="origin">(${escapeHtml(line.origin)})</span></span>`);
382
+ }
383
+ function requestHtml(rendering, index) {
384
+ const key = rendering.actionKey;
385
+ const id = slug(key, index);
386
+ const parts = [
387
+ `<article id="${escapeHtml(id)}">`,
388
+ `<h2><code>${escapeHtml(key)}</code></h2>`,
389
+ `<label><input type="checkbox" name="select" value="${escapeHtml(key)}" form="batch-form"> include in batch gesture</label>`,
390
+ `<h3>${escapeHtml(COMPUTED_HEADING)}</h3>`,
391
+ `<div class="computed">${rendering.computed.map(lineHtml).join("")}</div>`,
392
+ `<h3>${escapeHtml(`${CLAIMED_HEADING_PREFIX} ${rendering.author} — ${CLAIMED_HEADING_SUFFIX}`)}</h3>`,
393
+ `<div class="claimed">${rendering.claimed.map(lineHtml).join("")}</div>`,
394
+ ];
395
+ if (rendering.payloadText !== null) {
396
+ parts.push("<h3>FULL PAYLOAD — the exact bytes this approval binds to (SPEC.md §10.4)</h3>", `<pre class="payload">${escapeHtml(rendering.payloadText)}</pre>`);
397
+ }
398
+ parts.push("<h3>DECIDE</h3>", `<form method="post" action="/decide">`, `<input type="hidden" name="action_key" value="${escapeHtml(key)}">`, `<input type="hidden" name="decision" value="grant">`, `<label>note (optional) <input type="text" name="note"></label> `, `<button type="submit">Grant</button>`, "</form>", `<form method="post" action="/decide">`, `<input type="hidden" name="action_key" value="${escapeHtml(key)}">`, `<input type="hidden" name="decision" value="reject">`, `<label>note (REQUIRED — say why) <input type="text" name="note" required></label> `, `<button type="submit">Reject</button>`, "</form>", "</article>");
399
+ return parts.join("\n");
400
+ }
401
+ function page(title, body) {
402
+ return [
403
+ "<!doctype html>",
404
+ '<html lang="en"><head><meta charset="utf-8">',
405
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
406
+ `<title>${escapeHtml(title)}</title>`,
407
+ `<style>${STYLE}</style>`,
408
+ "</head><body>",
409
+ `<h1>${escapeHtml(title)}</h1>`,
410
+ `<div class="banner">${escapeHtml(TRUST_BANNER)}</div>`,
411
+ body,
412
+ `<script>${SELECT_ALL_SCRIPT}</script>`,
413
+ "</body></html>",
414
+ ].join("\n");
415
+ }
416
+ /** What the page says about the identity decisions are recorded against. */
417
+ function actorLine(actorLabel) {
418
+ if (actorLabel === null)
419
+ return "";
420
+ return `<p>Decisions recorded here are attributed to <code>${escapeHtml(actorLabel)}</code> — the actor this runtime was configured with, not the person at this browser.</p>`;
421
+ }
422
+ // ---------------------------------------------------------------------------
423
+ // The channel
424
+ // ---------------------------------------------------------------------------
425
+ export class WebChannel {
426
+ name;
427
+ configuredPort;
428
+ refresh;
429
+ notice;
430
+ actorLabel;
431
+ complain;
432
+ handler = null;
433
+ batchHandler = null;
434
+ /** Everything ever delivered, by action key. Delivery bookkeeping only. */
435
+ deliveries = new Map();
436
+ /** What the last render showed, in order. */
437
+ pending = [];
438
+ rendered = [];
439
+ httpServer = null;
440
+ counter = 0;
441
+ counters = { notified: 0, views: 0, decisions: 0, refused: 0 };
442
+ /** The policy's row layout for this channel (APRV-218). Pure input to the rendering. */
443
+ layout;
444
+ constructor(options = {}) {
445
+ this.layout = options.layout ?? WEB_PROMPT_LAYOUT;
446
+ this.name = options.name ?? "web";
447
+ this.configuredPort = options.port ?? WEB_DEFAULT_PORT;
448
+ this.refresh = options.refresh ?? null;
449
+ this.notice = options.decisionNotice ?? null;
450
+ this.actorLabel = options.actorLabel ?? null;
451
+ this.complain =
452
+ options.log ??
453
+ ((message) => {
454
+ process.stderr.write(`${message}\n`);
455
+ });
456
+ }
457
+ // -------------------------------------------------------------------------
458
+ // Channel
459
+ // -------------------------------------------------------------------------
460
+ /**
461
+ * Register a request (or a batch) as deliverable and return its delivery id.
462
+ *
463
+ * Sends nothing: the queue page is pulled, not pushed (see the module
464
+ * header). The rendering split is computed here anyway, so `lastRendered()`
465
+ * is meaningful before anyone has opened a browser — and it is computed by
466
+ * the same {@link renderWebRequest} that produces the served HTML.
467
+ */
468
+ notify(target) {
469
+ const isBatch = "requests" in target;
470
+ const members = isBatch ? target.requests : [target];
471
+ this.counter += 1;
472
+ const deliveryId = isBatch ? `web-batch-${this.counter}` : `web-${this.counter}`;
473
+ for (const member of members) {
474
+ this.deliveries.set(member.action_key.value, {
475
+ deliveryId,
476
+ ...(isBatch ? { batchDeliveryId: deliveryId } : {}),
477
+ });
478
+ this.counters.notified += 1;
479
+ }
480
+ this.pending = members;
481
+ this.rendered = members.map((member) => reportOf(renderWebRequest(member, this.layout), isBatch ? deliveryId : undefined));
482
+ return deliveryId;
483
+ }
484
+ onDecision(handler) {
485
+ this.handler = handler;
486
+ }
487
+ /** Register the runtime's batch recorder. Optional; see {@link WebBatchHandler}. */
488
+ onBatchDecision(handler) {
489
+ this.batchHandler = handler;
490
+ }
491
+ health() {
492
+ const address = this.address();
493
+ if (address === null) {
494
+ return { ok: false, detail: `not listening (configured port ${this.configuredPort})` };
495
+ }
496
+ return {
497
+ ok: true,
498
+ detail: `listening on http://${address.address}:${address.port}/ (loopback only); ` +
499
+ `${this.counters.views} page view(s), ${this.counters.notified} request(s) delivered, ` +
500
+ `${this.counters.decisions} decision(s), ${this.counters.refused} refused POST(s)`,
501
+ };
502
+ }
503
+ /** The most recent rendering — a GET when there has been one, else `notify`. */
504
+ lastRendered() {
505
+ return this.rendered;
506
+ }
507
+ stats() {
508
+ return { ...this.counters };
509
+ }
510
+ /** The bound address, or `null` when the server is not listening. */
511
+ address() {
512
+ const address = this.httpServer?.address() ?? null;
513
+ return address === null || typeof address === "string" ? null : address;
514
+ }
515
+ /** The bound port, or the configured one before {@link start}. */
516
+ get port() {
517
+ return this.address()?.port ?? this.configuredPort;
518
+ }
519
+ /** The underlying server, for tests that assert on the socket itself. */
520
+ get server() {
521
+ return this.httpServer;
522
+ }
523
+ /** The page's own origin, as a browser would send it. */
524
+ get origin() {
525
+ return `http://${WEB_LOOPBACK_HOST}:${this.port}`;
526
+ }
527
+ // -------------------------------------------------------------------------
528
+ // The server
529
+ // -------------------------------------------------------------------------
530
+ /**
531
+ * Bind and serve — on `127.0.0.1` and nowhere else.
532
+ *
533
+ * The host argument is a constant. See the module header for why there is
534
+ * deliberately no option to widen it.
535
+ */
536
+ start() {
537
+ if (this.httpServer !== null) {
538
+ const address = this.address();
539
+ if (address !== null)
540
+ return Promise.resolve(address);
541
+ }
542
+ const server = createServer((request, response) => {
543
+ void this.handle(request, response).catch((cause) => {
544
+ this.complain(`approval: web channel failed to handle a request: ${cause instanceof Error ? cause.message : String(cause)}`);
545
+ if (!response.headersSent) {
546
+ this.send(response, 500, page("approval.md — error", "<p>internal error</p>"));
547
+ }
548
+ else
549
+ response.end();
550
+ });
551
+ });
552
+ this.httpServer = server;
553
+ return new Promise((resolve, reject) => {
554
+ const onError = (cause) => {
555
+ server.off("error", onError);
556
+ reject(cause);
557
+ };
558
+ server.on("error", onError);
559
+ server.listen(this.configuredPort, WEB_LOOPBACK_HOST, () => {
560
+ server.off("error", onError);
561
+ const address = this.address();
562
+ if (address === null)
563
+ reject(new Error("the web channel bound no address"));
564
+ else
565
+ resolve(address);
566
+ });
567
+ });
568
+ }
569
+ /** Stop listening. Safe when never started. */
570
+ close() {
571
+ const server = this.httpServer;
572
+ if (server === null)
573
+ return Promise.resolve();
574
+ this.httpServer = null;
575
+ return new Promise((resolve) => {
576
+ server.closeAllConnections?.();
577
+ server.close(() => resolve());
578
+ });
579
+ }
580
+ // -------------------------------------------------------------------------
581
+ // Routing
582
+ // -------------------------------------------------------------------------
583
+ async handle(request, response) {
584
+ const path = (request.url ?? "/").split("?", 1)[0] ?? "/";
585
+ if (request.method === "GET" && (path === "/" || path === "/index.html")) {
586
+ this.counters.views += 1;
587
+ this.send(response, 200, this.queuePage());
588
+ return;
589
+ }
590
+ if (request.method === "POST" && (path === "/decide" || path === "/decide-batch")) {
591
+ const origin = this.checkOrigin(request);
592
+ if (origin !== null) {
593
+ this.counters.refused += 1;
594
+ this.complain(`approval: web channel refused a cross-origin POST (${origin})`);
595
+ this.send(response, 403, page("approval.md — refused", `<div class="error">${escapeHtml(`Refused: this POST came from ${origin}, not from this page. Nothing was recorded. (Best-effort same-origin check; see SPEC.md §11 for the trust boundary this server actually relies on.)`)}</div>`));
596
+ return;
597
+ }
598
+ const body = await this.readBody(request);
599
+ if (!body.ok) {
600
+ this.counters.refused += 1;
601
+ this.send(response, 413, page("approval.md — refused", `<div class="error">${escapeHtml(body.message)}</div>`));
602
+ return;
603
+ }
604
+ const form = new URLSearchParams(body.text);
605
+ if (path === "/decide")
606
+ await this.decide(form, response);
607
+ else
608
+ await this.decideBatch(form, response);
609
+ return;
610
+ }
611
+ if (request.method !== "GET" && request.method !== "POST") {
612
+ this.send(response, 405, page("approval.md — method not allowed", "<p>GET or POST.</p>"));
613
+ return;
614
+ }
615
+ this.send(response, 404, page("approval.md — not found", '<p>Nothing here. <a href="/">Queue</a>.</p>'));
616
+ }
617
+ /**
618
+ * The best-effort same-origin check. Returns the offending origin, or `null`
619
+ * when the POST is acceptable.
620
+ *
621
+ * A request with neither `Origin` nor `Referer` is accepted: `curl`, a test's
622
+ * `fetch` and some browsers' form posts all send neither, and an attacker can
623
+ * omit a header as easily as anyone else — refusing them would cost the
624
+ * documented scripting path and buy nothing. See the module header's CSRF
625
+ * section, which is flagged for review.
626
+ */
627
+ checkOrigin(request) {
628
+ const origin = request.headers["origin"];
629
+ const referer = request.headers["referer"];
630
+ const candidate = typeof origin === "string" && origin.length > 0 && origin !== "null"
631
+ ? origin
632
+ : typeof referer === "string" && referer.length > 0
633
+ ? referer
634
+ : null;
635
+ if (candidate === null)
636
+ return null;
637
+ let parsed;
638
+ try {
639
+ parsed = new URL(candidate);
640
+ }
641
+ catch {
642
+ return candidate;
643
+ }
644
+ const port = String(this.port);
645
+ const loopback = ["127.0.0.1", "localhost", "[::1]", "::1"];
646
+ if (parsed.protocol !== "http:" || parsed.port !== port)
647
+ return parsed.origin;
648
+ return loopback.includes(parsed.hostname) ? null : parsed.origin;
649
+ }
650
+ readBody(request) {
651
+ return new Promise((resolve) => {
652
+ const chunks = [];
653
+ let size = 0;
654
+ let settled = false;
655
+ const finish = (result) => {
656
+ if (settled)
657
+ return;
658
+ settled = true;
659
+ resolve(result);
660
+ };
661
+ request.on("data", (chunk) => {
662
+ size += chunk.length;
663
+ if (size > WEB_MAX_BODY_BYTES) {
664
+ finish({
665
+ ok: false,
666
+ message: `form body exceeds ${WEB_MAX_BODY_BYTES} bytes; nothing was recorded`,
667
+ });
668
+ request.destroy();
669
+ return;
670
+ }
671
+ chunks.push(chunk);
672
+ });
673
+ request.on("end", () => finish({ ok: true, text: Buffer.concat(chunks).toString("utf8") }));
674
+ request.on("error", (cause) => finish({ ok: false, message: `form body could not be read: ${cause.message}` }));
675
+ });
676
+ }
677
+ // -------------------------------------------------------------------------
678
+ // The queue page
679
+ // -------------------------------------------------------------------------
680
+ /** The live pending set: the runtime's queue when one is supplied. */
681
+ currentRequests() {
682
+ if (this.refresh === null)
683
+ return this.pending;
684
+ const requests = this.refresh();
685
+ for (const request of requests) {
686
+ const key = request.action_key.value;
687
+ if (this.deliveries.has(key))
688
+ continue;
689
+ this.counter += 1;
690
+ this.deliveries.set(key, { deliveryId: `web-${this.counter}` });
691
+ this.counters.notified += 1;
692
+ }
693
+ this.pending = requests;
694
+ return requests;
695
+ }
696
+ /**
697
+ * Render the queue. The `lastRendered()` report is built here, from the same
698
+ * values the HTML is built from — not from a parallel description of it.
699
+ */
700
+ queuePage(error, notice) {
701
+ const requests = this.currentRequests();
702
+ const renderings = requests.map((request) => renderWebRequest(request, this.layout));
703
+ this.rendered = renderings.map((rendering) => reportOf(rendering, this.deliveries.get(rendering.actionKey)?.batchDeliveryId));
704
+ const body = [];
705
+ if (error !== undefined)
706
+ body.push(`<div class="error">${escapeHtml(error)}</div>`);
707
+ if (notice !== undefined)
708
+ body.push(`<div class="notice">${escapeHtml(notice)}</div>`);
709
+ body.push(actorLine(this.actorLabel));
710
+ if (renderings.length === 0) {
711
+ body.push("<p>queue: empty — no requests awaiting a decision.</p>");
712
+ return page("approval.md — pending decisions", body.join("\n"));
713
+ }
714
+ body.push(`<p>${renderings.length} request(s) awaiting a decision. Every field below is marked ${escapeHtml(COMPUTED_MARKER)} (the runtime derived it) or ${escapeHtml(CLAIMED_MARKER)} (the party under oversight wrote it; it is never evidence).</p>`);
715
+ body.push(...renderings.map((rendering, index) => requestHtml(rendering, index)));
716
+ body.push("<h2>BATCH — one gesture over the selected requests</h2>", "<p>Each selected request still receives its own log event carrying this batch's delivery id: the log never batches (SPEC.md §10.3).</p>", '<label><input type="checkbox" data-select-all> select all (needs JavaScript; the checkboxes above do not)</label>', '<form method="post" action="/decide-batch" id="batch-form">', '<label>note (REQUIRED to reject) <input type="text" name="note"></label> ', '<button type="submit" name="decision" value="grant">Grant selected</button> ', '<button type="submit" name="decision" value="reject">Reject selected</button>', "</form>");
717
+ return page("approval.md — pending decisions", body.join("\n"));
718
+ }
719
+ // -------------------------------------------------------------------------
720
+ // Decisions
721
+ // -------------------------------------------------------------------------
722
+ async decide(form, response) {
723
+ const actionKey = (form.get("action_key") ?? "").trim();
724
+ const verb = form.get("decision");
725
+ const note = (form.get("note") ?? "").trim();
726
+ if (actionKey.length === 0 || (verb !== "grant" && verb !== "reject")) {
727
+ this.counters.refused += 1;
728
+ this.send(response, 422, this.queuePage("The form was incomplete: an action key and a decision of grant or reject are required. Nothing was recorded."));
729
+ return;
730
+ }
731
+ // A refusal an agent cannot read is a refusal it will retry. Enforced on
732
+ // the server, because `required` in the markup is a courtesy the client
733
+ // controls.
734
+ if (verb === "reject" && note.length === 0) {
735
+ this.counters.refused += 1;
736
+ this.send(response, 422, this.queuePage(`A note is REQUIRED to reject ${actionKey}: an unexplained refusal cannot be acted on. Nothing was recorded — say why and submit again.`));
737
+ return;
738
+ }
739
+ // A key this process has never delivered is refused rather than reported:
740
+ // the runtime's queue is consulted once first, because a POST may be the
741
+ // first thing a scripted client does and a request that IS pending should
742
+ // be decidable without a page view.
743
+ if (!this.deliveries.has(actionKey))
744
+ this.currentRequests();
745
+ const delivery = this.deliveries.get(actionKey);
746
+ if (delivery === undefined) {
747
+ this.counters.refused += 1;
748
+ this.send(response, 404, this.queuePage(`${actionKey} was never delivered by this server, so no decision was reported for it. Nothing was recorded.`));
749
+ return;
750
+ }
751
+ const handler = this.handler;
752
+ if (handler === null) {
753
+ this.counters.refused += 1;
754
+ this.send(response, 503, this.queuePage("The runtime has not registered a decision handler yet, so nothing could be recorded. A channel never records its own decisions (SPEC.md §10.3)."));
755
+ return;
756
+ }
757
+ const decision = {
758
+ action_key: actionKey,
759
+ decision: verb,
760
+ deliveryId: delivery.deliveryId,
761
+ // A per-request form on a member that was delivered as part of a batch
762
+ // still belongs to that gesture, so the id rides along and the event
763
+ // keeps the audit granularity §10.3 asks for.
764
+ ...(delivery.batchDeliveryId === undefined
765
+ ? {}
766
+ : { batchDeliveryId: delivery.batchDeliveryId }),
767
+ ...(note.length === 0 ? {} : { note }),
768
+ };
769
+ const outcome = handler(decision);
770
+ this.counters.decisions += 1;
771
+ const once = this.notice === null ? null : (this.notice(decision, outcome) ?? null);
772
+ this.send(response, outcome.ok ? 200 : 409, this.outcomePage([{ decision, outcome, once }]));
773
+ }
774
+ async decideBatch(form, response) {
775
+ const verb = form.get("decision");
776
+ const note = (form.get("note") ?? "").trim();
777
+ const keys = form.getAll("select").map((key) => key.trim()).filter((key) => key.length > 0);
778
+ if (verb !== "grant" && verb !== "reject") {
779
+ this.counters.refused += 1;
780
+ this.send(response, 422, this.queuePage("A batch gesture must be grant or reject. Nothing was recorded."));
781
+ return;
782
+ }
783
+ if (keys.length === 0) {
784
+ this.counters.refused += 1;
785
+ this.send(response, 422, this.queuePage("No request was selected, so there was no batch to decide. Nothing was recorded."));
786
+ return;
787
+ }
788
+ if (verb === "reject" && note.length === 0) {
789
+ this.counters.refused += 1;
790
+ this.send(response, 422, this.queuePage("A note is REQUIRED to reject, batch or not: an unexplained refusal cannot be acted on. Nothing was recorded."));
791
+ return;
792
+ }
793
+ const requests = this.currentRequests();
794
+ const selected = [];
795
+ for (const key of keys) {
796
+ const request = requests.find((entry) => entry.action_key.value === key);
797
+ if (request === undefined) {
798
+ this.counters.refused += 1;
799
+ this.send(response, 404, this.queuePage(`${key} is not in the pending queue this server is showing, so the batch was not assembled. Nothing was recorded.`));
800
+ return;
801
+ }
802
+ selected.push(request);
803
+ }
804
+ // B7 (SPEC.md §10.3). The refusal happens BEFORE anything is recorded, and
805
+ // the page shows the contract's own code and message rather than a
806
+ // paraphrase: the operator needs to be able to look the code up.
807
+ const assembled = assembleBatch(selected);
808
+ if (!assembled.ok) {
809
+ this.counters.refused += 1;
810
+ const refusal = assembled;
811
+ this.send(response, 422, this.queuePage(`${refusal.code}: ${refusal.message} Nothing was recorded.`));
812
+ return;
813
+ }
814
+ this.counter += 1;
815
+ const batchDeliveryId = `web-batch-${this.counter}`;
816
+ for (const request of selected) {
817
+ this.deliveries.set(request.action_key.value, {
818
+ deliveryId: batchDeliveryId,
819
+ batchDeliveryId,
820
+ });
821
+ }
822
+ const decisions = selected.map((request) => ({
823
+ action_key: request.action_key.value,
824
+ decision: verb,
825
+ deliveryId: batchDeliveryId,
826
+ batchDeliveryId,
827
+ ...(note.length === 0 ? {} : { note }),
828
+ }));
829
+ const results = [];
830
+ if (this.batchHandler !== null) {
831
+ const reported = await this.batchHandler(decisions, batchDeliveryId);
832
+ for (const decision of decisions) {
833
+ const entry = reported.find((item) => item.action_key === decision.action_key);
834
+ if (entry === undefined)
835
+ continue;
836
+ this.counters.decisions += 1;
837
+ results.push({ decision, outcome: entry.outcome, once: entry.notice ?? null });
838
+ }
839
+ }
840
+ else {
841
+ const handler = this.handler;
842
+ if (handler === null) {
843
+ this.counters.refused += 1;
844
+ this.send(response, 503, this.queuePage("The runtime has not registered a decision handler yet; nothing was recorded."));
845
+ return;
846
+ }
847
+ for (const decision of decisions) {
848
+ const outcome = handler(decision);
849
+ this.counters.decisions += 1;
850
+ results.push({
851
+ decision,
852
+ outcome,
853
+ once: this.notice === null ? null : (this.notice(decision, outcome) ?? null),
854
+ });
855
+ }
856
+ }
857
+ const ok = results.every((entry) => entry.outcome.ok);
858
+ this.send(response, ok ? 200 : 409, this.outcomePage(results, batchDeliveryId));
859
+ }
860
+ /**
861
+ * The response page for a gesture.
862
+ *
863
+ * Not a redirect. A redirect-after-POST would either drop the one-shot notice
864
+ * or put an execution token in a URL, and a URL is exactly the place a
865
+ * credential must never be: it lands in history, in referrers, and in every
866
+ * proxy log between here and nowhere.
867
+ */
868
+ outcomePage(results, batchDeliveryId) {
869
+ const body = [];
870
+ if (batchDeliveryId !== undefined) {
871
+ body.push(`<p>Batch <code>${escapeHtml(batchDeliveryId)}</code> — ${results.length} member(s), one log event each.</p>`);
872
+ }
873
+ for (const entry of results) {
874
+ const { decision, outcome } = entry;
875
+ if (outcome.ok) {
876
+ body.push(`<p><strong>${escapeHtml(outcome.decision === "grant" ? "GRANTED" : "REJECTED")}</strong> <code>${escapeHtml(decision.action_key)}</code> → ${escapeHtml(outcome.state)} at seq ${outcome.record.seq}.</p>`);
877
+ }
878
+ else {
879
+ body.push(`<div class="error">REFUSED <code>${escapeHtml(decision.action_key)}</code> (${escapeHtml(outcome.code)}): ${escapeHtml(outcome.message)}</div>`);
880
+ }
881
+ if (entry.once !== null && entry.once.length > 0) {
882
+ body.push(`<div class="notice"><p>SHOWN ONCE — copy it now. Reload this page and it is gone; the log records only its SHA-256 and nothing can recover it.</p><pre class="payload">${escapeHtml(entry.once)}</pre></div>`);
883
+ }
884
+ }
885
+ body.push('<p><a href="/">back to the queue</a></p>');
886
+ return page("approval.md — decision recorded", body.join("\n"));
887
+ }
888
+ send(response, status, html) {
889
+ response.writeHead(status, {
890
+ "content-type": "text/html; charset=utf-8",
891
+ // No caching anywhere: a queue page is a claim about *now*, and one of
892
+ // these responses may carry a single-use token.
893
+ "cache-control": "no-store, no-cache, must-revalidate, private",
894
+ "referrer-policy": "no-referrer",
895
+ "x-content-type-options": "nosniff",
896
+ // The page is entirely self-contained; nothing it renders should be able
897
+ // to reach the network even if an escaping bug ever let markup through.
898
+ "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; form-action 'self'; base-uri 'none'",
899
+ });
900
+ response.end(html);
901
+ }
902
+ }
903
+ //# sourceMappingURL=web.js.map