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,295 @@
1
+ /**
2
+ * The authenticated relay channel: its HTTP receiver (SPEC.md §10.3, APRV-455).
3
+ *
4
+ * The sibling of `channels/telegram-webhook.ts`, and deliberately the same
5
+ * shape: one header, one path, one method, one bounded body, a loopback bind by
6
+ * default with the operator's proxy terminating TLS, a refusal carried as a
7
+ * body with a frozen code, and no refusal ever written to the log. What differs
8
+ * is what arrives. A Telegram delivery is an Update the channel parses; a relay
9
+ * post is a gesture whose shape `channels/relay.ts` closes, whose freshness and
10
+ * nonce this file checks, and which `applyRelayGesture` turns into a log event
11
+ * through `recordChannelDecision`, the one place a reported gesture becomes one.
12
+ *
13
+ * ## The secret first
14
+ *
15
+ * `x-approval-relay-secret` is compared before the URL is parsed, the method is
16
+ * read or a byte of the body is consumed. A caller without it learns only that
17
+ * something refused. The comparison is over SHA-256 digests under
18
+ * `timingSafeEqual`, constant-time and length-independent, and a request
19
+ * carrying the header twice is refused rather than resolved, because Node joins
20
+ * repeats and the value compared would not be the value a reader names. These
21
+ * are the webhook's three rules, for the webhook's reasons.
22
+ *
23
+ * ## Why this is its own port and its own secret
24
+ *
25
+ * `approval serve` answers two bearer credentials, the agent's and the
26
+ * tenant's, and NEITHER may reach a decision: grant, reject and attest are
27
+ * human-only verbs that serve does not publish. This receiver answers a third
28
+ * credential held by a third party (the operator's control plane), and it is
29
+ * reachable only from the process `approval channel relay` starts. Nothing in
30
+ * `src/serve/` or `src/mcp/` imports this module, the verb registry marks the
31
+ * verb `human_only` so no wrapper publishes it, and `tests/channel-relay.test.ts`
32
+ * pins both.
33
+ *
34
+ * ## Replay
35
+ *
36
+ * Every gesture carries a nonce and an `issued_at`. The nonce is claimed in a
37
+ * ledger of exclusively linked files under the gate's `daemon/` directory before the
38
+ * gesture is applied, so a replayed post is refused `relay-nonce-replayed`
39
+ * whether it arrives at this process, after its restart, or at a second relay
40
+ * on the same gate; a post older than the window is refused
41
+ * `relay-gesture-stale` after the ledger has forgotten it. The gate is the
42
+ * belt: a decision a replay did reach would be refused `already-decided`.
43
+ */
44
+ import { createHash, timingSafeEqual } from "node:crypto";
45
+ import { createServer } from "node:http";
46
+ import { RELAY_DEFAULT_HOST, RELAY_DEFAULT_PORT, RELAY_DRAIN_LIMIT_BYTES, RELAY_MAX_BODY_BYTES, RELAY_PATH, RELAY_REFUSAL_CODES, RELAY_SECRET_HEADER, applyRelayGesture, parseRelayGesture, relayGestureFresh, relayRefusal, } from "./relay.js";
47
+ /** How long {@link RelayHandle.close} waits for requests in flight. */
48
+ export const RELAY_CLOSE_TIMEOUT_MS = 10_000;
49
+ function digest(value) {
50
+ return createHash("sha256").update(value, "utf8").digest();
51
+ }
52
+ /** Is `offered` the secret? Constant-time over equal-length digests. */
53
+ export function relaySecretMatches(expected, offered) {
54
+ if (offered === null)
55
+ return false;
56
+ return timingSafeEqual(digest(expected), digest(offered));
57
+ }
58
+ /** The one secret header offered, `null` for none, `"duplicate"` for more than one. */
59
+ export function relaySecretHeaderOf(rawHeaders) {
60
+ const values = [];
61
+ for (let index = 0; index + 1 < rawHeaders.length; index += 2) {
62
+ const name = rawHeaders[index];
63
+ if (name !== undefined && name.toLowerCase() === RELAY_SECRET_HEADER) {
64
+ values.push(rawHeaders[index + 1] ?? "");
65
+ }
66
+ }
67
+ if (values.length === 0)
68
+ return null;
69
+ if (values.length > 1)
70
+ return "duplicate";
71
+ return values[0] ?? "";
72
+ }
73
+ async function readBody(req) {
74
+ const chunks = [];
75
+ let size = 0;
76
+ let oversized = false;
77
+ const tooLarge = {
78
+ ok: false,
79
+ code: "relay-body-too-large",
80
+ message: `request body exceeds ${String(RELAY_MAX_BODY_BYTES)} bytes`,
81
+ };
82
+ try {
83
+ for await (const chunk of req) {
84
+ const buffer = chunk;
85
+ size += buffer.length;
86
+ if (size > RELAY_MAX_BODY_BYTES) {
87
+ // Drained rather than answered mid-upload, for the webhook's reason: a
88
+ // response written while the client is still writing closes the socket
89
+ // under it, and the caller sees a transport error instead of a code.
90
+ oversized = true;
91
+ chunks.length = 0;
92
+ if (size > RELAY_DRAIN_LIMIT_BYTES) {
93
+ req.destroy();
94
+ break;
95
+ }
96
+ continue;
97
+ }
98
+ chunks.push(buffer);
99
+ }
100
+ if (oversized)
101
+ return tooLarge;
102
+ }
103
+ catch (cause) {
104
+ if (oversized)
105
+ return tooLarge;
106
+ return {
107
+ ok: false,
108
+ code: "relay-body-unreadable",
109
+ message: cause instanceof Error ? cause.message : String(cause),
110
+ };
111
+ }
112
+ return { ok: true, text: Buffer.concat(chunks).toString("utf8") };
113
+ }
114
+ function emptyRefusals() {
115
+ const counts = {};
116
+ for (const code of RELAY_REFUSAL_CODES)
117
+ counts[code] = 0;
118
+ return counts;
119
+ }
120
+ /** Start the receiver. Resolves once bound; rejects when the bind fails. */
121
+ export async function serveRelay(options) {
122
+ const host = options.host ?? RELAY_DEFAULT_HOST;
123
+ const now = options.now ?? (() => Date.now());
124
+ const complain = options.log ??
125
+ ((message) => {
126
+ process.stderr.write(`${message}\n`);
127
+ });
128
+ const stats = { requests: 0, gestures: 0, refusals: emptyRefusals() };
129
+ const sockets = new Set();
130
+ let inFlight = 0;
131
+ let closed = null;
132
+ function send(res, response) {
133
+ const bytes = Buffer.from(JSON.stringify(response.body), "utf8");
134
+ res.writeHead(response.status, {
135
+ "content-type": "application/json",
136
+ "content-length": bytes.length,
137
+ "cache-control": "no-store",
138
+ });
139
+ res.end(bytes);
140
+ }
141
+ /** Counted, said on stderr, answered with its code. Never an event. */
142
+ function refuse(res, status, code, message) {
143
+ stats.refusals[code] += 1;
144
+ complain(`approval: relay refused a post (${code}): ${message}`);
145
+ send(res, relayRefusal(status, code, message));
146
+ }
147
+ const http = createServer((req, res) => {
148
+ void (async () => {
149
+ stats.requests += 1;
150
+ inFlight += 1;
151
+ try {
152
+ const offered = relaySecretHeaderOf(req.rawHeaders);
153
+ if (offered === "duplicate") {
154
+ refuse(res, 401, "relay-duplicate-secret-header", `this request carries more than one ${RELAY_SECRET_HEADER} header; exactly one is accepted`);
155
+ return;
156
+ }
157
+ if (!relaySecretMatches(options.secret, offered)) {
158
+ refuse(res, 401, "relay-secret-mismatch", offered === null
159
+ ? `no ${RELAY_SECRET_HEADER} header. A post without the relay's secret decides nothing and is not written to the log`
160
+ : `the ${RELAY_SECRET_HEADER} header is not this relay's secret. Nothing reached the gate and nothing was appended`);
161
+ return;
162
+ }
163
+ let url;
164
+ try {
165
+ url = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
166
+ }
167
+ catch {
168
+ refuse(res, 400, "relay-malformed-request", "the request line and Host header do not form a URL");
169
+ return;
170
+ }
171
+ if (url.pathname !== RELAY_PATH) {
172
+ refuse(res, 404, "relay-unknown-path", `no endpoint at ${url.pathname}; the relay answers ${RELAY_PATH}`);
173
+ return;
174
+ }
175
+ const method = req.method ?? "GET";
176
+ if (method !== "POST") {
177
+ refuse(res, 405, "relay-method-not-allowed", `${RELAY_PATH} answers POST, not ${method}`);
178
+ return;
179
+ }
180
+ const body = await readBody(req);
181
+ if (!body.ok) {
182
+ refuse(res, body.code === "relay-body-too-large" ? 413 : 400, body.code, body.message);
183
+ return;
184
+ }
185
+ let decoded;
186
+ try {
187
+ decoded = JSON.parse(body.text);
188
+ }
189
+ catch (cause) {
190
+ refuse(res, 400, "relay-body-unreadable", `request body is not JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
191
+ return;
192
+ }
193
+ const parsed = parseRelayGesture(decoded);
194
+ if (!parsed.ok) {
195
+ refuse(res, 400, parsed.code, parsed.message);
196
+ return;
197
+ }
198
+ const gesture = parsed.gesture;
199
+ const nowMs = now();
200
+ if (!relayGestureFresh(gesture.issuedAtMs, nowMs)) {
201
+ refuse(res, 400, "relay-gesture-stale", `issued_at ${new Date(gesture.issuedAtMs).toISOString()} is more than five minutes from this relay's clock (${new Date(nowMs).toISOString()}); a gesture is carried while the person who made it is still there, and an old one is refused rather than honoured`);
202
+ return;
203
+ }
204
+ // Claimed BEFORE the gesture is applied, so a replay racing the
205
+ // original loses at the file system rather than at the gate. A claim
206
+ // that cannot be recorded fails closed: replay protection that could
207
+ // not be promised is not offered as though it were.
208
+ const claim = options.ledger.claim(gesture.nonce, nowMs);
209
+ if (claim === "replayed") {
210
+ refuse(res, 409, "relay-nonce-replayed", "this nonce was already used. Each gesture carries its own; a retry is a new post with a new nonce, and the gate answers a repeated decision already-decided");
211
+ return;
212
+ }
213
+ if (claim !== "claimed") {
214
+ refuse(res, 503, "relay-nonce-unavailable", `the nonce ledger could not record this gesture (${claim.error}), so a replay of it could not be refused later; nothing was decided`);
215
+ return;
216
+ }
217
+ let response;
218
+ try {
219
+ response = applyRelayGesture(options.logPath, gesture, options);
220
+ }
221
+ catch (cause) {
222
+ refuse(res, 500, "relay-handler-failed", `applying the gesture failed (${cause instanceof Error ? cause.message : String(cause)}); read the log for what, if anything, was appended`);
223
+ return;
224
+ }
225
+ stats.gestures += 1;
226
+ if (options.afterGesture !== undefined) {
227
+ try {
228
+ options.afterGesture(response);
229
+ }
230
+ catch {
231
+ // A reporting hook that throws must not turn an answered gesture
232
+ // into a failed request.
233
+ }
234
+ }
235
+ send(res, response);
236
+ }
237
+ catch (cause) {
238
+ if (!res.headersSent) {
239
+ refuse(res, 500, "relay-handler-failed", `the request failed: ${cause instanceof Error ? cause.message : String(cause)}`);
240
+ }
241
+ else {
242
+ res.end();
243
+ }
244
+ }
245
+ finally {
246
+ inFlight -= 1;
247
+ }
248
+ })();
249
+ });
250
+ http.on("connection", (socket) => {
251
+ sockets.add(socket);
252
+ socket.on("close", () => sockets.delete(socket));
253
+ });
254
+ await new Promise((settle, fail) => {
255
+ const onError = (cause) => fail(cause);
256
+ http.once("error", onError);
257
+ http.listen(options.port ?? RELAY_DEFAULT_PORT, host, () => {
258
+ http.off("error", onError);
259
+ settle();
260
+ });
261
+ });
262
+ const address = http.address();
263
+ const boundPort = typeof address === "object" && address !== null ? address.port : (options.port ?? RELAY_DEFAULT_PORT);
264
+ const boundHost = typeof address === "object" && address !== null ? address.address : host;
265
+ async function stop() {
266
+ const accepted = new Promise((settle) => {
267
+ http.close(() => settle());
268
+ });
269
+ const deadline = Date.now() + (options.closeTimeoutMs ?? RELAY_CLOSE_TIMEOUT_MS);
270
+ while (inFlight > 0 && Date.now() < deadline) {
271
+ await new Promise((settle) => {
272
+ setTimeout(settle, 5).unref?.();
273
+ });
274
+ }
275
+ if (inFlight > 0) {
276
+ complain(`approval: relay: ${String(inFlight)} request(s) still in flight at the close deadline; closing anyway`);
277
+ }
278
+ for (const socket of sockets)
279
+ socket.destroy();
280
+ sockets.clear();
281
+ await accepted;
282
+ }
283
+ return {
284
+ host: boundHost,
285
+ port: boundPort,
286
+ path: RELAY_PATH,
287
+ stats: () => ({ ...stats, refusals: { ...stats.refusals } }),
288
+ inFlight: () => inFlight,
289
+ close: () => {
290
+ closed ??= stop();
291
+ return closed;
292
+ },
293
+ };
294
+ }
295
+ //# sourceMappingURL=relay-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-server.js","sourceRoot":"","sources":["../../../src/channels/relay-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,273 @@
1
+ /**
2
+ * The authenticated relay channel: its protocol (SPEC.md §10.3, APRV-455).
3
+ *
4
+ * Agent Village residents review their approval.md settings in the Edge City
5
+ * onboarding app, and that review is meant to be the attestation of the policy
6
+ * in force. The app signs the resident in through EdgeOS, and the operator's
7
+ * control plane is what holds that session. This module is the daemon's side
8
+ * of the hop from there: a gesture the control plane carries, attributed to the
9
+ * EdgeOS `/humans/me` id of the session that made it, arriving over an
10
+ * authenticated HTTP post (`channels/relay-server.ts`) and turned into a log
11
+ * event by the SAME function a Telegram tap goes through.
12
+ *
13
+ * ## What the relay is trusted for, and what it is not
14
+ *
15
+ * The daemon trusts the relay's report of WHO acted, exactly as it trusts the
16
+ * Telegram Bot API's report of `callback_query.from.id`. That is operator
17
+ * trust (APRV-422: no hosted service has authority over decisions), and it is
18
+ * the whole of what the shared secret buys: a post that carries it is a report
19
+ * from the operator's relay, and a post that does not is nothing. Every
20
+ * question of AUTHORITY is answered here, by the runtime, from the verified log
21
+ * and the attested policy:
22
+ *
23
+ * - which approver the account is, by `core/sender-identity.ts` against the
24
+ * attested mapping, exactly as a tap is resolved;
25
+ * - whether that approver may decide this request, attest this policy, or
26
+ * neither, by `core/gate.ts` and `core/policy-proposal.ts`, unchanged;
27
+ * - whether the request is still answerable (expiry, policy drift, an earlier
28
+ * answer), by the same gate, with the same codes.
29
+ *
30
+ * The relay holds NO human identity of its own. A Telegram listener launched
31
+ * `--as human:carter` falls back to that identity for a channel the policy maps
32
+ * no sender for; this surface does not, ever (`requireSenderMapping`). A post
33
+ * naming an account the attested policy does not map is refused
34
+ * `sender-unmapped` with one `audit.decision_refused`, and a policy that maps
35
+ * nobody on `edgeos` refuses every gesture the same way.
36
+ *
37
+ * ## The body, closed
38
+ *
39
+ * ```json
40
+ * { "gesture": "attest" | "decline", "policy_sha256": "<64 hex>",
41
+ * "sender": { "channel": "edgeos", "id": "<EdgeOS /humans/me id>" },
42
+ * "nonce": "<16..128 of A-Z a-z 0-9 _ ->", "issued_at": "<RFC 3339 UTC>" }
43
+ * { "gesture": "grant" | "reject", "action_key": "<the request's key>",
44
+ * "sender": { ... }, "nonce": "...", "issued_at": "..." }
45
+ * { "gesture": "propose", "policy_sha256": "<64 hex>",
46
+ * "nonce": "...", "issued_at": "..." }
47
+ * ```
48
+ *
49
+ * Unknown keys are refused rather than ignored, so a field that looks like it
50
+ * carries authority (an `actor`, a second sender, a `ts`) cannot ride along on
51
+ * a valid post and be read by something later. `issued_at` is used for one
52
+ * thing only, the freshness window that bounds how long a nonce must be
53
+ * remembered; it is never recorded, and every record's `ts` is still the
54
+ * runtime's own (SPEC.md §11.1 invariant 2).
55
+ *
56
+ * ## Why `propose` exists here
57
+ *
58
+ * The control plane renders the resident's policy and writes it into the
59
+ * store; somebody then has to append the `policy.proposed` an acceptance
60
+ * answers. The existing route for that is `approval policy amend`, which is a
61
+ * git-aware terminal ceremony that blocks for its own `--wait`. `propose` is the
62
+ * same `core/policy-proposal.ts` call, under the same secret, recorded under the
63
+ * actor the operator started the relay with (`--proposer`), and it returns at
64
+ * once. It binds the hash the control plane rendered: bytes on disk that are not
65
+ * the bytes it names are refused, so the prompt the resident answers is the file
66
+ * the control plane showed them.
67
+ *
68
+ * It also admits the one proposal `policy amend` refuses: bytes that are
69
+ * ALREADY the policy in force (`ProposeInput.reaffirm`). The onboarding review
70
+ * most residents make is "accept, unchanged", and the operator attested those
71
+ * bytes at provisioning (APRV-449). The reaffirmation changes the attester of
72
+ * record and nothing else: same hash, a diff that reads "no semantic change",
73
+ * and an acceptance resolved against the policy in force like any other.
74
+ *
75
+ * ## No token leaves this surface
76
+ *
77
+ * A grant may mint a single-use execution token for the deciding surface
78
+ * (manual delivery). A relay is not a place a token can go: the control plane
79
+ * is not the requester and the HTTP response is not a secrets channel. The
80
+ * token `recordChannelDecision` returns is dropped here, unread, and the
81
+ * response says only whether one was minted. The deployments this exists for
82
+ * mint none (a harness-executed request) or seal it to the requester's own
83
+ * key, so nothing is lost in practice, and the residual is stated rather than
84
+ * hidden: a manual-delivery request granted through the relay has a token
85
+ * nobody holds, and its requester asks again.
86
+ */
87
+ import type { DecideOptions } from "../core/gate.js";
88
+ import { type ChannelSender } from "../core/sender-identity.js";
89
+ /** The channel name every relay gesture is recorded under, and its senders' namespace. */
90
+ export declare const RELAY_CHANNEL = "edgeos";
91
+ /** The header the shared secret travels in, lowercased as Node presents it. */
92
+ export declare const RELAY_SECRET_HEADER = "x-approval-relay-secret";
93
+ /** The one path this transport answers. */
94
+ export declare const RELAY_PATH = "/relay/gesture";
95
+ /** The interface it binds when the operator names none. Loopback, always. */
96
+ export declare const RELAY_DEFAULT_HOST = "127.0.0.1";
97
+ /** The port it binds when the operator names none: `serve` is 4682, the webhook 4683. */
98
+ export declare const RELAY_DEFAULT_PORT = 4684;
99
+ /** Largest body accepted. A gesture is a handful of short strings. */
100
+ export declare const RELAY_MAX_BODY_BYTES: number;
101
+ /** How much of an oversized body is drained before the socket is dropped. */
102
+ export declare const RELAY_DRAIN_LIMIT_BYTES: number;
103
+ /**
104
+ * How far `issued_at` may sit from this process's clock, either side.
105
+ *
106
+ * Five minutes: a control plane and a sandbox on the same platform agree to
107
+ * well under a second, and a gesture older than this was not made by a person
108
+ * who is still looking at the screen that made it.
109
+ */
110
+ export declare const RELAY_GESTURE_WINDOW_MS: number;
111
+ /**
112
+ * How long a claimed nonce is remembered.
113
+ *
114
+ * Longer than the whole window a fresh gesture can occupy (issued up to one
115
+ * window in the future, accepted up to one window late), with a minute of
116
+ * margin, so a replay is refused by the nonce while it is still fresh and by
117
+ * the window once the nonce is forgotten. There is no instant at which neither
118
+ * holds.
119
+ */
120
+ export declare const RELAY_NONCE_RETENTION_MS: number;
121
+ /**
122
+ * How long a relay proposal stays open (APRV-455 refuter L2).
123
+ *
124
+ * The onboarding review is answered in the screen that rendered it, so an hour
125
+ * is generous; past it the prompt retires from every channel queue by
126
+ * derivation, and a control plane whose resident came back later proposes
127
+ * again. Until a proposal is attested the bytes on disk are not the policy in
128
+ * force, so the control plane restores the in-force bytes when a proposal is
129
+ * declined or lapses.
130
+ */
131
+ export declare const RELAY_PROPOSAL_TTL_MS: number;
132
+ /** The actor a proposal is recorded under when the operator names none. */
133
+ export declare const RELAY_DEFAULT_PROPOSER = "agent:edgeos-relay";
134
+ /** The gestures, closed. */
135
+ export declare const RELAY_GESTURES: readonly ["propose", "attest", "decline", "grant", "reject"];
136
+ export type RelayGestureName = (typeof RELAY_GESTURES)[number];
137
+ /**
138
+ * Every refusal THIS transport makes, and nothing else. Frozen, per SPEC.md
139
+ * §11.1 invariant 6, and pinned by `conformance/vectors/refusal-unions.v1.json`
140
+ * because the control plane on the other side of the hop is a second
141
+ * implementation that branches on these strings.
142
+ *
143
+ * Refusals the GATE or the decision surface makes (`sender-unmapped`,
144
+ * `expired`, `policy-drift`, `already-decided`, `proposal-stale`,
145
+ * `proposal-not-found`, ...) are not here: they arrive verbatim under
146
+ * `refusal` in the response body, from their own unions, exactly as a Telegram
147
+ * tap would surface them.
148
+ *
149
+ * None of these appends to the log. A post the relay refuses is a fact about
150
+ * the transport and is counted and reported on stderr, because an endpoint
151
+ * that wrote its refusals into the approval log would let anyone who can reach
152
+ * it grow the record a human is asked to trust.
153
+ */
154
+ export declare const RELAY_REFUSAL_CODES: readonly [
155
+ /** No secret header, or one that is not the launch environment's secret. */
156
+ "relay-secret-mismatch",
157
+ /** More than one secret header. Malformed, and not resolvable by choosing. */
158
+ "relay-duplicate-secret-header",
159
+ /** The request line and the Host header do not form a URL. */
160
+ "relay-malformed-request",
161
+ /** A path this transport does not serve. */
162
+ "relay-unknown-path",
163
+ /** The right path, a method other than POST. */
164
+ "relay-method-not-allowed",
165
+ /** Larger than {@link RELAY_MAX_BODY_BYTES}. */
166
+ "relay-body-too-large",
167
+ /** The body could not be read off the socket, or is not JSON. */
168
+ "relay-body-unreadable",
169
+ /** JSON that is not a gesture of the closed shape. */
170
+ "relay-body-invalid",
171
+ /**
172
+ * The sender is not an EdgeOS account this transport can report: a channel
173
+ * other than `edgeos`, or an id outside the grammar `senders.edgeos` pins.
174
+ */
175
+ "relay-sender-invalid",
176
+ /** `issued_at` is further than {@link RELAY_GESTURE_WINDOW_MS} from now. */
177
+ "relay-gesture-stale",
178
+ /** This nonce was already used within {@link RELAY_NONCE_RETENTION_MS}. */
179
+ "relay-nonce-replayed",
180
+ /**
181
+ * The nonce ledger could not be written, so a replay could not be refused.
182
+ * Fails closed: nothing is decided without it.
183
+ */
184
+ "relay-nonce-unavailable",
185
+ /**
186
+ * `propose` named a hash that is not the hash of the policy file on disk.
187
+ * Distinct from the gate's `proposal-stale`, which is the same fact found at
188
+ * ACCEPTANCE: this one is found before anything is proposed.
189
+ */
190
+ "relay-policy-mismatch",
191
+ /** The gesture threw while being applied. Nothing is claimed about the log. */
192
+ "relay-handler-failed"];
193
+ export type RelayRefusalCode = (typeof RELAY_REFUSAL_CODES)[number];
194
+ /** A parsed, shape-checked gesture. */
195
+ export type RelayGesture = {
196
+ gesture: "attest" | "decline";
197
+ policySha256: string;
198
+ sender: ChannelSender;
199
+ nonce: string;
200
+ issuedAtMs: number;
201
+ } | {
202
+ gesture: "grant" | "reject";
203
+ actionKey: string;
204
+ sender: ChannelSender;
205
+ nonce: string;
206
+ issuedAtMs: number;
207
+ } | {
208
+ gesture: "propose";
209
+ policySha256: string;
210
+ nonce: string;
211
+ issuedAtMs: number;
212
+ };
213
+ export type RelayParse = {
214
+ ok: true;
215
+ gesture: RelayGesture;
216
+ } | {
217
+ ok: false;
218
+ code: RelayRefusalCode;
219
+ message: string;
220
+ };
221
+ /**
222
+ * Check one decoded body against the closed shape.
223
+ *
224
+ * Pure: no clock, no file, no log. Freshness and the nonce are the server's,
225
+ * because both need state this function deliberately does not hold.
226
+ */
227
+ export declare function parseRelayGesture(value: unknown): RelayParse;
228
+ /** Is `issuedAtMs` within the window of `nowMs`? */
229
+ export declare function relayGestureFresh(issuedAtMs: number, nowMs: number): boolean;
230
+ /** What claiming one nonce came to. */
231
+ export type NonceClaim = "claimed" | "replayed" | {
232
+ error: string;
233
+ };
234
+ /**
235
+ * Where used nonces are remembered.
236
+ *
237
+ * One file per nonce, named by its SHA-256, written whole to a private temp and
238
+ * then hard-linked into place, under the gate's own `daemon/` directory. The
239
+ * file system settles the race: `link` fails with EEXIST exactly as `O_EXCL`
240
+ * does, so of any number of processes claiming one nonce at once exactly one
241
+ * wins, and a claim file never exists without the time it was claimed.
242
+ * That is why two relays on one gate need no lease between them for replay
243
+ * protection, and why a restart forgets nothing it was still obliged to
244
+ * remember. Files older than {@link RELAY_NONCE_RETENTION_MS} are pruned.
245
+ */
246
+ export interface NonceLedger {
247
+ claim(nonce: string, nowMs: number): NonceClaim;
248
+ }
249
+ /** The file ledger described at {@link NonceLedger}. */
250
+ export declare function fileNonceLedger(dir: string): NonceLedger;
251
+ /** What applying a gesture produced: an HTTP status and the JSON body. */
252
+ export interface RelayResponse {
253
+ status: number;
254
+ body: Record<string, unknown>;
255
+ }
256
+ export interface ApplyRelayOptions {
257
+ /** The gate options a decision runs under: the policy location, the clock. */
258
+ gateOptions: DecideOptions;
259
+ /** The actor a `propose` is recorded under. */
260
+ proposer: string;
261
+ }
262
+ /** A transport refusal as a response. Never appended. */
263
+ export declare function relayRefusal(status: number, code: RelayRefusalCode, message: string): RelayResponse;
264
+ /**
265
+ * Turn one checked gesture into a log event, or into the refusal that replaces
266
+ * one.
267
+ *
268
+ * Decisions and attestations go through {@link recordChannelDecision}, the one
269
+ * place a reported gesture becomes a log event, with `requireSenderMapping` set
270
+ * and the channel named `edgeos`. A proposal goes through `proposeAttestation`.
271
+ * Nothing here decides anything those two do not already decide.
272
+ */
273
+ export declare function applyRelayGesture(logPath: string, gesture: RelayGesture, options: ApplyRelayOptions): RelayResponse;