approval-md 0.2.0 → 0.4.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 (378) hide show
  1. package/README.md +85 -877
  2. package/SPEC.md +150 -19
  3. package/cli.js +66 -7
  4. package/dist/src/adapters/smtp.d.ts +18 -0
  5. package/dist/src/adapters/smtp.js +24 -3
  6. package/dist/src/adapters/smtp.js.map +1 -1
  7. package/dist/src/channels/contract.d.ts +53 -1
  8. package/dist/src/channels/contract.js +304 -7
  9. package/dist/src/channels/contract.js.map +1 -1
  10. package/dist/src/channels/relay-server.d.ts +90 -0
  11. package/dist/src/channels/relay-server.js +295 -0
  12. package/dist/src/channels/relay-server.js.map +1 -0
  13. package/dist/src/channels/relay.d.ts +273 -0
  14. package/dist/src/channels/relay.js +564 -0
  15. package/dist/src/channels/relay.js.map +1 -0
  16. package/dist/src/channels/tagging.js +6 -1
  17. package/dist/src/channels/tagging.js.map +1 -1
  18. package/dist/src/channels/telegram-webhook.d.ts +273 -0
  19. package/dist/src/channels/telegram-webhook.js +536 -0
  20. package/dist/src/channels/telegram-webhook.js.map +1 -0
  21. package/dist/src/channels/telegram.d.ts +394 -12
  22. package/dist/src/channels/telegram.js +621 -68
  23. package/dist/src/channels/telegram.js.map +1 -1
  24. package/dist/src/channels/web.d.ts +9 -0
  25. package/dist/src/channels/web.js +17 -0
  26. package/dist/src/channels/web.js.map +1 -1
  27. package/dist/src/cli/amend.js +564 -34
  28. package/dist/src/cli/amend.js.map +1 -1
  29. package/dist/src/cli/attest.d.ts +9 -0
  30. package/dist/src/cli/attest.js +177 -13
  31. package/dist/src/cli/attest.js.map +1 -1
  32. package/dist/src/cli/channel-relay.d.ts +108 -0
  33. package/dist/src/cli/channel-relay.js +334 -0
  34. package/dist/src/cli/channel-relay.js.map +1 -0
  35. package/dist/src/cli/channel-telegram-webhook.d.ts +273 -0
  36. package/dist/src/cli/channel-telegram-webhook.js +709 -0
  37. package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
  38. package/dist/src/cli/channel-telegram.d.ts +347 -28
  39. package/dist/src/cli/channel-telegram.js +763 -28
  40. package/dist/src/cli/channel-telegram.js.map +1 -1
  41. package/dist/src/cli/channel.d.ts +10 -1
  42. package/dist/src/cli/channel.js +14 -1
  43. package/dist/src/cli/channel.js.map +1 -1
  44. package/dist/src/cli/codex-bridge.d.ts +819 -0
  45. package/dist/src/cli/codex-bridge.js +1607 -0
  46. package/dist/src/cli/codex-bridge.js.map +1 -0
  47. package/dist/src/cli/codex.d.ts +1 -1
  48. package/dist/src/cli/codex.js +304 -7
  49. package/dist/src/cli/codex.js.map +1 -1
  50. package/dist/src/cli/daemon.js +41 -6
  51. package/dist/src/cli/daemon.js.map +1 -1
  52. package/dist/src/cli/doctor.d.ts +63 -0
  53. package/dist/src/cli/doctor.js +1322 -122
  54. package/dist/src/cli/doctor.js.map +1 -1
  55. package/dist/src/cli/execute.d.ts +17 -1
  56. package/dist/src/cli/execute.js +366 -22
  57. package/dist/src/cli/execute.js.map +1 -1
  58. package/dist/src/cli/exit-codes.d.ts +12 -0
  59. package/dist/src/cli/exit-codes.js +13 -0
  60. package/dist/src/cli/exit-codes.js.map +1 -1
  61. package/dist/src/cli/gate.d.ts +2 -0
  62. package/dist/src/cli/gate.js +233 -2
  63. package/dist/src/cli/gate.js.map +1 -1
  64. package/dist/src/cli/help.d.ts +14 -2
  65. package/dist/src/cli/help.js +395 -82
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook-codex.d.ts +49 -1
  68. package/dist/src/cli/hook-codex.js +60 -1
  69. package/dist/src/cli/hook-codex.js.map +1 -1
  70. package/dist/src/cli/hook.d.ts +828 -4
  71. package/dist/src/cli/hook.js +2972 -142
  72. package/dist/src/cli/hook.js.map +1 -1
  73. package/dist/src/cli/import.js +1 -1
  74. package/dist/src/cli/import.js.map +1 -1
  75. package/dist/src/cli/init.js +16 -7
  76. package/dist/src/cli/init.js.map +1 -1
  77. package/dist/src/cli/instructions.js +24 -3
  78. package/dist/src/cli/instructions.js.map +1 -1
  79. package/dist/src/cli/log-anchor.d.ts +55 -0
  80. package/dist/src/cli/log-anchor.js +199 -60
  81. package/dist/src/cli/log-anchor.js.map +1 -1
  82. package/dist/src/cli/main.d.ts +0 -12
  83. package/dist/src/cli/main.js +56 -3
  84. package/dist/src/cli/main.js.map +1 -1
  85. package/dist/src/cli/muse.d.ts +2 -0
  86. package/dist/src/cli/muse.js +62 -0
  87. package/dist/src/cli/muse.js.map +1 -0
  88. package/dist/src/cli/payload.js +79 -1
  89. package/dist/src/cli/payload.js.map +1 -1
  90. package/dist/src/cli/policy-apply.d.ts +195 -0
  91. package/dist/src/cli/policy-apply.js +573 -0
  92. package/dist/src/cli/policy-apply.js.map +1 -0
  93. package/dist/src/cli/policy.js +14 -1
  94. package/dist/src/cli/policy.js.map +1 -1
  95. package/dist/src/cli/preflight.d.ts +151 -13
  96. package/dist/src/cli/preflight.js +398 -41
  97. package/dist/src/cli/preflight.js.map +1 -1
  98. package/dist/src/cli/sandbox.js +17 -1
  99. package/dist/src/cli/sandbox.js.map +1 -1
  100. package/dist/src/cli/scaffold.d.ts +1 -1
  101. package/dist/src/cli/scaffold.js +7 -1
  102. package/dist/src/cli/scaffold.js.map +1 -1
  103. package/dist/src/cli/serve.d.ts +53 -0
  104. package/dist/src/cli/serve.js +270 -0
  105. package/dist/src/cli/serve.js.map +1 -0
  106. package/dist/src/cli/setup-channel.d.ts +9 -0
  107. package/dist/src/cli/setup-channel.js +28 -1
  108. package/dist/src/cli/setup-channel.js.map +1 -1
  109. package/dist/src/cli/setup-common.d.ts +3 -1
  110. package/dist/src/cli/setup-common.js +3 -2
  111. package/dist/src/cli/setup-common.js.map +1 -1
  112. package/dist/src/cli/setup.d.ts +2 -0
  113. package/dist/src/cli/setup.js +94 -2
  114. package/dist/src/cli/setup.js.map +1 -1
  115. package/dist/src/cli/up.js +134 -51
  116. package/dist/src/cli/up.js.map +1 -1
  117. package/dist/src/cli/values.js +3 -4
  118. package/dist/src/cli/values.js.map +1 -1
  119. package/dist/src/cli/verb-registry.d.ts +2 -0
  120. package/dist/src/cli/verb-registry.js +510 -47
  121. package/dist/src/cli/verb-registry.js.map +1 -1
  122. package/dist/src/cli/wordmark.d.ts +2 -2
  123. package/dist/src/cli/wordmark.js +2 -2
  124. package/dist/src/codex/broker.d.ts +229 -0
  125. package/dist/src/codex/broker.js +548 -0
  126. package/dist/src/codex/broker.js.map +1 -0
  127. package/dist/src/codex/runner.d.ts +178 -0
  128. package/dist/src/codex/runner.js +231 -0
  129. package/dist/src/codex/runner.js.map +1 -0
  130. package/dist/src/codex/serve.d.ts +56 -0
  131. package/dist/src/codex/serve.js +98 -0
  132. package/dist/src/codex/serve.js.map +1 -0
  133. package/dist/src/codex/workspace-commit.d.ts +219 -0
  134. package/dist/src/codex/workspace-commit.js +549 -0
  135. package/dist/src/codex/workspace-commit.js.map +1 -0
  136. package/dist/src/core/advance-cycle.d.ts +51 -0
  137. package/dist/src/core/advance-cycle.js +66 -2
  138. package/dist/src/core/advance-cycle.js.map +1 -1
  139. package/dist/src/core/agents-md.d.ts +20 -18
  140. package/dist/src/core/agents-md.js +33 -31
  141. package/dist/src/core/agents-md.js.map +1 -1
  142. package/dist/src/core/attest-bootstrap.d.ts +95 -0
  143. package/dist/src/core/attest-bootstrap.js +135 -0
  144. package/dist/src/core/attest-bootstrap.js.map +1 -0
  145. package/dist/src/core/attest.d.ts +234 -2
  146. package/dist/src/core/attest.js +342 -7
  147. package/dist/src/core/attest.js.map +1 -1
  148. package/dist/src/core/audit.d.ts +73 -3
  149. package/dist/src/core/audit.js +65 -5
  150. package/dist/src/core/audit.js.map +1 -1
  151. package/dist/src/core/channel-lease.d.ts +322 -0
  152. package/dist/src/core/channel-lease.js +663 -0
  153. package/dist/src/core/channel-lease.js.map +1 -0
  154. package/dist/src/core/channel-owner.d.ts +213 -0
  155. package/dist/src/core/channel-owner.js +358 -0
  156. package/dist/src/core/channel-owner.js.map +1 -0
  157. package/dist/src/core/command-class.d.ts +205 -0
  158. package/dist/src/core/command-class.js +1433 -28
  159. package/dist/src/core/command-class.js.map +1 -1
  160. package/dist/src/core/commit-guard.d.ts +272 -0
  161. package/dist/src/core/commit-guard.js +424 -0
  162. package/dist/src/core/commit-guard.js.map +1 -0
  163. package/dist/src/core/daemon-actor.d.ts +45 -0
  164. package/dist/src/core/daemon-actor.js +54 -0
  165. package/dist/src/core/daemon-actor.js.map +1 -0
  166. package/dist/src/core/daemon-host.d.ts +178 -0
  167. package/dist/src/core/daemon-host.js +228 -0
  168. package/dist/src/core/daemon-host.js.map +1 -0
  169. package/dist/src/core/daemon-identity.d.ts +238 -0
  170. package/dist/src/core/daemon-identity.js +252 -0
  171. package/dist/src/core/daemon-identity.js.map +1 -0
  172. package/dist/src/core/dark-session.d.ts +109 -8
  173. package/dist/src/core/dark-session.js +266 -82
  174. package/dist/src/core/dark-session.js.map +1 -1
  175. package/dist/src/core/decision-refusal.d.ts +23 -2
  176. package/dist/src/core/decision-refusal.js +24 -2
  177. package/dist/src/core/decision-refusal.js.map +1 -1
  178. package/dist/src/core/env-file.d.ts +5 -0
  179. package/dist/src/core/env-file.js +60 -1
  180. package/dist/src/core/env-file.js.map +1 -1
  181. package/dist/src/core/execute.d.ts +15 -2
  182. package/dist/src/core/execute.js +15 -2
  183. package/dist/src/core/execute.js.map +1 -1
  184. package/dist/src/core/gate.d.ts +392 -6
  185. package/dist/src/core/gate.js +852 -24
  186. package/dist/src/core/gate.js.map +1 -1
  187. package/dist/src/core/gesture-refusal.d.ts +166 -0
  188. package/dist/src/core/gesture-refusal.js +188 -0
  189. package/dist/src/core/gesture-refusal.js.map +1 -0
  190. package/dist/src/core/harness-version.d.ts +128 -4
  191. package/dist/src/core/harness-version.js +181 -12
  192. package/dist/src/core/harness-version.js.map +1 -1
  193. package/dist/src/core/harness-wait.d.ts +82 -3
  194. package/dist/src/core/harness-wait.js +89 -3
  195. package/dist/src/core/harness-wait.js.map +1 -1
  196. package/dist/src/core/instance.d.ts +59 -2
  197. package/dist/src/core/instance.js +113 -0
  198. package/dist/src/core/instance.js.map +1 -1
  199. package/dist/src/core/log-subscribe.d.ts +21 -0
  200. package/dist/src/core/log-subscribe.js +16 -6
  201. package/dist/src/core/log-subscribe.js.map +1 -1
  202. package/dist/src/core/log-write-layer.d.ts +55 -0
  203. package/dist/src/core/log-write-layer.js +91 -0
  204. package/dist/src/core/log-write-layer.js.map +1 -0
  205. package/dist/src/core/log.d.ts +89 -22
  206. package/dist/src/core/log.js +197 -19
  207. package/dist/src/core/log.js.map +1 -1
  208. package/dist/src/core/payload-census.d.ts +51 -0
  209. package/dist/src/core/payload-census.js +125 -2
  210. package/dist/src/core/payload-census.js.map +1 -1
  211. package/dist/src/core/payload-store.d.ts +31 -0
  212. package/dist/src/core/payload-store.js +113 -8
  213. package/dist/src/core/payload-store.js.map +1 -1
  214. package/dist/src/core/payload.d.ts +18 -3
  215. package/dist/src/core/payload.js +20 -4
  216. package/dist/src/core/payload.js.map +1 -1
  217. package/dist/src/core/policy-diff.d.ts +10 -0
  218. package/dist/src/core/policy-diff.js +10 -2
  219. package/dist/src/core/policy-diff.js.map +1 -1
  220. package/dist/src/core/policy-explain.d.ts +17 -1
  221. package/dist/src/core/policy-explain.js +48 -1
  222. package/dist/src/core/policy-explain.js.map +1 -1
  223. package/dist/src/core/policy-load.d.ts +65 -1
  224. package/dist/src/core/policy-load.js +21 -3
  225. package/dist/src/core/policy-load.js.map +1 -1
  226. package/dist/src/core/policy-match.d.ts +81 -0
  227. package/dist/src/core/policy-match.js +87 -0
  228. package/dist/src/core/policy-match.js.map +1 -1
  229. package/dist/src/core/policy-proposal.d.ts +75 -0
  230. package/dist/src/core/policy-proposal.js +110 -4
  231. package/dist/src/core/policy-proposal.js.map +1 -1
  232. package/dist/src/core/protected-path-guard.d.ts +117 -4
  233. package/dist/src/core/protected-path-guard.js +362 -48
  234. package/dist/src/core/protected-path-guard.js.map +1 -1
  235. package/dist/src/core/question-preempted.d.ts +141 -0
  236. package/dist/src/core/question-preempted.js +152 -0
  237. package/dist/src/core/question-preempted.js.map +1 -0
  238. package/dist/src/core/read-scope.d.ts +182 -0
  239. package/dist/src/core/read-scope.js +256 -0
  240. package/dist/src/core/read-scope.js.map +1 -0
  241. package/dist/src/core/run-payload.d.ts +160 -0
  242. package/dist/src/core/run-payload.js +304 -0
  243. package/dist/src/core/run-payload.js.map +1 -0
  244. package/dist/src/core/sandbox.d.ts +81 -0
  245. package/dist/src/core/sandbox.js +190 -1
  246. package/dist/src/core/sandbox.js.map +1 -1
  247. package/dist/src/core/sender-identity.d.ts +501 -0
  248. package/dist/src/core/sender-identity.js +609 -0
  249. package/dist/src/core/sender-identity.js.map +1 -0
  250. package/dist/src/core/shlex.d.ts +102 -0
  251. package/dist/src/core/shlex.js +159 -0
  252. package/dist/src/core/shlex.js.map +1 -0
  253. package/dist/src/core/state.d.ts +52 -1
  254. package/dist/src/core/state.js +52 -6
  255. package/dist/src/core/state.js.map +1 -1
  256. package/dist/src/core/telegram-config.d.ts +79 -0
  257. package/dist/src/core/telegram-config.js +108 -0
  258. package/dist/src/core/telegram-config.js.map +1 -1
  259. package/dist/src/core/values.d.ts +18 -8
  260. package/dist/src/core/values.js +36 -1
  261. package/dist/src/core/values.js.map +1 -1
  262. package/dist/src/core/verify.d.ts +23 -1
  263. package/dist/src/core/verify.js +20 -2
  264. package/dist/src/core/verify.js.map +1 -1
  265. package/dist/src/daemon/advance.d.ts +10 -0
  266. package/dist/src/daemon/advance.js +25 -4
  267. package/dist/src/daemon/advance.js.map +1 -1
  268. package/dist/src/daemon/audit.d.ts +80 -5
  269. package/dist/src/daemon/audit.js +126 -6
  270. package/dist/src/daemon/audit.js.map +1 -1
  271. package/dist/src/daemon/daemon.d.ts +174 -2
  272. package/dist/src/daemon/daemon.js +307 -7
  273. package/dist/src/daemon/daemon.js.map +1 -1
  274. package/dist/src/daemon/git-evidence.d.ts +2 -2
  275. package/dist/src/daemon/git-evidence.js +1 -1
  276. package/dist/src/daemon/projection.d.ts +13 -2
  277. package/dist/src/daemon/projection.js +39 -6
  278. package/dist/src/daemon/projection.js.map +1 -1
  279. package/dist/src/mcp/server.d.ts +39 -3
  280. package/dist/src/mcp/server.js +30 -3
  281. package/dist/src/mcp/server.js.map +1 -1
  282. package/dist/src/muse/server.d.ts +16 -0
  283. package/dist/src/muse/server.js +238 -0
  284. package/dist/src/muse/server.js.map +1 -0
  285. package/dist/src/serve/archive.d.ts +158 -0
  286. package/dist/src/serve/archive.js +354 -0
  287. package/dist/src/serve/archive.js.map +1 -0
  288. package/dist/src/serve/arguments.d.ts +93 -0
  289. package/dist/src/serve/arguments.js +228 -0
  290. package/dist/src/serve/arguments.js.map +1 -0
  291. package/dist/src/serve/credentials.d.ts +91 -0
  292. package/dist/src/serve/credentials.js +143 -0
  293. package/dist/src/serve/credentials.js.map +1 -0
  294. package/dist/src/serve/follow.d.ts +86 -0
  295. package/dist/src/serve/follow.js +101 -0
  296. package/dist/src/serve/follow.js.map +1 -0
  297. package/dist/src/serve/hook-thread.d.ts +134 -0
  298. package/dist/src/serve/hook-thread.js +333 -0
  299. package/dist/src/serve/hook-thread.js.map +1 -0
  300. package/dist/src/serve/hook-worker.d.ts +134 -0
  301. package/dist/src/serve/hook-worker.js +173 -0
  302. package/dist/src/serve/hook-worker.js.map +1 -0
  303. package/dist/src/serve/server.d.ts +412 -0
  304. package/dist/src/serve/server.js +1251 -0
  305. package/dist/src/serve/server.js.map +1 -0
  306. package/docs/README-extended.md +766 -0
  307. package/docs/cli-reference.md +2563 -73
  308. package/docs/codex-enforced-session.md +75 -2
  309. package/docs/codex-workspace-broker.md +118 -0
  310. package/package.json +6 -2
  311. package/schema/event.schema.json +595 -9
  312. package/schema/fixtures/event/invalid/approval-granted-sender-hashed-false.json +20 -0
  313. package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
  314. package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
  315. package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
  316. package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
  317. package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
  318. package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
  319. package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
  320. package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
  321. package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
  322. package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
  323. package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
  324. package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
  325. package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
  326. package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
  327. package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
  328. package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
  329. package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
  330. package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
  331. package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
  332. package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
  333. package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
  334. package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
  335. package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
  336. package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
  337. package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
  338. package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
  339. package/schema/fixtures/event/valid/daemon-identity.json +15 -0
  340. package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
  341. package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
  342. package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
  343. package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
  344. package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
  345. package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
  346. package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
  347. package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
  348. package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
  349. package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
  350. package/schema/fixtures/policy/valid/canonical.json +1 -1
  351. package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
  352. package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
  353. package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
  354. package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
  355. package/schema/fixtures/policy-md/valid/canonical.md +1 -1
  356. package/schema/fixtures/policy-md/valid/with-values.md +5 -7
  357. package/schema/fixtures/values/invalid/class-shaped.json +1 -1
  358. package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
  359. package/schema/fixtures/values/invalid/non-string-item.json +1 -1
  360. package/schema/fixtures/values/invalid/over-cap.json +1 -1
  361. package/schema/fixtures/values/invalid/unknown-key.json +1 -1
  362. package/schema/fixtures/values/invalid/version-float.json +1 -0
  363. package/schema/fixtures/values/invalid/version-integer.json +1 -0
  364. package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
  365. package/schema/fixtures/values/valid/empty-lists.json +2 -3
  366. package/schema/fixtures/values/valid/full.json +5 -7
  367. package/schema/fixtures/values/valid/minimal.json +1 -1
  368. package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
  369. package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
  370. package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
  371. package/schema/fixtures/values-md/invalid/version-1.md +69 -0
  372. package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
  373. package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
  374. package/schema/fixtures/values-md/valid/absent.md +1 -1
  375. package/schema/fixtures/values-md/valid/with-values.md +5 -7
  376. package/schema/policy.schema.json +100 -4
  377. package/schema/values.schema.json +7 -11
  378. package/schema/fixtures/values/invalid/version-string.json +0 -1
@@ -0,0 +1,228 @@
1
+ /**
2
+ * What a caller on this transport may say: nothing about the filesystem
3
+ * outside the store, and nothing shaped like a flag where a value belongs
4
+ * (APRV-421, review findings 3 and 1).
5
+ *
6
+ * ## The two holes this closes
7
+ *
8
+ * **A published path flag is a filesystem oracle.** `approval mcp serve`
9
+ * publishes `--log`, `--dir` and `--policy` on the verbs that accept them and
10
+ * injects the operator's pins only where the operator passed any. On a pipe
11
+ * the operator handed the server that is harmless: the party at the other end
12
+ * is the party who started it. Over HTTP the party at the other end is a
13
+ * sandboxed harness on another machine, and the same flag answers questions
14
+ * about files it cannot see.
15
+ *
16
+ * **A positional is a flag if it starts with a dash.** `buildArgv` emits
17
+ * positionals FIRST and verbatim, and `parseFlags` reads any token beginning
18
+ * with `-` as a flag. So a caller refused `{"flags":{"--payload":"…"}}` could
19
+ * write `{"positionals":["--payload","/tmp/secret","t1"]}` and reach the same
20
+ * flag through the front door — including the `--payload=/tmp/secret`
21
+ * spelling, which `parseFlags` splits on `=`. Positionals are VALUES. One that
22
+ * looks like a flag is refused rather than escaped, because escaping is a
23
+ * thing to get subtly wrong once and lose.
24
+ *
25
+ * `trailing` needs no such rule and deliberately does not get one. `buildArgv`
26
+ * always emits `--` before it, and `parseFlags` STOPS at `--`, pushing the
27
+ * remainder into positionals without reading a single token as a flag. A
28
+ * blanket refusal of dash-leading trailing elements would instead break the
29
+ * one agent verb that needs them: `hook classify -- git push -f` classifies a
30
+ * command line, and command lines have flags. The invariant that makes this
31
+ * safe is pinned by a test rather than assumed here.
32
+ *
33
+ * ## The rule, by scope
34
+ *
35
+ * **Both scopes.** No positional may begin with `-`. `--dir`, `--log` and
36
+ * `--policy` are appended to every call from the launch configuration and are
37
+ * refused from a caller even when the value is correct: one process serves one
38
+ * store, and a caller restating it is a caller that believes it can choose.
39
+ *
40
+ * **Agent scope.** Every path-typed flag is refused OUTRIGHT rather than
41
+ * confined: a sandboxed harness has no files on the daemon's machine, so a
42
+ * path it names is either useless or somebody else's. Payload bytes reach the
43
+ * gate for such a harness through the hook route, which builds the envelope
44
+ * from the tool call itself. On top of that, only the flags named in
45
+ * {@link AGENT_FLAGS} are accepted at all, per verb, so a flag added to one of
46
+ * the seven agent verbs tomorrow is refused until somebody decides otherwise.
47
+ *
48
+ * **Tenant scope.** Every path-typed flag is CONFINED to the store, and which
49
+ * flags those are comes from the registry ({@link pathFlagsOf}) rather than
50
+ * from a list kept here, so a flag declared `"path"` tomorrow is confined on
51
+ * the day it appears. Positionals that name files are confined the same way.
52
+ */
53
+ import { realpathSync } from "node:fs";
54
+ import { dirname, isAbsolute, resolve, sep } from "node:path";
55
+ import { pathFlagsOf, verbLabel } from "../cli/verb-registry.js";
56
+ /**
57
+ * The three the server pins itself, refused from any caller in any scope.
58
+ *
59
+ * They ARE the store. Everything else this module decides is about values
60
+ * inside it.
61
+ */
62
+ export const PINNED_FLAGS = ["--dir", "--log", "--policy"];
63
+ /**
64
+ * What each AGENT verb may be given, flag by flag.
65
+ *
66
+ * A positive list per verb, and short on purpose. The agent surface is seven
67
+ * verbs; between them they need an action key, a decision deadline, a note,
68
+ * a proposal's class, key, summary and inline payload, and `--json`. Everything else on those verbs is either the store (pinned), the
69
+ * identity (`--as`, refused by the argv builder both transports share), a path
70
+ * (refused below), or something nobody has decided a sandboxed harness should
71
+ * have.
72
+ *
73
+ * `--help` is absent deliberately: a help page over a transport whose client
74
+ * is a program is an odd thing to serve, and `instructions` is the verb for
75
+ * that.
76
+ */
77
+ export const AGENT_FLAGS = new Map([
78
+ ["instructions", new Set(["--schemas", "--json"])],
79
+ ["hook classify", new Set(["--json"])],
80
+ ["request", new Set(["--action", "--json"])],
81
+ ["wait", new Set(["--timeout", "--interval", "--withdraw-on-timeout", "--json"])],
82
+ ["withdraw", new Set(["--action", "--reason", "--note", "--json"])],
83
+ // APRV-445. The payload travels as a string VALUE, which is the whole reason
84
+ // these two verbs exist on this surface: a path would name the host's file.
85
+ ["propose", new Set(["--class", "--key", "--summary", "--payload-json", "--json"])],
86
+ ["start", new Set(["--action", "--payload-json", "--json"])],
87
+ ]);
88
+ /**
89
+ * Verbs whose POSITIONAL argument names a file, by registry label.
90
+ *
91
+ * Read off the registry's own positional titles: each declares a `file` or a
92
+ * `task-file`. None is on the agent surface any more, and the confinement
93
+ * still runs for the tenant, because one process serves one store whoever is
94
+ * asking.
95
+ */
96
+ const PATH_POSITIONAL_VERBS = new Map([
97
+ ["payload hash", 0],
98
+ ["register", 0],
99
+ ["import agents-md", 0],
100
+ ]);
101
+ const OUTSIDE = "serve-path-outside-store";
102
+ const PINNED = "serve-path-pinned";
103
+ const DASH = "serve-positional-flag";
104
+ const UNSUPPORTED = "serve-flag-not-permitted";
105
+ /**
106
+ * Is `candidate` inside `root`?
107
+ *
108
+ * Resolved through `realpath` on the deepest existing ancestor, so a symlink
109
+ * in the middle of the path cannot walk out of the store and back in on paper.
110
+ * A path that does not exist is judged by where it WOULD be, which is the
111
+ * right answer for a verb that is about to create it.
112
+ */
113
+ function insideStore(root, candidate) {
114
+ const real = (path) => {
115
+ let current = path;
116
+ const tail = [];
117
+ for (;;) {
118
+ try {
119
+ return resolve(realpathSync(current), ...tail.reverse());
120
+ }
121
+ catch {
122
+ const parent = dirname(current);
123
+ if (parent === current)
124
+ return resolve(path);
125
+ tail.push(current.slice(parent.length + 1));
126
+ current = parent;
127
+ }
128
+ }
129
+ };
130
+ const realRoot = real(root);
131
+ const realCandidate = real(candidate);
132
+ return realCandidate === realRoot || realCandidate.startsWith(`${realRoot}${sep}`);
133
+ }
134
+ function confine(root, cwd, label, value) {
135
+ // `-` is stdin and is refused by the argv builder both transports share.
136
+ if (value === "-")
137
+ return { ok: true };
138
+ const absolute = isAbsolute(value) ? value : resolve(cwd, value);
139
+ if (insideStore(root, absolute))
140
+ return { ok: true };
141
+ return {
142
+ ok: false,
143
+ code: OUTSIDE,
144
+ message: `${label} names ${JSON.stringify(value)}, which is outside the store this server was started for. This transport serves ONE store and reads no path beyond it: a path argument that reached the host filesystem would answer questions about files the caller cannot see and did not put there`,
145
+ };
146
+ }
147
+ /**
148
+ * Check one verb call's arguments before an argv is built from them.
149
+ *
150
+ * Runs for EVERY scope. The tenant credential is the operator's and is trusted
151
+ * with far more than the agent's, and that is still not a reason to let one
152
+ * process serve two stores: the pins are the launch configuration's, and a
153
+ * second store reached through an argument would be a second gate with no
154
+ * attestation, no daemon id, and no record of which one answered.
155
+ */
156
+ export function checkVerbArguments(spec, rawArgs, storeRoot, cwd, scope) {
157
+ const args = (rawArgs ?? {});
158
+ if (typeof args !== "object" || Array.isArray(args))
159
+ return { ok: true };
160
+ const label = verbLabel(spec);
161
+ // A POSITIONAL THAT LOOKS LIKE A FLAG IS A FLAG. Checked first and in both
162
+ // scopes, because it is the route around every other rule in this file.
163
+ const positionals = args["positionals"];
164
+ if (Array.isArray(positionals)) {
165
+ for (const value of positionals) {
166
+ if (typeof value !== "string" || !value.startsWith("-"))
167
+ continue;
168
+ return {
169
+ ok: false,
170
+ code: DASH,
171
+ message: `positional ${JSON.stringify(value)} begins with a dash. Positionals on this surface are VALUES, passed to the verb verbatim, and the verb reads anything starting with \`-\` as a flag: a value spelled this way reaches the flag parser through the front door. Send flags in \`flags\``,
172
+ };
173
+ }
174
+ }
175
+ const pathFlags = new Set(pathFlagsOf(spec));
176
+ const permitted = AGENT_FLAGS.get(label);
177
+ const flags = args["flags"];
178
+ if (typeof flags === "object" && flags !== null && !Array.isArray(flags)) {
179
+ for (const [flag, value] of Object.entries(flags)) {
180
+ if (PINNED_FLAGS.includes(flag)) {
181
+ return {
182
+ ok: false,
183
+ code: PINNED,
184
+ message: `${flag} is not accepted from a caller. This server serves the one store it was started for, and ${PINNED_FLAGS.join(", ")} are appended to every verb call from its launch configuration; a call that could restate them could point this process at another tenant's log, at another tenant's policy, or at the host's filesystem`,
185
+ };
186
+ }
187
+ if (scope === "agent") {
188
+ // `--as` is not this module's to refuse. The argv builder both
189
+ // transports share owns identity and refuses it with
190
+ // `mcp-identity-fixed`, whose message is the whole reason this surface
191
+ // is safe to run; catching it here first would replace that with a
192
+ // vaguer code and leave the rule stated in two places.
193
+ if (flag === "--as")
194
+ continue;
195
+ if (pathFlags.has(flag)) {
196
+ return {
197
+ ok: false,
198
+ code: UNSUPPORTED,
199
+ message: `${flag} names a file on the machine this server runs on, and the agent credential never names one. A harness reached through this transport has no files there; payload bytes reach the gate through \`POST /hook/<harness>\`, which builds the envelope from the tool call itself`,
200
+ };
201
+ }
202
+ if (permitted !== undefined && !permitted.has(flag)) {
203
+ return {
204
+ ok: false,
205
+ code: UNSUPPORTED,
206
+ message: `${flag} is not accepted on \`approval ${label}\` from the agent credential, which takes ${[...permitted].sort().join(", ")} on this verb and nothing else. A flag added to it later is refused until somebody decides a harness under oversight should have it`,
207
+ };
208
+ }
209
+ continue;
210
+ }
211
+ if (!pathFlags.has(flag) || typeof value !== "string")
212
+ continue;
213
+ const checked = confine(storeRoot, cwd, flag, value);
214
+ if (!checked.ok)
215
+ return checked;
216
+ }
217
+ }
218
+ const index = PATH_POSITIONAL_VERBS.get(label);
219
+ if (index === undefined)
220
+ return { ok: true };
221
+ if (!Array.isArray(positionals))
222
+ return { ok: true };
223
+ const value = positionals[index];
224
+ if (typeof value !== "string")
225
+ return { ok: true };
226
+ return confine(storeRoot, cwd, `\`approval ${label}\`'s file argument`, value);
227
+ }
228
+ //# sourceMappingURL=arguments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arguments.js","sourceRoot":"","sources":["../../../src/serve/arguments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The two bearer credentials `approval serve` reads from its launch
3
+ * environment, and the scope each one buys (APRV-421).
4
+ *
5
+ * ## Two credentials, because there are two parties
6
+ *
7
+ * SPEC.md §11 names the agent the untrusted policy and the human the trusted
8
+ * overseer, and a hosted deployment adds a third fact: the log and the policy
9
+ * belong to the TENANT, while the harness that is judged by them runs
10
+ * somewhere else entirely (design/hosted-daemon-identity.md, §1). So this
11
+ * server has two doors and never one.
12
+ *
13
+ * - The **agent credential** ({@link AGENT_TOKEN_ENV}) reaches the
14
+ * agent-facing verb surface and the harness hook. It is the credential a
15
+ * sandbox holds, and the sandbox is the party under oversight.
16
+ * - The **tenant credential** ({@link TENANT_TOKEN_ENV}) reaches the log, the
17
+ * export and the status report. It is the credential the tenant's own tools
18
+ * hold.
19
+ *
20
+ * **The agent credential must never read the log it is judged by.** That is
21
+ * the sentence the split exists for. An agent that can stream the verified log
22
+ * can read every decision made about it, every other action in the tenant's
23
+ * store, and the shape of the oversight it is under; an agent that can export
24
+ * the store can carry all of it away. Neither is a thing the gate would ever
25
+ * grant, so neither is reachable with the credential the gate hands the
26
+ * harness. The refusal is a scope refusal and not a not-found, because a
27
+ * caller that guessed a path deserves to learn that the door exists and is not
28
+ * theirs, rather than to probe for one that answers differently.
29
+ *
30
+ * ## Why the environment
31
+ *
32
+ * SPEC.md §11.1 invariant 7: configuration is never loaded implicitly from the
33
+ * working tree. A credential in a file this process discovered would be a
34
+ * credential anything able to write that file could rotate. The host that
35
+ * launches one process per tenant is the party that sets these, exactly as it
36
+ * sets the bot token, the sampling secret and the vault passphrase
37
+ * (design/hosted-daemon-identity.md §1.2).
38
+ *
39
+ * ## What comparison costs
40
+ *
41
+ * Both values are compared as SHA-256 digests under `timingSafeEqual`, so the
42
+ * comparison is constant-time AND length-independent: `timingSafeEqual` throws
43
+ * on a length mismatch, which would itself be an oracle for the length of the
44
+ * real credential. Nothing here logs, prints, or returns either value, and the
45
+ * server never puts one in a record, a message or a header.
46
+ */
47
+ /** The environment variable naming the credential a sandboxed harness holds. */
48
+ export declare const AGENT_TOKEN_ENV = "APPROVAL_SERVE_AGENT_TOKEN";
49
+ /** The environment variable naming the credential the tenant's own tools hold. */
50
+ export declare const TENANT_TOKEN_ENV = "APPROVAL_SERVE_TENANT_TOKEN";
51
+ /**
52
+ * The shortest credential this server will start with.
53
+ *
54
+ * Not a password policy: a floor under the one mistake that makes the split
55
+ * meaningless, which is a memorable value somebody typed. The credential is
56
+ * generated by the host, never by a person, so the floor costs nothing.
57
+ */
58
+ export declare const MIN_TOKEN_LENGTH = 24;
59
+ /** Which of the two parties a request is acting as. */
60
+ export type ServeScope = "agent" | "tenant";
61
+ /**
62
+ * The frozen refusal vocabulary of the credential layer (SPEC.md §11.1
63
+ * invariant 6). Distinct codes, because the repairs are distinct: one is a
64
+ * missing or wrong credential, and the other two are the right credential at
65
+ * the wrong door, in each direction.
66
+ */
67
+ export declare const SERVE_CREDENTIAL_REFUSAL_CODES: readonly ["serve-unauthorized", "serve-agent-forbidden", "serve-tenant-forbidden"];
68
+ export type ServeCredentialRefusalCode = (typeof SERVE_CREDENTIAL_REFUSAL_CODES)[number];
69
+ /** Hold the digests, never the values. */
70
+ export interface ServeCredentials {
71
+ /** Which scope this bearer value belongs to, or `null` for neither. */
72
+ identify(bearer: string): ServeScope | null;
73
+ }
74
+ export type CredentialResolution = {
75
+ ok: true;
76
+ credentials: ServeCredentials;
77
+ } | {
78
+ ok: false;
79
+ message: string;
80
+ };
81
+ /**
82
+ * Resolve both credentials, or say why the server may not start.
83
+ *
84
+ * Every failure is a STARTUP failure. A server that began listening and then
85
+ * discovered it had no tenant credential would be a server something had
86
+ * already spoken to, which is the rule `cli/mcp.ts` states for identity and
87
+ * this file inherits for authority.
88
+ */
89
+ export declare function resolveServeCredentials(env?: NodeJS.ProcessEnv): CredentialResolution;
90
+ /** The bearer value of an `Authorization` header, or null. */
91
+ export declare function bearerOf(header: string | string[] | undefined): string | null;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The two bearer credentials `approval serve` reads from its launch
3
+ * environment, and the scope each one buys (APRV-421).
4
+ *
5
+ * ## Two credentials, because there are two parties
6
+ *
7
+ * SPEC.md §11 names the agent the untrusted policy and the human the trusted
8
+ * overseer, and a hosted deployment adds a third fact: the log and the policy
9
+ * belong to the TENANT, while the harness that is judged by them runs
10
+ * somewhere else entirely (design/hosted-daemon-identity.md, §1). So this
11
+ * server has two doors and never one.
12
+ *
13
+ * - The **agent credential** ({@link AGENT_TOKEN_ENV}) reaches the
14
+ * agent-facing verb surface and the harness hook. It is the credential a
15
+ * sandbox holds, and the sandbox is the party under oversight.
16
+ * - The **tenant credential** ({@link TENANT_TOKEN_ENV}) reaches the log, the
17
+ * export and the status report. It is the credential the tenant's own tools
18
+ * hold.
19
+ *
20
+ * **The agent credential must never read the log it is judged by.** That is
21
+ * the sentence the split exists for. An agent that can stream the verified log
22
+ * can read every decision made about it, every other action in the tenant's
23
+ * store, and the shape of the oversight it is under; an agent that can export
24
+ * the store can carry all of it away. Neither is a thing the gate would ever
25
+ * grant, so neither is reachable with the credential the gate hands the
26
+ * harness. The refusal is a scope refusal and not a not-found, because a
27
+ * caller that guessed a path deserves to learn that the door exists and is not
28
+ * theirs, rather than to probe for one that answers differently.
29
+ *
30
+ * ## Why the environment
31
+ *
32
+ * SPEC.md §11.1 invariant 7: configuration is never loaded implicitly from the
33
+ * working tree. A credential in a file this process discovered would be a
34
+ * credential anything able to write that file could rotate. The host that
35
+ * launches one process per tenant is the party that sets these, exactly as it
36
+ * sets the bot token, the sampling secret and the vault passphrase
37
+ * (design/hosted-daemon-identity.md §1.2).
38
+ *
39
+ * ## What comparison costs
40
+ *
41
+ * Both values are compared as SHA-256 digests under `timingSafeEqual`, so the
42
+ * comparison is constant-time AND length-independent: `timingSafeEqual` throws
43
+ * on a length mismatch, which would itself be an oracle for the length of the
44
+ * real credential. Nothing here logs, prints, or returns either value, and the
45
+ * server never puts one in a record, a message or a header.
46
+ */
47
+ import { createHash, timingSafeEqual } from "node:crypto";
48
+ /** The environment variable naming the credential a sandboxed harness holds. */
49
+ export const AGENT_TOKEN_ENV = "APPROVAL_SERVE_AGENT_TOKEN";
50
+ /** The environment variable naming the credential the tenant's own tools hold. */
51
+ export const TENANT_TOKEN_ENV = "APPROVAL_SERVE_TENANT_TOKEN";
52
+ /**
53
+ * The shortest credential this server will start with.
54
+ *
55
+ * Not a password policy: a floor under the one mistake that makes the split
56
+ * meaningless, which is a memorable value somebody typed. The credential is
57
+ * generated by the host, never by a person, so the floor costs nothing.
58
+ */
59
+ export const MIN_TOKEN_LENGTH = 24;
60
+ /**
61
+ * The frozen refusal vocabulary of the credential layer (SPEC.md §11.1
62
+ * invariant 6). Distinct codes, because the repairs are distinct: one is a
63
+ * missing or wrong credential, and the other two are the right credential at
64
+ * the wrong door, in each direction.
65
+ */
66
+ export const SERVE_CREDENTIAL_REFUSAL_CODES = [
67
+ "serve-unauthorized",
68
+ "serve-agent-forbidden",
69
+ "serve-tenant-forbidden",
70
+ ];
71
+ function digest(value) {
72
+ return createHash("sha256").update(value, "utf8").digest();
73
+ }
74
+ function sameDigest(left, right) {
75
+ // Equal by construction (two SHA-256 digests), so `timingSafeEqual` can never
76
+ // throw here and the comparison leaks nothing about the length of either
77
+ // input.
78
+ return timingSafeEqual(left, right);
79
+ }
80
+ /**
81
+ * Resolve both credentials, or say why the server may not start.
82
+ *
83
+ * Every failure is a STARTUP failure. A server that began listening and then
84
+ * discovered it had no tenant credential would be a server something had
85
+ * already spoken to, which is the rule `cli/mcp.ts` states for identity and
86
+ * this file inherits for authority.
87
+ */
88
+ export function resolveServeCredentials(env = process.env) {
89
+ const agent = env[AGENT_TOKEN_ENV] ?? "";
90
+ const tenant = env[TENANT_TOKEN_ENV] ?? "";
91
+ for (const [name, value] of [
92
+ [AGENT_TOKEN_ENV, agent],
93
+ [TENANT_TOKEN_ENV, tenant],
94
+ ]) {
95
+ if (value.length === 0) {
96
+ return {
97
+ ok: false,
98
+ message: `no ${name} in the environment: this server has two doors and starts only when the host has named both. The agent credential reaches the verbs and the hook; the tenant credential reaches the log, the export and status`,
99
+ };
100
+ }
101
+ if (value.length < MIN_TOKEN_LENGTH) {
102
+ return {
103
+ ok: false,
104
+ message: `${name} is shorter than ${String(MIN_TOKEN_LENGTH)} characters. Generate it (\`openssl rand -hex 32\`) rather than choosing it: this value is the whole of what separates the two parties`,
105
+ };
106
+ }
107
+ }
108
+ if (sameDigest(digest(agent), digest(tenant))) {
109
+ return {
110
+ ok: false,
111
+ message: `${AGENT_TOKEN_ENV} and ${TENANT_TOKEN_ENV} are the same value, which is the same as having neither: the agent credential would reach the log it is judged by`,
112
+ };
113
+ }
114
+ const agentDigest = digest(agent);
115
+ const tenantDigest = digest(tenant);
116
+ return {
117
+ ok: true,
118
+ credentials: {
119
+ identify(bearer) {
120
+ const offered = digest(bearer);
121
+ // Both compared, always, in the same order: returning as soon as the
122
+ // agent digest matched would make an agent credential measurably
123
+ // faster to present than a tenant one.
124
+ const isAgent = sameDigest(offered, agentDigest);
125
+ const isTenant = sameDigest(offered, tenantDigest);
126
+ if (isAgent)
127
+ return "agent";
128
+ if (isTenant)
129
+ return "tenant";
130
+ return null;
131
+ },
132
+ },
133
+ };
134
+ }
135
+ /** The bearer value of an `Authorization` header, or null. */
136
+ export function bearerOf(header) {
137
+ const raw = Array.isArray(header) ? header[0] : header;
138
+ if (raw === undefined)
139
+ return null;
140
+ const match = /^Bearer[ \t]+(?<token>\S+)$/iu.exec(raw.trim());
141
+ return match?.groups?.["token"] ?? null;
142
+ }
143
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../src/serve/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * `log/follow`, paged (APRV-421).
3
+ *
4
+ * `approval log follow` is a foreground stream: it prints verified records as
5
+ * JSON Lines and stays. Over HTTP that shape is wrong in both directions — a
6
+ * request has to end, and a tenant behind a sleeping Firecracker host has to
7
+ * be able to come back tomorrow and resume — so this answers ONE page and
8
+ * hands back the cursor to ask with next time.
9
+ *
10
+ * Everything else is the streaming verb's, unchanged, because it is literally
11
+ * the same generator: `core/log-subscribe.ts` verifies the chain from genesis
12
+ * before it emits anything, binds the exclusive `(seq, hash)` cursor to the
13
+ * prefix the caller says it consumed, and refuses a mismatch as an integrity
14
+ * failure with `cursor-mismatch` as its reason. Nothing in this file verifies,
15
+ * parses or re-derives a record; it counts to a limit and stops.
16
+ *
17
+ * **At-least-once, and the cursor is the caller's.** This server keeps no
18
+ * subscription state: a page is a function of the cursor in the request and
19
+ * the bytes on disk. A caller that performs an external effect persists its
20
+ * cursor after the effect, exactly as SPEC.md §8 requires of any consumer, and
21
+ * a restart of this process on either side changes nothing about what the next
22
+ * page contains. That is what "no in-memory state a restart loses" means here.
23
+ */
24
+ import type { EventRecord } from "../core/log.js";
25
+ /** Records one page carries when the caller names no limit. */
26
+ export declare const DEFAULT_FOLLOW_LIMIT = 200;
27
+ /** The most any caller may ask for in one page. */
28
+ export declare const MAX_FOLLOW_LIMIT = 1000;
29
+ export interface FollowCursor {
30
+ /** Exclusive: the caller has consumed through this sequence number. */
31
+ seq: number;
32
+ /** The hash of record `seq`, or null when the caller never held one. */
33
+ hash: string | null;
34
+ }
35
+ export interface FollowPage {
36
+ records: EventRecord[];
37
+ /** Where to resume. The caller sends this back verbatim. */
38
+ cursor: FollowCursor;
39
+ /**
40
+ * True when this page exhausted the verified log rather than the limit.
41
+ *
42
+ * A caller that gets `false` asks again immediately; one that gets `true`
43
+ * waits before it does. It is a statement about the snapshot this page was
44
+ * cut from and never a promise about the next instant.
45
+ */
46
+ caught_up: boolean;
47
+ }
48
+ export type FollowResult = {
49
+ ok: true;
50
+ page: FollowPage;
51
+ } | {
52
+ ok: false;
53
+ /** The CLI's own code for this failure: `integrity`, `torn-tail` or `io`. */
54
+ code: string;
55
+ message: string;
56
+ /** `cursor-mismatch` and the rest of the subscription's own reasons. */
57
+ reason: string | null;
58
+ };
59
+ /**
60
+ * The exit code `approval log follow` would have exited with for this failure.
61
+ *
62
+ * Carried on the response so a caller reproducing the CLI locally reports the
63
+ * same thing. The numbers are `cli/exit-codes.ts`'s and are frozen public API;
64
+ * they are mapped here exactly as `main.ts`'s `commandFollow` maps them.
65
+ */
66
+ export declare function followFailureExit(code: string): number;
67
+ /**
68
+ * There is deliberately NO clamp here any more (APRV-421, review).
69
+ *
70
+ * A limit outside the range above used to be pulled quietly into it, so
71
+ * `limit=0` returned two hundred records and a caller paging by a number it
72
+ * had computed could not tell that the server had answered a different
73
+ * question than the one it asked. The transport refuses an out-of-range limit
74
+ * with `serve-invalid-cursor` instead, and `docs/cli-reference.md` states the
75
+ * range. A silent correction is the failure mode this project exists to
76
+ * dislike.
77
+ */
78
+ /**
79
+ * Read one page of verified records after `cursor`.
80
+ *
81
+ * A failure returns NO records at all, including the ones a partial drain had
82
+ * already produced: SPEC.md §8 says a subscription "MUST emit no record from a
83
+ * batch that fails verification or its retained cursor binding", and a page
84
+ * that carried half a batch beside an integrity refusal would be exactly that.
85
+ */
86
+ export declare function followPage(logPath: string, cursor: FollowCursor, limit: number): Promise<FollowResult>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `log/follow`, paged (APRV-421).
3
+ *
4
+ * `approval log follow` is a foreground stream: it prints verified records as
5
+ * JSON Lines and stays. Over HTTP that shape is wrong in both directions — a
6
+ * request has to end, and a tenant behind a sleeping Firecracker host has to
7
+ * be able to come back tomorrow and resume — so this answers ONE page and
8
+ * hands back the cursor to ask with next time.
9
+ *
10
+ * Everything else is the streaming verb's, unchanged, because it is literally
11
+ * the same generator: `core/log-subscribe.ts` verifies the chain from genesis
12
+ * before it emits anything, binds the exclusive `(seq, hash)` cursor to the
13
+ * prefix the caller says it consumed, and refuses a mismatch as an integrity
14
+ * failure with `cursor-mismatch` as its reason. Nothing in this file verifies,
15
+ * parses or re-derives a record; it counts to a limit and stops.
16
+ *
17
+ * **At-least-once, and the cursor is the caller's.** This server keeps no
18
+ * subscription state: a page is a function of the cursor in the request and
19
+ * the bytes on disk. A caller that performs an external effect persists its
20
+ * cursor after the effect, exactly as SPEC.md §8 requires of any consumer, and
21
+ * a restart of this process on either side changes nothing about what the next
22
+ * page contains. That is what "no in-memory state a restart loses" means here.
23
+ */
24
+ import { LogSubscriptionError, subscribeVerifiedLog } from "../core/log-subscribe.js";
25
+ /** Records one page carries when the caller names no limit. */
26
+ export const DEFAULT_FOLLOW_LIMIT = 200;
27
+ /** The most any caller may ask for in one page. */
28
+ export const MAX_FOLLOW_LIMIT = 1000;
29
+ /**
30
+ * The exit code `approval log follow` would have exited with for this failure.
31
+ *
32
+ * Carried on the response so a caller reproducing the CLI locally reports the
33
+ * same thing. The numbers are `cli/exit-codes.ts`'s and are frozen public API;
34
+ * they are mapped here exactly as `main.ts`'s `commandFollow` maps them.
35
+ */
36
+ export function followFailureExit(code) {
37
+ if (code === "integrity")
38
+ return 1;
39
+ if (code === "torn-tail")
40
+ return 3;
41
+ return 4;
42
+ }
43
+ /**
44
+ * There is deliberately NO clamp here any more (APRV-421, review).
45
+ *
46
+ * A limit outside the range above used to be pulled quietly into it, so
47
+ * `limit=0` returned two hundred records and a caller paging by a number it
48
+ * had computed could not tell that the server had answered a different
49
+ * question than the one it asked. The transport refuses an out-of-range limit
50
+ * with `serve-invalid-cursor` instead, and `docs/cli-reference.md` states the
51
+ * range. A silent correction is the failure mode this project exists to
52
+ * dislike.
53
+ */
54
+ /**
55
+ * Read one page of verified records after `cursor`.
56
+ *
57
+ * A failure returns NO records at all, including the ones a partial drain had
58
+ * already produced: SPEC.md §8 says a subscription "MUST emit no record from a
59
+ * batch that fails verification or its retained cursor binding", and a page
60
+ * that carried half a batch beside an integrity refusal would be exactly that.
61
+ */
62
+ export async function followPage(logPath, cursor, limit) {
63
+ const records = [];
64
+ let caughtUp = true;
65
+ const options = {
66
+ from: cursor.seq,
67
+ once: true,
68
+ ...(cursor.hash === null ? {} : { expectedHash: cursor.hash }),
69
+ };
70
+ try {
71
+ for await (const record of subscribeVerifiedLog(logPath, options)) {
72
+ records.push(record);
73
+ if (records.length >= limit) {
74
+ // The generator's `finally` runs on this break: the iterator is closed
75
+ // and nothing is left watching or waiting.
76
+ caughtUp = false;
77
+ break;
78
+ }
79
+ }
80
+ }
81
+ catch (cause) {
82
+ if (!(cause instanceof LogSubscriptionError))
83
+ throw cause;
84
+ return {
85
+ ok: false,
86
+ code: cause.kind === "torn-tail" ? "torn-tail" : cause.kind === "io" ? "io" : "integrity",
87
+ message: cause.message,
88
+ reason: cause.reason,
89
+ };
90
+ }
91
+ const last = records.at(-1);
92
+ return {
93
+ ok: true,
94
+ page: {
95
+ records,
96
+ cursor: last === undefined ? cursor : { seq: last.seq, hash: last.hash },
97
+ caught_up: caughtUp,
98
+ },
99
+ };
100
+ }
101
+ //# sourceMappingURL=follow.js.map