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
@@ -34,22 +34,59 @@
34
34
  * notify, long-poll, callbacks, failure modes — against a local mock Bot API
35
35
  * server and never touches the real network.
36
36
  *
37
+ * ## Two transports, one update path (APRV-424)
38
+ *
39
+ * An update reaches this channel either because it long-polled for it
40
+ * ({@link TelegramChannel.pollOnce}) or because Telegram posted it to a URL the
41
+ * runtime registered ({@link TelegramChannel.deliverUpdate}, fed by
42
+ * `channels/telegram-webhook.ts`). That is the WHOLE of the difference. Both
43
+ * entry points hand the update to the same {@link TelegramChannel.handleUpdate}
44
+ * and therefore to the same chat check, the same `senderOf`, the same
45
+ * resolution ladder, the same `onDecision` handler and the same
46
+ * annotate-after-decision edit; a decision does not know which door it came
47
+ * through, and there is no second copy of any of it to drift.
48
+ *
49
+ * The two are mutually exclusive per bot, and the Bot API says so first:
50
+ * `getUpdates` is refused outright while a webhook is set.
51
+ * {@link TelegramChannel.claimTransport} refuses the in-process form of the
52
+ * mistake, and the verbs above ask `getWebhookInfo` and the ownership registry
53
+ * about the cross-process one.
54
+ *
37
55
  * ## Config-declared identity — SPEC.md §11
38
56
  *
39
57
  * > Human identity in v0.1 is config-declared (an environment variable or
40
58
  * > flag); the trust boundary is the local machine, and anyone who can set that
41
59
  * > configuration and write to the log is inside it.
42
60
  *
43
- * This channel does **not** authenticate the person who tapped the button. It
44
- * checks that the callback arrived from the configured chat id, and the
45
- * decision is then recorded against the human actor the *runtime* was
46
- * configured with (`APPROVAL_HUMAN` / `--as`), not against anything the
47
- * callback carried. So the guarantee is "someone with access to the configured
48
- * chat, on a runtime configured by someone with local control, tapped Approve"
49
- * — not "alice tapped Approve". Anyone in that chat can approve as the
50
- * configured actor. Use a private chat with the bot, and treat the chat's
51
- * membership as part of the trust boundary. Cryptographic identity is future
52
- * work and is not a v0.1 claim.
61
+ * What this channel authenticates, exactly: that the callback arrived **from
62
+ * the configured chat id**, and which **Telegram account** the Bot API
63
+ * attributes the tap to (`callback_query.from.id`). It authenticates no
64
+ * person, because no transport can: an account id is evidence about an
65
+ * account.
66
+ *
67
+ * What is done with the second fact is the operator's to decide, and since
68
+ * APRV-324 there are two settings:
69
+ *
70
+ * - **No `senders` block in the policy.** Nothing changes from every build
71
+ * before it. The decision is recorded against the human actor the *runtime*
72
+ * was configured with (`APPROVAL_HUMAN` / `--as`), so the guarantee is
73
+ * "someone with access to the configured chat, on a runtime configured by
74
+ * someone with local control, tapped Approve" — not "alice tapped Approve".
75
+ * Anyone in that chat can approve as the configured actor. Use a private chat
76
+ * with the bot, and treat the chat's membership as part of the trust
77
+ * boundary.
78
+ * - **A `senders` block mapping account ids to approvers.** The decision is
79
+ * recorded against the person the operator attested that account to, and a
80
+ * tap from an account the policy does not name is REFUSED rather than
81
+ * recorded under the listener's identity. The guarantee becomes "the account
82
+ * the operator attested to alice tapped Approve", which is a statement about
83
+ * Telegram's session handling and the operator's assertion, and still not a
84
+ * proof of personhood. Cryptographic identity is future work and is not a
85
+ * v0.1 claim.
86
+ *
87
+ * The channel itself resolves nothing either way. It reports the account it
88
+ * saw; `channels/contract.ts` resolves it against the attested policy, and
89
+ * `core/gate.ts` decides. See `design/channel-sender-identity.md`.
53
90
  *
54
91
  * ## Formatting: HTML, not MarkdownV2 — a deliberate choice
55
92
  *
@@ -183,6 +220,7 @@
183
220
  * and the log is not opened.
184
221
  */
185
222
  import type { ChannelBatch, ChannelDecision, ChannelHealth, ChannelRequest, DecisionOutcome, DeliveryId, RenderedRequest, TaggedField, TestableChannel } from "./contract.js";
223
+ import type { ChannelSender } from "../core/sender-identity.js";
186
224
  import { type Reaction, type ReviewVerdict } from "../core/audit.js";
187
225
  import { type PromptLayout } from "../core/prompt-layout.js";
188
226
  export { TELEGRAM_CHAT_ENV, TELEGRAM_TOKEN_ENV, telegramChatEnvFor, telegramTokenEnvFor, } from "../core/telegram-config.js";
@@ -341,6 +379,13 @@ export declare function decidedLine(actor: string, ts: string, seq: number): str
341
379
  export declare const TELEGRAM_DEFAULT_RETENTION_MS: number;
342
380
  /** Least time between two sweeps. A sweep is O(map); once a minute is plenty. */
343
381
  export declare const TELEGRAM_SWEEP_INTERVAL_MS = 60000;
382
+ /**
383
+ * How many refused cards' nonces a channel remembers as dead (APRV-442).
384
+ *
385
+ * A bound on memory and nothing else: a nonce that falls off the end reaches
386
+ * the action-reference fallback, and the gate judges that tap in full.
387
+ */
388
+ export declare const TELEGRAM_REFUSED_NONCE_CAP = 1024;
344
389
  /**
345
390
  * The slice of `fetch` this module uses, structurally.
346
391
  *
@@ -556,6 +601,13 @@ export type CheckpointTapHandler = (tap: {
556
601
  seq: number;
557
602
  hash: string;
558
603
  };
604
+ /**
605
+ * The account the transport authenticated (APRV-324 follow-up), when it
606
+ * authenticated one. The channel resolves nothing: the runtime's handler
607
+ * asks the attested policy who this is, refuses an account it does not name,
608
+ * and signs as the person it does.
609
+ */
610
+ sender?: ChannelSender;
559
611
  }) => CheckpointTapResponse | Promise<CheckpointTapResponse>;
560
612
  export declare const TELEGRAM_COMMANDS: readonly ["queue", "skip", "next"];
561
613
  export type TelegramCommand = (typeof TELEGRAM_COMMANDS)[number];
@@ -804,6 +856,14 @@ export interface DigestState {
804
856
  * the request's own `ts` remains the only instant a TTL is measured from.
805
857
  */
806
858
  deliveredAtMs: number;
859
+ /**
860
+ * How long after {@link deliveredAtMs} the LAST of this digest's members can
861
+ * still be decided, or `null` when some member is bounded by nothing
862
+ * (APRV-423, second review pass). The widest of the members' own windows
863
+ * ({@link Delivery.windowMs}), because a digest is droppable only once every
864
+ * member is past deciding.
865
+ */
866
+ windowMs: number | null;
807
867
  }
808
868
  /**
809
869
  * The computed facts a digest's members share, as the digest states them.
@@ -904,6 +964,30 @@ export declare function parseCheckpointCallback(data: unknown): {
904
964
  sign: boolean;
905
965
  nonce: string;
906
966
  } | null;
967
+ /**
968
+ * The account the Bot API attributes a callback to (APRV-324, amended SPEC.md
969
+ * §10.3).
970
+ *
971
+ * `callback_query.from.id` and nothing else. Telegram assembles the `from`
972
+ * object itself, from the session the tap arrived on, which is why it is the
973
+ * one field on an update this channel treats as evidence about a person. Three
974
+ * neighbours are deliberately not read:
975
+ *
976
+ * - **`from.username`.** Mutable and reusable, so a mapping keyed on it would
977
+ * hand an identity over with a handle. Never a key here and never recorded
978
+ * (`core/sender-identity.ts` states the argument in full).
979
+ * - **`message.from`.** The author of the message the button sits on — this bot
980
+ * — rather than the person who pressed it.
981
+ * - **`data`, and any text.** What the sender says about themselves, which
982
+ * §11.1 invariant 4 says may raise scrutiny and never lower it. A callback
983
+ * whose payload names a user id names it about itself; the id here comes from
984
+ * the transport, and the two disagreeing changes nothing.
985
+ *
986
+ * `undefined` when the update carries no usable id: a sender this channel could
987
+ * not read is not a sender it may guess at, and the decision then travels with
988
+ * none, which the contract reads as today's configured attribution.
989
+ */
990
+ export declare function senderOf(query: Record<string, unknown>, channel: string): ChannelSender | undefined;
907
991
  interface ParsedCallback {
908
992
  decision: "grant" | "reject";
909
993
  /** `all` for a digest's "all" button; `one` for every per-request button. */
@@ -978,6 +1062,18 @@ export interface ReviewTap {
978
1062
  reaction?: Reaction;
979
1063
  /** The human's words, when a note prompt collected any. */
980
1064
  note?: string;
1065
+ /**
1066
+ * The account the transport authenticated (APRV-324 follow-up), when it
1067
+ * authenticated one.
1068
+ *
1069
+ * A review confers no authority, which is why it took the paragraph above so
1070
+ * long to stop being true. It is still recorded as a HUMAN's observation and
1071
+ * `approval feedback` presents it to agents as human-authored guidance, so a
1072
+ * review attributed to the wrong person is guidance in somebody else's name.
1073
+ * The channel resolves nothing; the runtime's handler asks the attested
1074
+ * policy, refuses an account it does not name, and records the one it does.
1075
+ */
1076
+ sender?: ChannelSender;
981
1077
  }
982
1078
  /** What the runtime did with a review tap, as it reports it back. */
983
1079
  export interface ReviewTapResponse {
@@ -1051,11 +1147,20 @@ export interface ReviewCardState {
1051
1147
  headline: string;
1052
1148
  lines: string[];
1053
1149
  } | null;
1054
- /** The outstanding note prompt, and what its reply will record. */
1150
+ /**
1151
+ * The outstanding note prompt, and what its reply will record.
1152
+ *
1153
+ * `sender` is the account that asked for the prompt (APRV-324 follow-up),
1154
+ * when the transport authenticated one. A ForceReply prompt is addressed to
1155
+ * the person who tapped, and the words it collects are recorded as theirs, so
1156
+ * a reply from a different account is not the answer to this question: it is
1157
+ * left unrecorded and the prompt stays live for whoever armed it.
1158
+ */
1055
1159
  awaitingNote: {
1056
1160
  promptId: DeliveryId;
1057
1161
  verdict: ReviewVerdict;
1058
1162
  reaction: Reaction;
1163
+ sender?: ChannelSender;
1059
1164
  } | null;
1060
1165
  /** When this process delivered the card, on {@link TelegramConfig.now}'s clock. */
1061
1166
  deliveredAtMs: number;
@@ -1161,6 +1266,17 @@ export declare class TelegramApiError extends Error {
1161
1266
  * that goes unreported.
1162
1267
  */
1163
1268
  export declare function isMessageNotModified(cause: unknown): boolean;
1269
+ /**
1270
+ * Whether a failed poll is the Bot API saying another process holds this bot
1271
+ * (APRV-390).
1272
+ *
1273
+ * It is the one poll failure that is not transient and not the network's
1274
+ * fault: retrying it forever produces an identical line every few seconds and
1275
+ * never recovers, because the other poller is not going to stop. {@link
1276
+ * TelegramChannel.listen} reports it once, with whatever the caller knows
1277
+ * about who the other process is, and then stops repeating itself.
1278
+ */
1279
+ export declare function isPollConflict(cause: unknown): boolean;
1164
1280
  /** What one `pollOnce()` did, for tests and for programmatic drivers. */
1165
1281
  export interface TelegramPollResult {
1166
1282
  /** Updates received in this batch. */
@@ -1187,6 +1303,50 @@ export interface TelegramPollResult {
1187
1303
  ok: boolean;
1188
1304
  }[];
1189
1305
  }
1306
+ /**
1307
+ * How updates reach this channel (APRV-424).
1308
+ *
1309
+ * `poll` is `getUpdates`, the transport every build before APRV-424 had.
1310
+ * `webhook` is Telegram posting each update to a URL the runtime registered.
1311
+ * They are alternatives and never a pair: the Bot API refuses `getUpdates`
1312
+ * outright while a webhook is set, so a process that did both would spend its
1313
+ * life in a 409 loop and the approver's tap would land on whichever half was
1314
+ * lucky.
1315
+ */
1316
+ export type TelegramTransport = "poll" | "webhook";
1317
+ /**
1318
+ * Why a transport claim was refused. Frozen, per SPEC §11.1 invariant 6.
1319
+ *
1320
+ * One code, because there is one fact: this channel is already receiving
1321
+ * updates by the other transport.
1322
+ */
1323
+ export declare const TELEGRAM_TRANSPORT_REFUSAL_CODES: readonly ["transport-conflict"];
1324
+ export type TelegramTransportRefusalCode = (typeof TELEGRAM_TRANSPORT_REFUSAL_CODES)[number];
1325
+ export type TelegramTransportClaim = {
1326
+ ok: true;
1327
+ transport: TelegramTransport;
1328
+ } | {
1329
+ ok: false;
1330
+ code: TelegramTransportRefusalCode;
1331
+ message: string;
1332
+ };
1333
+ /**
1334
+ * The in-process half of "long-poll and webhook are mutually exclusive"
1335
+ * (APRV-424).
1336
+ *
1337
+ * It is a belt on the braces and says so. The exclusion that matters is per
1338
+ * BOT and lives above this class, where a process can ask the Bot API
1339
+ * (`getWebhookInfo`) and the per-machine ownership registry who else is
1340
+ * receiving this bot's updates; a channel object knows only about itself.
1341
+ * What this rules out is the one failure a caller could cause here and
1342
+ * nowhere else: feeding one channel instance from both a poll loop and an
1343
+ * HTTP handler, where `handleUpdate`'s single ack slot and the delivery maps
1344
+ * would be written by two arrivals nobody ordered.
1345
+ */
1346
+ export declare class TelegramTransportError extends Error {
1347
+ readonly code: TelegramTransportRefusalCode;
1348
+ constructor(message: string);
1349
+ }
1190
1350
  export interface TelegramListenOptions {
1191
1351
  /** Process exactly one successful `getUpdates` batch, then return. */
1192
1352
  once?: boolean;
@@ -1206,6 +1366,17 @@ export interface TelegramListenOptions {
1206
1366
  * failure mode this loop exists to rule out.
1207
1367
  */
1208
1368
  beforePoll?: () => Promise<void>;
1369
+ /**
1370
+ * What the caller knows about who else might be polling this bot (APRV-390).
1371
+ *
1372
+ * Consulted only when a poll fails with the Bot API's 409, and appended to
1373
+ * the one line that failure produces. The CLI builds it from the per-machine
1374
+ * ownership registry (`core/channel-owner.ts`), so this class keeps no
1375
+ * knowledge of instances, directories or files — it asks the question and
1376
+ * prints the caller's answer, the same arrangement {@link
1377
+ * TelegramConfig.describeAction} uses for the log.
1378
+ */
1379
+ conflictAdvice?: () => string | null;
1209
1380
  }
1210
1381
  /**
1211
1382
  * What {@link TelegramChannel.notifyBatch} did with a set (APRV-115).
@@ -1299,11 +1470,60 @@ export declare class TelegramChannel implements TestableChannel {
1299
1470
  private readonly digests;
1300
1471
  /** "All" nonce -> the digest message it was issued for. */
1301
1472
  private readonly allNonces;
1473
+ /**
1474
+ * Action keys whose card this process disarmed on a tap the gate did NOT
1475
+ * settle (APRV-442): a refusal (`sender-unmapped`, a budget, an unattested
1476
+ * policy, anything `ok: false`) or a handler that threw. Drained by
1477
+ * {@link takeReleased}.
1478
+ *
1479
+ * The disarm is right and stays, and {@link refusedNonces} keeps it final:
1480
+ * the refused card's bytes resolve to nothing, not even through the
1481
+ * action-reference fallback to the card that replaces it. What the disarm
1482
+ * must not do is leave the listener believing the REQUEST is on the
1483
+ * approver's phone, which is what `DispatchState.delivered` goes on saying
1484
+ * until something tells it otherwise. This set is that something: it names
1485
+ * a card, never an outcome, and the listener re-derives from the verified
1486
+ * log whether the request is still pending before it offers a new card.
1487
+ * Nothing here decides, and losing it to a restart costs nothing, because a
1488
+ * restarted listener re-sends everything pending anyway.
1489
+ */
1490
+ private readonly released;
1491
+ /**
1492
+ * Nonces of cards a refused or failed tap disarmed (APRV-442), oldest first,
1493
+ * capped at {@link TELEGRAM_REFUSED_NONCE_CAP}.
1494
+ *
1495
+ * Without this, a released request's fresh card would give the refused
1496
+ * card's bytes somewhere to go: the nonce misses, the action reference
1497
+ * resolves to the new live card, and a Telegram redelivery of the refused
1498
+ * tap (a webhook retry) or a client replaying the removed buttons would be
1499
+ * refused again, append again, disarm the new card and cause another
1500
+ * re-send, once per replay. A nonce in this set never takes the fallback, so
1501
+ * a refused card stays exactly as dead as it was before APRV-442 and only
1502
+ * the REQUEST is offered again. Forgetting an entry past the cap degrades to
1503
+ * the fallback, whose tap the gate still judges in full: volume, never a
1504
+ * decision.
1505
+ */
1506
+ private readonly refusedNonces;
1302
1507
  private rendered;
1303
1508
  private offset;
1304
1509
  private counter;
1305
1510
  private stopped;
1306
1511
  private inFlight;
1512
+ /**
1513
+ * The transport this channel has started receiving updates by, or `null`
1514
+ * before the first one (APRV-424). Sticky for the life of the object: a
1515
+ * process that stopped polling has not stopped being a poller, and the
1516
+ * question this answers is which arrival path owns the maps below.
1517
+ */
1518
+ private transport;
1519
+ /**
1520
+ * Everything {@link redact} scrubs out of anything that leaves this class.
1521
+ *
1522
+ * The bot token from construction, plus any secret a later call was handed:
1523
+ * {@link registerWebhook} adds the `secret_token` before it sends it, so a
1524
+ * Bot API error quoting the request cannot put it on an operator's terminal.
1525
+ */
1526
+ private readonly secrets;
1307
1527
  private readonly counters;
1308
1528
  constructor(config: TelegramConfig);
1309
1529
  onDecision(handler: (decision: ChannelDecision) => DecisionOutcome): void;
@@ -1459,6 +1679,28 @@ export declare class TelegramChannel implements TestableChannel {
1459
1679
  * a decided one is not that.
1460
1680
  */
1461
1681
  private disarm;
1682
+ /**
1683
+ * How long after delivery the request behind a prompt can still be decided,
1684
+ * on this process's clock, or `null` when nothing bounds it (APRV-423, second
1685
+ * review pass).
1686
+ *
1687
+ * The SHORTER of two bounds, each of which is at or past the gate's own
1688
+ * lapse, so the sweep never forgets a button a decision could still reach:
1689
+ *
1690
+ * - the request's `ttl_remaining_ms`, computed by the listener from the
1691
+ * verified log against the window the gate judges by (the policy's TTL
1692
+ * narrowed by the harness cap the requesting hook declared). It was
1693
+ * measured at tagging time, and delivery is at or after tagging, so
1694
+ * counting it from delivery ends at or after the real lapse;
1695
+ * - the policy's own TTL ({@link TelegramConfig.approvalTtlMs}), counted from
1696
+ * delivery, which is at or after the `approval.requested` it really runs
1697
+ * from. This was the only bound before APRV-423 and it stays as one.
1698
+ *
1699
+ * A request neither bounds is decidable forever, and its entry is dropped
1700
+ * only on an observed settlement. This channel still reads no policy and no
1701
+ * log: both numbers arrive computed, by the verb and by the tagger.
1702
+ */
1703
+ private retentionWindowMs;
1462
1704
  /**
1463
1705
  * Drop the delivery bookkeeping no callback can still be honoured against
1464
1706
  * (APRV-135).
@@ -1552,6 +1794,28 @@ export declare class TelegramChannel implements TestableChannel {
1552
1794
  * {@link Channel} interface names. Its wording is unchanged.
1553
1795
  */
1554
1796
  retract(deliveryId: DeliveryId, reason: string, actionKey?: string): Promise<void>;
1797
+ /**
1798
+ * The action keys whose card a refused or failed tap disarmed since the last
1799
+ * call, and forget them (APRV-442).
1800
+ *
1801
+ * The listener's dispatch cycle calls this once per cycle and offers a fresh
1802
+ * card for every key the verified log still calls pending. It is a statement
1803
+ * about this process's cards and nothing else: it carries no decision, no
1804
+ * sender and no outcome, and a key in it that the log has since settled is
1805
+ * simply not offered.
1806
+ */
1807
+ takeReleased(): string[];
1808
+ /**
1809
+ * Mark `delivery`'s card dead and its request released, BEFORE the
1810
+ * annotation that disarms it (APRV-442).
1811
+ *
1812
+ * Every nonce still armed for this request on this message is recorded in
1813
+ * {@link refusedNonces}, together with the nonce the tap carried (which
1814
+ * differs when the tap came from an earlier copy through the fallback), so
1815
+ * none of those bytes can later resolve to the fresh card. On a digest only
1816
+ * this member's nonce is taken: its siblings stay armed on the digest.
1817
+ */
1818
+ private release;
1555
1819
  /**
1556
1820
  * Send one plain message that carries no question (APRV-196).
1557
1821
  *
@@ -1563,6 +1827,24 @@ export declare class TelegramChannel implements TestableChannel {
1563
1827
  * HTML-mode message is.
1564
1828
  */
1565
1829
  announce(lines: string[]): Promise<DeliveryId>;
1830
+ /**
1831
+ * Which bot this token is, from one `getMe` (APRV-390).
1832
+ *
1833
+ * `getMe` is the only Bot API call that answers it, and it is the call
1834
+ * `approval doctor` and `approval setup channel telegram` already make for
1835
+ * the same reason: it mutates nothing, sends nothing, and acknowledges
1836
+ * nothing. In particular it is NOT `getUpdates`, so asking who this bot is
1837
+ * consumes no update and steals no pending tap from a listener that is
1838
+ * already running — which matters here above all, because the answer is
1839
+ * what decides whether this process is allowed to poll at all.
1840
+ *
1841
+ * Throws {@link TelegramApiError} like every other call; the caller decides
1842
+ * whether an unreachable Bot API is a refusal or a shrug.
1843
+ */
1844
+ identify(): Promise<{
1845
+ id: string;
1846
+ username: string;
1847
+ }>;
1566
1848
  /**
1567
1849
  * Long-poll `getUpdates` until {@link stop} is called (or one batch, with
1568
1850
  * `once`).
@@ -1588,6 +1870,99 @@ export declare class TelegramChannel implements TestableChannel {
1588
1870
  * what {@link listen} catches and retries.
1589
1871
  */
1590
1872
  pollOnce(): Promise<TelegramPollResult>;
1873
+ /**
1874
+ * Which update types this channel asks for, on either transport (APRV-424).
1875
+ *
1876
+ * APRV-216. `message` is asked for only while a command handler is
1877
+ * registered: see {@link onCommand} for why a listener that reads messages
1878
+ * nobody asked for would break `approval setup channel telegram`'s chat
1879
+ * discovery. APRV-299 adds the second reason to read messages: a `loved` or
1880
+ * `disliked` on a review card collects the human's words as a REPLY, because
1881
+ * an inline keyboard has no text input.
1882
+ *
1883
+ * One function since APRV-424, because `setWebhook` takes the same list as
1884
+ * `getUpdates` and two spellings of it would be two answers to "what does
1885
+ * this listener read" — with the webhook's answer frozen at registration
1886
+ * time, where nobody would look for it.
1887
+ */
1888
+ allowedUpdates(): string[];
1889
+ /**
1890
+ * One update, from whichever transport carried it (APRV-424).
1891
+ *
1892
+ * The whole of what the webhook adds is the ARRIVAL. Everything a tap then
1893
+ * passes through — the chat check, `senderOf`, the checkpoint and review
1894
+ * parsers, the resolution ladder, the early ack, `this.handler` and the
1895
+ * annotation written from the record the gate appended — is
1896
+ * {@link handleUpdate}, called from here and from {@link pollOnce}, and
1897
+ * there is no second copy of any of it. `tests/telegram-webhook.test.ts`
1898
+ * drives one callback through both entry points and compares the records the
1899
+ * log ends up holding.
1900
+ *
1901
+ * The sweep is `pollOnce`'s, for `pollOnce`'s reason: this is the moment the
1902
+ * process is about to be idle, and a channel that only ever wakes for taps
1903
+ * would otherwise never run one.
1904
+ */
1905
+ deliverUpdate(update: unknown): Promise<TelegramPollResult>;
1906
+ /** The per-update bookkeeping both transports do, before `handleUpdate`. */
1907
+ private intake;
1908
+ /**
1909
+ * Bind this channel to one transport, or say which one already holds it
1910
+ * (APRV-424).
1911
+ *
1912
+ * Idempotent for the transport already claimed, so a webhook server that
1913
+ * claims at start-up and then delivers, or a driver calling `pollOnce` in a
1914
+ * loop, pays nothing. Not released by {@link stop}: a process that stopped
1915
+ * polling has not stopped being a poller, and re-pointing a live channel's
1916
+ * delivery maps at a second arrival path is the state this exists to refuse.
1917
+ */
1918
+ claimTransport(transport: TelegramTransport): TelegramTransportClaim;
1919
+ /** {@link claimTransport}, for the paths where a refusal is a caller's bug. */
1920
+ private claimOrThrow;
1921
+ /**
1922
+ * Point this bot's updates at `url`, and require `secret` on every post.
1923
+ *
1924
+ * Three properties, and each is a refusal to be clever:
1925
+ *
1926
+ * - **The secret is Telegram's own mechanism**, `secret_token`, echoed back
1927
+ * on every delivery in the `X-Telegram-Bot-Api-Secret-Token` header. The
1928
+ * receiver compares it and refuses anything else, so a URL that leaks (a
1929
+ * proxy log, a referrer, a screenshot) is a URL that still decides
1930
+ * nothing.
1931
+ * - **It is redacted from this moment on.** The value is added to the
1932
+ * channel's secret list BEFORE the call, so a Bot API error that quotes
1933
+ * the request cannot print it.
1934
+ * - **Pending updates are kept.** `drop_pending_updates` is left false:
1935
+ * taps that arrived while the process was down are the approver's answers,
1936
+ * and the gate is what decides whether they are still honourable.
1937
+ */
1938
+ registerWebhook(url: string, secret: string): Promise<void>;
1939
+ /**
1940
+ * Take this bot's webhook away, so `getUpdates` works again.
1941
+ *
1942
+ * Called on a clean stop of the webhook runner. Pending updates are kept for
1943
+ * {@link registerWebhook}'s reason: the next process to start — a poller or
1944
+ * another webhook — is entitled to the taps that arrived in between.
1945
+ *
1946
+ * `timeoutMs` is what the stop path passes, and it is shorter than this
1947
+ * channel's ordinary request timeout on purpose (APRV-424, second review,
1948
+ * note 6). A stop already waits for the receiver to drain, and an operator
1949
+ * pressing Ctrl-C a second time is asking for the process to end: half a
1950
+ * minute of an unreachable Bot API on top of that reads as a hang, and the
1951
+ * webhook it failed to remove is reported rather than silently retried.
1952
+ */
1953
+ deleteWebhook(timeoutMs?: number): Promise<void>;
1954
+ /**
1955
+ * What the Bot API says is receiving this bot's updates right now.
1956
+ *
1957
+ * The one authority on the question, and the reason it is asked at start-up
1958
+ * by both transports: the ownership registry knows about processes on THIS
1959
+ * machine, and a webhook registered from a laptop in another timezone is
1960
+ * still a webhook swallowing every tap this poller is waiting for.
1961
+ */
1962
+ webhookInfo(): Promise<{
1963
+ url: string;
1964
+ pendingUpdateCount: number;
1965
+ }>;
1591
1966
  /**
1592
1967
  * Exactly one `answerCallbackQuery` per callback query, on every path
1593
1968
  * (APRV-196).
@@ -1807,7 +2182,14 @@ export declare class TelegramChannel implements TestableChannel {
1807
2182
  * settled, so a member still in the map is one still armed on a live message.
1808
2183
  */
1809
2184
  private liveDeliveryFor;
1810
- /** Replace the token with a placeholder anywhere it appears in `text`. */
2185
+ /**
2186
+ * Replace every secret this channel holds with a placeholder in `text`.
2187
+ *
2188
+ * The bot token always, and since APRV-424 the webhook `secret_token` too,
2189
+ * from the moment {@link registerWebhook} is handed it. Both are values that
2190
+ * only ever travel TO the Bot API, so anything coming back that quotes one
2191
+ * is a diagnostic about to print a credential.
2192
+ */
1811
2193
  private redact;
1812
2194
  private describe;
1813
2195
  /**