approval-md 0.3.0 → 0.4.1

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 (270) hide show
  1. package/README.md +83 -914
  2. package/SPEC.md +101 -13
  3. package/cli.js +134 -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 +72 -0
  8. package/dist/src/channels/contract.js +161 -1
  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.d.ts +25 -0
  17. package/dist/src/channels/tagging.js +63 -6
  18. package/dist/src/channels/tagging.js.map +1 -1
  19. package/dist/src/channels/telegram-minimal.d.ts +251 -0
  20. package/dist/src/channels/telegram-minimal.js +618 -0
  21. package/dist/src/channels/telegram-minimal.js.map +1 -0
  22. package/dist/src/channels/telegram-webhook.d.ts +273 -0
  23. package/dist/src/channels/telegram-webhook.js +536 -0
  24. package/dist/src/channels/telegram-webhook.js.map +1 -0
  25. package/dist/src/channels/telegram.d.ts +725 -20
  26. package/dist/src/channels/telegram.js +1332 -123
  27. package/dist/src/channels/telegram.js.map +1 -1
  28. package/dist/src/cli/amend.js +358 -5
  29. package/dist/src/cli/amend.js.map +1 -1
  30. package/dist/src/cli/attest.js +43 -6
  31. package/dist/src/cli/attest.js.map +1 -1
  32. package/dist/src/cli/audit-card.d.ts +1 -1
  33. package/dist/src/cli/audit-card.js +40 -1
  34. package/dist/src/cli/audit-card.js.map +1 -1
  35. package/dist/src/cli/audit.d.ts +7 -5
  36. package/dist/src/cli/audit.js +22 -10
  37. package/dist/src/cli/audit.js.map +1 -1
  38. package/dist/src/cli/channel-relay.d.ts +108 -0
  39. package/dist/src/cli/channel-relay.js +334 -0
  40. package/dist/src/cli/channel-relay.js.map +1 -0
  41. package/dist/src/cli/channel-telegram-webhook.d.ts +275 -0
  42. package/dist/src/cli/channel-telegram-webhook.js +716 -0
  43. package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
  44. package/dist/src/cli/channel-telegram.d.ts +347 -28
  45. package/dist/src/cli/channel-telegram.js +648 -56
  46. package/dist/src/cli/channel-telegram.js.map +1 -1
  47. package/dist/src/cli/channel.d.ts +1 -1
  48. package/dist/src/cli/channel.js +5 -1
  49. package/dist/src/cli/channel.js.map +1 -1
  50. package/dist/src/cli/daemon.js +37 -5
  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 +856 -111
  54. package/dist/src/cli/doctor.js.map +1 -1
  55. package/dist/src/cli/execute.d.ts +29 -1
  56. package/dist/src/cli/execute.js +387 -21
  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 +10 -1
  65. package/dist/src/cli/help.js +283 -54
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook.d.ts +485 -6
  68. package/dist/src/cli/hook.js +2370 -70
  69. package/dist/src/cli/hook.js.map +1 -1
  70. package/dist/src/cli/init.js +16 -7
  71. package/dist/src/cli/init.js.map +1 -1
  72. package/dist/src/cli/instructions.js +24 -3
  73. package/dist/src/cli/instructions.js.map +1 -1
  74. package/dist/src/cli/log-anchor.d.ts +55 -0
  75. package/dist/src/cli/log-anchor.js +199 -60
  76. package/dist/src/cli/log-anchor.js.map +1 -1
  77. package/dist/src/cli/log-unlock.d.ts +23 -0
  78. package/dist/src/cli/log-unlock.js +109 -0
  79. package/dist/src/cli/log-unlock.js.map +1 -0
  80. package/dist/src/cli/main.d.ts +0 -12
  81. package/dist/src/cli/main.js +72 -4
  82. package/dist/src/cli/main.js.map +1 -1
  83. package/dist/src/cli/muse.d.ts +2 -0
  84. package/dist/src/cli/muse.js +62 -0
  85. package/dist/src/cli/muse.js.map +1 -0
  86. package/dist/src/cli/payload.js +79 -1
  87. package/dist/src/cli/payload.js.map +1 -1
  88. package/dist/src/cli/scaffold.js +6 -0
  89. package/dist/src/cli/scaffold.js.map +1 -1
  90. package/dist/src/cli/serve.d.ts +53 -0
  91. package/dist/src/cli/serve.js +270 -0
  92. package/dist/src/cli/serve.js.map +1 -0
  93. package/dist/src/cli/up.js +26 -2
  94. package/dist/src/cli/up.js.map +1 -1
  95. package/dist/src/cli/verb-registry.d.ts +2 -0
  96. package/dist/src/cli/verb-registry.js +338 -39
  97. package/dist/src/cli/verb-registry.js.map +1 -1
  98. package/dist/src/cli/wordmark.d.ts +2 -2
  99. package/dist/src/cli/wordmark.js +2 -2
  100. package/dist/src/core/attest-bootstrap.d.ts +95 -0
  101. package/dist/src/core/attest-bootstrap.js +135 -0
  102. package/dist/src/core/attest-bootstrap.js.map +1 -0
  103. package/dist/src/core/attest.d.ts +20 -3
  104. package/dist/src/core/attest.js +25 -0
  105. package/dist/src/core/attest.js.map +1 -1
  106. package/dist/src/core/audit.d.ts +153 -12
  107. package/dist/src/core/audit.js +415 -20
  108. package/dist/src/core/audit.js.map +1 -1
  109. package/dist/src/core/channel-lease.d.ts +322 -0
  110. package/dist/src/core/channel-lease.js +663 -0
  111. package/dist/src/core/channel-lease.js.map +1 -0
  112. package/dist/src/core/command-class.d.ts +51 -0
  113. package/dist/src/core/command-class.js +766 -8
  114. package/dist/src/core/command-class.js.map +1 -1
  115. package/dist/src/core/daemon-host.d.ts +178 -0
  116. package/dist/src/core/daemon-host.js +228 -0
  117. package/dist/src/core/daemon-host.js.map +1 -0
  118. package/dist/src/core/daemon-identity.d.ts +238 -0
  119. package/dist/src/core/daemon-identity.js +252 -0
  120. package/dist/src/core/daemon-identity.js.map +1 -0
  121. package/dist/src/core/gate.d.ts +364 -6
  122. package/dist/src/core/gate.js +802 -27
  123. package/dist/src/core/gate.js.map +1 -1
  124. package/dist/src/core/harness-version.d.ts +128 -4
  125. package/dist/src/core/harness-version.js +179 -12
  126. package/dist/src/core/harness-version.js.map +1 -1
  127. package/dist/src/core/harness-wait.d.ts +82 -3
  128. package/dist/src/core/harness-wait.js +89 -3
  129. package/dist/src/core/harness-wait.js.map +1 -1
  130. package/dist/src/core/log-lock.d.ts +435 -0
  131. package/dist/src/core/log-lock.js +1093 -0
  132. package/dist/src/core/log-lock.js.map +1 -0
  133. package/dist/src/core/log-subscribe.d.ts +21 -0
  134. package/dist/src/core/log-subscribe.js +16 -6
  135. package/dist/src/core/log-subscribe.js.map +1 -1
  136. package/dist/src/core/log-write-layer.d.ts +55 -0
  137. package/dist/src/core/log-write-layer.js +91 -0
  138. package/dist/src/core/log-write-layer.js.map +1 -0
  139. package/dist/src/core/log.d.ts +130 -22
  140. package/dist/src/core/log.js +473 -50
  141. package/dist/src/core/log.js.map +1 -1
  142. package/dist/src/core/payload-census.d.ts +51 -0
  143. package/dist/src/core/payload-census.js +125 -2
  144. package/dist/src/core/payload-census.js.map +1 -1
  145. package/dist/src/core/payload-store.d.ts +31 -0
  146. package/dist/src/core/payload-store.js +113 -8
  147. package/dist/src/core/payload-store.js.map +1 -1
  148. package/dist/src/core/payload.d.ts +18 -3
  149. package/dist/src/core/payload.js +20 -4
  150. package/dist/src/core/payload.js.map +1 -1
  151. package/dist/src/core/policy-diff.d.ts +10 -0
  152. package/dist/src/core/policy-diff.js +10 -2
  153. package/dist/src/core/policy-diff.js.map +1 -1
  154. package/dist/src/core/policy-explain.d.ts +7 -1
  155. package/dist/src/core/policy-explain.js +16 -1
  156. package/dist/src/core/policy-explain.js.map +1 -1
  157. package/dist/src/core/policy-load.d.ts +24 -0
  158. package/dist/src/core/policy-load.js.map +1 -1
  159. package/dist/src/core/policy-match.d.ts +38 -0
  160. package/dist/src/core/policy-match.js +35 -0
  161. package/dist/src/core/policy-match.js.map +1 -1
  162. package/dist/src/core/policy-proposal.d.ts +40 -0
  163. package/dist/src/core/policy-proposal.js +37 -16
  164. package/dist/src/core/policy-proposal.js.map +1 -1
  165. package/dist/src/core/prompt-layout.d.ts +186 -1
  166. package/dist/src/core/prompt-layout.js +345 -3
  167. package/dist/src/core/prompt-layout.js.map +1 -1
  168. package/dist/src/core/read-scope.d.ts +10 -0
  169. package/dist/src/core/read-scope.js +4 -0
  170. package/dist/src/core/read-scope.js.map +1 -1
  171. package/dist/src/core/run-payload.d.ts +160 -0
  172. package/dist/src/core/run-payload.js +304 -0
  173. package/dist/src/core/run-payload.js.map +1 -0
  174. package/dist/src/core/sender-identity.d.ts +59 -11
  175. package/dist/src/core/sender-identity.js +79 -11
  176. package/dist/src/core/sender-identity.js.map +1 -1
  177. package/dist/src/core/state.d.ts +52 -1
  178. package/dist/src/core/state.js +52 -6
  179. package/dist/src/core/state.js.map +1 -1
  180. package/dist/src/core/telegram-config.d.ts +79 -0
  181. package/dist/src/core/telegram-config.js +108 -0
  182. package/dist/src/core/telegram-config.js.map +1 -1
  183. package/dist/src/core/validate.d.ts +20 -6
  184. package/dist/src/core/validate.js +0 -0
  185. package/dist/src/core/validate.js.map +1 -1
  186. package/dist/src/core/verify.d.ts +23 -1
  187. package/dist/src/core/verify.js +20 -2
  188. package/dist/src/core/verify.js.map +1 -1
  189. package/dist/src/daemon/audit.d.ts +80 -5
  190. package/dist/src/daemon/audit.js +126 -6
  191. package/dist/src/daemon/audit.js.map +1 -1
  192. package/dist/src/daemon/daemon.d.ts +174 -2
  193. package/dist/src/daemon/daemon.js +298 -7
  194. package/dist/src/daemon/daemon.js.map +1 -1
  195. package/dist/src/daemon/git-evidence.d.ts +2 -2
  196. package/dist/src/daemon/git-evidence.js +1 -1
  197. package/dist/src/daemon/projection.d.ts +13 -2
  198. package/dist/src/daemon/projection.js +39 -6
  199. package/dist/src/daemon/projection.js.map +1 -1
  200. package/dist/src/mcp/server.d.ts +39 -3
  201. package/dist/src/mcp/server.js +22 -3
  202. package/dist/src/mcp/server.js.map +1 -1
  203. package/dist/src/muse/server.d.ts +16 -0
  204. package/dist/src/muse/server.js +238 -0
  205. package/dist/src/muse/server.js.map +1 -0
  206. package/dist/src/serve/archive.d.ts +166 -0
  207. package/dist/src/serve/archive.js +363 -0
  208. package/dist/src/serve/archive.js.map +1 -0
  209. package/dist/src/serve/arguments.d.ts +93 -0
  210. package/dist/src/serve/arguments.js +228 -0
  211. package/dist/src/serve/arguments.js.map +1 -0
  212. package/dist/src/serve/credentials.d.ts +91 -0
  213. package/dist/src/serve/credentials.js +143 -0
  214. package/dist/src/serve/credentials.js.map +1 -0
  215. package/dist/src/serve/follow.d.ts +86 -0
  216. package/dist/src/serve/follow.js +101 -0
  217. package/dist/src/serve/follow.js.map +1 -0
  218. package/dist/src/serve/hook-thread.d.ts +134 -0
  219. package/dist/src/serve/hook-thread.js +333 -0
  220. package/dist/src/serve/hook-thread.js.map +1 -0
  221. package/dist/src/serve/hook-worker.d.ts +134 -0
  222. package/dist/src/serve/hook-worker.js +173 -0
  223. package/dist/src/serve/hook-worker.js.map +1 -0
  224. package/dist/src/serve/server.d.ts +412 -0
  225. package/dist/src/serve/server.js +1251 -0
  226. package/dist/src/serve/server.js.map +1 -0
  227. package/docs/README-extended.md +768 -0
  228. package/docs/cli-reference.md +2139 -67
  229. package/package.json +4 -2
  230. package/schema/event.schema.json +413 -30
  231. package/schema/fixtures/event/invalid/audit-lock-reclaimed-age-without-created.json +19 -0
  232. package/schema/fixtures/event/invalid/audit-lock-reclaimed-agent-actor.json +14 -0
  233. package/schema/fixtures/event/invalid/audit-lock-reclaimed-lockfile-path.json +14 -0
  234. package/schema/fixtures/event/invalid/audit-lock-reclaimed-operator-cleared-system-actor.json +14 -0
  235. package/schema/fixtures/event/invalid/audit-lock-reclaimed-unknown-reason.json +14 -0
  236. package/schema/fixtures/event/invalid/audit-lock-reclaimed-without-age.json +13 -0
  237. package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +6 -1
  238. package/schema/fixtures/event/invalid/audit-reviewed-bad-payload-hash.json +21 -0
  239. package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +5 -0
  240. package/schema/fixtures/event/invalid/audit-reviewed-no-subject-hash.json +19 -0
  241. package/schema/fixtures/event/invalid/audit-reviewed-no-subject-seq.json +19 -0
  242. package/schema/fixtures/event/invalid/audit-reviewed-no-verdict-source.json +19 -0
  243. package/schema/fixtures/event/invalid/audit-reviewed-no-verdict.json +19 -0
  244. package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +6 -1
  245. package/schema/fixtures/event/invalid/audit-sampled-no-policy-hash.json +14 -0
  246. package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
  247. package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
  248. package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
  249. package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
  250. package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
  251. package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
  252. package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
  253. package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
  254. package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
  255. package/schema/fixtures/event/valid/audit-lock-reclaimed-implausible-created.json +18 -0
  256. package/schema/fixtures/event/valid/audit-lock-reclaimed-legacy.json +14 -0
  257. package/schema/fixtures/event/valid/audit-lock-reclaimed-unlock.json +19 -0
  258. package/schema/fixtures/event/valid/audit-lock-reclaimed.json +19 -0
  259. package/schema/fixtures/event/valid/audit-reviewed-reaction.json +5 -0
  260. package/schema/fixtures/event/valid/audit-reviewed.json +6 -1
  261. package/schema/fixtures/event/valid/audit-sampled.json +2 -1
  262. package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
  263. package/schema/fixtures/event/valid/daemon-identity.json +16 -0
  264. package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
  265. package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
  266. package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
  267. package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
  268. package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
  269. package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
  270. package/schema/policy.schema.json +102 -6
@@ -0,0 +1,536 @@
1
+ /**
2
+ * The Telegram webhook receiver (SPEC.md §10.3, APRV-424).
3
+ *
4
+ * §10.3 has always written the channel interface as `notify(request) ->
5
+ * delivery_id`, `poll()/webhook() -> decision`. Until this task only the first
6
+ * half of the second clause existed: the Telegram channel long-polled
7
+ * `getUpdates`, which needs one always-running process per bot and forbids a
8
+ * second poller on the same token. This is the other half. Telegram posts each
9
+ * update to a URL the runtime registered, and a host that sleeps between
10
+ * requests can therefore serve a gate.
11
+ *
12
+ * ## It decides nothing, and it barely reads
13
+ *
14
+ * This file checks one header, one path, one method and one body size, parses
15
+ * JSON, and hands the object to {@link TelegramChannel.deliverUpdate}. Which
16
+ * account the Bot API attributed the tap to, which approver the attested
17
+ * policy maps that account to, whether the gate will take the answer, and what
18
+ * the message is edited to say afterwards are all decided exactly where they
19
+ * were decided before this task existed — in `channels/telegram.ts`'s
20
+ * `handleUpdate` and in `channels/contract.ts`'s `recordChannelDecision`,
21
+ * reached by the same call the poll loop makes. An update that arrives here is
22
+ * an update; it is not evidence about itself.
23
+ *
24
+ * ## The secret is the whole of the authentication
25
+ *
26
+ * A webhook URL is a public endpoint. Anyone who can route to it can POST a
27
+ * JSON object that LOOKS like a tap from the approver's chat, and nothing in
28
+ * the body could tell the difference: SPEC.md §11.1 invariant 4 — a
29
+ * self-reported field never reduces scrutiny — is exactly the rule that makes
30
+ * a body-only check worthless here. So Telegram's own `secret_token` is
31
+ * REQUIRED rather than optional. It is set on `setWebhook`, echoed on every
32
+ * delivery in `X-Telegram-Bot-Api-Secret-Token`, compared here in constant
33
+ * time, and a post without the matching value is refused before its path, its
34
+ * method or its body is looked at.
35
+ *
36
+ * Three consequences, each deliberate:
37
+ *
38
+ * - **The comparison is over SHA-256 digests under `timingSafeEqual`**, which
39
+ * is constant-time AND length-independent (`timingSafeEqual` throws on a
40
+ * length mismatch, which would itself be an oracle for the secret's length).
41
+ * `serve/credentials.ts` states the same argument for the same reason.
42
+ * - **Duplicate headers are refused rather than resolved.** Node joins
43
+ * repeated headers, so two of them would make the value this server compares
44
+ * differ from the one a reader of the request would name. There is no
45
+ * correct choice; the request is malformed.
46
+ * - **A refusal appends NOTHING to the log.** It is counted, complained about
47
+ * on the runner's stderr and answered with its own machine-readable code,
48
+ * and it never reaches the decision path. This is `TELEGRAM_ANOMALY_KINDS`'s
49
+ * rule, and it matters more here than it does there: writing "somebody
50
+ * posted a forgery" into an append-only approval log would let anyone who
51
+ * can reach this URL grow the record a human is asked to trust. A refusal is
52
+ * a fact about this transport, and this transport reports it where facts
53
+ * about transports belong.
54
+ *
55
+ * ## What is reused from `approval serve`, and what could not be
56
+ *
57
+ * The shape is APRV-421's, deliberately: loopback bind by default with the
58
+ * operator's proxy terminating TLS, the credential checked before the URL is
59
+ * parsed, a refusal carried as a body with a frozen code rather than as a bare
60
+ * status, an oversized body drained before the refusal is written (a response
61
+ * written while the client is still uploading closes the socket under it), and
62
+ * no state a restart loses.
63
+ *
64
+ * Its CODE could not be reused, and the reason is structural rather than
65
+ * stylistic. `src/serve/` imports `src/cli/`, which imports `src/channels/`;
66
+ * an import from here back into `src/serve/` would close an ESM cycle, which
67
+ * is not a compile error but a binding that is `undefined` in one direction on
68
+ * the day module initialisation order changes. `tests/layering.test.ts` pins
69
+ * that direction. The two servers are also different surfaces: that one
70
+ * answers two bearer credentials for two parties across six routes, this one
71
+ * answers Telegram's echoed secret on one.
72
+ *
73
+ * ## One update at a time
74
+ *
75
+ * Deliveries are serialized through a promise chain. `handleUpdate` keeps a
76
+ * single callback-query ack slot and the delivery maps are plain `Map`s, so
77
+ * two updates handled concurrently would interleave in ways nobody ordered.
78
+ * The poll loop gets this for free by awaiting each update in turn; an HTTP
79
+ * server has to say it.
80
+ *
81
+ * ## Stopping waits for the update it is holding
82
+ *
83
+ * A stop closes the listener, drains what is in flight, and only then drops
84
+ * the sockets. The first version of this file did the opposite — it destroyed
85
+ * every socket synchronously and resolved in about a millisecond — so
86
+ * `deleteWebhook`, the "stopped" line and the process's exit all ran while an
87
+ * update was still being handled: an append in progress, a dispatch cycle
88
+ * halfway through a send, and a caller whose response never arrived. The order
89
+ * is now stop accepting, drain, drop, and the verb's own stop path does its
90
+ * `deleteWebhook` after this resolves rather than beside it.
91
+ *
92
+ * ## Redelivery is safe because the gate is
93
+ *
94
+ * Telegram retries a delivery it did not get a 2xx for, so the same tap can
95
+ * arrive twice. Nothing here deduplicates, and nothing needs to: the second
96
+ * one reaches `decide()` through the same path as the first and is refused
97
+ * `already-decided` with the first human answer standing. That is the same
98
+ * property a button pressed twice has had since APRV-26.
99
+ */
100
+ import { createHash, timingSafeEqual } from "node:crypto";
101
+ import { createServer } from "node:http";
102
+ /**
103
+ * The header Telegram echoes the `secret_token` in, lowercased as Node
104
+ * presents it.
105
+ */
106
+ export const TELEGRAM_SECRET_HEADER = "x-telegram-bot-api-secret-token";
107
+ /** The path this server answers when the operator names none. */
108
+ export const TELEGRAM_WEBHOOK_DEFAULT_PATH = "/telegram/webhook";
109
+ /** The interface it binds when the operator names none. Loopback, always. */
110
+ export const TELEGRAM_WEBHOOK_DEFAULT_HOST = "127.0.0.1";
111
+ /** The port it binds when the operator names none. */
112
+ export const TELEGRAM_WEBHOOK_DEFAULT_PORT = 4683;
113
+ /**
114
+ * Largest body accepted. A Telegram update is a small JSON object; the
115
+ * largest thing one can carry is a message's 4096-character text.
116
+ */
117
+ export const TELEGRAM_WEBHOOK_MAX_BODY_BYTES = 256 * 1024;
118
+ /**
119
+ * How much of an oversized body is read and thrown away before the socket is
120
+ * dropped. `serve/server.ts` carries the argument: the refusal cannot be
121
+ * written until the client stops writing, or the response closes the socket
122
+ * under it and the caller sees a transport error instead of a refusal.
123
+ */
124
+ export const TELEGRAM_WEBHOOK_DRAIN_LIMIT_BYTES = 4 * 1024 * 1024;
125
+ /**
126
+ * How long {@link TelegramWebhookHandle.close} waits for requests already in
127
+ * flight before it destroys their sockets (APRV-424, review finding 2).
128
+ *
129
+ * A stop has to end. What this bounds is the pathological case — a client that
130
+ * opened a request and stopped writing, a handler that will never return — and
131
+ * not the ordinary one, which is an update whose decision is being appended
132
+ * and takes milliseconds. The ordinary one is what the drain exists for: the
133
+ * first version of this file destroyed every socket synchronously and
134
+ * resolved in about a millisecond, so `deleteWebhook`, the "stopped" line and
135
+ * the process's exit all happened while an append and a dispatch were still
136
+ * running.
137
+ */
138
+ export const TELEGRAM_WEBHOOK_CLOSE_TIMEOUT_MS = 10_000;
139
+ /** How long the close drain waits between checks of the in-flight count. */
140
+ export const TELEGRAM_WEBHOOK_DRAIN_POLL_MS = 5;
141
+ /**
142
+ * Everything this receiver refuses, and nothing else. Frozen, per SPEC.md
143
+ * §11.1 invariant 6: a refusal is machine-readable and distinct.
144
+ *
145
+ * Distinct because the repairs are distinct. A secret mismatch is a forgery or
146
+ * a rotation the registration did not follow; an unknown path is a URL that
147
+ * does not match the one registered; a body too large is a caller that is not
148
+ * Telegram. None of them is a decision, and none of them is the same fact.
149
+ */
150
+ export const TELEGRAM_WEBHOOK_REFUSAL_CODES = [
151
+ /** No `X-Telegram-Bot-Api-Secret-Token`, or one that is not the registered value. */
152
+ "webhook-secret-mismatch",
153
+ /** More than one secret header. Malformed, and not resolvable by choosing. */
154
+ "webhook-duplicate-secret-header",
155
+ /**
156
+ * The request line and the `Host` header do not form a URL (400).
157
+ *
158
+ * Split from `webhook-unknown-path` in the review of this task: one code for
159
+ * both collapsed "what arrived is not a request this server can parse" into
160
+ * "you are posting to a path this server does not serve", and those two
161
+ * share nothing but a status family — the first is a broken proxy or a
162
+ * hand-written client, the second is a registration that does not match the
163
+ * bind. `src/serve/server.ts` makes the same split, between
164
+ * `serve-malformed-url` and `serve-unknown-path`.
165
+ */
166
+ "webhook-malformed-request",
167
+ /** A path this server does not serve (404). */
168
+ "webhook-unknown-path",
169
+ /** The right path, the wrong method. Telegram POSTs. */
170
+ "webhook-method-not-allowed",
171
+ /** Larger than {@link TELEGRAM_WEBHOOK_MAX_BODY_BYTES}. */
172
+ "webhook-body-too-large",
173
+ /** The body could not be read off the socket, or is not JSON. */
174
+ "webhook-body-unreadable",
175
+ /** JSON that is not an Update object. */
176
+ "webhook-not-an-update",
177
+ /** The channel is already receiving updates by the other transport. */
178
+ "webhook-transport-conflict",
179
+ /** The channel threw while handling the update. Nothing is claimed about the log. */
180
+ "webhook-handler-failed",
181
+ ];
182
+ function emptyRefusals() {
183
+ return {
184
+ "webhook-secret-mismatch": 0,
185
+ "webhook-duplicate-secret-header": 0,
186
+ "webhook-malformed-request": 0,
187
+ "webhook-unknown-path": 0,
188
+ "webhook-method-not-allowed": 0,
189
+ "webhook-body-too-large": 0,
190
+ "webhook-body-unreadable": 0,
191
+ "webhook-not-an-update": 0,
192
+ "webhook-transport-conflict": 0,
193
+ "webhook-handler-failed": 0,
194
+ };
195
+ }
196
+ function digest(value) {
197
+ return createHash("sha256").update(value, "utf8").digest();
198
+ }
199
+ /**
200
+ * Is `offered` the registered secret?
201
+ *
202
+ * Both sides are hashed first, so the comparison is over two equal-length
203
+ * buffers and `timingSafeEqual` can never throw — which is what keeps the
204
+ * length of the real secret out of the answer.
205
+ */
206
+ export function secretMatches(expected, offered) {
207
+ if (offered === null)
208
+ return false;
209
+ return timingSafeEqual(digest(expected), digest(offered));
210
+ }
211
+ /**
212
+ * The one secret header a request offered, `null` for none, and `"duplicate"`
213
+ * for more than one.
214
+ *
215
+ * Read off `rawHeaders` rather than `headers`, because Node joins repeats of a
216
+ * non-special header into one comma-separated value and the join is exactly
217
+ * what hides the second one.
218
+ */
219
+ export function secretHeaderOf(rawHeaders) {
220
+ const values = [];
221
+ for (let index = 0; index + 1 < rawHeaders.length; index += 2) {
222
+ const name = rawHeaders[index];
223
+ if (name !== undefined && name.toLowerCase() === TELEGRAM_SECRET_HEADER) {
224
+ values.push(rawHeaders[index + 1] ?? "");
225
+ }
226
+ }
227
+ if (values.length === 0)
228
+ return null;
229
+ if (values.length > 1)
230
+ return "duplicate";
231
+ return values[0] ?? "";
232
+ }
233
+ /** Read one request body, bounded, draining an oversized one before refusing. */
234
+ async function readBody(req) {
235
+ const chunks = [];
236
+ let size = 0;
237
+ let oversized = false;
238
+ const tooLarge = {
239
+ ok: false,
240
+ code: "webhook-body-too-large",
241
+ message: `request body exceeds ${String(TELEGRAM_WEBHOOK_MAX_BODY_BYTES)} bytes`,
242
+ };
243
+ try {
244
+ for await (const chunk of req) {
245
+ const buffer = chunk;
246
+ size += buffer.length;
247
+ if (size > TELEGRAM_WEBHOOK_MAX_BODY_BYTES) {
248
+ // Discarded as it arrives rather than returned here: a response
249
+ // written while the client is still uploading closes the socket under
250
+ // it, and a caller that got a transport error has no refusal to read.
251
+ // Bounded twice over — past the drain limit the socket is simply
252
+ // dropped, because a caller that streams forever is not owed a polite
253
+ // answer.
254
+ oversized = true;
255
+ chunks.length = 0;
256
+ if (size > TELEGRAM_WEBHOOK_DRAIN_LIMIT_BYTES) {
257
+ req.destroy();
258
+ break;
259
+ }
260
+ continue;
261
+ }
262
+ chunks.push(buffer);
263
+ }
264
+ if (oversized)
265
+ return tooLarge;
266
+ }
267
+ catch (cause) {
268
+ if (oversized)
269
+ return tooLarge;
270
+ return {
271
+ ok: false,
272
+ code: "webhook-body-unreadable",
273
+ message: cause instanceof Error ? cause.message : String(cause),
274
+ };
275
+ }
276
+ return { ok: true, text: Buffer.concat(chunks).toString("utf8") };
277
+ }
278
+ /**
279
+ * Start the receiver. Resolves once it is bound and its real port is known;
280
+ * rejects when the bind fails, so the verb can report it and exit.
281
+ *
282
+ * The channel's transport is claimed BEFORE the listener exists, for the
283
+ * reason `approval serve` settles its credentials first: a server that bound
284
+ * and then discovered it could not receive would be a server Telegram had
285
+ * already posted to.
286
+ */
287
+ export async function serveTelegramWebhook(options) {
288
+ const claim = options.channel.claimTransport("webhook");
289
+ if (!claim.ok)
290
+ throw new Error(claim.message);
291
+ const path = options.path ?? TELEGRAM_WEBHOOK_DEFAULT_PATH;
292
+ const host = options.host ?? TELEGRAM_WEBHOOK_DEFAULT_HOST;
293
+ const complain = options.log ??
294
+ ((message) => {
295
+ process.stderr.write(`${message}\n`);
296
+ });
297
+ const stats = { requests: 0, updates: 0, refusals: emptyRefusals() };
298
+ const sockets = new Set();
299
+ /**
300
+ * Requests accepted and not yet answered.
301
+ *
302
+ * The serialize queue alone is not the whole of "busy": a request that is
303
+ * still having its body read off the socket, or one whose refusal has been
304
+ * decided and not yet written, is on no queue and would be torn off by a
305
+ * socket destroy. So the drain waits for this to reach zero as well.
306
+ */
307
+ let inFlight = 0;
308
+ let closed = null;
309
+ // One update at a time, for the reason the module doc gives: `handleUpdate`
310
+ // holds a single ack slot and the delivery maps are not reentrant. A plain
311
+ // promise chain, because the ordering wanted is arrival order and nothing
312
+ // cleverer.
313
+ let queue = Promise.resolve();
314
+ function serialize(work) {
315
+ const next = queue.then(work, work);
316
+ queue = next.then(() => undefined, () => undefined);
317
+ return next;
318
+ }
319
+ function send(res, status, body) {
320
+ const bytes = Buffer.from(JSON.stringify(body), "utf8");
321
+ res.writeHead(status, { "content-type": "application/json", "content-length": bytes.length });
322
+ res.end(bytes);
323
+ }
324
+ /**
325
+ * A refusal: counted, said on stderr, and answered with its code.
326
+ *
327
+ * Never an event. See the module doc: an unauthenticated endpoint that could
328
+ * append would be an endpoint anybody could use to pad the log.
329
+ */
330
+ function refuse(res, status, code, message) {
331
+ stats.refusals[code] += 1;
332
+ complain(`approval: telegram webhook refused a post (${code}): ${message}`);
333
+ send(res, status, { error: { code, message } });
334
+ }
335
+ const http = createServer((req, res) => {
336
+ void (async () => {
337
+ stats.requests += 1;
338
+ inFlight += 1;
339
+ try {
340
+ // THE SECRET FIRST, before the path, the method or the body. A caller
341
+ // without it learns only that something refused: no path is confirmed
342
+ // to exist, no method is named, and no body is read off the socket.
343
+ const offered = secretHeaderOf(req.rawHeaders);
344
+ if (offered === "duplicate") {
345
+ refuse(res, 401, "webhook-duplicate-secret-header", `this request carries more than one ${TELEGRAM_SECRET_HEADER} header. Exactly one is accepted: where there are several, the one this server would check is not the one a reader of the request would name`);
346
+ return;
347
+ }
348
+ if (!secretMatches(options.secret, offered)) {
349
+ refuse(res, 401, "webhook-secret-mismatch", offered === null
350
+ ? `no ${TELEGRAM_SECRET_HEADER} header. Every delivery Telegram makes to this URL carries the secret_token the runtime registered; a post without it decides nothing, and is counted and reported here as a refusal rather than written to the log`
351
+ : `the ${TELEGRAM_SECRET_HEADER} header is not the registered secret_token. No decision reached the gate, and nothing was appended`);
352
+ return;
353
+ }
354
+ let url;
355
+ try {
356
+ url = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
357
+ }
358
+ catch {
359
+ refuse(res, 400, "webhook-malformed-request", "the request line and Host header do not form a URL this server can parse");
360
+ return;
361
+ }
362
+ if (url.pathname !== path) {
363
+ refuse(res, 404, "webhook-unknown-path", `no endpoint at ${url.pathname}`);
364
+ return;
365
+ }
366
+ const method = req.method ?? "GET";
367
+ if (method !== "POST") {
368
+ refuse(res, 405, "webhook-method-not-allowed", `${path} answers POST, not ${method}`);
369
+ return;
370
+ }
371
+ const body = await readBody(req);
372
+ if (!body.ok) {
373
+ refuse(res, body.code === "webhook-body-too-large" ? 413 : 400, body.code, body.message);
374
+ return;
375
+ }
376
+ let parsed;
377
+ try {
378
+ parsed = JSON.parse(body.text);
379
+ }
380
+ catch (cause) {
381
+ refuse(res, 400, "webhook-body-unreadable", `request body is not JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
382
+ return;
383
+ }
384
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
385
+ refuse(res, 400, "webhook-not-an-update", "request body is not a Telegram Update object");
386
+ return;
387
+ }
388
+ // Handled BEFORE the response, deliberately. Telegram retries a
389
+ // delivery it got no 2xx for, and answering first would mean the retry
390
+ // raced the gate rather than finding a decided request; the redelivery
391
+ // that does happen is refused `already-decided` with the first human
392
+ // answer standing.
393
+ let result;
394
+ try {
395
+ result = await serialize(async () => {
396
+ const handled = await options.channel.deliverUpdate(parsed);
397
+ stats.updates += handled.updates;
398
+ if (options.afterUpdate !== undefined) {
399
+ try {
400
+ await options.afterUpdate(handled);
401
+ }
402
+ catch (cause) {
403
+ complain(`approval: telegram webhook: the dispatch cycle after an update failed (${cause instanceof Error ? cause.message : String(cause)}); the update was handled and the listener is still up`);
404
+ }
405
+ }
406
+ return handled;
407
+ });
408
+ }
409
+ catch (cause) {
410
+ // The channel threw. Whatever the gate appended has already
411
+ // happened, so this says where to look rather than what happened —
412
+ // the wording `TELEGRAM_HANDLER_FAILED` uses, for the same reason.
413
+ const message = cause instanceof Error ? cause.message : String(cause);
414
+ // `in` rather than a property read, so a thrown null or a thrown
415
+ // string cannot turn this diagnostic into a second failure.
416
+ const conflict = typeof cause === "object" &&
417
+ cause !== null &&
418
+ "code" in cause &&
419
+ cause.code === "transport-conflict";
420
+ refuse(res, conflict ? 409 : 500, conflict ? "webhook-transport-conflict" : "webhook-handler-failed", message);
421
+ return;
422
+ }
423
+ send(res, 200, {
424
+ ok: true,
425
+ updates: result.updates,
426
+ decisions: result.outcomes.length,
427
+ ignored: result.ignored.length,
428
+ });
429
+ }
430
+ catch (cause) {
431
+ if (!res.headersSent) {
432
+ refuse(res, 500, "webhook-handler-failed", `the request failed: ${cause instanceof Error ? cause.message : String(cause)}`);
433
+ }
434
+ else {
435
+ res.end();
436
+ }
437
+ }
438
+ finally {
439
+ inFlight -= 1;
440
+ }
441
+ })();
442
+ });
443
+ http.on("connection", (socket) => {
444
+ sockets.add(socket);
445
+ socket.on("close", () => sockets.delete(socket));
446
+ });
447
+ await new Promise((settle, fail) => {
448
+ const onError = (cause) => fail(cause);
449
+ http.once("error", onError);
450
+ http.listen(options.port ?? TELEGRAM_WEBHOOK_DEFAULT_PORT, host, () => {
451
+ http.off("error", onError);
452
+ settle();
453
+ });
454
+ });
455
+ const address = http.address();
456
+ const boundPort = typeof address === "object" && address !== null
457
+ ? address.port
458
+ : (options.port ?? TELEGRAM_WEBHOOK_DEFAULT_PORT);
459
+ const boundHost = typeof address === "object" && address !== null ? address.address : host;
460
+ /**
461
+ * Wait for the work already accepted, bounded.
462
+ *
463
+ * Two things are waited on, and both are needed. `serialize` empties the
464
+ * update queue, which is where a `deliverUpdate` and the dispatch cycle that
465
+ * follows it run; `inFlight` covers the rest of a request's life, from the
466
+ * body read to the byte the response ends with. A request that never
467
+ * finishes is what the deadline is for: past it the sockets go, because a
468
+ * stop that could be held open forever is not a stop.
469
+ */
470
+ async function drain(timeoutMs) {
471
+ const deadline = Date.now() + timeoutMs;
472
+ const giveUp = () => {
473
+ complain(`approval: telegram webhook: ${String(inFlight)} request(s) were still in flight after ${String(timeoutMs)}ms; closing anyway`);
474
+ };
475
+ for (;;) {
476
+ const remaining = deadline - Date.now();
477
+ // Giving up leaves a pending handler where it is. Nothing holds the
478
+ // event loop open for it (the sockets are destroyed below, and both
479
+ // timers here are unref'd), and `cli.js` sets `process.exitCode` rather
480
+ // than calling `process.exit()`, so the process ends by the normal path
481
+ // once the loop drains rather than being cut off mid-write.
482
+ if (remaining <= 0)
483
+ return giveUp();
484
+ // The deadline covers the QUEUE WAIT as well, and that is not a detail:
485
+ // a handler that never returns leaves work on the serialize chain
486
+ // forever, so a drain that simply awaited the chain would be the hang it
487
+ // was meant to bound.
488
+ const quiet = await Promise.race([
489
+ serialize(async () => true),
490
+ new Promise((settle) => {
491
+ setTimeout(() => settle(false), remaining).unref?.();
492
+ }),
493
+ ]);
494
+ if (!quiet)
495
+ return giveUp();
496
+ if (inFlight === 0)
497
+ return;
498
+ await new Promise((settle) => {
499
+ setTimeout(settle, TELEGRAM_WEBHOOK_DRAIN_POLL_MS).unref?.();
500
+ });
501
+ }
502
+ }
503
+ async function stop() {
504
+ // 1. Stop accepting. `close` refuses new connections at once and calls
505
+ // back only once every existing one is gone, so the callback is the
506
+ // thing to await and not the thing to race.
507
+ const accepted = new Promise((settle) => {
508
+ http.close(() => settle());
509
+ });
510
+ // 2. Let what is in flight finish. This is the whole of the fix: an update
511
+ // mid-append keeps its socket, writes its response, and only then is
512
+ // anything torn down — so `deleteWebhook` and the "stopped" line, which
513
+ // the caller runs after this resolves, cannot land inside an append.
514
+ await drain(options.closeTimeoutMs ?? TELEGRAM_WEBHOOK_CLOSE_TIMEOUT_MS);
515
+ // 3. Now the idle ones. A keep-alive socket with nothing on it would hold
516
+ // the callback above open until its own timeout, which is a stop that
517
+ // looks like a hang.
518
+ for (const socket of sockets)
519
+ socket.destroy();
520
+ sockets.clear();
521
+ await accepted;
522
+ }
523
+ return {
524
+ host: boundHost,
525
+ port: boundPort,
526
+ path,
527
+ stats: () => ({ ...stats, refusals: { ...stats.refusals } }),
528
+ serialize,
529
+ inFlight: () => inFlight,
530
+ close: () => {
531
+ closed ??= stop();
532
+ return closed;
533
+ },
534
+ };
535
+ }
536
+ //# sourceMappingURL=telegram-webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram-webhook.js","sourceRoot":"","sources":["../../../src/channels/telegram-webhook.ts"],"names":[],"mappings":"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"}