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,334 @@
1
+ /**
2
+ * `approval channel relay` — gestures an operator's authenticated relay
3
+ * carries (SPEC.md §10.3, APRV-455).
4
+ *
5
+ * The third arrival of a human gesture, beside a Telegram tap and a terminal.
6
+ * An operator's control plane holds an EdgeOS session for the person, shows
7
+ * them a request or their policy, and posts what they did here: the gesture,
8
+ * the EdgeOS `/humans/me` id of the session that made it, the hash or the key
9
+ * it answers, and a nonce. `channels/relay-server.ts` authenticates the post
10
+ * and `channels/relay.ts` turns it into a log event through the one function a
11
+ * Telegram tap goes through. This file resolves configuration, refuses what it
12
+ * must refuse, binds a port and reports; it decides nothing.
13
+ *
14
+ * ## The refusals that are the point
15
+ *
16
+ * 1. **The secret comes from the launch environment, never from the tree.**
17
+ * `APPROVAL_RELAY_SECRET`, refused when unset, when shorter than a value a
18
+ * host would generate, and when it holds a character a header cannot carry
19
+ * unambiguously (SPEC.md §11.1 invariant 7). It is a conventional name,
20
+ * beside `APPROVAL_TG_WEBHOOK_SECRET`, for that variable's reason: the
21
+ * policy decides WHETHER a channel exists, never the value that
22
+ * authenticates it, and a second declaration would be a second place for
23
+ * one fact to be wrong. Under the `APPROVAL_` prefix it is withheld from
24
+ * every child `approval run` spawns (`core/child-env.ts`), and the hook
25
+ * classifier holds this command line `policy.core`, so an agent session
26
+ * cannot start a relay with a secret of its own.
27
+ * 2. **No human identity.** This verb takes no `--as` and reads no
28
+ * `APPROVAL_HUMAN`. Every gesture is attributed to the account the attested
29
+ * policy maps, or refused `sender-unmapped`.
30
+ * 3. **A post without the secret decides nothing and appends nothing.**
31
+ *
32
+ * ## Why there is no transport lease
33
+ *
34
+ * A Telegram bot admits one receiver, so its transports take a lease. A relay
35
+ * has no such constraint: two relays on one gate would each record through
36
+ * compare-and-append, the gate would answer a duplicate decision
37
+ * `already-decided`, and the nonce ledger is a directory of exclusively linked files that
38
+ * refuses a replay whichever relay it reaches. A second relay on the SAME port
39
+ * fails to bind and says so.
40
+ *
41
+ * ## Where TLS is
42
+ *
43
+ * Not here, exactly as for `approval serve` and the webhook. Loopback by
44
+ * default, behind whatever the operator puts in front of it; a routable bind
45
+ * takes two flags and prints a banner, because the secret travels in a header.
46
+ */
47
+ import { isAbsolute, join, resolve as resolvePathSegments } from "node:path";
48
+ import { RELAY_DEFAULT_HOST, RELAY_DEFAULT_PORT, RELAY_DEFAULT_PROPOSER, RELAY_PATH, RELAY_SECRET_HEADER, fileNonceLedger, } from "../channels/relay.js";
49
+ import { serveRelay } from "../channels/relay-server.js";
50
+ import { channelLeaseDirFor } from "../core/channel-lease.js";
51
+ import { readGatePolicy } from "../core/gate.js";
52
+ import { mapsSendersFor } from "../core/sender-identity.js";
53
+ import { readVerifiedRecords } from "../core/state.js";
54
+ import { boolFlag, parseFlags, stringFlag } from "./args.js";
55
+ import { EXIT_INTEGRITY, EXIT_IO, EXIT_OK, EXIT_TORN_TAIL, EXIT_USAGE } from "./exit-codes.js";
56
+ import { CHANNEL_RELAY_HELP } from "./help.js";
57
+ import { isLoopbackHost, parseListen } from "./mcp.js";
58
+ import { DEFAULT_LOG_PATH, resolvePath } from "./paths.js";
59
+ import { usageErrorText } from "./usage.js";
60
+ /** The environment variable the shared secret is read from. */
61
+ export const RELAY_SECRET_ENV = "APPROVAL_RELAY_SECRET";
62
+ /** The shortest secret this verb starts with: `serve`'s floor, for its reason. */
63
+ export const RELAY_MIN_SECRET_LENGTH = 24;
64
+ /** What a header carries unambiguously, and what `openssl rand -hex` produces. */
65
+ const SECRET_CHARSET = /^[A-Za-z0-9_-]{1,256}$/u;
66
+ /**
67
+ * A proposer is an `agent:` id. The relay is a machine carrying a proposal the
68
+ * control plane rendered, and recording it under a `human:` id would say a
69
+ * person proposed something nobody typed (APRV-455 refuter L1).
70
+ */
71
+ const PROPOSER_PATTERN = /^agent:[^\s]+$/u;
72
+ const RELAY_FLAGS = {
73
+ "--listen": "string",
74
+ "--port": "string",
75
+ "--allow-non-loopback": "boolean",
76
+ "--log": "string",
77
+ "--policy": "string",
78
+ "--dir": "string",
79
+ "--proposer": "string",
80
+ "--json": "boolean",
81
+ "--help": "boolean",
82
+ "-h": "boolean",
83
+ };
84
+ /**
85
+ * Why the relay could not start. A closed union, distinct per repair (SPEC.md
86
+ * §11.1 invariant 6). Its per-request refusals are `RELAY_REFUSAL_CODES`.
87
+ */
88
+ export const RELAY_START_REFUSAL_CODES = [
89
+ /** `APPROVAL_RELAY_SECRET` is unset or empty. */
90
+ "relay-secret-missing",
91
+ /** It is shorter than {@link RELAY_MIN_SECRET_LENGTH}. */
92
+ "relay-secret-weak",
93
+ /** It holds a character outside A-Z a-z 0-9 _ -, or is longer than 256. */
94
+ "relay-secret-charset",
95
+ /** `--proposer` is not an `agent:` id. */
96
+ "relay-proposer-invalid",
97
+ /** The bind flags do not name a usable loopback (or explicitly widened) address. */
98
+ "relay-bind-invalid",
99
+ ];
100
+ /** Everything that can fail without binding a port. Prints nothing. */
101
+ export function prepareRelay(request) {
102
+ const environment = request.env ?? process.env;
103
+ const raw = environment[RELAY_SECRET_ENV];
104
+ const secret = raw === undefined ? "" : raw.trim();
105
+ if (secret.length === 0) {
106
+ return {
107
+ ok: false,
108
+ code: "relay-secret-missing",
109
+ message: `no ${RELAY_SECRET_ENV} in the environment. The relay's only authentication is this shared secret, carried in ${RELAY_SECRET_HEADER}; without one, any post that looked like a gesture would be one. Generate it (\`openssl rand -hex 32\`), give the same value to the control plane, and set it in the environment this process is launched from, never in a file in the tree`,
110
+ };
111
+ }
112
+ if (!SECRET_CHARSET.test(secret)) {
113
+ return {
114
+ ok: false,
115
+ code: "relay-secret-charset",
116
+ message: `${RELAY_SECRET_ENV} holds a value outside 1 to 256 characters of A-Z, a-z, 0-9, underscore and hyphen, which a header cannot be relied on to carry byte for byte. Its value is not shown here and is in no message this process writes`,
117
+ };
118
+ }
119
+ if (secret.length < RELAY_MIN_SECRET_LENGTH) {
120
+ return {
121
+ ok: false,
122
+ code: "relay-secret-weak",
123
+ message: `${RELAY_SECRET_ENV} is shorter than ${String(RELAY_MIN_SECRET_LENGTH)} characters. Generate it (\`openssl rand -hex 32\`) rather than choosing it: it is the whole of what separates the operator's relay from anyone who can reach this port`,
124
+ };
125
+ }
126
+ const proposer = request.proposer ?? RELAY_DEFAULT_PROPOSER;
127
+ if (!PROPOSER_PATTERN.test(proposer)) {
128
+ return {
129
+ ok: false,
130
+ code: "relay-proposer-invalid",
131
+ message: `--proposer ${JSON.stringify(proposer)} is not an agent:<id> actor. A proposal is recorded under it, and the relay is a machine carrying what the control plane rendered: a human: id would say a person proposed something nobody typed`,
132
+ };
133
+ }
134
+ if (request.listen !== null && request.port !== null) {
135
+ return {
136
+ ok: false,
137
+ code: "relay-bind-invalid",
138
+ message: "--listen and --port name the same thing; pass one. --listen <[host:]port> is the only way to bind a non-loopback interface",
139
+ };
140
+ }
141
+ let host = RELAY_DEFAULT_HOST;
142
+ let port = RELAY_DEFAULT_PORT;
143
+ if (request.listen !== null) {
144
+ const bind = parseListen(request.listen);
145
+ if (!bind.ok)
146
+ return { ok: false, code: "relay-bind-invalid", message: bind.message };
147
+ host = bind.host;
148
+ port = bind.port;
149
+ }
150
+ else if (request.port !== null) {
151
+ if (!/^\d+$/u.test(request.port.trim())) {
152
+ return {
153
+ ok: false,
154
+ code: "relay-bind-invalid",
155
+ message: `--port expects a whole number, got ${JSON.stringify(request.port)}`,
156
+ };
157
+ }
158
+ port = Number(request.port.trim());
159
+ }
160
+ if (port < 1 || port > 65535) {
161
+ return {
162
+ ok: false,
163
+ code: "relay-bind-invalid",
164
+ message: `the bind port ${String(port)} is outside 1..65535. Port 0 is refused too: the control plane posts to a fixed address, and an ephemeral port is one nobody would find`,
165
+ };
166
+ }
167
+ if (!isLoopbackHost(host) && !request.allowNonLoopback) {
168
+ return {
169
+ ok: false,
170
+ code: "relay-bind-invalid",
171
+ message: `--listen ${JSON.stringify(host)} is not the loopback interface, and this process speaks plain HTTP: the relay secret arrives in a header and a cleartext hop hands it to whoever is on it. Add --allow-non-loopback if you meant it and TLS is terminated in front of this process`,
172
+ };
173
+ }
174
+ return {
175
+ ok: true,
176
+ setup: {
177
+ logPath: request.logPath,
178
+ policy: request.policy,
179
+ secret,
180
+ host,
181
+ port,
182
+ proposer,
183
+ json: request.json,
184
+ },
185
+ };
186
+ }
187
+ function usageError(streams, json, code, message) {
188
+ if (json)
189
+ streams.err(`${JSON.stringify({ error: { code, message } })}\n`);
190
+ else
191
+ streams.err(usageErrorText(message, CHANNEL_RELAY_HELP));
192
+ return EXIT_USAGE;
193
+ }
194
+ function failure(streams, json, code, message, exit) {
195
+ if (json)
196
+ streams.err(`${JSON.stringify({ error: { code, message } })}\n`);
197
+ else
198
+ streams.err(`approval: ${message}\n`);
199
+ return exit;
200
+ }
201
+ /** The banner a routable bind prints, every time, on stderr. */
202
+ export function relayNonLoopbackBanner(host, port) {
203
+ return [
204
+ "",
205
+ " !! the approval relay IS BOUND TO A NON-LOOPBACK INTERFACE !!",
206
+ ` ${host}:${String(port)} is reachable by anyone who can route to it, and this`,
207
+ " process speaks PLAIN HTTP. The relay secret travels in a header, and a",
208
+ " cleartext hop hands it to whoever is on it. Terminate TLS in front of this",
209
+ " process, or give it a loopback bind.",
210
+ "",
211
+ "",
212
+ ].join("\n");
213
+ }
214
+ /** Run the relay until a signal stops it. */
215
+ export async function runRelay(setup, streams) {
216
+ const json = setup.json;
217
+ // The log must be readable before anything binds: an operator who pointed
218
+ // at the wrong store learns it here, not from the first refused gesture.
219
+ const read = readVerifiedRecords(setup.logPath);
220
+ if (!read.ok) {
221
+ const exit = read.code === "log-torn-tail" ? EXIT_TORN_TAIL : read.code === "log-unreadable" ? EXIT_IO : EXIT_INTEGRITY;
222
+ return failure(streams, json, read.code, read.message, exit);
223
+ }
224
+ // Not a refusal: a policy that maps no EdgeOS account is a legitimate state
225
+ // (the operator has not mapped the resident yet), and the relay refuses
226
+ // every gesture `sender-unmapped` until it does. Said once, at start.
227
+ const load = readGatePolicy({ policy: setup.policy });
228
+ if (!load.ok || !mapsSendersFor(load.policy.approvers, "edgeos")) {
229
+ streams.err(`approval: relay: the policy ${load.ok ? "maps no edgeos sender" : `does not load (${load.code})`}, so every attest, decline, grant and reject will be refused sender-unmapped until an attested policy maps the person's EdgeOS id under approvers.<id>.senders.edgeos\n`);
230
+ }
231
+ const report = (line, text) => {
232
+ if (json)
233
+ streams.out(`${JSON.stringify(line)}\n`);
234
+ else
235
+ streams.out(`${text}\n`);
236
+ };
237
+ let handle;
238
+ try {
239
+ handle = await serveRelay({
240
+ logPath: setup.logPath,
241
+ secret: setup.secret,
242
+ ledger: fileNonceLedger(join(channelLeaseDirFor(setup.logPath), "relay-nonces")),
243
+ host: setup.host,
244
+ port: setup.port,
245
+ gateOptions: { policy: setup.policy },
246
+ proposer: setup.proposer,
247
+ log: (message) => streams.err(`${message}\n`),
248
+ // What reached the gate, by code: never the body, the sender id or the
249
+ // secret. The record in the log is where the attribution lives.
250
+ afterGesture: (response) => {
251
+ const body = response.body;
252
+ const gesture = String(body["gesture"] ?? "?");
253
+ if (body["ok"] === true) {
254
+ report({
255
+ event: "relay_gesture",
256
+ gesture,
257
+ ok: true,
258
+ ...(body["seq"] === undefined ? {} : { seq: body["seq"] }),
259
+ ...(body["event"] === undefined ? {} : { record: body["event"] }),
260
+ ...(body["actor"] === undefined ? {} : { actor: body["actor"] }),
261
+ }, `relay ${gesture}: recorded seq ${String(body["seq"])}${body["actor"] === undefined ? "" : ` by ${String(body["actor"])}`}`);
262
+ }
263
+ else {
264
+ const refusal = body["refusal"];
265
+ const code = String(refusal?.code ?? "?");
266
+ report({ event: "relay_gesture", gesture, ok: false, code }, `relay ${gesture}: refused (${code})`);
267
+ }
268
+ },
269
+ });
270
+ }
271
+ catch (cause) {
272
+ return failure(streams, json, "io", `the relay could not bind ${setup.host}:${String(setup.port)}: ${cause instanceof Error ? cause.message : String(cause)}`, EXIT_IO);
273
+ }
274
+ if (!isLoopbackHost(handle.host))
275
+ streams.err(relayNonLoopbackBanner(handle.host, handle.port));
276
+ if (json) {
277
+ streams.out(`${JSON.stringify({ event: "relay_started", host: handle.host, port: handle.port, path: RELAY_PATH, proposer: setup.proposer })}\n`);
278
+ }
279
+ streams.err(`approval: relay bound http://${handle.host}:${String(handle.port)}${RELAY_PATH}. Every post must carry ${RELAY_SECRET_HEADER}; gestures are attributed to the EdgeOS account the attested policy maps, never to this process. TLS is your proxy's. Press Ctrl-C to stop.\n`);
280
+ return await new Promise((settle) => {
281
+ let stopping = false;
282
+ const stop = () => {
283
+ if (stopping)
284
+ return;
285
+ stopping = true;
286
+ void (async () => {
287
+ await handle.close();
288
+ if (json)
289
+ streams.out(`${JSON.stringify({ event: "stopped", relay: handle.stats() })}\n`);
290
+ process.off("SIGINT", stop);
291
+ process.off("SIGTERM", stop);
292
+ settle(EXIT_OK);
293
+ })();
294
+ };
295
+ process.on("SIGINT", stop);
296
+ process.on("SIGTERM", stop);
297
+ });
298
+ }
299
+ function absolute(value, cwd) {
300
+ return isAbsolute(value) ? value : resolvePathSegments(cwd, value);
301
+ }
302
+ /** `approval channel relay [flags]`. */
303
+ export async function commandChannelRelay(argv, streams, cwd) {
304
+ const json = argv.includes("--json");
305
+ const parsed = parseFlags(argv, RELAY_FLAGS);
306
+ if (!parsed.ok)
307
+ return usageError(streams, json, "usage", parsed.message);
308
+ if (boolFlag(parsed.flags, "--help") || boolFlag(parsed.flags, "-h")) {
309
+ streams.out(`${CHANNEL_RELAY_HELP}\n`);
310
+ return EXIT_OK;
311
+ }
312
+ const extra = parsed.positionals[0];
313
+ if (extra !== undefined)
314
+ return usageError(streams, json, "usage", `unexpected argument ${JSON.stringify(extra)}`);
315
+ const flags = parsed.flags;
316
+ const policyFlag = stringFlag(flags, "--policy");
317
+ const dirFlag = stringFlag(flags, "--dir");
318
+ const policy = policyFlag !== null
319
+ ? { file: absolute(policyFlag, cwd) }
320
+ : { dir: dirFlag === null ? cwd : absolute(dirFlag, cwd) };
321
+ const prepared = prepareRelay({
322
+ logPath: resolvePath(stringFlag(flags, "--log"), DEFAULT_LOG_PATH, cwd),
323
+ policy,
324
+ listen: stringFlag(flags, "--listen"),
325
+ port: stringFlag(flags, "--port"),
326
+ allowNonLoopback: boolFlag(flags, "--allow-non-loopback"),
327
+ proposer: stringFlag(flags, "--proposer"),
328
+ json,
329
+ });
330
+ if (!prepared.ok)
331
+ return usageError(streams, json, prepared.code, prepared.message);
332
+ return await runRelay(prepared.setup, streams);
333
+ }
334
+ //# sourceMappingURL=channel-relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-relay.js","sourceRoot":"","sources":["../../../src/cli/channel-relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,273 @@
1
+ /**
2
+ * `approval channel telegram webhook` — the same channel, the other arrival
3
+ * (SPEC.md §10.3, APRV-424).
4
+ *
5
+ * `approval channel telegram listen` holds a socket open against `getUpdates`
6
+ * for as long as the gate exists. That works, and it costs one always-running
7
+ * process per bot and forbids a second poller on the same token. This verb
8
+ * registers a URL with `setWebhook` instead and serves the callback, which is
9
+ * what makes a sleep-when-idle host able to run a gate, and what lets an
10
+ * operator put the receiver behind a proxy they already own.
11
+ *
12
+ * **Everything below the arrival is unchanged, and deliberately not restated
13
+ * here.** The pending queue is still `dispatchPending`'s, re-derived from the
14
+ * verified log every cycle. The handlers are still {@link wireListener}'s, the
15
+ * same four function objects `listen` registers. A tap still becomes a
16
+ * decision through `channels/telegram.ts`'s `handleUpdate` and
17
+ * `channels/contract.ts`'s `recordChannelDecision`. This file resolves
18
+ * configuration, refuses what it must refuse, binds a port and keeps a cycle
19
+ * running; it decides nothing and it renders nothing.
20
+ *
21
+ * ## The three refusals that are the point of the task
22
+ *
23
+ * 1. **The secret comes from the launch environment, never from the tree.**
24
+ * `APPROVAL_TG_WEBHOOK_SECRET` (`core/telegram-config.ts` says why the name
25
+ * is conventional rather than policy-declared), refused when it is unset,
26
+ * when it is shorter than a value a host would generate, and when it holds
27
+ * a character Telegram will not accept. SPEC.md §11.1 invariant 7.
28
+ * 2. **A post without the matching header decides nothing.** That check is
29
+ * `channels/telegram-webhook.ts`'s, before the path, the method or the
30
+ * body, and its refusal is counted and complained about rather than
31
+ * appended: an endpoint the internet can reach must not be able to grow the
32
+ * tenant's log.
33
+ * 3. **One transport per bot.** The preflight asks the Bot API which webhook
34
+ * holds this bot and refuses a foreign one; `listen` and `approval up` ask
35
+ * the same question and refuse when any webhook holds it. Within one
36
+ * process the channel refuses the second claim outright.
37
+ *
38
+ * ## Where TLS is, and why the bind is loopback
39
+ *
40
+ * Telegram will only deliver to an HTTPS URL, and this process terminates no
41
+ * TLS and holds no certificate — exactly as `approval serve` does not
42
+ * (APRV-421, `cli/serve.ts`). The supported deployment is a loopback bind
43
+ * behind a proxy or a tunnel the operator owns, which is what `--url` names
44
+ * and what `docs/cli-reference.md#channel-telegram-webhook` states. A
45
+ * non-loopback bind takes two flags and prints a banner, because the secret
46
+ * arrives in a header and a cleartext hop hands it to whoever is on it.
47
+ */
48
+ import type { DaemonIdSource } from "../core/daemon-identity.js";
49
+ import { type ListenRefusalCode, type ListenSetup } from "./channel-telegram.js";
50
+ import type { GlossRunner } from "./gloss.js";
51
+ import type { Streams } from "./main.js";
52
+ /**
53
+ * The shortest `secret_token` this verb will start with.
54
+ *
55
+ * `serve/credentials.ts`'s floor, for its reason: not a password policy, a
56
+ * floor under the one mistake that makes the check meaningless, which is a
57
+ * memorable value somebody typed. The host generates this; the floor costs it
58
+ * nothing.
59
+ */
60
+ export declare const WEBHOOK_MIN_SECRET_LENGTH = 24;
61
+ /** How often the dispatch cycle runs when the operator names no period. */
62
+ export declare const WEBHOOK_DEFAULT_CYCLE_MS = 30000;
63
+ /**
64
+ * How long the stop path waits for `deleteWebhook` (APRV-424, second review,
65
+ * note 6).
66
+ *
67
+ * The channel's ordinary request timeout is thirty seconds, which is right for
68
+ * a delivery and wrong for a stop: on top of the receiver's drain it made a
69
+ * second Ctrl-C look swallowed for the better part of a minute. A removal that
70
+ * does not land in five seconds is reported, and the operator is told the
71
+ * webhook is still registered, which is a sentence they can act on.
72
+ */
73
+ export declare const WEBHOOK_STOP_DELETE_TIMEOUT_MS = 5000;
74
+ /**
75
+ * Why a webhook runner could not be built. A closed union, distinct per
76
+ * repair, per SPEC.md §11.1 invariant 6.
77
+ *
78
+ * It sits BESIDE `LISTEN_REFUSAL_CODES` rather than inside it: every reason a
79
+ * listener cannot start is a reason this cannot either (it needs the same
80
+ * token, chat, identity, log and policy), and these are the reasons peculiar
81
+ * to a transport that binds a port and registers a URL.
82
+ */
83
+ export declare const WEBHOOK_REFUSAL_CODES: readonly [
84
+ /** `APPROVAL_TG_WEBHOOK_SECRET` is unset or empty. */
85
+ "webhook-secret-missing",
86
+ /** It is shorter than {@link WEBHOOK_MIN_SECRET_LENGTH}. */
87
+ "webhook-secret-weak",
88
+ /** It holds a character Telegram will not accept in a `secret_token`. */
89
+ "webhook-secret-charset",
90
+ /** No `--url`: nothing to register, so nothing would ever be delivered. */
91
+ "webhook-url-missing",
92
+ /** `--url` is not a URL, or its scheme is not `https`. */
93
+ "webhook-url-insecure",
94
+ /** `--url` names a port Telegram does not deliver to. */
95
+ "webhook-url-port",
96
+ /**
97
+ * `--url` carries userinfo (`https://user:pw@host/hook`) (review finding 10).
98
+ *
99
+ * Refused rather than stripped. A credential in a url is a credential the
100
+ * operator put on a command line, and a runtime that quietly dropped it
101
+ * would register an endpoint that answers differently from the one they
102
+ * typed; a runtime that kept it would carry it into `setWebhook`, into the
103
+ * start-up banner and into every refusal that names the url.
104
+ */
105
+ "webhook-url-userinfo",
106
+ /**
107
+ * `--path` is not a servable path: it does not start with `/`, or it holds a
108
+ * `..` segment (review finding 3).
109
+ */
110
+ "webhook-path-invalid",
111
+ /**
112
+ * `--path` is a servable path, and not the one `--url` names (review finding
113
+ * 3).
114
+ *
115
+ * The pair used to be free to disagree, described as an override for a proxy
116
+ * that rewrites on the way. What it actually bought was a receiver that
117
+ * registered cleanly, verified the secret on every real delivery, and then
118
+ * answered all of them 404: `telegram/webhook` against a url path of
119
+ * `/telegram/webhook`, or `/hook/` against a url ending `/hook`, both start
120
+ * without a word. A path mismatch is now a startup refusal.
121
+ */
122
+ "webhook-path-mismatch",
123
+ /** `--cycle` was not a duration. */
124
+ "webhook-cycle",
125
+ /**
126
+ * `setWebhook` was refused by the Bot API.
127
+ *
128
+ * Emitted by {@link runWebhook} since the review of this task; before it the
129
+ * code was declared here and the failure was reported as a bare `io`, which
130
+ * made "Telegram will not take this registration" indistinguishable from
131
+ * "the log could not be read". The status and the Bot API's own description
132
+ * are carried, redacted of the token and the secret by the channel.
133
+ */
134
+ "webhook-registration-failed"];
135
+ export type WebhookRefusalCode = (typeof WEBHOOK_REFUSAL_CODES)[number];
136
+ export interface WebhookSetup {
137
+ /** Everything a listener has: the channel, the log, the identity, the policy. */
138
+ listen: ListenSetup;
139
+ /** The `secret_token`, as a value. Never printed, never logged, never recorded. */
140
+ secret: string;
141
+ /** The public URL registered with `setWebhook`. */
142
+ url: string;
143
+ /** The path this process answers. Derived from `--url` unless `--path` says otherwise. */
144
+ path: string;
145
+ /** The interface to bind. Loopback unless the operator widened it. */
146
+ host: string;
147
+ /** The port to bind. Distinct from the URL's port, which is the proxy's. */
148
+ port: number;
149
+ /** How often the dispatch cycle runs. */
150
+ cycleMs: number;
151
+ /**
152
+ * `--reclaim`: register over a webhook that already holds this bot (review
153
+ * finding 6).
154
+ *
155
+ * Carried on the setup rather than consulted from the flags again, because
156
+ * the check it overrides is made by {@link claimListenerBot}, which this
157
+ * verb shares with the poller.
158
+ */
159
+ reclaim: boolean;
160
+ /**
161
+ * The daemon id every record this process appends carries (APRV-448),
162
+ * resolved by {@link prepareWebhook} from the launch environment and the log
163
+ * path with the function the daemon loop uses, and declared by
164
+ * {@link runWebhook} before anything can append. An unusable declared id never
165
+ * reaches a setup: it is refused `daemon-id-invalid` at preparation.
166
+ */
167
+ daemon: {
168
+ ok: true;
169
+ id: string;
170
+ source: DaemonIdSource;
171
+ };
172
+ }
173
+ export type WebhookPreparation = {
174
+ ok: true;
175
+ setup: WebhookSetup;
176
+ } | {
177
+ ok: false;
178
+ /**
179
+ * This verb's own codes, the listener's, and `daemon-id-invalid`
180
+ * (APRV-448), which is the write boundary's own code for the same fault,
181
+ * reported here before anything binds rather than on the first append.
182
+ */
183
+ code: WebhookRefusalCode | ListenRefusalCode | "daemon-id-invalid";
184
+ message: string;
185
+ };
186
+ /** Everything {@link prepareWebhook} needs, already resolved. */
187
+ export interface WebhookRequest {
188
+ logPath: string;
189
+ policy: {
190
+ dir?: string;
191
+ file?: string;
192
+ };
193
+ as: string | null;
194
+ payloads: string | null;
195
+ apiBase: string | null;
196
+ json: boolean;
197
+ allowCrossInstance?: boolean;
198
+ /** `--url` as typed, or `null`. */
199
+ url: string | null;
200
+ /** `--path`, or `null` to take the URL's own path. */
201
+ path: string | null;
202
+ /** The bind, already parsed by the verb. */
203
+ host: string;
204
+ port: number;
205
+ /** `--cycle` as typed, or `null` for {@link WEBHOOK_DEFAULT_CYCLE_MS}. */
206
+ cycle: string | null;
207
+ /** `--reclaim` as passed. Defaults to false, which is the refusing side. */
208
+ reclaim?: boolean;
209
+ /** The environment the secret is read from. Injectable for tests. */
210
+ env?: NodeJS.ProcessEnv;
211
+ log(message: string): void;
212
+ /** The gloss runner, if the caller wants one. See {@link ListenSetup.gloss}. */
213
+ gloss?: GlossRunner;
214
+ }
215
+ /**
216
+ * Everything that can fail without touching the network, in order.
217
+ *
218
+ * {@link prepareListen} first, because a webhook runner that has no bot token,
219
+ * no chat and no human identity is not a webhook problem: the two verbs refuse
220
+ * those the same way, in the same sentences, for the reason `prepareListen`
221
+ * exists at all. Then the three this transport adds.
222
+ *
223
+ * It PRINTS NOTHING and CHOOSES NO EXIT CODE, exactly as `prepareListen` does.
224
+ */
225
+ export declare function prepareWebhook(request: WebhookRequest): WebhookPreparation;
226
+ /**
227
+ * `--listen` / `--port` -> the interface and port this process binds (review
228
+ * finding 9).
229
+ *
230
+ * Its own function, and exported, because three refusals live in it and each
231
+ * one is a thing an operator can type:
232
+ *
233
+ * - **Port 0 is refused.** The kernel hands out an ephemeral port for it, and
234
+ * an ephemeral port is the one thing this transport cannot use: the whole
235
+ * deployment is a proxy forwarding a fixed public url to a fixed local
236
+ * address, so a receiver that bound 51423 registered a url nothing would
237
+ * ever reach and said nothing about it. Tests bind 0 by calling the receiver
238
+ * directly, which is the level where an ephemeral port is the point.
239
+ * - **A port outside 1..65535 is refused**, on either flag. `parseListen`
240
+ * accepts 0 for `approval serve`'s own reasons, so the floor is applied
241
+ * here rather than assumed there.
242
+ * - **A routable interface needs `--allow-non-loopback`**, because this
243
+ * process speaks plain HTTP and the secret arrives in a header.
244
+ */
245
+ export type WebhookBind = {
246
+ ok: true;
247
+ host: string;
248
+ port: number;
249
+ } | {
250
+ ok: false;
251
+ message: string;
252
+ };
253
+ export declare function resolveWebhookBind(listenFlag: string | null, portFlag: string | null, allowNonLoopback: boolean): WebhookBind;
254
+ /**
255
+ * The banner a non-loopback bind prints, every time, on stderr.
256
+ *
257
+ * `cli/serve.ts`'s, with the one credential this surface has in place of that
258
+ * one's two. Reached only when the operator passed both `--listen` naming a
259
+ * routable interface and `--allow-non-loopback`, so it is not a warning about
260
+ * something that happened by accident.
261
+ */
262
+ export declare function webhookNonLoopbackBanner(host: string, port: number): string;
263
+ /**
264
+ * Run the receiver until a signal stops it.
265
+ *
266
+ * The order is the one `cli/serve.ts` argues for, with one change that is this
267
+ * transport's own: everything that can refuse refuses first, and the LOCAL
268
+ * bind happens before `setWebhook`, so there is no window in which Telegram is
269
+ * posting to a port nothing is listening on.
270
+ */
271
+ export declare function runWebhook(setup: WebhookSetup, streams: Streams): Promise<number>;
272
+ /** `approval channel telegram webhook [flags]`. */
273
+ export declare function commandTelegramWebhook(argv: string[], streams: Streams, cwd: string): Promise<number>;