@cello-protocol/daemon 0.0.227 → 0.0.229

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 (895) hide show
  1. package/dist/agent-admin-handlers.d.ts +56 -0
  2. package/dist/agent-admin-handlers.d.ts.map +1 -0
  3. package/dist/agent-admin-handlers.js +73 -0
  4. package/dist/agent-admin-handlers.js.map +1 -0
  5. package/dist/agent-handlers.d.ts +72 -0
  6. package/dist/agent-handlers.d.ts.map +1 -0
  7. package/dist/agent-handlers.js +604 -0
  8. package/dist/agent-handlers.js.map +1 -0
  9. package/dist/agent-id-migration.d.ts +70 -0
  10. package/dist/agent-id-migration.d.ts.map +1 -0
  11. package/dist/agent-id-migration.js +468 -0
  12. package/dist/agent-id-migration.js.map +1 -0
  13. package/dist/agent-loader.d.ts +37 -0
  14. package/dist/agent-loader.d.ts.map +1 -0
  15. package/dist/agent-loader.js +37 -0
  16. package/dist/agent-loader.js.map +1 -0
  17. package/dist/agent-selection-root.d.ts +29 -0
  18. package/dist/agent-selection-root.d.ts.map +1 -0
  19. package/dist/agent-selection-root.js +126 -0
  20. package/dist/agent-selection-root.js.map +1 -0
  21. package/dist/agent-selection.d.ts +73 -0
  22. package/dist/agent-selection.d.ts.map +1 -0
  23. package/dist/agent-selection.js +80 -0
  24. package/dist/agent-selection.js.map +1 -0
  25. package/dist/agent-settings-keys.d.ts +52 -0
  26. package/dist/agent-settings-keys.d.ts.map +1 -0
  27. package/dist/agent-settings-keys.js +114 -0
  28. package/dist/agent-settings-keys.js.map +1 -0
  29. package/dist/agent-state.d.ts +67 -0
  30. package/dist/agent-state.d.ts.map +1 -0
  31. package/dist/agent-state.js +66 -0
  32. package/dist/agent-state.js.map +1 -0
  33. package/dist/assignment-verify.d.ts +90 -0
  34. package/dist/assignment-verify.d.ts.map +1 -0
  35. package/dist/assignment-verify.js +301 -0
  36. package/dist/assignment-verify.js.map +1 -0
  37. package/dist/attendance-wiring.d.ts +23 -0
  38. package/dist/attendance-wiring.d.ts.map +1 -0
  39. package/dist/attendance-wiring.js +248 -0
  40. package/dist/attendance-wiring.js.map +1 -0
  41. package/dist/attestation-body.d.ts +47 -0
  42. package/dist/attestation-body.d.ts.map +1 -0
  43. package/dist/attestation-body.js +71 -0
  44. package/dist/attestation-body.js.map +1 -0
  45. package/dist/authorship-verification.d.ts +91 -0
  46. package/dist/authorship-verification.d.ts.map +1 -0
  47. package/dist/authorship-verification.js +532 -0
  48. package/dist/authorship-verification.js.map +1 -0
  49. package/dist/away-detection.d.ts +139 -0
  50. package/dist/away-detection.d.ts.map +1 -0
  51. package/dist/away-detection.js +186 -0
  52. package/dist/away-detection.js.map +1 -0
  53. package/dist/away-inbox-oneshot.d.ts +40 -0
  54. package/dist/away-inbox-oneshot.d.ts.map +1 -0
  55. package/dist/away-inbox-oneshot.js +311 -0
  56. package/dist/away-inbox-oneshot.js.map +1 -0
  57. package/dist/backup-restore-handlers.d.ts +10 -0
  58. package/dist/backup-restore-handlers.d.ts.map +1 -0
  59. package/dist/backup-restore-handlers.js +82 -0
  60. package/dist/backup-restore-handlers.js.map +1 -0
  61. package/dist/backup-restore.d.ts +102 -0
  62. package/dist/backup-restore.d.ts.map +1 -0
  63. package/dist/backup-restore.js +341 -0
  64. package/dist/backup-restore.js.map +1 -0
  65. package/dist/bin/cello-daemon.d.ts +13 -0
  66. package/dist/bin/cello-daemon.d.ts.map +1 -0
  67. package/dist/bin/cello-daemon.js.map +1 -0
  68. package/dist/boot-agents.d.ts +64 -0
  69. package/dist/boot-agents.d.ts.map +1 -0
  70. package/dist/boot-agents.js +241 -0
  71. package/dist/boot-agents.js.map +1 -0
  72. package/dist/boot-connection-state.d.ts +38 -0
  73. package/dist/boot-connection-state.d.ts.map +1 -0
  74. package/dist/boot-connection-state.js +95 -0
  75. package/dist/boot-connection-state.js.map +1 -0
  76. package/dist/boot-core.d.ts +36 -0
  77. package/dist/boot-core.d.ts.map +1 -0
  78. package/dist/boot-core.js +293 -0
  79. package/dist/boot-core.js.map +1 -0
  80. package/dist/boot-parked-content.d.ts +34 -0
  81. package/dist/boot-parked-content.d.ts.map +1 -0
  82. package/dist/boot-parked-content.js +528 -0
  83. package/dist/boot-parked-content.js.map +1 -0
  84. package/dist/boot-sweeps.d.ts +22 -0
  85. package/dist/boot-sweeps.d.ts.map +1 -0
  86. package/dist/boot-sweeps.js +51 -0
  87. package/dist/boot-sweeps.js.map +1 -0
  88. package/dist/bundled-consortium-manifest.d.ts +47 -0
  89. package/dist/bundled-consortium-manifest.d.ts.map +1 -0
  90. package/dist/bundled-consortium-manifest.js +116 -0
  91. package/dist/bundled-consortium-manifest.js.map +1 -0
  92. package/dist/cbor-blob-migration.d.ts +8 -0
  93. package/dist/cbor-blob-migration.d.ts.map +1 -0
  94. package/dist/cbor-blob-migration.js +116 -0
  95. package/dist/cbor-blob-migration.js.map +1 -0
  96. package/dist/challenge-verifier.d.ts +12 -0
  97. package/dist/challenge-verifier.d.ts.map +1 -0
  98. package/dist/challenge-verifier.js +11 -0
  99. package/dist/challenge-verifier.js.map +1 -0
  100. package/dist/channel-log-store.d.ts +80 -0
  101. package/dist/channel-log-store.d.ts.map +1 -0
  102. package/dist/channel-log-store.js +290 -0
  103. package/dist/channel-log-store.js.map +1 -0
  104. package/dist/close-commitment.d.ts +67 -0
  105. package/dist/close-commitment.d.ts.map +1 -0
  106. package/dist/close-commitment.js +78 -0
  107. package/dist/close-commitment.js.map +1 -0
  108. package/dist/close-session-handler.d.ts +94 -0
  109. package/dist/close-session-handler.d.ts.map +1 -0
  110. package/dist/close-session-handler.js +1418 -0
  111. package/dist/close-session-handler.js.map +1 -0
  112. package/dist/co-attendance.d.ts +29 -0
  113. package/dist/co-attendance.d.ts.map +1 -0
  114. package/dist/co-attendance.js +32 -0
  115. package/dist/co-attendance.js.map +1 -0
  116. package/dist/column-birth.d.ts +40 -0
  117. package/dist/column-birth.d.ts.map +1 -0
  118. package/dist/column-birth.js +61 -0
  119. package/dist/column-birth.js.map +1 -0
  120. package/dist/connect-or-start.d.ts +25 -0
  121. package/dist/connect-or-start.d.ts.map +1 -0
  122. package/dist/connect-or-start.js +175 -0
  123. package/dist/connect-or-start.js.map +1 -0
  124. package/dist/connection-agents.d.ts +16 -0
  125. package/dist/connection-agents.d.ts.map +1 -0
  126. package/dist/connection-agents.js +68 -0
  127. package/dist/connection-agents.js.map +1 -0
  128. package/dist/consent-migration.d.ts +49 -0
  129. package/dist/consent-migration.d.ts.map +1 -0
  130. package/dist/consent-migration.js +128 -0
  131. package/dist/consent-migration.js.map +1 -0
  132. package/dist/consortium-bootstrap.d.ts +138 -0
  133. package/dist/consortium-bootstrap.d.ts.map +1 -0
  134. package/dist/consortium-bootstrap.js +339 -0
  135. package/dist/consortium-bootstrap.js.map +1 -0
  136. package/dist/consortium-fingerprint.d.ts +115 -0
  137. package/dist/consortium-fingerprint.d.ts.map +1 -0
  138. package/dist/consortium-fingerprint.js +175 -0
  139. package/dist/consortium-fingerprint.js.map +1 -0
  140. package/dist/contact-handlers.d.ts +59 -0
  141. package/dist/contact-handlers.d.ts.map +1 -0
  142. package/dist/contact-handlers.js +343 -0
  143. package/dist/contact-handlers.js.map +1 -0
  144. package/dist/contact-pubkey-case.d.ts +65 -0
  145. package/dist/contact-pubkey-case.d.ts.map +1 -0
  146. package/dist/contact-pubkey-case.js +136 -0
  147. package/dist/contact-pubkey-case.js.map +1 -0
  148. package/dist/contacts-tier-migration.d.ts +90 -0
  149. package/dist/contacts-tier-migration.d.ts.map +1 -0
  150. package/dist/contacts-tier-migration.js +150 -0
  151. package/dist/contacts-tier-migration.js.map +1 -0
  152. package/dist/content-encryption-status.d.ts +111 -0
  153. package/dist/content-encryption-status.d.ts.map +1 -0
  154. package/dist/content-encryption-status.js +158 -0
  155. package/dist/content-encryption-status.js.map +1 -0
  156. package/dist/content-park-client.d.ts +90 -0
  157. package/dist/content-park-client.d.ts.map +1 -0
  158. package/dist/content-park-client.js +362 -0
  159. package/dist/content-park-client.js.map +1 -0
  160. package/dist/content-park.d.ts +52 -0
  161. package/dist/content-park.d.ts.map +1 -0
  162. package/dist/content-park.js +1309 -0
  163. package/dist/content-park.js.map +1 -0
  164. package/dist/cross-node-negotiation.d.ts +44 -0
  165. package/dist/cross-node-negotiation.d.ts.map +1 -0
  166. package/dist/cross-node-negotiation.js +34 -0
  167. package/dist/cross-node-negotiation.js.map +1 -0
  168. package/dist/daemon-handle.d.ts +57 -0
  169. package/dist/daemon-handle.d.ts.map +1 -0
  170. package/dist/daemon-handle.js +2 -0
  171. package/dist/daemon-handle.js.map +1 -0
  172. package/dist/daemon-status-report.d.ts +50 -0
  173. package/dist/daemon-status-report.d.ts.map +1 -0
  174. package/dist/daemon-status-report.js +81 -0
  175. package/dist/daemon-status-report.js.map +1 -0
  176. package/dist/daemon.d.ts +45 -0
  177. package/dist/daemon.d.ts.map +1 -0
  178. package/dist/daemon.js +1177 -0
  179. package/dist/daemon.js.map +1 -0
  180. package/dist/db-identity-store.d.ts +159 -0
  181. package/dist/db-identity-store.d.ts.map +1 -0
  182. package/dist/db-identity-store.js +502 -0
  183. package/dist/db-identity-store.js.map +1 -0
  184. package/dist/delivery-open-registry.d.ts +92 -0
  185. package/dist/delivery-open-registry.d.ts.map +1 -0
  186. package/dist/delivery-open-registry.js +121 -0
  187. package/dist/delivery-open-registry.js.map +1 -0
  188. package/dist/delivery-session-suspects.d.ts +56 -0
  189. package/dist/delivery-session-suspects.d.ts.map +1 -0
  190. package/dist/delivery-session-suspects.js +94 -0
  191. package/dist/delivery-session-suspects.js.map +1 -0
  192. package/dist/directory-auth-posture.d.ts +87 -0
  193. package/dist/directory-auth-posture.d.ts.map +1 -0
  194. package/dist/directory-auth-posture.js +134 -0
  195. package/dist/directory-auth-posture.js.map +1 -0
  196. package/dist/directory-bootstrap.d.ts +310 -0
  197. package/dist/directory-bootstrap.d.ts.map +1 -0
  198. package/dist/directory-bootstrap.js +557 -0
  199. package/dist/directory-bootstrap.js.map +1 -0
  200. package/dist/directory-connect.d.ts +27 -0
  201. package/dist/directory-connect.d.ts.map +1 -0
  202. package/dist/directory-connect.js +106 -0
  203. package/dist/directory-connect.js.map +1 -0
  204. package/dist/disconnect-cleanup.d.ts +51 -0
  205. package/dist/disconnect-cleanup.d.ts.map +1 -0
  206. package/dist/disconnect-cleanup.js +72 -0
  207. package/dist/disconnect-cleanup.js.map +1 -0
  208. package/dist/document-amendment-store.d.ts +120 -0
  209. package/dist/document-amendment-store.d.ts.map +1 -0
  210. package/dist/document-amendment-store.js +266 -0
  211. package/dist/document-amendment-store.js.map +1 -0
  212. package/dist/document-delivery-transport.d.ts +168 -0
  213. package/dist/document-delivery-transport.d.ts.map +1 -0
  214. package/dist/document-delivery-transport.js +206 -0
  215. package/dist/document-delivery-transport.js.map +1 -0
  216. package/dist/document-engine.d.ts +134 -0
  217. package/dist/document-engine.d.ts.map +1 -0
  218. package/dist/document-engine.js +282 -0
  219. package/dist/document-engine.js.map +1 -0
  220. package/dist/document-flag.d.ts +58 -0
  221. package/dist/document-flag.d.ts.map +1 -0
  222. package/dist/document-flag.js +70 -0
  223. package/dist/document-flag.js.map +1 -0
  224. package/dist/document-frame-router.d.ts +245 -0
  225. package/dist/document-frame-router.d.ts.map +1 -0
  226. package/dist/document-frame-router.js +397 -0
  227. package/dist/document-frame-router.js.map +1 -0
  228. package/dist/document-gate-wiring.d.ts +61 -0
  229. package/dist/document-gate-wiring.d.ts.map +1 -0
  230. package/dist/document-gate-wiring.js +125 -0
  231. package/dist/document-gate-wiring.js.map +1 -0
  232. package/dist/document-gate.d.ts +149 -0
  233. package/dist/document-gate.d.ts.map +1 -0
  234. package/dist/document-gate.js +509 -0
  235. package/dist/document-gate.js.map +1 -0
  236. package/dist/document-handlers.d.ts +47 -0
  237. package/dist/document-handlers.d.ts.map +1 -0
  238. package/dist/document-handlers.js +2203 -0
  239. package/dist/document-handlers.js.map +1 -0
  240. package/dist/document-handshake.d.ts +176 -0
  241. package/dist/document-handshake.d.ts.map +1 -0
  242. package/dist/document-handshake.js +452 -0
  243. package/dist/document-handshake.js.map +1 -0
  244. package/dist/document-inbound.d.ts +162 -0
  245. package/dist/document-inbound.d.ts.map +1 -0
  246. package/dist/document-inbound.js +530 -0
  247. package/dist/document-inbound.js.map +1 -0
  248. package/dist/document-json.d.ts +120 -0
  249. package/dist/document-json.d.ts.map +1 -0
  250. package/dist/document-json.js +191 -0
  251. package/dist/document-json.js.map +1 -0
  252. package/dist/document-layer.d.ts +215 -0
  253. package/dist/document-layer.d.ts.map +1 -0
  254. package/dist/document-layer.js +1025 -0
  255. package/dist/document-layer.js.map +1 -0
  256. package/dist/document-lifecycle.d.ts +52 -0
  257. package/dist/document-lifecycle.d.ts.map +1 -0
  258. package/dist/document-lifecycle.js +134 -0
  259. package/dist/document-lifecycle.js.map +1 -0
  260. package/dist/document-live-docs.d.ts +58 -0
  261. package/dist/document-live-docs.d.ts.map +1 -0
  262. package/dist/document-live-docs.js +126 -0
  263. package/dist/document-live-docs.js.map +1 -0
  264. package/dist/document-notify.d.ts +228 -0
  265. package/dist/document-notify.d.ts.map +1 -0
  266. package/dist/document-notify.js +580 -0
  267. package/dist/document-notify.js.map +1 -0
  268. package/dist/document-profile.d.ts +61 -0
  269. package/dist/document-profile.d.ts.map +1 -0
  270. package/dist/document-profile.js +112 -0
  271. package/dist/document-profile.js.map +1 -0
  272. package/dist/document-publish.d.ts +87 -0
  273. package/dist/document-publish.d.ts.map +1 -0
  274. package/dist/document-publish.js +173 -0
  275. package/dist/document-publish.js.map +1 -0
  276. package/dist/document-reachability.d.ts +42 -0
  277. package/dist/document-reachability.d.ts.map +1 -0
  278. package/dist/document-reachability.js +80 -0
  279. package/dist/document-reachability.js.map +1 -0
  280. package/dist/document-reconcile-engine.d.ts +66 -0
  281. package/dist/document-reconcile-engine.d.ts.map +1 -0
  282. package/dist/document-reconcile-engine.js +225 -0
  283. package/dist/document-reconcile-engine.js.map +1 -0
  284. package/dist/document-reconcile-scheduler.d.ts +163 -0
  285. package/dist/document-reconcile-scheduler.d.ts.map +1 -0
  286. package/dist/document-reconcile-scheduler.js +303 -0
  287. package/dist/document-reconcile-scheduler.js.map +1 -0
  288. package/dist/document-rejection.d.ts +251 -0
  289. package/dist/document-rejection.d.ts.map +1 -0
  290. package/dist/document-rejection.js +435 -0
  291. package/dist/document-rejection.js.map +1 -0
  292. package/dist/document-screen.d.ts +114 -0
  293. package/dist/document-screen.d.ts.map +1 -0
  294. package/dist/document-screen.js +223 -0
  295. package/dist/document-screen.js.map +1 -0
  296. package/dist/document-store.d.ts +372 -0
  297. package/dist/document-store.d.ts.map +1 -0
  298. package/dist/document-store.js +931 -0
  299. package/dist/document-store.js.map +1 -0
  300. package/dist/document-surface.d.ts +32 -0
  301. package/dist/document-surface.d.ts.map +1 -0
  302. package/dist/document-surface.js +151 -0
  303. package/dist/document-surface.js.map +1 -0
  304. package/dist/document-types.d.ts +94 -0
  305. package/dist/document-types.d.ts.map +1 -0
  306. package/dist/document-types.js +90 -0
  307. package/dist/document-types.js.map +1 -0
  308. package/dist/document-watch.d.ts +69 -0
  309. package/dist/document-watch.d.ts.map +1 -0
  310. package/dist/document-watch.js +108 -0
  311. package/dist/document-watch.js.map +1 -0
  312. package/dist/document-wiring.d.ts +53 -0
  313. package/dist/document-wiring.d.ts.map +1 -0
  314. package/dist/document-wiring.js +323 -0
  315. package/dist/document-wiring.js.map +1 -0
  316. package/dist/document-write-guard.d.ts +66 -0
  317. package/dist/document-write-guard.d.ts.map +1 -0
  318. package/dist/document-write-guard.js +98 -0
  319. package/dist/document-write-guard.js.map +1 -0
  320. package/dist/document-write-path.d.ts +94 -0
  321. package/dist/document-write-path.d.ts.map +1 -0
  322. package/dist/document-write-path.js +530 -0
  323. package/dist/document-write-path.js.map +1 -0
  324. package/dist/error-message.d.ts +7 -0
  325. package/dist/error-message.d.ts.map +1 -0
  326. package/dist/error-message.js +19 -0
  327. package/dist/error-message.js.map +1 -0
  328. package/dist/file-manifest-provider.d.ts +37 -0
  329. package/dist/file-manifest-provider.d.ts.map +1 -0
  330. package/dist/file-manifest-provider.js +105 -0
  331. package/dist/file-manifest-provider.js.map +1 -0
  332. package/dist/frame-values.d.ts +3 -0
  333. package/dist/frame-values.d.ts.map +1 -0
  334. package/dist/frame-values.js +84 -0
  335. package/dist/frame-values.js.map +1 -0
  336. package/dist/frontier-mismatch.d.ts +73 -0
  337. package/dist/frontier-mismatch.d.ts.map +1 -0
  338. package/dist/frontier-mismatch.js +89 -0
  339. package/dist/frontier-mismatch.js.map +1 -0
  340. package/dist/gateway-config-handlers.d.ts +26 -0
  341. package/dist/gateway-config-handlers.d.ts.map +1 -0
  342. package/dist/gateway-config-handlers.js +454 -0
  343. package/dist/gateway-config-handlers.js.map +1 -0
  344. package/dist/held-content.d.ts +145 -0
  345. package/dist/held-content.d.ts.map +1 -0
  346. package/dist/held-content.js +389 -0
  347. package/dist/held-content.js.map +1 -0
  348. package/dist/http-manifest-poll.d.ts +67 -0
  349. package/dist/http-manifest-poll.d.ts.map +1 -0
  350. package/dist/http-manifest-poll.js +150 -0
  351. package/dist/http-manifest-poll.js.map +1 -0
  352. package/dist/identity-migration.d.ts +40 -0
  353. package/dist/identity-migration.d.ts.map +1 -0
  354. package/dist/identity-migration.js +461 -0
  355. package/dist/identity-migration.js.map +1 -0
  356. package/dist/inbound-refusals.d.ts +283 -0
  357. package/dist/inbound-refusals.d.ts.map +1 -0
  358. package/dist/inbound-refusals.js +919 -0
  359. package/dist/inbound-refusals.js.map +1 -0
  360. package/dist/inbound-seal-request.d.ts +32 -0
  361. package/dist/inbound-seal-request.d.ts.map +1 -0
  362. package/dist/inbound-seal-request.js +228 -0
  363. package/dist/inbound-seal-request.js.map +1 -0
  364. package/dist/inbound-sessions.d.ts +291 -0
  365. package/dist/inbound-sessions.d.ts.map +1 -0
  366. package/dist/inbound-sessions.js +1609 -0
  367. package/dist/inbound-sessions.js.map +1 -0
  368. package/dist/inclusion-proof-handlers.d.ts +43 -0
  369. package/dist/inclusion-proof-handlers.d.ts.map +1 -0
  370. package/dist/inclusion-proof-handlers.js +565 -0
  371. package/dist/inclusion-proof-handlers.js.map +1 -0
  372. package/dist/inclusion-proof.d.ts +151 -0
  373. package/dist/inclusion-proof.d.ts.map +1 -0
  374. package/dist/inclusion-proof.js +228 -0
  375. package/dist/inclusion-proof.js.map +1 -0
  376. package/dist/index.d.ts +32 -0
  377. package/dist/index.d.ts.map +1 -0
  378. package/dist/index.js.map +1 -0
  379. package/dist/initiate-session-handler.d.ts +55 -0
  380. package/dist/initiate-session-handler.d.ts.map +1 -0
  381. package/dist/initiate-session-handler.js +310 -0
  382. package/dist/initiate-session-handler.js.map +1 -0
  383. package/dist/ipc-client.d.ts +31 -0
  384. package/dist/ipc-client.d.ts.map +1 -0
  385. package/dist/ipc-client.js +113 -0
  386. package/dist/ipc-client.js.map +1 -0
  387. package/dist/ipc-server.d.ts +63 -0
  388. package/dist/ipc-server.d.ts.map +1 -0
  389. package/dist/ipc-server.js +429 -0
  390. package/dist/ipc-server.js.map +1 -0
  391. package/dist/ipc-surface.d.ts +44 -0
  392. package/dist/ipc-surface.d.ts.map +1 -0
  393. package/dist/ipc-surface.js +107 -0
  394. package/dist/ipc-surface.js.map +1 -0
  395. package/dist/line-lcs.d.ts +51 -0
  396. package/dist/line-lcs.d.ts.map +1 -0
  397. package/dist/line-lcs.js +71 -0
  398. package/dist/line-lcs.js.map +1 -0
  399. package/dist/lock-file.d.ts +39 -0
  400. package/dist/lock-file.d.ts.map +1 -0
  401. package/dist/lock-file.js +120 -0
  402. package/dist/lock-file.js.map +1 -0
  403. package/dist/log-collapse.d.ts +66 -0
  404. package/dist/log-collapse.d.ts.map +1 -0
  405. package/dist/log-collapse.js +244 -0
  406. package/dist/log-collapse.js.map +1 -0
  407. package/dist/log-rotate.d.ts +67 -0
  408. package/dist/log-rotate.d.ts.map +1 -0
  409. package/dist/log-rotate.js +134 -0
  410. package/dist/log-rotate.js.map +1 -0
  411. package/dist/manifest-deps.d.ts +25 -0
  412. package/dist/manifest-deps.d.ts.map +1 -0
  413. package/dist/manifest-deps.js +151 -0
  414. package/dist/manifest-deps.js.map +1 -0
  415. package/dist/manifest-poll-scheduler.d.ts +31 -0
  416. package/dist/manifest-poll-scheduler.d.ts.map +1 -0
  417. package/dist/manifest-poll-scheduler.js +59 -0
  418. package/dist/manifest-poll-scheduler.js.map +1 -0
  419. package/dist/manifest-validity.d.ts +153 -0
  420. package/dist/manifest-validity.d.ts.map +1 -0
  421. package/dist/manifest-validity.js +268 -0
  422. package/dist/manifest-validity.js.map +1 -0
  423. package/dist/manifest-version-store-db.d.ts +24 -0
  424. package/dist/manifest-version-store-db.d.ts.map +1 -0
  425. package/dist/manifest-version-store-db.js +58 -0
  426. package/dist/manifest-version-store-db.js.map +1 -0
  427. package/dist/manifest-version-store.d.ts +16 -0
  428. package/dist/manifest-version-store.d.ts.map +1 -0
  429. package/dist/manifest-version-store.js +15 -0
  430. package/dist/manifest-version-store.js.map +1 -0
  431. package/dist/network-directory-node.d.ts +136 -0
  432. package/dist/network-directory-node.d.ts.map +1 -0
  433. package/dist/network-directory-node.js +810 -0
  434. package/dist/network-directory-node.js.map +1 -0
  435. package/dist/nonce-dedup.d.ts +68 -0
  436. package/dist/nonce-dedup.d.ts.map +1 -0
  437. package/dist/nonce-dedup.js +205 -0
  438. package/dist/nonce-dedup.js.map +1 -0
  439. package/dist/notification-dispatcher.d.ts +92 -0
  440. package/dist/notification-dispatcher.d.ts.map +1 -0
  441. package/dist/notification-dispatcher.js +210 -0
  442. package/dist/notification-dispatcher.js.map +1 -0
  443. package/dist/notification-handlers.d.ts +53 -0
  444. package/dist/notification-handlers.d.ts.map +1 -0
  445. package/dist/notification-handlers.js +461 -0
  446. package/dist/notification-handlers.js.map +1 -0
  447. package/dist/onboarding-guidance.d.ts +79 -0
  448. package/dist/onboarding-guidance.d.ts.map +1 -0
  449. package/dist/onboarding-guidance.js +95 -0
  450. package/dist/onboarding-guidance.js.map +1 -0
  451. package/dist/operator-guidance.d.ts +25 -0
  452. package/dist/operator-guidance.d.ts.map +1 -0
  453. package/dist/operator-guidance.js +50 -0
  454. package/dist/operator-guidance.js.map +1 -0
  455. package/dist/orphan-triage.d.ts +130 -0
  456. package/dist/orphan-triage.d.ts.map +1 -0
  457. package/dist/orphan-triage.js +207 -0
  458. package/dist/orphan-triage.js.map +1 -0
  459. package/dist/outbound-sessions.d.ts +126 -0
  460. package/dist/outbound-sessions.d.ts.map +1 -0
  461. package/dist/outbound-sessions.js +1052 -0
  462. package/dist/outbound-sessions.js.map +1 -0
  463. package/dist/park-envelope.d.ts +329 -0
  464. package/dist/park-envelope.d.ts.map +1 -0
  465. package/dist/park-envelope.js +509 -0
  466. package/dist/park-envelope.js.map +1 -0
  467. package/dist/park-recovery.d.ts +273 -0
  468. package/dist/park-recovery.d.ts.map +1 -0
  469. package/dist/park-recovery.js +717 -0
  470. package/dist/park-recovery.js.map +1 -0
  471. package/dist/park-refusals.d.ts +147 -0
  472. package/dist/park-refusals.d.ts.map +1 -0
  473. package/dist/park-refusals.js +331 -0
  474. package/dist/park-refusals.js.map +1 -0
  475. package/dist/quarantine-framing.d.ts +92 -0
  476. package/dist/quarantine-framing.d.ts.map +1 -0
  477. package/dist/quarantine-framing.js +111 -0
  478. package/dist/quarantine-framing.js.map +1 -0
  479. package/dist/reconnect-drain.d.ts +22 -0
  480. package/dist/reconnect-drain.d.ts.map +1 -0
  481. package/dist/reconnect-drain.js +65 -0
  482. package/dist/reconnect-drain.js.map +1 -0
  483. package/dist/recovered-position.d.ts +14 -0
  484. package/dist/recovered-position.d.ts.map +1 -0
  485. package/dist/recovered-position.js +59 -0
  486. package/dist/recovered-position.js.map +1 -0
  487. package/dist/refusal-notices.d.ts +196 -0
  488. package/dist/refusal-notices.d.ts.map +1 -0
  489. package/dist/refusal-notices.js +516 -0
  490. package/dist/refusal-notices.js.map +1 -0
  491. package/dist/refusal-reasons.d.ts +245 -0
  492. package/dist/refusal-reasons.d.ts.map +1 -0
  493. package/dist/refusal-reasons.js +363 -0
  494. package/dist/refusal-reasons.js.map +1 -0
  495. package/dist/register-handler.d.ts +36 -0
  496. package/dist/register-handler.d.ts.map +1 -0
  497. package/dist/register-handler.js +285 -0
  498. package/dist/register-handler.js.map +1 -0
  499. package/dist/registration-context.d.ts +72 -0
  500. package/dist/registration-context.d.ts.map +1 -0
  501. package/dist/registration-context.js +126 -0
  502. package/dist/registration-context.js.map +1 -0
  503. package/dist/registration-manager.d.ts +94 -0
  504. package/dist/registration-manager.d.ts.map +1 -0
  505. package/dist/registration-manager.js +585 -0
  506. package/dist/registration-manager.js.map +1 -0
  507. package/dist/registration-persistence.d.ts +183 -0
  508. package/dist/registration-persistence.d.ts.map +1 -0
  509. package/dist/registration-persistence.js +263 -0
  510. package/dist/registration-persistence.js.map +1 -0
  511. package/dist/registry-poll.d.ts +52 -0
  512. package/dist/registry-poll.d.ts.map +1 -0
  513. package/dist/registry-poll.js +140 -0
  514. package/dist/registry-poll.js.map +1 -0
  515. package/dist/registry-version-store-db.d.ts +22 -0
  516. package/dist/registry-version-store-db.d.ts.map +1 -0
  517. package/dist/registry-version-store-db.js +50 -0
  518. package/dist/registry-version-store-db.js.map +1 -0
  519. package/dist/relay-endpoints.d.ts +18 -0
  520. package/dist/relay-endpoints.d.ts.map +1 -0
  521. package/dist/relay-endpoints.js +9 -0
  522. package/dist/relay-endpoints.js.map +1 -0
  523. package/dist/relay-only.d.ts +140 -0
  524. package/dist/relay-only.d.ts.map +1 -0
  525. package/dist/relay-only.js +193 -0
  526. package/dist/relay-only.js.map +1 -0
  527. package/dist/relay-receipt-store.d.ts +155 -0
  528. package/dist/relay-receipt-store.d.ts.map +1 -0
  529. package/dist/relay-receipt-store.js +284 -0
  530. package/dist/relay-receipt-store.js.map +1 -0
  531. package/dist/relay-reconnect.d.ts +32 -0
  532. package/dist/relay-reconnect.d.ts.map +1 -0
  533. package/dist/relay-reconnect.js +29 -0
  534. package/dist/relay-reconnect.js.map +1 -0
  535. package/dist/reply-lag.d.ts +11 -0
  536. package/dist/reply-lag.d.ts.map +1 -0
  537. package/dist/reply-lag.js +46 -0
  538. package/dist/reply-lag.js.map +1 -0
  539. package/dist/resolve-named-agent.d.ts +49 -0
  540. package/dist/resolve-named-agent.d.ts.map +1 -0
  541. package/dist/resolve-named-agent.js +77 -0
  542. package/dist/resolve-named-agent.js.map +1 -0
  543. package/dist/restart-seal-resolver.d.ts +110 -0
  544. package/dist/restart-seal-resolver.d.ts.map +1 -0
  545. package/dist/restart-seal-resolver.js +353 -0
  546. package/dist/restart-seal-resolver.js.map +1 -0
  547. package/dist/resume-last-seen.d.ts +15 -0
  548. package/dist/resume-last-seen.d.ts.map +1 -0
  549. package/dist/resume-last-seen.js +44 -0
  550. package/dist/resume-last-seen.js.map +1 -0
  551. package/dist/retry-queue.d.ts +203 -0
  552. package/dist/retry-queue.d.ts.map +1 -0
  553. package/dist/retry-queue.js +701 -0
  554. package/dist/retry-queue.js.map +1 -0
  555. package/dist/roster-freshness.d.ts +160 -0
  556. package/dist/roster-freshness.d.ts.map +1 -0
  557. package/dist/roster-freshness.js +250 -0
  558. package/dist/roster-freshness.js.map +1 -0
  559. package/dist/screening-status.d.ts +15 -0
  560. package/dist/screening-status.d.ts.map +1 -0
  561. package/dist/screening-status.js +65 -0
  562. package/dist/screening-status.js.map +1 -0
  563. package/dist/seal-carried-close.d.ts +37 -0
  564. package/dist/seal-carried-close.d.ts.map +1 -0
  565. package/dist/seal-carried-close.js +179 -0
  566. package/dist/seal-carried-close.js.map +1 -0
  567. package/dist/seal-certificate-pull.d.ts +79 -0
  568. package/dist/seal-certificate-pull.d.ts.map +1 -0
  569. package/dist/seal-certificate-pull.js +186 -0
  570. package/dist/seal-certificate-pull.js.map +1 -0
  571. package/dist/seal-certified-root-check.d.ts +37 -0
  572. package/dist/seal-certified-root-check.d.ts.map +1 -0
  573. package/dist/seal-certified-root-check.js +127 -0
  574. package/dist/seal-certified-root-check.js.map +1 -0
  575. package/dist/seal-coordinator.d.ts +118 -0
  576. package/dist/seal-coordinator.d.ts.map +1 -0
  577. package/dist/seal-coordinator.js +927 -0
  578. package/dist/seal-coordinator.js.map +1 -0
  579. package/dist/seal-escalation.d.ts +80 -0
  580. package/dist/seal-escalation.d.ts.map +1 -0
  581. package/dist/seal-escalation.js +283 -0
  582. package/dist/seal-escalation.js.map +1 -0
  583. package/dist/seal-evidence-root-check.d.ts +70 -0
  584. package/dist/seal-evidence-root-check.d.ts.map +1 -0
  585. package/dist/seal-evidence-root-check.js +199 -0
  586. package/dist/seal-evidence-root-check.js.map +1 -0
  587. package/dist/seal-failure-store.d.ts +139 -0
  588. package/dist/seal-failure-store.d.ts.map +1 -0
  589. package/dist/seal-failure-store.js +207 -0
  590. package/dist/seal-failure-store.js.map +1 -0
  591. package/dist/seal-flows.d.ts +107 -0
  592. package/dist/seal-flows.d.ts.map +1 -0
  593. package/dist/seal-flows.js +638 -0
  594. package/dist/seal-flows.js.map +1 -0
  595. package/dist/seal-frontier-verify.d.ts +103 -0
  596. package/dist/seal-frontier-verify.d.ts.map +1 -0
  597. package/dist/seal-frontier-verify.js +143 -0
  598. package/dist/seal-frontier-verify.js.map +1 -0
  599. package/dist/seal-leaf.d.ts +58 -0
  600. package/dist/seal-leaf.d.ts.map +1 -0
  601. package/dist/seal-leaf.js +112 -0
  602. package/dist/seal-leaf.js.map +1 -0
  603. package/dist/seal-legibility-tbs.d.ts +25 -0
  604. package/dist/seal-legibility-tbs.d.ts.map +1 -0
  605. package/dist/seal-legibility-tbs.js +77 -0
  606. package/dist/seal-legibility-tbs.js.map +1 -0
  607. package/dist/seal-local-terminus.d.ts +41 -0
  608. package/dist/seal-local-terminus.d.ts.map +1 -0
  609. package/dist/seal-local-terminus.js +170 -0
  610. package/dist/seal-local-terminus.js.map +1 -0
  611. package/dist/seal-receipt-upgrade.d.ts +30 -0
  612. package/dist/seal-receipt-upgrade.d.ts.map +1 -0
  613. package/dist/seal-receipt-upgrade.js +49 -0
  614. package/dist/seal-receipt-upgrade.js.map +1 -0
  615. package/dist/seal-relay-silence.d.ts +51 -0
  616. package/dist/seal-relay-silence.d.ts.map +1 -0
  617. package/dist/seal-relay-silence.js +76 -0
  618. package/dist/seal-relay-silence.js.map +1 -0
  619. package/dist/seal-settle.d.ts +38 -0
  620. package/dist/seal-settle.d.ts.map +1 -0
  621. package/dist/seal-settle.js +33 -0
  622. package/dist/seal-settle.js.map +1 -0
  623. package/dist/seal-upgrade.d.ts +107 -0
  624. package/dist/seal-upgrade.d.ts.map +1 -0
  625. package/dist/seal-upgrade.js +200 -0
  626. package/dist/seal-upgrade.js.map +1 -0
  627. package/dist/sealed-conversation.d.ts +36 -0
  628. package/dist/sealed-conversation.d.ts.map +1 -0
  629. package/dist/sealed-conversation.js +171 -0
  630. package/dist/sealed-conversation.js.map +1 -0
  631. package/dist/sealed-leaf-set.d.ts +92 -0
  632. package/dist/sealed-leaf-set.d.ts.map +1 -0
  633. package/dist/sealed-leaf-set.js +122 -0
  634. package/dist/sealed-leaf-set.js.map +1 -0
  635. package/dist/send-claims.d.ts +56 -0
  636. package/dist/send-claims.d.ts.map +1 -0
  637. package/dist/send-claims.js +50 -0
  638. package/dist/send-claims.js.map +1 -0
  639. package/dist/session-assignment-parser.d.ts +104 -0
  640. package/dist/session-assignment-parser.d.ts.map +1 -0
  641. package/dist/session-assignment-parser.js +345 -0
  642. package/dist/session-assignment-parser.js.map +1 -0
  643. package/dist/session-category.d.ts +19 -0
  644. package/dist/session-category.d.ts.map +1 -0
  645. package/dist/session-category.js +14 -0
  646. package/dist/session-category.js.map +1 -0
  647. package/dist/session-ceremony.d.ts +297 -0
  648. package/dist/session-ceremony.d.ts.map +1 -0
  649. package/dist/session-ceremony.js +964 -0
  650. package/dist/session-ceremony.js.map +1 -0
  651. package/dist/session-closed.d.ts +79 -0
  652. package/dist/session-closed.d.ts.map +1 -0
  653. package/dist/session-closed.js +189 -0
  654. package/dist/session-closed.js.map +1 -0
  655. package/dist/session-connection-gater.d.ts +148 -0
  656. package/dist/session-connection-gater.d.ts.map +1 -0
  657. package/dist/session-connection-gater.js +332 -0
  658. package/dist/session-connection-gater.js.map +1 -0
  659. package/dist/session-content-context.d.ts +162 -0
  660. package/dist/session-content-context.d.ts.map +1 -0
  661. package/dist/session-content-context.js +2 -0
  662. package/dist/session-content-context.js.map +1 -0
  663. package/dist/session-content-handlers.d.ts +82 -0
  664. package/dist/session-content-handlers.d.ts.map +1 -0
  665. package/dist/session-content-handlers.js +1382 -0
  666. package/dist/session-content-handlers.js.map +1 -0
  667. package/dist/session-content-ingest.d.ts +206 -0
  668. package/dist/session-content-ingest.d.ts.map +1 -0
  669. package/dist/session-content-ingest.js +2167 -0
  670. package/dist/session-content-ingest.js.map +1 -0
  671. package/dist/session-content-send.d.ts +191 -0
  672. package/dist/session-content-send.d.ts.map +1 -0
  673. package/dist/session-content-send.js +1360 -0
  674. package/dist/session-content-send.js.map +1 -0
  675. package/dist/session-delivery-acks.d.ts +169 -0
  676. package/dist/session-delivery-acks.d.ts.map +1 -0
  677. package/dist/session-delivery-acks.js +569 -0
  678. package/dist/session-delivery-acks.js.map +1 -0
  679. package/dist/session-ephemerals.d.ts +279 -0
  680. package/dist/session-ephemerals.d.ts.map +1 -0
  681. package/dist/session-ephemerals.js +591 -0
  682. package/dist/session-ephemerals.js.map +1 -0
  683. package/dist/session-leaf-records.d.ts +159 -0
  684. package/dist/session-leaf-records.d.ts.map +1 -0
  685. package/dist/session-leaf-records.js +408 -0
  686. package/dist/session-leaf-records.js.map +1 -0
  687. package/dist/session-lifecycle.d.ts +303 -0
  688. package/dist/session-lifecycle.d.ts.map +1 -0
  689. package/dist/session-lifecycle.js +1679 -0
  690. package/dist/session-lifecycle.js.map +1 -0
  691. package/dist/session-liveness.d.ts +135 -0
  692. package/dist/session-liveness.d.ts.map +1 -0
  693. package/dist/session-liveness.js +347 -0
  694. package/dist/session-liveness.js.map +1 -0
  695. package/dist/session-name.d.ts +35 -0
  696. package/dist/session-name.d.ts.map +1 -0
  697. package/dist/session-name.js +60 -0
  698. package/dist/session-name.js.map +1 -0
  699. package/dist/session-node-factory.d.ts +18 -0
  700. package/dist/session-node-factory.d.ts.map +1 -0
  701. package/dist/session-node-factory.js +182 -0
  702. package/dist/session-node-factory.js.map +1 -0
  703. package/dist/session-node-manager.d.ts +877 -0
  704. package/dist/session-node-manager.d.ts.map +1 -0
  705. package/dist/session-node-manager.js +2998 -0
  706. package/dist/session-node-manager.js.map +1 -0
  707. package/dist/session-node-types.d.ts +1057 -0
  708. package/dist/session-node-types.d.ts.map +1 -0
  709. package/dist/session-node-types.js +657 -0
  710. package/dist/session-node-types.js.map +1 -0
  711. package/dist/session-notify.d.ts +46 -0
  712. package/dist/session-notify.d.ts.map +1 -0
  713. package/dist/session-notify.js +116 -0
  714. package/dist/session-notify.js.map +1 -0
  715. package/dist/session-own-chain-store.d.ts +65 -0
  716. package/dist/session-own-chain-store.d.ts.map +1 -0
  717. package/dist/session-own-chain-store.js +75 -0
  718. package/dist/session-own-chain-store.js.map +1 -0
  719. package/dist/session-queries.d.ts +476 -0
  720. package/dist/session-queries.d.ts.map +1 -0
  721. package/dist/session-queries.js +1007 -0
  722. package/dist/session-queries.js.map +1 -0
  723. package/dist/session-read-handlers.d.ts +87 -0
  724. package/dist/session-read-handlers.d.ts.map +1 -0
  725. package/dist/session-read-handlers.js +689 -0
  726. package/dist/session-read-handlers.js.map +1 -0
  727. package/dist/session-records.d.ts +341 -0
  728. package/dist/session-records.d.ts.map +1 -0
  729. package/dist/session-records.js +858 -0
  730. package/dist/session-records.js.map +1 -0
  731. package/dist/session-relay-client.d.ts +659 -0
  732. package/dist/session-relay-client.d.ts.map +1 -0
  733. package/dist/session-relay-client.js +2876 -0
  734. package/dist/session-relay-client.js.map +1 -0
  735. package/dist/session-relay.d.ts +397 -0
  736. package/dist/session-relay.d.ts.map +1 -0
  737. package/dist/session-relay.js +1636 -0
  738. package/dist/session-relay.js.map +1 -0
  739. package/dist/session-salt-agreement.d.ts +331 -0
  740. package/dist/session-salt-agreement.d.ts.map +1 -0
  741. package/dist/session-salt-agreement.js +472 -0
  742. package/dist/session-salt-agreement.js.map +1 -0
  743. package/dist/session-salts.d.ts +432 -0
  744. package/dist/session-salts.d.ts.map +1 -0
  745. package/dist/session-salts.js +1540 -0
  746. package/dist/session-salts.js.map +1 -0
  747. package/dist/session-schema.d.ts +30 -0
  748. package/dist/session-schema.d.ts.map +1 -0
  749. package/dist/session-schema.js +877 -0
  750. package/dist/session-schema.js.map +1 -0
  751. package/dist/session-seal-leaf-store.d.ts +70 -0
  752. package/dist/session-seal-leaf-store.d.ts.map +1 -0
  753. package/dist/session-seal-leaf-store.js +105 -0
  754. package/dist/session-seal-leaf-store.js.map +1 -0
  755. package/dist/session-seal.d.ts +334 -0
  756. package/dist/session-seal.d.ts.map +1 -0
  757. package/dist/session-seal.js +1017 -0
  758. package/dist/session-seal.js.map +1 -0
  759. package/dist/session-terminal-refusal.d.ts +65 -0
  760. package/dist/session-terminal-refusal.d.ts.map +1 -0
  761. package/dist/session-terminal-refusal.js +87 -0
  762. package/dist/session-terminal-refusal.js.map +1 -0
  763. package/dist/session-tree.d.ts +110 -0
  764. package/dist/session-tree.d.ts.map +1 -0
  765. package/dist/session-tree.js +144 -0
  766. package/dist/session-tree.js.map +1 -0
  767. package/dist/session-views.d.ts +47 -0
  768. package/dist/session-views.d.ts.map +1 -0
  769. package/dist/session-views.js +278 -0
  770. package/dist/session-views.js.map +1 -0
  771. package/dist/signal-handlers.d.ts +63 -0
  772. package/dist/signal-handlers.d.ts.map +1 -0
  773. package/dist/signal-handlers.js +980 -0
  774. package/dist/signal-handlers.js.map +1 -0
  775. package/dist/signal-requirement-policy.d.ts +51 -0
  776. package/dist/signal-requirement-policy.d.ts.map +1 -0
  777. package/dist/signal-requirement-policy.js +89 -0
  778. package/dist/signal-requirement-policy.js.map +1 -0
  779. package/dist/signal-revocability.d.ts +49 -0
  780. package/dist/signal-revocability.d.ts.map +1 -0
  781. package/dist/signal-revocability.js +93 -0
  782. package/dist/signal-revocability.js.map +1 -0
  783. package/dist/signal-submission.d.ts +181 -0
  784. package/dist/signal-submission.d.ts.map +1 -0
  785. package/dist/signal-submission.js +368 -0
  786. package/dist/signal-submission.js.map +1 -0
  787. package/dist/signaling-connect.d.ts +118 -0
  788. package/dist/signaling-connect.d.ts.map +1 -0
  789. package/dist/signaling-connect.js +538 -0
  790. package/dist/signaling-connect.js.map +1 -0
  791. package/dist/signaling-wiring.d.ts +105 -0
  792. package/dist/signaling-wiring.d.ts.map +1 -0
  793. package/dist/signaling-wiring.js +393 -0
  794. package/dist/signaling-wiring.js.map +1 -0
  795. package/dist/singleton-lock.d.ts +85 -0
  796. package/dist/singleton-lock.d.ts.map +1 -0
  797. package/dist/singleton-lock.js +219 -0
  798. package/dist/singleton-lock.js.map +1 -0
  799. package/dist/sqlcipher-db.d.ts +139 -0
  800. package/dist/sqlcipher-db.d.ts.map +1 -0
  801. package/dist/sqlcipher-db.js +357 -0
  802. package/dist/sqlcipher-db.js.map +1 -0
  803. package/dist/standing-receivers.d.ts +319 -0
  804. package/dist/standing-receivers.d.ts.map +1 -0
  805. package/dist/standing-receivers.js +1295 -0
  806. package/dist/standing-receivers.js.map +1 -0
  807. package/dist/start-agent.d.ts +64 -0
  808. package/dist/start-agent.d.ts.map +1 -0
  809. package/dist/start-agent.js +136 -0
  810. package/dist/start-agent.js.map +1 -0
  811. package/dist/status-handler.d.ts +54 -0
  812. package/dist/status-handler.d.ts.map +1 -0
  813. package/dist/status-handler.js +61 -0
  814. package/dist/status-handler.js.map +1 -0
  815. package/dist/submission-retry.d.ts +208 -0
  816. package/dist/submission-retry.d.ts.map +1 -0
  817. package/dist/submission-retry.js +506 -0
  818. package/dist/submission-retry.js.map +1 -0
  819. package/dist/telegram-bot-client.d.ts +34 -0
  820. package/dist/telegram-bot-client.d.ts.map +1 -0
  821. package/dist/telegram-bot-client.js +36 -0
  822. package/dist/telegram-bot-client.js.map +1 -0
  823. package/dist/telegram-doorbell.d.ts +38 -0
  824. package/dist/telegram-doorbell.d.ts.map +1 -0
  825. package/dist/telegram-doorbell.js +140 -0
  826. package/dist/telegram-doorbell.js.map +1 -0
  827. package/dist/test-handlers.d.ts +62 -0
  828. package/dist/test-handlers.d.ts.map +1 -0
  829. package/dist/test-handlers.js +240 -0
  830. package/dist/test-handlers.js.map +1 -0
  831. package/dist/testing.d.ts +10 -0
  832. package/dist/testing.d.ts.map +1 -0
  833. package/dist/testing.js +10 -0
  834. package/dist/testing.js.map +1 -0
  835. package/dist/transport-composition.d.ts +31 -0
  836. package/dist/transport-composition.d.ts.map +1 -0
  837. package/dist/transport-composition.js +55 -0
  838. package/dist/transport-composition.js.map +1 -0
  839. package/dist/transport-selector.d.ts +202 -0
  840. package/dist/transport-selector.d.ts.map +1 -0
  841. package/dist/transport-selector.js +196 -0
  842. package/dist/transport-selector.js.map +1 -0
  843. package/dist/trust-signal-pickup-listener.d.ts +43 -0
  844. package/dist/trust-signal-pickup-listener.d.ts.map +1 -0
  845. package/dist/trust-signal-pickup-listener.js +48 -0
  846. package/dist/trust-signal-pickup-listener.js.map +1 -0
  847. package/dist/trust-signal-store.d.ts +406 -0
  848. package/dist/trust-signal-store.d.ts.map +1 -0
  849. package/dist/trust-signal-store.js +939 -0
  850. package/dist/trust-signal-store.js.map +1 -0
  851. package/dist/trust-signal-sweep-tick.d.ts +60 -0
  852. package/dist/trust-signal-sweep-tick.d.ts.map +1 -0
  853. package/dist/trust-signal-sweep-tick.js +101 -0
  854. package/dist/trust-signal-sweep-tick.js.map +1 -0
  855. package/dist/trust-signal-sweep.d.ts +94 -0
  856. package/dist/trust-signal-sweep.d.ts.map +1 -0
  857. package/dist/trust-signal-sweep.js +147 -0
  858. package/dist/trust-signal-sweep.js.map +1 -0
  859. package/dist/type-registry.d.ts +42 -0
  860. package/dist/type-registry.d.ts.map +1 -0
  861. package/dist/type-registry.js +37 -0
  862. package/dist/type-registry.js.map +1 -0
  863. package/dist/types.d.ts +746 -0
  864. package/dist/types.d.ts.map +1 -0
  865. package/dist/types.js +20 -0
  866. package/dist/types.js.map +1 -0
  867. package/dist/unresolved-nodes-report.d.ts +14 -0
  868. package/dist/unresolved-nodes-report.d.ts.map +1 -0
  869. package/dist/unresolved-nodes-report.js +96 -0
  870. package/dist/unresolved-nodes-report.js.map +1 -0
  871. package/dist/vocabulary.d.ts +150 -0
  872. package/dist/vocabulary.d.ts.map +1 -0
  873. package/dist/vocabulary.js +386 -0
  874. package/dist/vocabulary.js.map +1 -0
  875. package/dist/who-label.d.ts +28 -0
  876. package/dist/who-label.d.ts.map +1 -0
  877. package/dist/who-label.js +31 -0
  878. package/dist/who-label.js.map +1 -0
  879. package/dist/who-resolver.d.ts +15 -0
  880. package/dist/who-resolver.d.ts.map +1 -0
  881. package/dist/who-resolver.js +47 -0
  882. package/dist/who-resolver.js.map +1 -0
  883. package/dist/wire-content-hash.d.ts +93 -0
  884. package/dist/wire-content-hash.d.ts.map +1 -0
  885. package/dist/wire-content-hash.js +116 -0
  886. package/dist/wire-content-hash.js.map +1 -0
  887. package/dist/withheld-content.d.ts +13 -0
  888. package/dist/withheld-content.d.ts.map +1 -0
  889. package/dist/withheld-content.js +46 -0
  890. package/dist/withheld-content.js.map +1 -0
  891. package/dist/witness-alerts.d.ts +40 -0
  892. package/dist/witness-alerts.d.ts.map +1 -0
  893. package/dist/witness-alerts.js +102 -0
  894. package/dist/witness-alerts.js.map +1 -0
  895. package/package.json +5 -5
@@ -0,0 +1,1636 @@
1
+ /**
2
+ * CELLO Daemon — THE BLIND WITNESS, AND STAYING REACHABLE THROUGH IT
3
+ *
4
+ * Split out of `session-node-manager.ts`. A relay countersigns the ORDER of a conversation and is
5
+ * given only hashes, never plaintext — so what lives here is everything about talking to one:
6
+ * connecting a session to its witness, proving key possession to it, holding a circuit reservation
7
+ * open so a counterparty behind a NAT can still reach us, watching that reservation decay and
8
+ * renewing it, quarantining a relay that misbehaves, and detaching cleanly when a session ends.
9
+ *
10
+ * **Moved verbatim, comments included.**
11
+ *
12
+ * ⚠️ **A RELAY IS NOT TRUSTED, AND THE CODE HERE IS WHERE THAT IS ENFORCED.** It authenticates to
13
+ * us as much as we authenticate to it; it is quarantined rather than believed when it misbehaves;
14
+ * and the one thing it must never learn — the operator's own address — is why the circuit-dial
15
+ * authorisation below is narrow rather than convenient. Anything added here that widens what a
16
+ * relay can see or do is a protocol change, not a refactor.
17
+ */
18
+ import { randomUUID } from "node:crypto";
19
+ import * as lp from "it-length-prefixed";
20
+ import { decode } from "cbor-x";
21
+ import { isValidMultiaddr } from "@cello-protocol/transport";
22
+ import { contentHashFor } from "./wire-content-hash.js";
23
+ import { extractErrorMessage } from "./error-message.js";
24
+ import { RelayReceiptStore } from "./relay-receipt-store.js";
25
+ import { SessionSealLeafStore } from "./session-seal-leaf-store.js";
26
+ import { AgentRelayClient } from "./session-relay-client.js";
27
+ import { mergeRelayEndpoints } from "./relay-endpoints.js";
28
+ import { RELAY_QUARANTINE_MS, SR_RESERVATION_MAX_RETRIES, heldRelayIdsOf, relayPeerIdOf, } from "./session-node-types.js";
29
+ export class SessionRelay {
30
+ #ctx;
31
+ constructor(ctx) {
32
+ this.#ctx = ctx;
33
+ }
34
+ /** A getter so the moved queries still read `this.#db` and narrow exactly as they did. */
35
+ get #db() {
36
+ return this.#ctx.db();
37
+ }
38
+ /**
39
+ * DOD-M15-RELAYAUTH-1: authenticate a fresh standing receiver to its reservation relay over the
40
+ * CELLO relay protocol — proof of K_local key possession, not a session. Reuses the SAME
41
+ * `#relayClients` cache `connectSessionRelay`/`#resolveSealTransport` read from, keyed
42
+ * identically (`${agentName}::${relayPeerId}`), so a session created moments later on this same
43
+ * relay finds an already-authenticated client instead of dialing and authenticating twice.
44
+ *
45
+ * The manager holds no K_local (M12-P15's own rationale for `#detachedRelayClientBuilder`) —
46
+ * without a builder wired (a narrow startup race, or a test harness that never wires one), this
47
+ * is a no-op and the relay's own grace-window revoke is the backstop, not a defect in this path.
48
+ */
49
+ async authenticateStandingReceiver(agentName, node, relayPeerId, heldCircuitAddr, correlationId) {
50
+ const clientKey = `${agentName}::${relayPeerId}`;
51
+ let client = this.#ctx.relayClients.get(clientKey);
52
+ if (!client) {
53
+ if (!this.#ctx.relayReceiptStore && this.#db)
54
+ this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
55
+ if (!this.#ctx.sealLeafStore && this.#db)
56
+ this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
57
+ /**
58
+ * ⚠️ SPLIT, NOT AN ANCHORED STRIP. The held address is
59
+ * `/ip4/…/tcp/…/p2p/<relay>/p2p-circuit/p2p/<self>` — the `/p2p-circuit` marker is in the
60
+ * MIDDLE, not at the end, so a `/\/p2p-circuit$/` replace matches nothing and silently
61
+ * hands the relay client a circuit address as its DIAL address. Measured, not assumed:
62
+ * that first version failed this file's own test because the client could not dial.
63
+ */
64
+ const baseRelayAddr = heldCircuitAddr.split("/p2p-circuit")[0] ?? heldCircuitAddr;
65
+ client = this.#ctx.detachedRelayClientBuilder?.(agentName, relayPeerId, [baseRelayAddr], {
66
+ receiptStore: this.#ctx.relayReceiptStore ?? undefined,
67
+ sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
68
+ ownChainStore: this.#ctx.ownChainStore ?? undefined,
69
+ // DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
70
+ onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
71
+ });
72
+ if (!client) {
73
+ /**
74
+ * Review M5: this was a `debug` line, and it is not a debug-level event.
75
+ *
76
+ * If no builder is wired we return without proving key possession, the relay revokes this
77
+ * receiver's reservation about fifteen seconds later, and the agent stops being reachable
78
+ * from behind NAT — while reporting itself perfectly healthy. Calling that "a narrow startup
79
+ * race" in a comment concedes it happens in production, and a system that is unreachable
80
+ * must not be quieter about it than a system that is merely slow.
81
+ */
82
+ this.#ctx.logger.warn("session.standing_receiver.relay_auth.no_builder", {
83
+ agentName,
84
+ relayPeerId,
85
+ correlationId,
86
+ impact: "this receiver cannot prove key possession, so the relay will revoke its reservation and " +
87
+ "the agent becomes unreachable from behind NAT — nobody can start a session with it — " +
88
+ "even though it still reports itself online.",
89
+ });
90
+ return;
91
+ }
92
+ this.#ctx.relayClients.set(clientKey, client);
93
+ }
94
+ /**
95
+ * ⚠️ `proveReservation`, NOT `connect`. Review HIGH-1: `connect()` short-circuits on the
96
+ * client's cached stream, which belongs to whichever node connected FIRST — so every
97
+ * REPLACEMENT standing receiver (the one built behind each new session) sent nothing, the relay
98
+ * never saw its transport identity, and its reservation was revoked ~15s later. The agent then
99
+ * churned reserve→revoke→rebuild for the life of the conversation, holding no usable circuit
100
+ * address, so while you were talking to one person nobody else could reach you.
101
+ *
102
+ * `proveReservation` always opens its own stream from THIS node, and marks it so the relay
103
+ * proves possession without rebinding the agent's delivery target away from the live session.
104
+ */
105
+ const proven = await client.proveReservation(node);
106
+ /**
107
+ * DOD-M15-RELAYSLOTS-1: keep the relay's refusal where the OPERATOR can reach it.
108
+ *
109
+ * The log line below is a good log line and it is not an answer to "why is my agent
110
+ * unreachable?" — nobody opens the file. The relay now refuses for reasons a person can act on
111
+ * (no token yet, too many sessions still open, this relay is misconfigured), each with its own
112
+ * next step, and every one of them is useless if it stops at a log.
113
+ */
114
+ if (!proven) {
115
+ const refusal = client.getLastAuthRefusal();
116
+ /**
117
+ * Review L2: the `else` is not symmetry for its own sake. `proveReservation` also fails for
118
+ * transport reasons, which leave `getLastAuthRefusal()` null — and without this branch the
119
+ * PREVIOUS refusal stayed in the map, so `cello_status` went on showing a cause and an
120
+ * affordance for something that was no longer what was wrong.
121
+ */
122
+ if (refusal)
123
+ this.#ctx.srRelayRefusal.set(agentName, { ...this.#ctx.withDirectoryCause(agentName, refusal), relayPeerId });
124
+ else
125
+ this.#ctx.srRelayRefusal.delete(agentName);
126
+ /**
127
+ * DOD-M15-RELAYSLOTS-1 clause 9 — **ACT on the classification, do not merely record it.**
128
+ * A relay-side fault means a different relay will work now, so quarantine this one and
129
+ * rebuild the receiver against the rest of the pool. Everything else stays put: a token
130
+ * problem reproduces on every relay, and walking the fleet would turn one client fault into
131
+ * what looks like a fleet-wide outage.
132
+ */
133
+ if (refusal?.tryAnotherRelay && !this.#ctx.shuttingDown) {
134
+ /**
135
+ * ⚠️ **THE QUARANTINE ACTS; THE REBUILD USED TO AND NO LONGER CAN — 056-SLOTDEAD.**
136
+ *
137
+ * This used to `rebuildStandingReceiver` so the new node would reserve against the rest of
138
+ * the pool. A rebuilt receiver reserves NOTHING now (055-ONDEMAND), so the rebuild bought
139
+ * nothing and cost the agent its transport identity. The quarantine is the half that still
140
+ * does the work: the next offer reserves against the pool minus this relay.
141
+ */
142
+ this.#quarantineRelay(agentName, relayPeerId, refusal.reason);
143
+ }
144
+ }
145
+ else {
146
+ this.#ctx.srRelayRefusal.delete(agentName);
147
+ }
148
+ this.#ctx.logger.info("session.standing_receiver.relay_auth.result", {
149
+ agentName,
150
+ relayPeerId,
151
+ // The node that actually proved itself — without this, HIGH-1 was invisible in the logs: the
152
+ // line said `connected: true` for a receiver that had sent nothing.
153
+ nodePeerId: node.getPeerId(),
154
+ proven,
155
+ ...(proven ? {} : {
156
+ refusalReason: client.getLastAuthRefusal()?.reason ?? "no_relay_verdict",
157
+ tryAnotherRelay: client.getLastAuthRefusal()?.tryAnotherRelay ?? false,
158
+ }),
159
+ correlationId,
160
+ });
161
+ }
162
+ /**
163
+ * RELAYSIG-1: the durably-stored, signature-verified relay ordering-record receipts for an agent
164
+ * (optionally a single session). Empty when no receipts have been recorded yet. Read-only.
165
+ */
166
+ getRelayReceipts(agentPubkeyHex, sessionIdHex) {
167
+ if (!this.#ctx.relayReceiptStore && this.#db) {
168
+ this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
169
+ }
170
+ return this.#ctx.relayReceiptStore?.getAll(agentPubkeyHex, sessionIdHex) ?? [];
171
+ }
172
+ /**
173
+ * DOD-M15-REFUSEDEVIDENCE-1 — retain a message refused OUTSIDE `ingestReceivedContent`
174
+ * (`session-content-ingest.ts`).
175
+ *
176
+ * Review F6. The park drain terminally-blocks a message that arrived for an already-committed
177
+ * session, then confirm-deletes the relay copy — the one other route in the tree that discarded
178
+ * refused content, and the highest-suspicion combination in the product: hostile bytes aimed at a
179
+ * conversation somebody has already sealed. Shipped guidance now tells every operator that
180
+ * refused messages are kept, so this is made true rather than the promise narrowed.
181
+ *
182
+ * A thin delegate, not a second implementation: the bound, the dedup, the sequence allocation and
183
+ * the logging are the ones every other refusal uses.
184
+ */
185
+ quarantineRefusedInbound(agentName, sessionId, reason, content, contentHashHex, senderPubkeyHex, correlationId) {
186
+ return this.#ctx.refusals.quarantineRefusedContent(agentName, sessionId, reason, content, contentHashHex, {
187
+ senderPubkeyHex, correlationId,
188
+ });
189
+ }
190
+ /** The metadata half of a framed quarantine read — everything known ABOUT the message, none of it
191
+ * taken from the message. Split out so the framing module never touches the database. */
192
+ quarantineFrameMeta(agentName, sessionId, rec) {
193
+ return {
194
+ reason: rec.reason,
195
+ senderPubkeyHex: rec.senderPubkeyHex,
196
+ senderLabel: rec.senderPubkeyHex === null ? null : this.#ctx.records.getContactMoniker(agentName, rec.senderPubkeyHex),
197
+ // `attribution` is the column that exists to answer exactly this, so it is read rather than
198
+ // inferred from `sender_sig` being non-null — a stored signature that was never checked
199
+ // against the sender's key would otherwise be reported as VERIFIED.
200
+ signature: rec.attribution === "verified_signature" ? "VERIFIED" : "NOT SIGNED",
201
+ sessionId,
202
+ position: rec.sequence,
203
+ arrivedAtMs: rec.createdAt,
204
+ /**
205
+ * The hash OF THE RETAINED BYTES, recomputed here — not the hash the sender committed to.
206
+ *
207
+ * On the highest-value case in the whole unit those two differ ON PURPOSE:
208
+ * `content_hash_mismatch` means the sender's committed hash does not describe these bytes.
209
+ * Printing their claim over our bytes would label the payload with a hash it does not have,
210
+ * which is the one thing a reader would use this line to check.
211
+ */
212
+ contentHashHex: Buffer.from(contentHashFor(rec.content, { alg: "sha256", salt: null })).toString("hex"),
213
+ };
214
+ }
215
+ /**
216
+ * The current standing receiver node's session-transport coordinates (peer id +
217
+ * listen multiaddrs), or null if it is not ready. These are the addresses a local
218
+ * SessionNegotiator advertises as this node's counterparty endpoint so the initiator
219
+ * can dial it, and the value an inbound session_assignment carries in its
220
+ * counterparty_session_* fields. Read-only — does NOT consume the standing receiver
221
+ * (unlike acceptSession, which hands it off).
222
+ */
223
+ /**
224
+ * 032-RELAYSPREAD — would this receiver ADMIT an inbound dial from this relay?
225
+ *
226
+ * The gater's inbound carve-out is the security-sensitive half of the spread: only relays whose
227
+ * own reservation is confirmed held earn it, so a directory that merely NAMES a relay cannot dial
228
+ * in behind the gate. Nothing could observe that from outside the manager, and the review found
229
+ * the consequence: substituting the CANDIDATE list for the held list at the `setReservedRelayPeers`
230
+ * call kept every test in the unit green while shipping exactly that hole. A guard whose wiring
231
+ * cannot be observed is a guard nothing can test.
232
+ *
233
+ * Reads the live gater rather than a copy, so it cannot drift from what the gate actually does.
234
+ */
235
+ isRelayCarvedOutInbound(agentName, relayPeerId) {
236
+ return this.#ctx.standingReceivers.get(agentName)?.gater.holdsInboundCarveOut(relayPeerId) ?? false;
237
+ }
238
+ /**
239
+ * M7 DOD-SPINE-6 / MSG-001-3b: connect a session node to the relay witness and
240
+ * store the client on the active entry. Best-effort: a connect/auth failure logs
241
+ * and leaves relayClient undefined — the session is NOT destroyed and the direct
242
+ * content path keeps working (the relay-park/recovery path is MSG-001-3b's domain).
243
+ *
244
+ * ⚠️ THIS BLOCK SPENT TWO MILESTONES ABOVE THE WRONG METHOD. It sat stacked on top of
245
+ * `relayLeafHandler`'s own docblock, so the file showed two descriptions in a row and the first
246
+ * one described a method further down the page. The content split then carried it verbatim into
247
+ * `session-content-ingest.ts`, where `connectSessionRelay` does not exist at all and the
248
+ * misattribution could no longer be worked out from context. Returned to the method it describes.
249
+ */
250
+ async connectSessionRelay(sessionId, node, agentName, relay, correlationId) {
251
+ try {
252
+ // The session node's gater admits only the counterparty; the relay witness is a
253
+ // third peer. Permit it OUTBOUND so the dial isn't denied — inbound stays
254
+ // counterparty-only (INV-5). The relay peer id comes from the signed assignment.
255
+ this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId))?.gater.setAllowedOutboundPeer(relay.relayPeerId);
256
+ // One relay client per (AGENT, RELAY NODE). The relay keys by agent pubkey, so the
257
+ // collision H1 addresses is per relay; CELLO is federated, so a different session for
258
+ // the same agent may be assigned a DIFFERENT relay — that needs its own client.
259
+ const clientKey = `${agentName}::${relay.relayPeerId}`;
260
+ let client = this.#ctx.relayClients.get(clientKey);
261
+ if (!client) {
262
+ // RELAYSIG-1: one shared receipt store (keyed by agent_pubkey, so a single instance serves all
263
+ // agents + relays). Lazy — the encrypted DB is open by the time sessions are active.
264
+ if (!this.#ctx.relayReceiptStore && this.#db) {
265
+ this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
266
+ }
267
+ // FED-OPTIONB-SEAL-001: one shared seal-leaf log (keyed by agent_pubkey), same lazy lifecycle.
268
+ if (!this.#ctx.sealLeafStore && this.#db)
269
+ this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
270
+ if (!this.#ctx.sealLeafStore)
271
+ this.#ctx.logger.error("session.relay.stores_absent", { agentName, sessionId, correlationId, impact: "no leaf record: this session cannot resume after a restart and keeps no seal evidence" });
272
+ client = new AgentRelayClient({
273
+ relayPeerId: relay.relayPeerId,
274
+ relayAddrs: relay.relayAddrs,
275
+ keyProvider: relay.keyProvider,
276
+ senderPubkey: relay.senderPubkey,
277
+ logger: this.#ctx.logger,
278
+ receiptStore: this.#ctx.relayReceiptStore ?? undefined,
279
+ sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
280
+ ownChainStore: this.#ctx.ownChainStore ?? undefined, // absent = own leaves not durably chained
281
+ // DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
282
+ onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
283
+ // DOD-M15-CORROBORATE-1: a relay's witness alert reaches the operator's inbox from here.
284
+ // The DETACHED clients get the same callback from the builder in daemon.ts.
285
+ onWitnessAlert: (alert) => { this.#ctx.witness.recordRelayWitnessAlert(agentName, alert); },
286
+ onWitnessUnreadable: (relayPeerId, why) => { this.#ctx.records.recordRelayWitnessUnreadable(agentName, relayPeerId, why); },
287
+ });
288
+ this.#ctx.relayClients.set(clientKey, client);
289
+ }
290
+ const sessionIdHexForRelay = Buffer.from(relay.sessionIdBytes).toString("hex");
291
+ /**
292
+ * ⚠️ THE GENESIS IS WRITTEN BEFORE THE REGISTRATION, NOT AFTER — review F10.
293
+ *
294
+ * `#leafRecords.sessionGenesisPrevRoot` reads the entry's assignment first and the column second,
295
+ * and BOTH were still unset at this line: the entry's assignment is set below and the column
296
+ * is written below that. So the argument was always `undefined` here on a first attach, and
297
+ * the seed only survived because `registerSession` falls back to deriving one from the
298
+ * assignment it is handed. That is a dead argument standing next to a live fallback, which
299
+ * reads as deliberate and is the shape a later edit removes the wrong half of.
300
+ */
301
+ const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
302
+ if (entry)
303
+ entry.relayAssignment = relay.assignment;
304
+ if (relay.assignment)
305
+ this.#ctx.leafRecords.persistGenesisPrevRoot(agentName, sessionId, relay.assignment);
306
+ client.registerSession(sessionIdHexForRelay, node, this.#ctx.contentIn.relayLeafHandler(agentName, sessionId, correlationId), relay.assignment, this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId), this.#ctx.leafRecords.sessionRelayAnchor(agentName, sessionId));
307
+ if (entry) {
308
+ entry.relayClient = client;
309
+ entry.relaySessionIdBytes = relay.sessionIdBytes;
310
+ entry.relayClientKey = clientKey;
311
+ // DOD-M15-AWAYSCOPE-1: the session just gained a relay, so tell it where this agent stands.
312
+ // The three attendance transitions fan out over sessions that already exist; this is the
313
+ // other direction — a session that arrives DURING an away stretch, which is exactly the
314
+ // case the order is about and the one the transitions cannot see.
315
+ client.announceAttendance(node, relay.sessionIdBytes, this.#ctx.currentAttendance(agentName));
316
+ // 2b: remember the relay endpoint so the content-park backstop deposits to the SAME relay.
317
+ entry.relayPeerId = relay.relayPeerId;
318
+ entry.relayAddrs = relay.relayAddrs;
319
+ // Review H1: the dial path needs the credential in hand, not just the endpoint.
320
+ // (Set above, before `registerSession`, so the genesis lookup it does has something to
321
+ // find — see the note there.)
322
+ // MSG-2 startup-flush: also PERSIST it, so a restart's crash-backstop flush (which runs
323
+ // before the in-memory entry exists) can deposit un-acked content to the same relay.
324
+ try {
325
+ this.#db
326
+ ?.prepare("UPDATE sessions SET relay_peer_id = ?, relay_addrs = ?, updated_at = ? WHERE agent_id = ? AND session_id = ?")
327
+ .run(relay.relayPeerId, JSON.stringify(relay.relayAddrs), Date.now(), this.#ctx.requireAgentId(agentName), sessionId);
328
+ }
329
+ catch (err) {
330
+ this.#ctx.logger.warn("session.relay.endpoint.persist.failed", {
331
+ sessionId,
332
+ error: extractErrorMessage(err),
333
+ });
334
+ }
335
+ }
336
+ else {
337
+ // The session was torn down while we were wiring — undo the registration.
338
+ client.unregisterSession(sessionIdHexForRelay);
339
+ if (!client.hasSessions() && this.#ctx.relayClients.get(clientKey) === client) {
340
+ client.close();
341
+ this.#ctx.relayClients.delete(clientKey);
342
+ }
343
+ return;
344
+ }
345
+ // Proactively connect so the relay has this agent's stream to deliver leaves to
346
+ // (the RECEIVER must be connected before the counterparty submits). Best-effort.
347
+ await client.connect(node);
348
+ // DOD-M15-RELAYAUTH-1 review HIGH-2 — see below. Best-effort and non-blocking: a session must
349
+ // never fail to come up because a SECOND relay could not be told about it.
350
+ //
351
+ // Review M1: `.catch()` is not decoration. This is an unawaited promise, so a throw it does not
352
+ // handle is an unhandled rejection — and this file already carries a comment elsewhere about an
353
+ // absent `await` that became a remote process kill. The method's own try/catch does not cover
354
+ // its prologue, so the catch here is the only thing standing between a torn-down node and the
355
+ // daemon dying.
356
+ void this.#presentAssignmentToReservationRelay(agentName, node, relay, sessionIdHexForRelay, correlationId, entry)
357
+ .catch((err) => {
358
+ this.#ctx.logger.warn("session.relay.assignment.reservation_relay_failed", {
359
+ agentName,
360
+ sessionId: sessionIdHexForRelay.slice(0, 16),
361
+ error: extractErrorMessage(err),
362
+ impact: "inbound relayed dials to this node may be refused by its reservation relay; the session still works over the direct path and the park backstop",
363
+ correlationId,
364
+ });
365
+ });
366
+ }
367
+ catch (err) {
368
+ this.#ctx.logger.warn("session.relay.connect.error", {
369
+ sessionId,
370
+ error: extractErrorMessage(err),
371
+ correlationId,
372
+ });
373
+ }
374
+ }
375
+ /**
376
+ * DOD-M15-RELAYAUTH-1 review HIGH-2 — **THE RELAY THAT GATES THE DIAL IS NOT ALWAYS THE RELAY
377
+ * THAT HOLDS THE ASSIGNMENT, AND THE GATE DENIES WHEN THEY DIFFER.**
378
+ *
379
+ * Two relays are in play for one session, chosen by unrelated rules:
380
+ * - the WITNESS relay, `assignment.relay_endpoint`, picked by the directory. Both parties
381
+ * present `client_record_assignment` to it and to nowhere else.
382
+ * - the RESERVATION relay, whichever one this node's circuit address is held on — the first
383
+ * candidate that granted when it was a standing receiver.
384
+ *
385
+ * The counterparty dials our CIRCUIT address, so it is the RESERVATION relay whose gater is asked
386
+ * `denyOutboundRelayedConnection(them, us)`. With no assignment recorded there it finds no binding
387
+ * and refuses a completely legitimate dial. Two relays run in production and the client's own
388
+ * logs show the fall-through to the second candidate is frequent, so this is the ordinary case,
389
+ * not a corner: the session still opens, but every message falls to the store-and-forward park
390
+ * path, and the only trace is a denial on a relay nobody is tailing.
391
+ *
392
+ * So the node that will be DIALLED presents the same assignment to the relay that will be asked
393
+ * to allow it. Safe by construction: the assignment is self-authenticating (a per-node directory
394
+ * signature the relay verifies against its consortium set), so presenting it more widely grants
395
+ * nothing that forging it would not already require. No directory change, no new frame.
396
+ */
397
+ async #presentAssignmentToReservationRelay(agentName, node, relay, sessionIdHex, correlationId, entry) {
398
+ // Review M1: the prologue below lives INSIDE the try. `listenAddresses()` on a node torn down
399
+ // while we were wiring is exactly the case handled 30 lines above at the caller, and out here it
400
+ // would have escaped both this method's catch and (before the caller's `.catch`) the process.
401
+ let reservationRelayPeerId;
402
+ try {
403
+ if (!relay.assignment)
404
+ return; // direct/legacy/persisted-reconnect: nothing to present anywhere
405
+ const heldCircuitAddr = node.listenAddresses().find((a) => a.includes("/p2p-circuit"));
406
+ if (!heldCircuitAddr)
407
+ return; // no reservation held → nobody will gate a dial to us
408
+ reservationRelayPeerId = /\/p2p\/([^/]+)\/p2p-circuit/.exec(heldCircuitAddr)?.[1];
409
+ if (!reservationRelayPeerId || reservationRelayPeerId === relay.relayPeerId)
410
+ return; // same relay — already recorded
411
+ const clientKey = `${agentName}::${reservationRelayPeerId}`;
412
+ let client = this.#ctx.relayClients.get(clientKey);
413
+ if (!client) {
414
+ if (!this.#ctx.relayReceiptStore && this.#db)
415
+ this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
416
+ if (!this.#ctx.sealLeafStore && this.#db)
417
+ this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
418
+ // Split on the marker, not an anchored strip: the held address is
419
+ // `…/p2p/<relay>/p2p-circuit/p2p/<self>`, so the marker is in the MIDDLE.
420
+ const baseRelayAddr = heldCircuitAddr.split("/p2p-circuit")[0] ?? heldCircuitAddr;
421
+ client = this.#ctx.detachedRelayClientBuilder?.(agentName, reservationRelayPeerId, [baseRelayAddr], {
422
+ receiptStore: this.#ctx.relayReceiptStore ?? undefined,
423
+ sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
424
+ // DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
425
+ onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
426
+ });
427
+ if (!client)
428
+ return;
429
+ this.#ctx.relayClients.set(clientKey, client);
430
+ // Review M4: record the key so teardown releases this client too — `relayClientKey` names
431
+ // only the witness relay, so before this the client and its session registration leaked.
432
+ if (entry)
433
+ entry.extraRelayClientKeys = [...(entry.extraRelayClientKeys ?? []), clientKey];
434
+ }
435
+ // registerSession presents the assignment eagerly (see its own comment). No leaf handler: this
436
+ // relay is not witnessing the session, it only needs the binding that authorizes the dial.
437
+ // 033-ACKEMIT: no genesis is passed and none is needed. This client is not witnessing the
438
+ // session — it never submits — and `registerSession` derives a seed from the assignment
439
+ // anyway. Reaching into the session record for one — or for the ANCHOR (069-ORDERPROOF), which
440
+ // is why none is passed — reaches with the RELAY session id, not the key it is stored under.
441
+ client.registerSession(sessionIdHex, node, undefined, relay.assignment, undefined, undefined);
442
+ this.#ctx.logger.info("session.relay.assignment.presented_to_reservation_relay", {
443
+ agentName,
444
+ sessionId: sessionIdHex.slice(0, 16),
445
+ witnessRelayPeerId: relay.relayPeerId,
446
+ reservationRelayPeerId,
447
+ impact: "the relay that will be asked to allow inbound circuit dials to this node now holds the assignment authorizing them",
448
+ correlationId,
449
+ });
450
+ }
451
+ catch (err) {
452
+ this.#ctx.logger.warn("session.relay.assignment.reservation_relay_failed", {
453
+ agentName,
454
+ sessionId: sessionIdHex.slice(0, 16),
455
+ reservationRelayPeerId,
456
+ error: extractErrorMessage(err),
457
+ impact: "inbound relayed dials to this node may be refused by its reservation relay; the session still works over the direct path and the park backstop",
458
+ correlationId,
459
+ });
460
+ }
461
+ }
462
+ /**
463
+ * M7 DOD-SPINE-6 / MSG-001-3b: detach a session from its (agent, relay) client and
464
+ * close the client when it has no remaining sessions. Idempotent and identity-guarded:
465
+ * the map delete only fires if the map still holds THIS client (a racing teardown of a
466
+ * sibling session must not close a freshly-created replacement client for the same key).
467
+ */
468
+ detachSessionRelay(entry) {
469
+ const client = entry.relayClient;
470
+ const key = entry.relayClientKey;
471
+ if (!entry.relaySessionIdBytes)
472
+ return;
473
+ const sidHex = Buffer.from(entry.relaySessionIdBytes).toString("hex");
474
+ /**
475
+ * Review M4: release the EXTRA relay clients first — the ones opened to relays that gate circuit
476
+ * dials. `relayClientKey` above names only the witness relay, so these were registered and never
477
+ * unregistered: an authenticated relay stream and a `#sessions` entry leaked per session, and
478
+ * relay-side the dial-through binding they hold is then cleared only by the idle timer, which is
479
+ * now 24h. Runs before the early return below so it happens even for a session that never got a
480
+ * witness client.
481
+ */
482
+ for (const extraKey of entry.extraRelayClientKeys ?? []) {
483
+ const extra = this.#ctx.relayClients.get(extraKey);
484
+ if (!extra)
485
+ continue;
486
+ extra.unregisterSession(sidHex);
487
+ if (!extra.hasSessions()) {
488
+ extra.close();
489
+ this.#ctx.relayClients.delete(extraKey);
490
+ }
491
+ }
492
+ entry.extraRelayClientKeys = undefined;
493
+ if (!client)
494
+ return;
495
+ // Idempotent: clear the entry's reference so a second teardown of the same entry no-ops.
496
+ entry.relayClient = undefined;
497
+ client.unregisterSession(sidHex);
498
+ if (!client.hasSessions() && key && this.#ctx.relayClients.get(key) === client) {
499
+ client.close();
500
+ this.#ctx.relayClients.delete(key);
501
+ }
502
+ }
503
+ /**
504
+ * DOD-M15-RELAYAUTH-1 review H1 — **THE GATE WAS DENYING THE LEGITIMATE DIAL, AND USUALLY.**
505
+ *
506
+ * With the gater installed, a relay refuses a circuit dial unless it already holds a
507
+ * directory-signed assignment naming both transport peer ids. Both parties get that assignment
508
+ * from the directory independently, and until this method existed, each only presented it to
509
+ * relays IT had chosen — so whether a dial was allowed came down to which of two independent
510
+ * network races finished first:
511
+ *
512
+ * 1. WE connect to the witness relay, then dial the counterparty's circuit address. ~2 RTT.
513
+ * 2. THEY connect to their witness relay, then — unawaited, on a fresh dial + auth + record —
514
+ * tell their RESERVATION relay about the session. ~3–4 RTT.
515
+ *
516
+ * Nothing sequenced (1) against (2), and (1) is shorter, so we usually arrived first and were
517
+ * refused. The session still opened and reported `transportMode: "relay"`, so the failure was
518
+ * invisible: every message for the life of that conversation quietly took the store-and-forward
519
+ * park path, and the only trace was a denial logged on a third machine nobody tails.
520
+ *
521
+ * The fix is to stop racing. Whoever is about to dial presents the assignment to the relay that
522
+ * will gate that dial, and WAITS for the relay to confirm it recorded it. The ordering becomes
523
+ * local to one thread of execution, so there is nothing left to lose.
524
+ *
525
+ * Safe by construction: we are a participant the assignment names, so the relay's own participant
526
+ * check passes; and the assignment is self-authenticating (a directory signature the relay
527
+ * verifies against its consortium set), so presenting it more widely grants nothing that forging
528
+ * it would not already require.
529
+ *
530
+ * Best-effort by design — a relay we cannot reach must not stop us from dialling. If the record
531
+ * fails we dial anyway: a dial that might be refused is strictly better than no dial.
532
+ */
533
+ async authorizeCircuitDialsToCounterparty(agentName, sessionId, entry, addrs) {
534
+ const assignment = entry.relayAssignment;
535
+ if (!assignment || !entry.relaySessionIdBytes)
536
+ return; // direct/legacy/persisted: no credential to present
537
+ const sessionIdHex = Buffer.from(entry.relaySessionIdBytes).toString("hex");
538
+ // One presentation per distinct relay, not per address: a counterparty commonly advertises
539
+ // several circuit addresses on the SAME relay.
540
+ const seen = new Set();
541
+ for (const addr of addrs) {
542
+ const relayPeerId = /\/p2p\/([^/]+)\/p2p-circuit/.exec(addr)?.[1];
543
+ if (!relayPeerId || seen.has(relayPeerId))
544
+ continue;
545
+ seen.add(relayPeerId);
546
+ // The witness relay already has it — registerSession presented it when the session was wired.
547
+ if (relayPeerId === entry.relayPeerId)
548
+ continue;
549
+ const baseRelayAddr = addr.split("/p2p-circuit")[0] ?? addr;
550
+ try {
551
+ const clientKey = `${agentName}::${relayPeerId}`;
552
+ let client = this.#ctx.relayClients.get(clientKey);
553
+ if (!client) {
554
+ if (!this.#ctx.relayReceiptStore && this.#db)
555
+ this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
556
+ if (!this.#ctx.sealLeafStore && this.#db)
557
+ this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
558
+ client = this.#ctx.detachedRelayClientBuilder?.(agentName, relayPeerId, [baseRelayAddr], {
559
+ receiptStore: this.#ctx.relayReceiptStore ?? undefined,
560
+ sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
561
+ // DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
562
+ onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
563
+ });
564
+ if (!client) {
565
+ this.#ctx.logger.warn("session.transport.dial_authorization.no_builder", {
566
+ sessionId,
567
+ relayPeerId,
568
+ impact: "cannot present the assignment to the relay that gates this dial; if the counterparty has not presented it either, the dial will be refused and every message will fall to the park path",
569
+ correlationId: entry.correlationId,
570
+ });
571
+ continue;
572
+ }
573
+ this.#ctx.relayClients.set(clientKey, client);
574
+ // Review M4: remember it so teardown releases it — this is a SECOND client for the
575
+ // session, and detach only knows about the witness one.
576
+ entry.extraRelayClientKeys = [...(entry.extraRelayClientKeys ?? []), clientKey];
577
+ }
578
+ // No leaf handler: this relay is not witnessing the session, it only needs the binding.
579
+ client.registerSession(sessionIdHex, entry.node, undefined, assignment, this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId), this.#ctx.leafRecords.sessionRelayAnchor(agentName, sessionId));
580
+ const recorded = await client.recordAssignmentAndWait(entry.node, sessionIdHex);
581
+ if (recorded) {
582
+ this.#ctx.logger.info("session.transport.dial_authorized", {
583
+ sessionId,
584
+ relayPeerId,
585
+ impact: "the relay that gates this circuit dial now holds the assignment authorizing it",
586
+ correlationId: entry.correlationId,
587
+ });
588
+ }
589
+ else {
590
+ this.#ctx.logger.warn("session.transport.dial_authorization.not_recorded", {
591
+ sessionId,
592
+ relayPeerId,
593
+ impact: "the relay did not confirm the assignment; the dial below may be refused and messages would fall to the park path",
594
+ correlationId: entry.correlationId,
595
+ });
596
+ }
597
+ }
598
+ catch (err) {
599
+ this.#ctx.logger.warn("session.transport.dial_authorization.failed", {
600
+ sessionId,
601
+ relayPeerId,
602
+ error: extractErrorMessage(err),
603
+ impact: "could not tell the relay that gates this dial about the session; the dial is attempted anyway",
604
+ correlationId: entry.correlationId,
605
+ });
606
+ }
607
+ }
608
+ }
609
+ /**
610
+ * TEST-ONLY (M8C-INBOX-1 reviewer F1): buffer a received message + persist its transcript row,
611
+ * exactly as the real inbound path (appendVerifiedContent, in `session-content-ingest.ts`) does,
612
+ * WITHOUT standing up a session
613
+ * tree — so a test can drive a live cello_receive that advances the read watermark (the N3
614
+ * "delivery marks read" coupling). Only reachable via the CELLO_ENV=test IPC hook.
615
+ */
616
+ /** CELLO_ENV=test only: patch a relay client and session-id bytes onto an existing active node entry
617
+ * so submitSealLeaf succeeds without a real relay handshake (used by the oneshot relay-path test). */
618
+ patchRelayClientForTest(agentName, sessionId, relayClient, relaySessionIdBytes,
619
+ /**
620
+ * 069-ORDERPROOF: the session's assignment ANCHOR — the relay key the directory named, which
621
+ * every relay ordering attestation is verified against. Production puts it on the node entry
622
+ * when the session is created with a relay; a fixture that built its node without one supplies
623
+ * it here, or every submit is refused for want of an anchor and the fixture measures that
624
+ * refusal instead of whatever it was written for.
625
+ */
626
+ assignment) {
627
+ const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
628
+ if (!entry)
629
+ throw new Error(`patchRelayClientForTest: no active node for (${agentName}, ${sessionId})`);
630
+ entry.relayClient = relayClient;
631
+ entry.relaySessionIdBytes = relaySessionIdBytes;
632
+ /**
633
+ * ⚠️ REGISTER THE SESSION TOO — the seam must leave the state production leaves.
634
+ *
635
+ * A relay client that has never been told about a session holds no starting point for it, and
636
+ * since `DOD-M15-SELFCHAIN-1` every submit on such a session is refused: there is nothing for
637
+ * the chain links to anchor to. Production always registers, because attaching a relay is what
638
+ * registration IS. A seam that attached the client and skipped the registration left fixtures
639
+ * exercising a refusal path, and the failure surfaced as "the seal never happened" in a test
640
+ * about away-mode replies.
641
+ */
642
+ relayClient.registerSession(Buffer.from(relaySessionIdBytes).toString("hex"), entry.node, undefined, entry.relayAssignment ?? assignment, this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId), this.#ctx.leafRecords.sessionRelayAnchor(agentName, sessionId) ?? assignment?.relayPubkeyHex);
643
+ }
644
+ /**
645
+ * M7-SESSION-001 AC-004/AC-005: Register a relay stream for an active session.
646
+ * Starts a background reader that watches for session_interrupted frames and
647
+ * stream close events. Both detection paths call markInterruptedWithDetails().
648
+ *
649
+ * The reader runs for the lifetime of the relay stream. If the stream closes
650
+ * without delivering a session_interrupted frame (AC-005 / 'stream_close' path),
651
+ * the session is still marked interrupted.
652
+ *
653
+ * @param sessionId The hex session ID
654
+ * @param stream The relay stream to monitor
655
+ * @param messageCount Number of message leaves at the time of registration
656
+ * (used as the count at interruption — best effort since exact count at frame
657
+ * receipt may differ, but this is the value available at stream setup time)
658
+ */
659
+ registerRelayStream(agentName, sessionId, stream, messageCount = 0) {
660
+ void this.#watchRelayStream(agentName, sessionId, stream, messageCount);
661
+ }
662
+ /**
663
+ * Background relay stream watcher.
664
+ * Pseudocode:
665
+ * 1. Create LP-framed iterator over the stream
666
+ * 2. For each frame:
667
+ * a. If type === 'session_interrupted':
668
+ * - Record receivedInterruptFrame = true
669
+ * - Call markInterruptedWithDetails(sessionId, messageCount, 'relay_frame')
670
+ * - Break (no more frames expected)
671
+ * 3. On stream close (loop ends normally or with error):
672
+ * a. If !receivedInterruptFrame:
673
+ * - Call markInterruptedWithDetails(sessionId, messageCount, 'stream_close')
674
+ */
675
+ async #watchRelayStream(agentName, sessionId, stream, messageCount) {
676
+ let receivedInterruptFrame = false;
677
+ // CELLO-M7-TRANSPORT-001: cast the stream input to lp.decode. Adding the
678
+ // @libp2p/autonat service (interface@3.2.2 / uint8arraylist v2) to the
679
+ // transport package surfaced a benign mixed-version split between the Stream
680
+ // type (now v2) and it-length-prefixed's expected Uint8ArrayList (v3). The two
681
+ // are structurally identical at runtime — this is a build-time-only artifact.
682
+ const lpSource = stream;
683
+ const source = lp.decode(lpSource)[Symbol.asyncIterator]();
684
+ try {
685
+ while (true) {
686
+ let result;
687
+ try {
688
+ result = await source.next();
689
+ }
690
+ catch {
691
+ // Stream error (e.g. stream aborted) — treat as stream close
692
+ break;
693
+ }
694
+ if (result.done || result.value === undefined)
695
+ break;
696
+ let frame;
697
+ try {
698
+ const bytes = result.value instanceof Uint8Array ? result.value
699
+ : Buffer.isBuffer(result.value) ? new Uint8Array(result.value)
700
+ : result.value.slice();
701
+ frame = decode(bytes);
702
+ }
703
+ catch {
704
+ continue;
705
+ }
706
+ if (frame["type"] === "session_interrupted") {
707
+ // H-3 SECURITY: this stream is registered (bound) to a specific
708
+ // sessionId. A malicious or buggy relay could put a DIFFERENT session_id
709
+ // in the frame body to target a session this stream is not authorized
710
+ // for (cross-session targeting). Never trust the frame's id: if the frame
711
+ // names a different session, reject it and keep watching the bound one.
712
+ const frameSessionId = typeof frame["session_id"] === "string"
713
+ ? frame["session_id"]
714
+ : (frame["session_id"] instanceof Uint8Array
715
+ ? Buffer.from(frame["session_id"]).toString("hex")
716
+ : null);
717
+ if (frameSessionId !== null && frameSessionId !== sessionId) {
718
+ this.#ctx.logger.warn("session.interrupt.frame.session_mismatch", {
719
+ boundSessionId: sessionId,
720
+ frameSessionId,
721
+ reason: "cross_session_frame_rejected",
722
+ });
723
+ continue; // ignore the hostile/mismatched frame; keep reading
724
+ }
725
+ receivedInterruptFrame = true;
726
+ // Always mark the BOUND sessionId — never the id carried in the frame.
727
+ await this.#ctx.markInterruptedWithDetails(agentName, sessionId, messageCount, "relay_frame");
728
+ break; // No more relay frames expected after session_interrupted
729
+ }
730
+ }
731
+ }
732
+ catch {
733
+ // Stream read loop ended — fall through to stream_close check
734
+ }
735
+ // AC-005: stream closed without a session_interrupted frame
736
+ if (!receivedInterruptFrame) {
737
+ // Only mark interrupted if this session is still active in SQLite
738
+ const record = this.#ctx.queries.getSessionRecord(agentName, sessionId);
739
+ if (record && record.status === "active") {
740
+ await this.#ctx.markInterruptedWithDetails(agentName, sessionId, messageCount, "stream_close");
741
+ }
742
+ }
743
+ }
744
+ /**
745
+ * DOD-NAT-REACHABILITY-1 (Phase 2): accept the directory's relay-pool endpoints
746
+ * for an agent (arrives with signaling_auth_ok, i.e. on every connect AND every
747
+ * reconnect). If the agent's standing receiver is up but holds NO reservation —
748
+ * the agent-online ensure raced ahead of auth_ok, or every relay was down at
749
+ * create time — rebuild it now so the agent becomes dialable without waiting
750
+ * for a session handoff that (being unreachable) would never come.
751
+ */
752
+ setDirectoryRelayEndpoints(agentName, endpoints) {
753
+ // DOD-M12B-RESERVATION-RETRY-1: a DIFFERENT relay pool re-arms the retry budget; the same one
754
+ // does not. This fires on every signaling connect AND reconnect, so clearing unconditionally
755
+ // would reset the bound on a short grid and defeat the whole point of having one. But once the
756
+ // budget is spent, `getStandingReceiverReachability` reports `unreachable` for the rest of the
757
+ // online episode — including while this path is actively re-attempting against relays we have
758
+ // never tried. A new relay is new information; a repeat of the same list is not.
759
+ const previous = this.#ctx.directoryRelayEndpoints.get(agentName);
760
+ const poolChanged = previous === undefined ||
761
+ previous.length !== endpoints.length ||
762
+ endpoints.some((e, i) => e.relayPeerId !== previous[i]?.relayPeerId);
763
+ this.#ctx.directoryRelayEndpoints.set(agentName, endpoints);
764
+ if (poolChanged) {
765
+ this.#ctx.srReservationRetry.delete(agentName);
766
+ this.#ctx.srLastRejectionReason.delete(agentName);
767
+ }
768
+ /**
769
+ * ⚠️ **ENDPOINTS ARRIVING NO LONGER REBUILD THE RECEIVER — 055-ONDEMAND.**
770
+ *
771
+ * This used to rebuild so the new node could reserve with the relays that had just been
772
+ * announced, because reservations were fixed at node creation and acquired at login. Neither is
773
+ * true now: nothing is reserved at login, and a running node can take one on demand.
774
+ *
775
+ * So a rebuild here would buy nothing and cost something real. The receiver it replaces may be
776
+ * holding the circuit a LIVE SESSION's counterparty was told to dial — throwing that away in
777
+ * response to a routine directory announcement would drop the route silently, at both ends.
778
+ *
779
+ * The endpoints are still recorded above, which is all they were ever needed for: they are the
780
+ * candidate list an offer reserves against.
781
+ */
782
+ }
783
+ /**
784
+ * Is this agent currently skipping this relay? The observable half of the failover decision — a
785
+ * test that asserts only on the classifier's boolean proves nothing about what the daemon does.
786
+ */
787
+ isRelayQuarantined(agentName, relayPeerId) {
788
+ return this.#relayQuarantineFor(agentName).has(relayPeerId);
789
+ }
790
+ /** Live quarantine entries for an agent, expired ones swept on read. */
791
+ #relayQuarantineFor(agentName) {
792
+ const byRelay = this.#ctx.relayQuarantine.get(agentName);
793
+ if (!byRelay)
794
+ return new Set();
795
+ const now = Date.now();
796
+ for (const [relayPeerId, expiresAt] of byRelay) {
797
+ if (now >= expiresAt)
798
+ byRelay.delete(relayPeerId);
799
+ }
800
+ if (byRelay.size === 0)
801
+ this.#ctx.relayQuarantine.delete(agentName);
802
+ return new Set(byRelay.keys());
803
+ }
804
+ /**
805
+ * Skip this relay for this agent for a while. Called only for refusals the classifier marks
806
+ * `tryAnotherRelay` — a fault of the relay's, not one that would follow us to the next one.
807
+ */
808
+ #quarantineRelay(agentName, relayPeerId, reason) {
809
+ let byRelay = this.#ctx.relayQuarantine.get(agentName);
810
+ if (!byRelay) {
811
+ byRelay = new Map();
812
+ this.#ctx.relayQuarantine.set(agentName, byRelay);
813
+ }
814
+ byRelay.set(relayPeerId, Date.now() + RELAY_QUARANTINE_MS);
815
+ this.#ctx.logger.warn("session.standing_receiver.relay_quarantined", {
816
+ agentName,
817
+ relayPeerId,
818
+ reason,
819
+ forMs: RELAY_QUARANTINE_MS,
820
+ impact: "this relay refused this agent for a fault of its own, so the agent will ask a " +
821
+ "different relay for its reservation until the quarantine lapses. Its inbound reachability " +
822
+ "is restored by moving, not by waiting for someone to fix that relay.",
823
+ });
824
+ }
825
+ reservationCircuitAddrs(agentName) {
826
+ let persisted;
827
+ try {
828
+ persisted = this.#ctx.queries.getAgentRelayEndpoints(agentName);
829
+ }
830
+ catch (err) {
831
+ // No DB / unknown agent — persisted source unavailable. The directory
832
+ // source may still serve; reachability degrades only if both are empty.
833
+ // Logged (not swallowed): a genuine DB failure must be distinguishable
834
+ // from "fresh agent, no history" in the reachability trail.
835
+ this.#ctx.logger.debug("session.standing_receiver.persisted_relays.unavailable", {
836
+ agentName,
837
+ error: extractErrorMessage(err),
838
+ });
839
+ persisted = [];
840
+ }
841
+ const merged = new Map(mergeRelayEndpoints(this.#ctx.directoryRelayEndpoints.get(agentName), persisted).map((ep) => [ep.relayPeerId, ep]));
842
+ /**
843
+ * DOD-M15-RELAYSLOTS-1 — **THE FAILOVER.** Skip relays that refused this agent for a fault of
844
+ * their own (today: a relay holding no directory public key, which can verify nobody and is
845
+ * refusing everyone). We run several relays precisely so one being broken is survivable.
846
+ *
847
+ * ⚠️ NEVER TO THE POINT OF HAVING NO RELAY AT ALL. If the quarantine would empty the candidate
848
+ * list it is ignored wholesale: a relay that refuses is strictly better than no relay, because
849
+ * the refusal at least has a cause the operator can read, while an agent with no candidates is
850
+ * simply unreachable with nothing to show for it. This is the same "refusing too eagerly is the
851
+ * failure mode" rule, applied to the client's own choice of where to ask.
852
+ */
853
+ const quarantined = this.#relayQuarantineFor(agentName);
854
+ const eligible = [...merged.values()].filter((ep) => !quarantined.has(ep.relayPeerId));
855
+ const usable = eligible.length > 0 ? eligible : [...merged.values()];
856
+ if (eligible.length === 0 && quarantined.size > 0 && merged.size > 0) {
857
+ this.#ctx.logger.warn("session.standing_receiver.relay_quarantine.ignored", {
858
+ agentName,
859
+ quarantined: [...quarantined],
860
+ impact: "every known relay has refused this agent for a relay-side fault, so the quarantine " +
861
+ "is being ignored and they are all being tried again. A relay that refuses with a cause " +
862
+ "is better than no relay at all — but if this persists, every relay this agent knows " +
863
+ "about is misconfigured, and that is the thing to look at.",
864
+ });
865
+ }
866
+ const addrs = [];
867
+ const relayPeerIds = [];
868
+ for (const ep of usable) {
869
+ const base = ep.relayAddrs[0];
870
+ if (!base)
871
+ continue;
872
+ const candidate = base.includes("/p2p/")
873
+ ? `${base}/p2p-circuit`
874
+ : `${base}/p2p/${ep.relayPeerId}/p2p-circuit`;
875
+ // These addresses are built from DIRECTORY-supplied endpoints — data from off
876
+ // this machine. A malformed one throws inside libp2p node construction, which
877
+ // would take the standing receiver down entirely and leave the agent deaf to
878
+ // ALL inbound (worse than the defect this fixes). A bad endpoint must cost one
879
+ // relay, never the receiver.
880
+ if (!isValidMultiaddr(candidate)) {
881
+ this.#ctx.logger.warn("session.standing_receiver.relay_endpoint.invalid", {
882
+ agentName,
883
+ relayPeerId: ep.relayPeerId,
884
+ addr: candidate,
885
+ });
886
+ continue;
887
+ }
888
+ addrs.push(candidate);
889
+ relayPeerIds.push(ep.relayPeerId);
890
+ }
891
+ return { addrs, relayPeerIds };
892
+ }
893
+ /**
894
+ * DOD-NAT-REACHABILITY-1: notice when a standing receiver has SILENTLY LOST its
895
+ * reservation, and get it another one.
896
+ *
897
+ * libp2p refreshes a circuit reservation before it expires. If the relay has died,
898
+ * that refresh fails and the /p2p-circuit address simply DISAPPEARS from the node's
899
+ * addresses. Nothing throws. The receiver is still up, still directly dialable, and
900
+ * still looks perfectly healthy — but no NAT'd peer can reach the agent any more.
901
+ * That is precisely the silent-loss-of-inbound failure this whole story exists to
902
+ * kill, so it cannot be left to chance: we watch for it and re-pick a relay.
903
+ *
904
+ * ⚠️ 056-SLOTDEAD: this paragraph used to end "Only receivers that HAD a reservation are watched.
905
+ * One that never got one is already degraded and already loud (reservation.none /
906
+ * reservation.timeout); rebuilding it on a timer would just thrash against relays we know are
907
+ * refusing." Every clause of that is now false — an idle receiver holds nothing BY DESIGN so
908
+ * "degraded" is the normal state, `reservation.none` is not emitted at build any more, and
909
+ * nothing rebuilds. What is watched is a receiver whose recorded set has shrunk, and what
910
+ * happens is a re-take in place for a LIVE session, never a rebuild.
911
+ */
912
+ /**
913
+ * DOD-M12B-RESERVATION-RETRY-1 / 055-ONDEMAND — is a re-attempt due, and claim it if so.
914
+ *
915
+ * ⚠️ **TWO DOC BLOCKS DESCRIBING `#retryReservationIfDue` USED TO SIT HERE — 056-SLOTDEAD, review
916
+ * F7.** That function is deleted. One of them said "the rebuild is the re-attempt … which is why
917
+ * it stays", so a reader following the comment landed on this function and concluded a rebuild
918
+ * ladder was still running. There is no rebuild anywhere in this file.
919
+ *
920
+ * What this does: a live session that lost its circuit re-takes it IN PLACE, on the node that
921
+ * already has the session, and this owns whether it is allowed to try yet. ONE ladder — a
922
+ * reservation is scarce, the relay holds it for its full TTL even after the client disconnects,
923
+ * and this file's own warning is that churning attempts across a fleet is how a relay is
924
+ * exhausted.
925
+ */
926
+ #retryDue(agentName) {
927
+ const now = Date.now();
928
+ const state = this.#ctx.srReservationRetry.get(agentName)
929
+ ?? { attempts: 0, nextAt: now + this.#ctx.srReservationRetryMs, correlationId: randomUUID() };
930
+ const lastReason = this.#ctx.srLastRejectionReason.get(agentName);
931
+ if (lastReason !== undefined)
932
+ state.lastReason = lastReason;
933
+ if (!this.#ctx.srReservationRetry.has(agentName)) {
934
+ // First sighting — schedule, do not fire. Something just tried.
935
+ this.#ctx.srReservationRetry.set(agentName, state);
936
+ return false;
937
+ }
938
+ if (now < state.nextAt)
939
+ return false;
940
+ if (state.attempts >= SR_RESERVATION_MAX_RETRIES) {
941
+ if (state.attempts === SR_RESERVATION_MAX_RETRIES) {
942
+ state.attempts += 1; // report once
943
+ this.#ctx.srReservationRetry.set(agentName, state);
944
+ this.#ctx.logger.error("session.standing_receiver.reservation.gave_up", {
945
+ agentName,
946
+ attempts: SR_RESERVATION_MAX_RETRIES,
947
+ correlationId: state.correlationId,
948
+ // WHY, not just the consequence. Three different problems reach this one message and they
949
+ // need three different responses: `relay_granted_no_reservation` is relay CAPACITY (and a
950
+ // trustless-cello problem), `relay_unreachable` is the NETWORK, and
951
+ // `reservation_did_not_complete_in_time` is LATENCY.
952
+ ...(state.lastReason !== undefined ? { lastRejectionReason: state.lastReason } : {}),
953
+ /**
954
+ * ⚠️ **THESE TWO CAME BACK — 056-SLOTDEAD, review F9.** The deleted emitter of this SAME
955
+ * event name carried them; the surviving one did not, so the fields quietly vanished from
956
+ * an operator's view while the event kept appearing.
957
+ *
958
+ * "No relay would grant" and "there was no relay to ask" are different facts that lead to
959
+ * different places — the first at relay capacity, the second at this agent's directory
960
+ * connection. Without them they are the same sentence.
961
+ *
962
+ * `hadRelayToAsk` reads the directory pool alone; `relaysOffered` is the merged,
963
+ * quarantine-filtered candidate list the re-take actually walks. Two populations, so two
964
+ * names — the mis-naming that made `reservationsRequested` unreadable is the reason this
965
+ * note exists.
966
+ */
967
+ hadRelayToAsk: (this.#ctx.directoryRelayEndpoints.get(agentName)?.length ?? 0) > 0,
968
+ relaysOffered: this.reservationCircuitAddrs(agentName).addrs.length,
969
+ impact: "a live session lost the circuit its counterparty dials, and no relay would give " +
970
+ "it back inside the retry budget. Messages still reach this agent through the relay's " +
971
+ "store-and-forward; a direct dial to it will not connect until the session is rebuilt.",
972
+ });
973
+ }
974
+ return false;
975
+ }
976
+ state.attempts += 1;
977
+ // Doubling, floored at the configured interval. 056-SLOTDEAD: this said "the same shape the
978
+ // rebuild ladder uses" — there is no rebuild ladder any more, this IS the ladder. The reason is
979
+ // unchanged: a fixed short interval is what exhausts a relay.
980
+ state.nextAt = now + this.#ctx.srReservationRetryMs * Math.pow(2, state.attempts - 1);
981
+ this.#ctx.srReservationRetry.set(agentName, state);
982
+ this.#ctx.logger.info("session.standing_receiver.reservation.retry", {
983
+ agentName, attempts: state.attempts, correlationId: state.correlationId,
984
+ });
985
+ return true;
986
+ }
987
+ async #reservationWatchdogTick() {
988
+ if (this.#ctx.shuttingDown)
989
+ return;
990
+ for (const [agentName, sr] of this.#ctx.standingReceivers) {
991
+ if (!this.#ctx.agentsWantingReceiver.has(agentName))
992
+ continue; // agent went offline
993
+ // DOD-M12B-RESERVATION-RETRY-1 — NEVER HAD ONE IS NOT "NOTHING TO DO".
994
+ //
995
+ // This used to `continue` unconditionally, on the grounds that a receiver with no reservation
996
+ // is "already degraded and already loud". Measured over 17 days: `reservation.none` fired 481
997
+ // times and `relay.rejected` 2,215 — every one of the latter `relay_granted_no_reservation`,
998
+ // a relay out of slots completing the handshake and granting nothing. Nothing ever acted on
999
+ // the noise, and each of those receivers is a plain TCP node with no circuit address: behind
1000
+ // NAT, dialable by NOBODY, for its whole life. That is the silent loss of inbound this file
1001
+ // says three lines below it exists to kill.
1002
+ //
1003
+ // A relay out of slots at boot may have one minutes later, so re-attempt — on a BACKOFF and
1004
+ // BOUNDED, never on this 30-second grid. A reservation is scarce: the relay holds it for its
1005
+ // full TTL even after the client disconnects, and churning attempts across a fleet is how a
1006
+ // relay is exhausted (`#startReceiverNode` records that hazard).
1007
+ if (sr.relayPeerIds.length === 0) {
1008
+ /**
1009
+ * ⚠️ **AN IDLE AGENT HOLDING NOTHING IS THE DESIGN NOW, NOT A DEGRADATION — 055-ONDEMAND.**
1010
+ *
1011
+ * Everything above this line was written when a receiver reserved at login and holding zero
1012
+ * meant an agent dialable by nobody for its whole life. A reservation is taken at OFFER time
1013
+ * now and given back at the seal, so zero is the correct steady state for an agent nobody is
1014
+ * calling — and retrying here would have every idle agent in the fleet asking relays for
1015
+ * slots the design says it must not hold. That is the exact churn the note below warns
1016
+ * about, pointed at the whole fleet instead of one receiver.
1017
+ *
1018
+ * The retry still matters for an agent that HAS a live session and lost the circuit that
1019
+ * session depends on, which is what the condition now says.
1020
+ */
1021
+ const liveSessions = [...this.#ctx.activeNodes.values()].filter((e) => e.agentName === agentName);
1022
+ if (liveSessions.length === 0) {
1023
+ /**
1024
+ * ⚠️ **AN AGENT WITH NO SESSION HAS NOTHING TO RETRY, SO IT MUST NOT LOOK LIKE IT DOES —
1025
+ * `DOD-M15-IDLERETRY-1`, second half.**
1026
+ *
1027
+ * 061 stopped the ladder being OPENED for a session that needs nothing. It did not clear
1028
+ * one that was legitimately opened and is now moot. A ladder exists to get a LIVE session
1029
+ * its circuit back; when that session ends there is nothing left to get, and the entry is
1030
+ * the only thing `getStandingReceiverReachability` consults — so a perfectly idle agent
1031
+ * went on reporting `retrying`, and eventually `unreachable`, until the daemon restarted.
1032
+ *
1033
+ * Observed on the shipped 0.0.217 build: an agent with no live session at all, reading
1034
+ * `retrying`. That can only happen through a stale entry, which is what sent this back for
1035
+ * a second pass.
1036
+ *
1037
+ * Cleared HERE rather than at teardown on purpose: this is the one place that already
1038
+ * knows, every tick, that an agent holds nothing and wants nothing. A teardown hook would
1039
+ * be a second thing to keep in step with session lifecycle, and the whole class of defect
1040
+ * in this file is two things drifting apart.
1041
+ */
1042
+ this.#ctx.srReservationRetry.delete(agentName);
1043
+ this.#ctx.srLastRejectionReason.delete(agentName);
1044
+ continue;
1045
+ }
1046
+ /**
1047
+ * ⚠️ **RE-TAKE THE SESSION'S CIRCUIT — REBUILDING THE RECEIVER NO LONGER DOES IT.**
1048
+ *
1049
+ * `#retryReservationIfDue` rebuilds the receiver, and a rebuilt receiver reserves NOTHING
1050
+ * (055-ONDEMAND). So on its own the retry ladder became a no-op by construction: a relay
1051
+ * that flapped mid-session would leave that session permanently un-dialable while the
1052
+ * ladder churned receivers to no effect.
1053
+ *
1054
+ * The circuit a live session needs is the one its ASSIGNMENT named, which is persisted with
1055
+ * the session — so ask for that one back, rather than rebuilding and hoping.
1056
+ */
1057
+ /**
1058
+ * ⚠️ **ON THE SAME BUDGET AND BACKOFF AS THE LADDER IT REPLACES.** A reservation is scarce:
1059
+ * the relay holds it for its full TTL even after the client disconnects, and this file's own
1060
+ * warning is that churning attempts across a fleet is how a relay is exhausted. Asking on
1061
+ * every 30-second tick would be exactly that churn, wearing a new name — measured here as 52
1062
+ * asks where the budget allows 37.
1063
+ */
1064
+ /**
1065
+ * ⚠️ **WHAT ACTUALLY NEEDS A CIRCUIT, DECIDED BEFORE THE BUDGET IS TOUCHED —
1066
+ * `DOD-M15-IDLERETRY-1`.**
1067
+ *
1068
+ * `#retryDue` was called FIRST, and it does not merely answer a question: on an agent it has
1069
+ * not seen before it WRITES a retry entry and returns false. So simply having a live session
1070
+ * while holding no circuit was enough to open a retry ladder — and for the INITIATOR of a
1071
+ * call that state is permanent and wrong, because an initiator DIALS OUT and never needs to
1072
+ * be dialable (055-ONDEMAND narrowed the reservation to the party being dialed, deliberately).
1073
+ *
1074
+ * Two things followed, and both were measured live on 2026-09-11 rather than reasoned:
1075
+ * - `cello_status` reported `retrying` for a healthy agent from its first call onward,
1076
+ * cleared only by a daemon restart — the same "a broken agent looks like a fine one"
1077
+ * defect 060-READY had just fixed, in a case it did not cover;
1078
+ * - worse, it is not cosmetic. Held past the retry interval, both agents emitted a real
1079
+ * `session.standing_receiver.reservation.retry` — the daemon asking relays for slots on
1080
+ * behalf of sessions that need none. That is churn against the scarce resource this
1081
+ * whole story exists to conserve.
1082
+ *
1083
+ * So the question "is there anything to re-take?" is answered from the SESSION NODES first.
1084
+ * A session that already holds its circuit, or has no relay endpoint recorded, needs
1085
+ * nothing. If none needs anything, this tick is a no-op and must leave no trace.
1086
+ */
1087
+ const needRetake = liveSessions.filter((entry) => {
1088
+ const ep = this.#ctx.queries.getPersistedRelayEndpoint(agentName, entry.sessionId);
1089
+ if (!ep || ep.relayAddrs.length === 0)
1090
+ return false;
1091
+ /**
1092
+ * ⚠️ `heldRelayIdsOf`, NOT a substring test — the same distinction 056-SLOTDEAD had to
1093
+ * make one layer up. A circuit address that does not NAME its relay cannot be dialled
1094
+ * through, so a session announcing one holds nothing usable and DOES need a re-take.
1095
+ * A substring test counts it as held and leaves that session silently unreachable.
1096
+ */
1097
+ return heldRelayIdsOf(entry.node).length === 0;
1098
+ });
1099
+ if (needRetake.length === 0)
1100
+ continue;
1101
+ if (!this.#retryDue(agentName))
1102
+ continue;
1103
+ /**
1104
+ * ⚠️ **ON THE SESSION'S OWN NODE, NOT THE IDLE RECEIVER — and the first version got this
1105
+ * wrong in a way that was worse than doing nothing.**
1106
+ *
1107
+ * It called the take path, which reserves on `standingReceivers.get(agentName).node`. That
1108
+ * is the fresh receiver built after the promotion — a DIFFERENT peer id from the one the
1109
+ * counterparty was told to dial. So the circuit it obtained helped no one, and it made an
1110
+ * IDLE receiver hold a relay slot, which is the exact thing this unit exists to stop. It
1111
+ * also set `relayPeerIds` non-empty, hiding the real loss from every later tick.
1112
+ */
1113
+ let retook = false;
1114
+ for (const entry of needRetake) {
1115
+ const ep = this.#ctx.queries.getPersistedRelayEndpoint(agentName, entry.sessionId);
1116
+ const base = ep.relayAddrs[0];
1117
+ const circuitAddr = base.includes(`/p2p/${ep.relayPeerId}`) ? `${base}/p2p-circuit` : `${base}/p2p/${ep.relayPeerId}/p2p-circuit`;
1118
+ if (await this.#ctx.retakeReservationOn(agentName, entry.node, circuitAddr, entry.correlationId))
1119
+ retook = true;
1120
+ }
1121
+ if (retook)
1122
+ continue;
1123
+ // …unless one has arrived since. Review F4, same class as the recompute below: the
1124
+ // slow-start path installs a receiver before every circuit has bound, so "held nothing at
1125
+ // install" is not the same fact as "holds nothing now". Adopting it here is what stops the
1126
+ // retry ladder rebuilding a receiver that is already reachable.
1127
+ const arrived = heldRelayIdsOf(sr.node)
1128
+ .filter((id) => sr.node.getConnections().some((c) => c.peerId === id && c.status === "open"));
1129
+ if (arrived.length === 0) {
1130
+ /**
1131
+ * ⚠️ **`#retryReservationIfDue` WAS CALLED HERE AND COULD NEVER DO ANYTHING — 056-SLOTDEAD.**
1132
+ *
1133
+ * It guards on `now < state.nextAt`, and `#retryDue` above advances that same state on
1134
+ * this very tick, so the second call always returned immediately. Reachable, referenced,
1135
+ * and inert — the shape a reference scan cannot find.
1136
+ *
1137
+ * *(The first version of this order claimed it DOUBLE-ADVANCED the budget and triggered a
1138
+ * useless rebuild. That was wrong, and it was wrong because it was reasoned rather than
1139
+ * run. It was a no-op.)*
1140
+ *
1141
+ * Nothing replaces it: `#retryDue` owns the budget and the re-take above owns the work.
1142
+ */
1143
+ continue;
1144
+ }
1145
+ sr.relayPeerIds = arrived;
1146
+ sr.gater.setReservedRelayPeers(arrived);
1147
+ this.#ctx.logger.info("session.standing_receiver.reservation.gained", {
1148
+ agentName,
1149
+ relayPeerIds: arrived,
1150
+ reservationsHeld: arrived.length,
1151
+ });
1152
+ }
1153
+ // It has at least one — any earlier retry budget, and the reason the last attempt failed, are
1154
+ // stale.
1155
+ this.#ctx.srReservationRetry.delete(agentName);
1156
+ this.#ctx.srLastRejectionReason.delete(agentName);
1157
+ // Watch the CONNECTION to the relay, not the circuit address.
1158
+ //
1159
+ // Killing the relay does NOT make the /p2p-circuit address disappear: libp2p
1160
+ // keeps the listen address until the reservation's own refresh, up to two hours
1161
+ // away. Watching the address would therefore miss a dead relay for hours — the
1162
+ // agent would advertise a circuit address that routes through a relay that no
1163
+ // longer exists, which is exactly the silent unreachability we are hunting. The
1164
+ // live connection to the relay is the honest signal: no connection, no relay,
1165
+ // no reservation.
1166
+ //
1167
+ // AND IT MUST BE OPEN. `getConnections()` returns libp2p's registry, which keeps a connection
1168
+ // listed while it is closing and after its muxer has died — the state the whole M12 Tier P5
1169
+ // investigation turned on. Without the status check a registered corpse reads as "still
1170
+ // connected", the rebuild never fires, and the agent silently stops being reachable while
1171
+ // this loop reports it healthy. The comment above claimed liveness; only this tests it.
1172
+ // 032-RELAYSPREAD — PER RELAY, and the health question is now a COUNT.
1173
+ //
1174
+ // This used to evaluate one peer id and rebuild the entire standing receiver when it went
1175
+ // false. With a pool of one that was the only thing it could do; with a pool of three it is
1176
+ // the churn engine — every relay is another watchdog subject, and a full rebuild per loss
1177
+ // multiplies the 30-second grid by the size of the pool while throwing away reservations that
1178
+ // are perfectly healthy.
1179
+ /**
1180
+ * RECOMPUTED FROM THE NODE, never filtered down from the stored list. Review F4: filtering
1181
+ * `sr.relayPeerIds` makes it SHRINK-ONLY, and a list that can only shrink cannot see a
1182
+ * circuit arrive. Three things went wrong with that, and the first one happens routinely:
1183
+ * - the slow-start path installs the receiver before every circuit has bound, so a relay
1184
+ * that binds four seconds later was invisible to this watchdog and absent from the
1185
+ * gater's carve-out set FOREVER — its AutoNAT probe reply refused by our own gate;
1186
+ * - shrinking to zero then rebuilt a receiver that was announcing live circuits, which is
1187
+ * the exact defect this unit is against;
1188
+ * - anything that ever restores a circuit could not be counted.
1189
+ * Reading the node's own addresses costs the same and has none of that.
1190
+ */
1191
+ const open = sr.node.getConnections().filter((c) => c.status === "open").map((c) => c.peerId);
1192
+ const stillHeld = heldRelayIdsOf(sr.node).filter((id) => open.includes(id));
1193
+ const lost = sr.relayPeerIds.filter((id) => !stillHeld.includes(id));
1194
+ const gained = stillHeld.filter((id) => !sr.relayPeerIds.includes(id));
1195
+ sr.relayPeerIds = stillHeld;
1196
+ if (gained.length > 0) {
1197
+ // A circuit this receiver did not have at install. Said out loud because it is the visible
1198
+ // half of the slow-start case, and because it is the moment that relay earns its inbound
1199
+ // carve-out — a silent widening of the gate is not something to do without a line.
1200
+ this.#ctx.logger.info("session.standing_receiver.reservation.gained", {
1201
+ agentName,
1202
+ relayPeerIds: gained,
1203
+ reservationsHeld: stillHeld.length,
1204
+ });
1205
+ }
1206
+ if (lost.length === 0) {
1207
+ // Nothing lost. The gater still gets the current set, because `gained` may have widened it.
1208
+ if (gained.length > 0)
1209
+ sr.gater.setReservedRelayPeers(stillHeld);
1210
+ continue;
1211
+ }
1212
+ // REVOKE FIRST. A relay whose reservation is gone must lose its inbound carve-out in the same
1213
+ // breath as the loss is noticed, or the gater's bound quietly becomes "granted one once".
1214
+ sr.gater.setReservedRelayPeers(stillHeld);
1215
+ for (const relayPeerId of lost) {
1216
+ // DOD-RELAY-KEEPALIVE-1 (review F4): carry the CAUSE, not just the exit point.
1217
+ // `relay_connection_gone` says where this was noticed — a poll of getConnections() — by
1218
+ // which time the abort reason that actually killed the link is long discarded. The relay
1219
+ // client for this (agent, relay) pair kept the error that ended its reader; that is the
1220
+ // nearest thing to an upstream cause available here, and its absence is how 2,061 of these
1221
+ // went untraced.
1222
+ /**
1223
+ * ⚠️ **A DIAGNOSTIC MUST NOT BE ABLE TO KILL THE WATCHDOG TICK.** This is optional-chained on
1224
+ * the map lookup but the METHOD was called unguarded, so a client without it threw — an
1225
+ * unhandled rejection inside the tick, which takes the rest of the sweep with it. Surfaced
1226
+ * when 055-ONDEMAND made this branch reachable in more cases. The cause line is worth
1227
+ * having; it is not worth the loss detection it rides on.
1228
+ */
1229
+ const client = this.#ctx.relayClients.get(`${agentName}::${relayPeerId}`);
1230
+ const upstreamReason = typeof client?.getLastReaderError === "function" ? client.getLastReaderError() : null;
1231
+ this.#ctx.logger.warn("session.standing_receiver.reservation.lost", {
1232
+ agentName,
1233
+ relayPeerId,
1234
+ reason: open.includes(relayPeerId) ? "circuit_address_vanished" : "relay_connection_gone",
1235
+ ...(upstreamReason ? { upstreamReason } : {}),
1236
+ reservationsHeld: stillHeld.length,
1237
+ /**
1238
+ * The line an operator reads, and the two cases are not the same event at all.
1239
+ *
1240
+ * ⚠️ **THE ZERO CASE PROMISED A REPAIR THAT NO LONGER HAPPENS — 056-SLOTDEAD, review F3.**
1241
+ * It ended "The receiver is being rebuilt against the rest of the pool", which was true
1242
+ * when this branch rebuilt. Nothing rebuilds now. An operator reading the old line would
1243
+ * wait for a recovery that was never coming, and the only thing that WOULD have told them
1244
+ * otherwise is a `zero_held` line at debug level they are not reading. Both branches now
1245
+ * say what actually happens next and who owns it.
1246
+ */
1247
+ impact: stillHeld.length > 0
1248
+ ? "this agent still holds " + stillHeld.length + " other circuit reservation(s), so it "
1249
+ + "stays dialable from behind NAT. Losing one relay costs this agent nothing it can feel."
1250
+ : "this agent now holds NO circuit reservation. If it has no live session that is the "
1251
+ + "normal idle state and costs nothing — a slot is taken when someone calls. If it "
1252
+ + "DOES have a live session, that session's counterparty can no longer dial it, and "
1253
+ + "the re-take above is what gets it back, on a bounded budget. Nothing rebuilds the "
1254
+ + "receiver any more, so do not wait for one.",
1255
+ });
1256
+ }
1257
+ /**
1258
+ * ⚠️ **DRAIN ON THE LOSS ITSELF — 056-SLOTDEAD.** This used to happen one step downstream, as
1259
+ * a side effect of the rebuild below: lose every reservation → rebuild → the new receiver
1260
+ * reports ready → drain. Deleting the rebuild would have deleted the drain with it, leaving
1261
+ * content the counterparty parked to sit until the periodic backstop. The loss is the cause
1262
+ * and always was; the rebuild was only where it happened to be noticed.
1263
+ */
1264
+ this.#ctx.park.fireParkedDrain(agentName, "reservation_lost");
1265
+ if (stillHeld.length === 0) {
1266
+ /**
1267
+ * ⚠️ **THIS REBUILT THE RECEIVER ON A JUSTIFICATION THAT STOPPED BEING TRUE IN UNIT 1, AND
1268
+ * UNDER ON-DEMAND IT DESTROYS AN IN-FLIGHT OFFER'S RESERVATION — 056-SLOTDEAD.**
1269
+ *
1270
+ * It read: *"only a new node can take a new reservation, because a circuit listener is fixed
1271
+ * at node creation."* `listenOnCircuit` removed that constraint (`DOD-M15-RELAYPROVE-ORDER-1`),
1272
+ * and a rebuilt receiver now reserves nothing at all.
1273
+ *
1274
+ * The state it fires in is the dangerous one: between an offer taking a reservation and the
1275
+ * session being created, the receiver holds exactly one circuit. Lose the connection in that
1276
+ * window and this rebuilt the node — discarding the slot the offer just took, after the
1277
+ * accept may already have advertised it. **Observed**, not reasoned: a fixture that reported
1278
+ * no relay connection drove this path and the test caught the rebuild.
1279
+ *
1280
+ * Nothing replaces it here. An idle agent holding zero is the design; a LIVE session that
1281
+ * lost its circuit is re-taken on its own node by the branch above, on a bounded budget.
1282
+ */
1283
+ this.#ctx.logger.debug("session.standing_receiver.reservation.zero_held", {
1284
+ agentName,
1285
+ impact: "this agent holds no circuit. If it has no live session that is the idle steady " +
1286
+ "state; if it does, the re-take above owns getting it back.",
1287
+ });
1288
+ continue;
1289
+ }
1290
+ }
1291
+ }
1292
+ /** Start the reservation watchdog (idempotent). Stopped by gracefulShutdown. */
1293
+ startReservationWatchdog() {
1294
+ if (this.#ctx.reservationWatchdog !== null)
1295
+ return;
1296
+ // Arm the backstop clock from the START of watching, not from the epoch — otherwise the first
1297
+ // tick always fires a sweep on top of the install drain that just ran.
1298
+ this.#ctx.park.armBackstopClock(Date.now());
1299
+ this.#ctx.reservationWatchdog = setInterval(() => {
1300
+ try {
1301
+ void this.#reservationWatchdogTick();
1302
+ this.#ctx.park.parkedDrainBackstopTick(Date.now());
1303
+ }
1304
+ catch (err) {
1305
+ this.#ctx.logger.warn("session.standing_receiver.watchdog.failed", { error: extractErrorMessage(err) });
1306
+ }
1307
+ }, this.#ctx.srWatchdogIntervalMs);
1308
+ // Never hold the process open on account of the watchdog.
1309
+ this.#ctx.reservationWatchdog.unref?.();
1310
+ }
1311
+ /**
1312
+ * Start the standing receiver's libp2p node, holding a circuit-relay reservation
1313
+ * if one can be had — and WITHOUT one if it cannot.
1314
+ *
1315
+ * THE INVARIANT, learned live: **standing-receiver creation must NEVER be gated on
1316
+ * a relay.** libp2p's circuit listener awaits a live connection to its relay before
1317
+ * start() resolves, and it does not time out. A relay that does not answer parks
1318
+ * start() forever: no created event, no failure, no retry, no alarm — the agent
1319
+ * simply has no receiver and is deaf to ALL inbound, including the direct path that
1320
+ * worked before reservations existed. Strictly worse than the NAT defect this whole
1321
+ * line exists to fix. So every attempt is raced against a deadline, and failure
1322
+ * ALWAYS falls through to a plain TCP receiver.
1323
+ *
1324
+ * ONE relay, tried on the REAL node — not a probe.
1325
+ *
1326
+ * A relay reservation is a scarce resource: the relay holds it for its full TTL
1327
+ * even after the client disconnects, and it has a finite number of slots. An
1328
+ * earlier design probed each relay on a throwaway node and then reserved AGAIN on
1329
+ * the receiver — burning TWO slots per agent to get one, and leaving the throwaway's
1330
+ * slot pinned for hours. That is how a fleet exhausts a relay. Here the receiver
1331
+ * itself makes the attempt: if the relay grants the reservation, we KEEP that node.
1332
+ * One slot per agent, which is the true cost.
1333
+ *
1334
+ * Candidates are tried in order (directory pool first). The first that actually
1335
+ * grants a reservation wins; the rest are never touched.
1336
+ */
1337
+ /**
1338
+ * DOD-M15-RELAYSLOTS-1 — tell the relay this transport identity belongs to a registered agent, so
1339
+ * the reservation it just refused is granted on the next attempt.
1340
+ *
1341
+ * Returns `"proven"` on success, or the shape of the failure so the candidate loop can act on it.
1342
+ * It never throws: throwing would turn one unreachable relay into a failure to build a receiver
1343
+ * at all.
1344
+ *
1345
+ * ─── Why this returns a verdict instead of a boolean ──────────────────────────────────────────
1346
+ *
1347
+ * Review HIGH-1/HIGH-2. Putting the reservation behind a proof MOVED THE FIRST REFUSAL onto this
1348
+ * path. `authenticateStandingReceiver` does everything right with a refusal — records it where
1349
+ * `cello_status` can read it, and quarantines a relay whose fault is its own — but it runs only
1350
+ * on a receiver that ALREADY HAS a reservation, so under the new gate a total failure never
1351
+ * reaches it. This method was logging `proven: false` and returning.
1352
+ *
1353
+ * What that cost the operator: an expired token, or an agent at its slot cap, refused by every
1354
+ * relay in the pool. `cello_status` shows an agent that is online and reachable by nobody, with
1355
+ * no cause anywhere the person will look — while the relay had computed the cause, the count, and
1356
+ * the next step, and put them on the wire. And `slot_cap_exceeded` is classified
1357
+ * `tryAnotherRelay: false` precisely so the client STOPS walking the fleet; without the verdict,
1358
+ * the loop walked it anyway, turning one client-side fault into what reads as a fleet outage.
1359
+ */
1360
+ /**
1361
+ * 055-ONDEMAND — **tell the relay this agent has finished with its slot.**
1362
+ *
1363
+ * The only thing that actually frees one: closing a circuit listener sends the relay nothing, and
1364
+ * the relay reclaims on its own only at the reservation TTL (two hours) or under reaper pressure.
1365
+ * Best-effort by design — an undelivered release costs a slot until that TTL, and must never fail
1366
+ * the seal that triggered it.
1367
+ */
1368
+ async tellRelayReleased(agentName, relayPeerId, node, correlationId) {
1369
+ /**
1370
+ * ⚠️ **THE NODE IS THE SOURCE OF TRUTH FOR WHERE THIS RELAY IS — review MEDIUM-6.**
1371
+ *
1372
+ * The first version looked the relay up in `directoryRelayEndpoints`. But the reservation was
1373
+ * taken on the relay the OFFER named, whose addresses came off that frame; if the directory's
1374
+ * last announcement does not contain it — a pool relay dropped from the roster, a stale
1375
+ * announcement — the release warns `no_endpoint` and the slot is held to its TTL. The node
1376
+ * announces the circuit it actually holds, so derive the address from that and fall back to the
1377
+ * directory list only when there is nothing to derive from.
1378
+ */
1379
+ const fromNode = node.listenAddresses()
1380
+ .filter((a) => a.split("/").includes("p2p-circuit") && a.includes(`/p2p/${relayPeerId}/`))
1381
+ .map((a) => a.split("/p2p-circuit")[0])
1382
+ .filter((a) => a.length > 0);
1383
+ const ep = this.#ctx.directoryRelayEndpoints.get(agentName)?.find((e) => e.relayPeerId === relayPeerId);
1384
+ const relayAddrs = fromNode.length > 0 ? [...new Set(fromNode)] : (ep ? [...ep.relayAddrs] : []);
1385
+ if (relayAddrs.length === 0) {
1386
+ this.#ctx.logger.warn("session.relay.reservation_release.no_endpoint", {
1387
+ agentName, relayPeerId, correlationId,
1388
+ impact: "no address is known for this relay, so it cannot be told the slot is free and " +
1389
+ "holds it until the reservation TTL expires.",
1390
+ });
1391
+ return;
1392
+ }
1393
+ let client;
1394
+ try {
1395
+ client = this.#ctx.detachedRelayClientBuilder?.(agentName, relayPeerId, relayAddrs, {
1396
+ receiptStore: this.#ctx.relayReceiptStore ?? undefined,
1397
+ sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
1398
+ onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
1399
+ });
1400
+ if (!client)
1401
+ return;
1402
+ await client.releaseReservation(node);
1403
+ }
1404
+ catch (err) {
1405
+ this.#ctx.logger.warn("session.relay.reservation_release.failed", {
1406
+ agentName, relayPeerId, correlationId,
1407
+ error: extractErrorMessage(err),
1408
+ impact: "the relay holds this slot until its TTL expires.",
1409
+ });
1410
+ }
1411
+ finally {
1412
+ client?.close();
1413
+ }
1414
+ }
1415
+ async proveToRelay(agentName, circuitAddr, node, correlationId,
1416
+ /**
1417
+ * Whether this proof is the STANDING RECEIVER's, and may therefore write the surface
1418
+ * `cello_status` reads as "your standing receiver was refused".
1419
+ *
1420
+ * A revival proves itself too, and its refusal is real — but it is not evidence about the
1421
+ * receiver. A receiver that proved thirty seconds ago and holds a slot, plus a revival refused
1422
+ * with `slot_cap_exceeded`, would otherwise have `cello_status` report a front door as refused
1423
+ * while it is open. The refusal is still logged and still steers the candidate loop; what it
1424
+ * does not do is claim to be about something it did not measure.
1425
+ */
1426
+ surfaceAsReceiverRefusal) {
1427
+ const relayPeerId = relayPeerIdOf(circuitAddr);
1428
+ const baseRelayAddr = circuitAddr.split("/p2p-circuit")[0];
1429
+ if (!relayPeerId || !baseRelayAddr) {
1430
+ this.#ctx.logger.warn("session.standing_receiver.prove.address_unreadable", {
1431
+ agentName,
1432
+ circuitAddr,
1433
+ correlationId,
1434
+ impact: "this candidate's circuit address does not name a relay peer, so there is nothing " +
1435
+ "to prove to and its reservation will stay refused. Skipped silently before — which made " +
1436
+ "a malformed address look identical to a relay that simply said no.",
1437
+ });
1438
+ return "unavailable";
1439
+ }
1440
+ /**
1441
+ * Declared out here so the `finally` can close it. Scoped inside the `try` before, so a throw
1442
+ * from `proveReservation` skipped the close and left the stream and its pending settles behind.
1443
+ */
1444
+ let client;
1445
+ try {
1446
+ client = this.#ctx.detachedRelayClientBuilder?.(agentName, relayPeerId, [baseRelayAddr], {
1447
+ receiptStore: this.#ctx.relayReceiptStore ?? undefined,
1448
+ sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
1449
+ onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
1450
+ });
1451
+ if (!client) {
1452
+ this.#ctx.logger.warn("session.standing_receiver.prove.no_builder", {
1453
+ agentName,
1454
+ relayPeerId,
1455
+ correlationId,
1456
+ impact: "no relay client could be built, so this receiver cannot prove itself and the " +
1457
+ "relay will refuse its reservation again. The agent is reachable only over a direct " +
1458
+ "connection until this is wired.",
1459
+ });
1460
+ return "unavailable";
1461
+ }
1462
+ const proven = await client.proveReservation(node);
1463
+ if (proven) {
1464
+ if (surfaceAsReceiverRefusal)
1465
+ this.#ctx.srRelayRefusal.delete(agentName);
1466
+ this.#ctx.logger.info("session.standing_receiver.prove.result", {
1467
+ agentName, relayPeerId, peerId: node.getPeerId(), proven: true, correlationId,
1468
+ });
1469
+ return "proven";
1470
+ }
1471
+ // ⚠️ A NULL REFUSAL IS A TRANSPORT FAILURE, NOT A VERDICT (review HIGH-1): set only on a
1472
+ // refusal ON THE MERITS, so the `else` clears a stale one `cello_status` would misreport, and
1473
+ // the RETURN below says `unavailable` — the caller decides whether to ask on it.
1474
+ const refusal = client.getLastAuthRefusal();
1475
+ if (surfaceAsReceiverRefusal) {
1476
+ if (refusal) {
1477
+ this.#ctx.srRelayRefusal.set(agentName, { ...this.#ctx.withDirectoryCause(agentName, refusal), relayPeerId });
1478
+ }
1479
+ else {
1480
+ this.#ctx.srRelayRefusal.delete(agentName);
1481
+ }
1482
+ }
1483
+ this.#ctx.logger.warn("session.standing_receiver.prove.result", {
1484
+ agentName,
1485
+ relayPeerId,
1486
+ peerId: node.getPeerId(),
1487
+ proven: false,
1488
+ refusalReason: refusal?.reason ?? "no_relay_verdict",
1489
+ tryAnotherRelay: refusal?.tryAnotherRelay ?? true,
1490
+ correlationId,
1491
+ impact: refusal?.advice ??
1492
+ "the relay said nothing about why, which is what a transport failure mid-handshake looks " +
1493
+ "like. No verdict was reached, so the reservation is still asked for.",
1494
+ });
1495
+ if (refusal && !refusal.tryAnotherRelay)
1496
+ return "refused_this_agent";
1497
+ if (refusal?.tryAnotherRelay && !this.#ctx.shuttingDown) {
1498
+ this.#quarantineRelay(agentName, relayPeerId, refusal.reason);
1499
+ }
1500
+ if (!refusal)
1501
+ return "unavailable"; // no verdict — see the refusal note above.
1502
+ return "refused_try_another_relay";
1503
+ }
1504
+ catch (err) {
1505
+ this.#ctx.logger.warn("session.standing_receiver.prove.failed", {
1506
+ agentName,
1507
+ correlationId,
1508
+ error: extractErrorMessage(err),
1509
+ impact: "this receiver could not prove itself, so its retry will be refused and the " +
1510
+ "candidate loop will try the next relay.",
1511
+ });
1512
+ return "unavailable";
1513
+ }
1514
+ finally {
1515
+ client?.close();
1516
+ }
1517
+ }
1518
+ /**
1519
+ * DOD-M12B-REVIVE-RELAY-1 — reconnect the session's relay WITNESS, which revival never did.
1520
+ *
1521
+ * THE FIRST-PRINCIPLES DEFECT, and the one that explains every symptom chased separately before
1522
+ * it. Establishment does five things: build the node, register the content handler, wire liveness,
1523
+ * **connect the relay**, and dial the counterparty. Revival did the first three. A revived session
1524
+ * was therefore not a session — it looked live, reported `active`, and had no live inbound path at
1525
+ * all.
1526
+ *
1527
+ * MEASURED 2026-08-18 with two real agents: a message on a reconnected session took **three
1528
+ * minutes**, against seconds on a fresh one, because only the five-minute mailbox backstop ever
1529
+ * found it. Doorbells stopped firing for the same reason — the relay stream is what rings them.
1530
+ * And `#parkContent` refuses without `entry.relayClient`, so sends could not park either.
1531
+ *
1532
+ * NO ASSIGNMENT IS PRESENTED, and that is by design rather than omission: `RelayConnectParams`
1533
+ * documents the reconnect mode itself — *"absent … on the restart/persisted reconnect path (the
1534
+ * relay already recorded the session at first establishment) — the client then just reconnects
1535
+ * without re-recording."* A revival is exactly that path.
1536
+ *
1537
+ * Best-effort and non-fatal: a session that comes back without its witness is still better than
1538
+ * one that does not come back, and the failure is named rather than silent.
1539
+ */
1540
+ async reconnectRevivedSessionRelay(agentName, sessionId, node, gater, correlationId, ep) {
1541
+ if (!ep) {
1542
+ this.#ctx.logger.warn("session.revive.relay.absent", {
1543
+ agentName,
1544
+ sessionId,
1545
+ impact: "no relay is recorded for this session, so it comes back with no live inbound path — "
1546
+ + "messages arrive only on the periodic mailbox poll, and a failed send cannot park and is "
1547
+ + "reported lost",
1548
+ });
1549
+ return;
1550
+ }
1551
+ try {
1552
+ // The gater admits only the counterparty inbound; the relay is a third peer and must be
1553
+ // permitted OUTBOUND or our own gate refuses the dial (INV-5 keeps inbound counterparty-only).
1554
+ gater.setAllowedOutboundPeer(ep.relayPeerId);
1555
+ const clientKey = `${agentName}::${ep.relayPeerId}`;
1556
+ let client = this.#ctx.relayClients.get(clientKey);
1557
+ if (!client) {
1558
+ if (!this.#ctx.relayReceiptStore && this.#db)
1559
+ this.#ctx.relayReceiptStore = new RelayReceiptStore(this.#db, this.#ctx.logger);
1560
+ if (!this.#ctx.sealLeafStore && this.#db)
1561
+ this.#ctx.sealLeafStore = new SessionSealLeafStore(this.#db, this.#ctx.logger);
1562
+ client = this.#ctx.detachedRelayClientBuilder?.(agentName, ep.relayPeerId, [...ep.relayAddrs], {
1563
+ receiptStore: this.#ctx.relayReceiptStore ?? undefined,
1564
+ sealLeafStore: this.#ctx.sealLeafStore ?? undefined,
1565
+ // DOD-M15-RELAYSLOTS-1: read at each auth, never snapshotted — the token expires hourly.
1566
+ onlineToken: () => this.#ctx.getDirectoryOnlineToken(agentName),
1567
+ });
1568
+ if (!client) {
1569
+ this.#ctx.logger.warn("session.revive.relay.builder_absent", {
1570
+ agentName,
1571
+ sessionId,
1572
+ impact: "no relay client could be built, so this revived session has no live inbound path",
1573
+ });
1574
+ return;
1575
+ }
1576
+ this.#ctx.relayClients.set(clientKey, client);
1577
+ }
1578
+ // 033-ACKEMIT: a revived session re-registers with no assignment in hand, so the genesis comes
1579
+ // from the entry that was just restored above.
1580
+ // 069-ORDERPROOF: a revived session re-registers with no assignment, so the anchor comes from
1581
+ // the session row — the reason it is stored there at all.
1582
+ client.registerSession(sessionId, node, this.#ctx.contentIn.relayLeafHandler(agentName, sessionId, correlationId), undefined, this.#ctx.leafRecords.sessionGenesisPrevRoot(agentName, sessionId), this.#ctx.leafRecords.sessionRelayAnchor(agentName, sessionId));
1583
+ const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
1584
+ if (entry) {
1585
+ entry.relayClient = client;
1586
+ entry.relaySessionIdBytes = Uint8Array.from(Buffer.from(sessionId, "hex"));
1587
+ entry.relayClientKey = clientKey;
1588
+ }
1589
+ /**
1590
+ * THE STEP THIS METHOD IS NAMED AFTER, and the first version did not take it (review HIGH-2).
1591
+ *
1592
+ * `registerSession` files a handler in a Map. It opens nothing — no dial, no auth, no reader
1593
+ * loop. `connectSessionRelay` ends with exactly this line and the reconnect ended without it,
1594
+ * so a revived session registered a handler on a client whose stream was `null` and then
1595
+ * logged that its live inbound path was back. It was not: the counterparty's leaves queued at
1596
+ * the relay, no doorbell fired, and delivery fell back to the five-minute mailbox poll — the
1597
+ * three-minutes-versus-seconds symptom the whole unit exists to remove.
1598
+ *
1599
+ * Worse, the client is usually BRAND NEW here: `markInterruptedWithDetails` closes and drops
1600
+ * the client for the last session on a relay, so a single-session agent always lands in the
1601
+ * build branch above with a fresh, unconnected client.
1602
+ *
1603
+ * `#ensureConnected` is idempotent, so this also repairs the cached-but-dead-stream case.
1604
+ */
1605
+ await client.connect(node);
1606
+ this.#ctx.logger.info("session.revive.relay.connected", {
1607
+ agentName,
1608
+ sessionId,
1609
+ relayPeerId: ep.relayPeerId,
1610
+ impact: "the revived session has its live inbound path back — messages arrive promptly "
1611
+ + "instead of waiting for the periodic mailbox poll",
1612
+ });
1613
+ }
1614
+ catch (err) {
1615
+ this.#ctx.logger.warn("session.revive.relay.failed", {
1616
+ agentName,
1617
+ sessionId,
1618
+ error: extractErrorMessage(err),
1619
+ impact: "the session is back but without its witness — delivery falls back to the periodic poll",
1620
+ });
1621
+ }
1622
+ }
1623
+ /** DOD-M12B-REVIVE-PARK-1 test seam: the relay the live entry will park to. Not otherwise
1624
+ * observable — `#activeNodes` is private and the park's own refusal is silent about which of its
1625
+ * four preconditions was missing. */
1626
+ getSessionRelayForTest(agentName, sessionId) {
1627
+ const entry = this.#ctx.activeNodes.get(this.#ctx.sessionKey(agentName, sessionId));
1628
+ if (!entry)
1629
+ return null;
1630
+ return {
1631
+ ...(entry.relayPeerId !== undefined ? { relayPeerId: entry.relayPeerId } : {}),
1632
+ ...(entry.relayAddrs !== undefined ? { relayAddrs: entry.relayAddrs } : {}),
1633
+ };
1634
+ }
1635
+ }
1636
+ //# sourceMappingURL=session-relay.js.map