@origintrail-official/dkg-agent 10.0.20 → 10.0.21

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 (535) hide show
  1. package/dist/approved-private-replica.d.ts +54 -0
  2. package/dist/approved-private-replica.d.ts.map +1 -0
  3. package/dist/approved-private-replica.js +148 -0
  4. package/dist/approved-private-replica.js.map +1 -0
  5. package/dist/bounded-operation.d.ts +2 -0
  6. package/dist/bounded-operation.d.ts.map +1 -1
  7. package/dist/bounded-operation.js +5 -0
  8. package/dist/bounded-operation.js.map +1 -1
  9. package/dist/chain-finality-confirmations-v1.d.ts +7 -0
  10. package/dist/chain-finality-confirmations-v1.d.ts.map +1 -0
  11. package/dist/chain-finality-confirmations-v1.js +9 -0
  12. package/dist/chain-finality-confirmations-v1.js.map +1 -0
  13. package/dist/chain-reconciler.d.ts +23 -0
  14. package/dist/chain-reconciler.d.ts.map +1 -1
  15. package/dist/chain-reconciler.js +35 -0
  16. package/dist/chain-reconciler.js.map +1 -1
  17. package/dist/context-graph-member-proof.d.ts +13 -0
  18. package/dist/context-graph-member-proof.d.ts.map +1 -1
  19. package/dist/context-graph-member-proof.js +36 -0
  20. package/dist/context-graph-member-proof.js.map +1 -1
  21. package/dist/context-graph-membership-persist-scheduler.d.ts +7 -23
  22. package/dist/context-graph-membership-persist-scheduler.d.ts.map +1 -1
  23. package/dist/context-graph-membership-persist-scheduler.js +12 -88
  24. package/dist/context-graph-membership-persist-scheduler.js.map +1 -1
  25. package/dist/context-graph-meta-projection.d.ts +13 -2
  26. package/dist/context-graph-meta-projection.d.ts.map +1 -1
  27. package/dist/context-graph-meta-projection.js +61 -24
  28. package/dist/context-graph-meta-projection.js.map +1 -1
  29. package/dist/context-graph-private-meta-proof.d.ts +5 -0
  30. package/dist/context-graph-private-meta-proof.d.ts.map +1 -1
  31. package/dist/context-graph-private-meta-proof.js +23 -0
  32. package/dist/context-graph-private-meta-proof.js.map +1 -1
  33. package/dist/context-graph-read-authority.d.ts +13 -0
  34. package/dist/context-graph-read-authority.d.ts.map +1 -1
  35. package/dist/context-graph-read-authority.js +23 -3
  36. package/dist/context-graph-read-authority.js.map +1 -1
  37. package/dist/context-graph-storage-discovery.d.ts +11 -2
  38. package/dist/context-graph-storage-discovery.d.ts.map +1 -1
  39. package/dist/context-graph-storage-discovery.js +11 -2
  40. package/dist/context-graph-storage-discovery.js.map +1 -1
  41. package/dist/context-graph-subscription-persist-scheduler.d.ts +52 -0
  42. package/dist/context-graph-subscription-persist-scheduler.d.ts.map +1 -0
  43. package/dist/context-graph-subscription-persist-scheduler.js +78 -0
  44. package/dist/context-graph-subscription-persist-scheduler.js.map +1 -0
  45. package/dist/curator-dial-address.d.ts +16 -0
  46. package/dist/curator-dial-address.d.ts.map +1 -0
  47. package/dist/curator-dial-address.js +58 -0
  48. package/dist/curator-dial-address.js.map +1 -0
  49. package/dist/curator-meta-refresh.d.ts +2 -0
  50. package/dist/curator-meta-refresh.d.ts.map +1 -1
  51. package/dist/curator-meta-refresh.js +2 -44
  52. package/dist/curator-meta-refresh.js.map +1 -1
  53. package/dist/curator-peer-connection.d.ts +21 -0
  54. package/dist/curator-peer-connection.d.ts.map +1 -0
  55. package/dist/curator-peer-connection.js +41 -0
  56. package/dist/curator-peer-connection.js.map +1 -0
  57. package/dist/dkg-agent-base.d.ts +94 -22
  58. package/dist/dkg-agent-base.d.ts.map +1 -1
  59. package/dist/dkg-agent-base.js +97 -144
  60. package/dist/dkg-agent-base.js.map +1 -1
  61. package/dist/dkg-agent-cg-registry.d.ts +10 -0
  62. package/dist/dkg-agent-cg-registry.d.ts.map +1 -1
  63. package/dist/dkg-agent-cg-registry.js +176 -115
  64. package/dist/dkg-agent-cg-registry.js.map +1 -1
  65. package/dist/dkg-agent-cg-resolve.d.ts +6 -4
  66. package/dist/dkg-agent-cg-resolve.d.ts.map +1 -1
  67. package/dist/dkg-agent-cg-resolve.js +22 -16
  68. package/dist/dkg-agent-cg-resolve.js.map +1 -1
  69. package/dist/dkg-agent-constants.d.ts +4 -2
  70. package/dist/dkg-agent-constants.d.ts.map +1 -1
  71. package/dist/dkg-agent-constants.js +4 -2
  72. package/dist/dkg-agent-constants.js.map +1 -1
  73. package/dist/dkg-agent-crypto.d.ts +101 -21
  74. package/dist/dkg-agent-crypto.d.ts.map +1 -1
  75. package/dist/dkg-agent-crypto.js +670 -152
  76. package/dist/dkg-agent-crypto.js.map +1 -1
  77. package/dist/dkg-agent-helpers.d.ts +2 -2
  78. package/dist/dkg-agent-helpers.d.ts.map +1 -1
  79. package/dist/dkg-agent-helpers.js +3 -3
  80. package/dist/dkg-agent-helpers.js.map +1 -1
  81. package/dist/dkg-agent-join.d.ts +12 -1
  82. package/dist/dkg-agent-join.d.ts.map +1 -1
  83. package/dist/dkg-agent-join.js +80 -92
  84. package/dist/dkg-agent-join.js.map +1 -1
  85. package/dist/dkg-agent-lifecycle.d.ts +58 -15
  86. package/dist/dkg-agent-lifecycle.d.ts.map +1 -1
  87. package/dist/dkg-agent-lifecycle.js +295 -114
  88. package/dist/dkg-agent-lifecycle.js.map +1 -1
  89. package/dist/dkg-agent-publish.d.ts +33 -40
  90. package/dist/dkg-agent-publish.d.ts.map +1 -1
  91. package/dist/dkg-agent-publish.js +190 -256
  92. package/dist/dkg-agent-publish.js.map +1 -1
  93. package/dist/dkg-agent-query.d.ts +7 -0
  94. package/dist/dkg-agent-query.d.ts.map +1 -1
  95. package/dist/dkg-agent-query.js +75 -63
  96. package/dist/dkg-agent-query.js.map +1 -1
  97. package/dist/dkg-agent-registered-private-empty-vm.d.ts +70 -0
  98. package/dist/dkg-agent-registered-private-empty-vm.d.ts.map +1 -0
  99. package/dist/dkg-agent-registered-private-empty-vm.js +133 -0
  100. package/dist/dkg-agent-registered-private-empty-vm.js.map +1 -0
  101. package/dist/dkg-agent-rfc64-catalog-auto-publish.d.ts +8 -3
  102. package/dist/dkg-agent-rfc64-catalog-auto-publish.d.ts.map +1 -1
  103. package/dist/dkg-agent-rfc64-catalog-auto-publish.js +45 -51
  104. package/dist/dkg-agent-rfc64-catalog-auto-publish.js.map +1 -1
  105. package/dist/dkg-agent-rfc64-catalog.d.ts +63 -87
  106. package/dist/dkg-agent-rfc64-catalog.d.ts.map +1 -1
  107. package/dist/dkg-agent-rfc64-catalog.js +595 -551
  108. package/dist/dkg-agent-rfc64-catalog.js.map +1 -1
  109. package/dist/dkg-agent-swm-host.d.ts +43 -52
  110. package/dist/dkg-agent-swm-host.d.ts.map +1 -1
  111. package/dist/dkg-agent-swm-host.js +629 -812
  112. package/dist/dkg-agent-swm-host.js.map +1 -1
  113. package/dist/dkg-agent-swm-state.d.ts +15 -0
  114. package/dist/dkg-agent-swm-state.d.ts.map +1 -1
  115. package/dist/dkg-agent-swm-state.js +43 -0
  116. package/dist/dkg-agent-swm-state.js.map +1 -1
  117. package/dist/dkg-agent-swm-substrate.d.ts +2 -0
  118. package/dist/dkg-agent-swm-substrate.d.ts.map +1 -1
  119. package/dist/dkg-agent-swm-substrate.js +110 -53
  120. package/dist/dkg-agent-swm-substrate.js.map +1 -1
  121. package/dist/dkg-agent-types.d.ts +62 -26
  122. package/dist/dkg-agent-types.d.ts.map +1 -1
  123. package/dist/dkg-agent-types.js.map +1 -1
  124. package/dist/dkg-agent-vm-promotion.d.ts +2 -0
  125. package/dist/dkg-agent-vm-promotion.d.ts.map +1 -1
  126. package/dist/dkg-agent-vm-promotion.js +39 -5
  127. package/dist/dkg-agent-vm-promotion.js.map +1 -1
  128. package/dist/dkg-agent.d.ts +8 -1
  129. package/dist/dkg-agent.d.ts.map +1 -1
  130. package/dist/dkg-agent.js +75 -45
  131. package/dist/dkg-agent.js.map +1 -1
  132. package/dist/exact-graph-content-verifier.d.ts.map +1 -1
  133. package/dist/exact-graph-content-verifier.js +3 -2
  134. package/dist/exact-graph-content-verifier.js.map +1 -1
  135. package/dist/finalization-handler.d.ts +19 -207
  136. package/dist/finalization-handler.d.ts.map +1 -1
  137. package/dist/finalization-handler.js +79 -1646
  138. package/dist/finalization-handler.js.map +1 -1
  139. package/dist/finalization-recovery-worker.js +1 -1
  140. package/dist/finalization-recovery-worker.js.map +1 -1
  141. package/dist/finalized-authority-cold-resolution.d.ts +6 -1
  142. package/dist/finalized-authority-cold-resolution.d.ts.map +1 -1
  143. package/dist/finalized-authority-cold-resolution.js +22 -3
  144. package/dist/finalized-authority-cold-resolution.js.map +1 -1
  145. package/dist/gossip-session.d.ts +29 -0
  146. package/dist/gossip-session.d.ts.map +1 -0
  147. package/dist/gossip-session.js +48 -0
  148. package/dist/gossip-session.js.map +1 -0
  149. package/dist/index.d.ts +3 -0
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +4 -0
  152. package/dist/index.js.map +1 -1
  153. package/dist/internal/context-graph-authority/context-graph-agent-gate-authority.d.ts +5 -5
  154. package/dist/internal/context-graph-authority/context-graph-agent-gate-authority.d.ts.map +1 -1
  155. package/dist/internal/context-graph-authority/context-graph-agent-gate-authority.js +59 -49
  156. package/dist/internal/context-graph-authority/context-graph-agent-gate-authority.js.map +1 -1
  157. package/dist/internal/context-graph-authority/finalized-target-projection.d.ts +17 -0
  158. package/dist/internal/context-graph-authority/finalized-target-projection.d.ts.map +1 -0
  159. package/dist/internal/context-graph-authority/finalized-target-projection.js +36 -0
  160. package/dist/internal/context-graph-authority/finalized-target-projection.js.map +1 -0
  161. package/dist/internal/context-graph-authority/swm-transport-authority.d.ts +43 -7
  162. package/dist/internal/context-graph-authority/swm-transport-authority.d.ts.map +1 -1
  163. package/dist/internal/context-graph-authority/swm-transport-authority.js +135 -18
  164. package/dist/internal/context-graph-authority/swm-transport-authority.js.map +1 -1
  165. package/dist/internal/context-graph-binding-abort.d.ts +3 -0
  166. package/dist/internal/context-graph-binding-abort.d.ts.map +1 -0
  167. package/dist/internal/context-graph-binding-abort.js +32 -0
  168. package/dist/internal/context-graph-binding-abort.js.map +1 -0
  169. package/dist/internal/context-graph-cache-invalidating-store.d.ts +3 -0
  170. package/dist/internal/context-graph-cache-invalidating-store.d.ts.map +1 -0
  171. package/dist/internal/context-graph-cache-invalidating-store.js +167 -0
  172. package/dist/internal/context-graph-cache-invalidating-store.js.map +1 -0
  173. package/dist/internal/context-graph-meta-record-copy.d.ts +3 -0
  174. package/dist/internal/context-graph-meta-record-copy.d.ts.map +1 -0
  175. package/dist/internal/context-graph-meta-record-copy.js +21 -0
  176. package/dist/internal/context-graph-meta-record-copy.js.map +1 -0
  177. package/dist/internal/gossip/host-mode-handler.d.ts +11 -0
  178. package/dist/internal/gossip/host-mode-handler.d.ts.map +1 -0
  179. package/dist/internal/gossip/host-mode-handler.js +41 -0
  180. package/dist/internal/gossip/host-mode-handler.js.map +1 -0
  181. package/dist/internal/join-encryption-key-bundle.d.ts +10 -0
  182. package/dist/internal/join-encryption-key-bundle.d.ts.map +1 -0
  183. package/dist/internal/join-encryption-key-bundle.js +72 -0
  184. package/dist/internal/join-encryption-key-bundle.js.map +1 -0
  185. package/dist/internal/knowledge-asset-vm-publish-request.d.ts +31 -0
  186. package/dist/internal/knowledge-asset-vm-publish-request.d.ts.map +1 -0
  187. package/dist/internal/knowledge-asset-vm-publish-request.js +91 -0
  188. package/dist/internal/knowledge-asset-vm-publish-request.js.map +1 -0
  189. package/dist/internal/lifecycle-sync-policy.d.ts +5 -0
  190. package/dist/internal/lifecycle-sync-policy.d.ts.map +1 -0
  191. package/dist/internal/lifecycle-sync-policy.js +12 -0
  192. package/dist/internal/lifecycle-sync-policy.js.map +1 -0
  193. package/dist/internal/local-private-member.d.ts +14 -0
  194. package/dist/internal/local-private-member.d.ts.map +1 -0
  195. package/dist/internal/local-private-member.js +21 -0
  196. package/dist/internal/local-private-member.js.map +1 -0
  197. package/dist/internal/promote/assertion-promote-precommit.d.ts +41 -1
  198. package/dist/internal/promote/assertion-promote-precommit.d.ts.map +1 -1
  199. package/dist/internal/promote/assertion-promote-precommit.js +74 -5
  200. package/dist/internal/promote/assertion-promote-precommit.js.map +1 -1
  201. package/dist/internal/storage-ack-owned-request.d.ts +16 -0
  202. package/dist/internal/storage-ack-owned-request.d.ts.map +1 -0
  203. package/dist/internal/storage-ack-owned-request.js +16 -0
  204. package/dist/internal/storage-ack-owned-request.js.map +1 -0
  205. package/dist/internal/vm-reconcile-local-admission-wait.d.ts +96 -0
  206. package/dist/internal/vm-reconcile-local-admission-wait.d.ts.map +1 -0
  207. package/dist/internal/vm-reconcile-local-admission-wait.js +226 -0
  208. package/dist/internal/vm-reconcile-local-admission-wait.js.map +1 -0
  209. package/dist/internal/vm-reconcile-read-authority.d.ts +27 -0
  210. package/dist/internal/vm-reconcile-read-authority.d.ts.map +1 -0
  211. package/dist/internal/vm-reconcile-read-authority.js +33 -0
  212. package/dist/internal/vm-reconcile-read-authority.js.map +1 -0
  213. package/dist/internal/vm-reconcile-sweep-admission.d.ts +2 -0
  214. package/dist/internal/vm-reconcile-sweep-admission.d.ts.map +1 -1
  215. package/dist/internal/vm-reconcile-sweep.d.ts +16 -4
  216. package/dist/internal/vm-reconcile-sweep.d.ts.map +1 -1
  217. package/dist/internal/vm-reconcile-sweep.js +91 -50
  218. package/dist/internal/vm-reconcile-sweep.js.map +1 -1
  219. package/dist/internal/workspace-projected-delegatees.d.ts +3 -0
  220. package/dist/internal/workspace-projected-delegatees.d.ts.map +1 -0
  221. package/dist/internal/workspace-projected-delegatees.js +34 -0
  222. package/dist/internal/workspace-projected-delegatees.js.map +1 -0
  223. package/dist/join-approval-restart-projection.d.ts +14 -0
  224. package/dist/join-approval-restart-projection.d.ts.map +1 -0
  225. package/dist/join-approval-restart-projection.js +32 -0
  226. package/dist/join-approval-restart-projection.js.map +1 -0
  227. package/dist/keyed-persist-scheduler.d.ts +72 -0
  228. package/dist/keyed-persist-scheduler.d.ts.map +1 -0
  229. package/dist/keyed-persist-scheduler.js +140 -0
  230. package/dist/keyed-persist-scheduler.js.map +1 -0
  231. package/dist/map-with-concurrency.d.ts +7 -0
  232. package/dist/map-with-concurrency.d.ts.map +1 -1
  233. package/dist/map-with-concurrency.js +36 -0
  234. package/dist/map-with-concurrency.js.map +1 -1
  235. package/dist/p2p/network-admission-coordinator.d.ts +15 -0
  236. package/dist/p2p/network-admission-coordinator.d.ts.map +1 -1
  237. package/dist/p2p/network-admission-coordinator.js +20 -2
  238. package/dist/p2p/network-admission-coordinator.js.map +1 -1
  239. package/dist/prepare-unscoped-context-graph-read-checks.d.ts.map +1 -1
  240. package/dist/prepare-unscoped-context-graph-read-checks.js +12 -0
  241. package/dist/prepare-unscoped-context-graph-read-checks.js.map +1 -1
  242. package/dist/registered-context-graph-authority.d.ts +7 -0
  243. package/dist/registered-context-graph-authority.d.ts.map +1 -1
  244. package/dist/registered-private-empty-vm-attempt-v1.d.ts +34 -0
  245. package/dist/registered-private-empty-vm-attempt-v1.d.ts.map +1 -0
  246. package/dist/registered-private-empty-vm-attempt-v1.js +120 -0
  247. package/dist/registered-private-empty-vm-attempt-v1.js.map +1 -0
  248. package/dist/reserved-ka-id-allocation.d.ts +12 -0
  249. package/dist/reserved-ka-id-allocation.d.ts.map +1 -0
  250. package/dist/reserved-ka-id-allocation.js +25 -0
  251. package/dist/reserved-ka-id-allocation.js.map +1 -0
  252. package/dist/rfc64/authority-rpc-circuit-breaker-v1.d.ts +5 -3
  253. package/dist/rfc64/authority-rpc-circuit-breaker-v1.d.ts.map +1 -1
  254. package/dist/rfc64/authority-rpc-circuit-breaker-v1.js +9 -0
  255. package/dist/rfc64/authority-rpc-circuit-breaker-v1.js.map +1 -1
  256. package/dist/rfc64/catalog-completion-evidence-v1.d.ts +38 -0
  257. package/dist/rfc64/catalog-completion-evidence-v1.d.ts.map +1 -0
  258. package/dist/rfc64/catalog-completion-evidence-v1.js +49 -0
  259. package/dist/rfc64/catalog-completion-evidence-v1.js.map +1 -0
  260. package/dist/rfc64/catalog-operational-applied-heads-v1.d.ts +6 -3
  261. package/dist/rfc64/catalog-operational-applied-heads-v1.d.ts.map +1 -1
  262. package/dist/rfc64/catalog-operational-applied-heads-v1.js +6 -3
  263. package/dist/rfc64/catalog-operational-applied-heads-v1.js.map +1 -1
  264. package/dist/rfc64/catalog-operational-reads-v1.d.ts +13 -0
  265. package/dist/rfc64/catalog-operational-reads-v1.d.ts.map +1 -0
  266. package/dist/rfc64/catalog-operational-reads-v1.js +56 -0
  267. package/dist/rfc64/catalog-operational-reads-v1.js.map +1 -0
  268. package/dist/rfc64/catalog-operational-targets-v1.d.ts +55 -0
  269. package/dist/rfc64/catalog-operational-targets-v1.d.ts.map +1 -0
  270. package/dist/rfc64/catalog-operational-targets-v1.js +379 -0
  271. package/dist/rfc64/catalog-operational-targets-v1.js.map +1 -0
  272. package/dist/rfc64/catalog-replay-recovery-runtime-v1.d.ts +2 -0
  273. package/dist/rfc64/catalog-replay-recovery-runtime-v1.d.ts.map +1 -1
  274. package/dist/rfc64/catalog-replay-recovery-runtime-v1.js +18 -25
  275. package/dist/rfc64/catalog-replay-recovery-runtime-v1.js.map +1 -1
  276. package/dist/rfc64/legacy-swm-boundary-codec-v1.d.ts +25 -0
  277. package/dist/rfc64/legacy-swm-boundary-codec-v1.d.ts.map +1 -0
  278. package/dist/rfc64/legacy-swm-boundary-codec-v1.js +127 -0
  279. package/dist/rfc64/legacy-swm-boundary-codec-v1.js.map +1 -0
  280. package/dist/rfc64/legacy-swm-boundary-v1.d.ts +12 -0
  281. package/dist/rfc64/legacy-swm-boundary-v1.d.ts.map +1 -1
  282. package/dist/rfc64/legacy-swm-boundary-v1.js +83 -144
  283. package/dist/rfc64/legacy-swm-boundary-v1.js.map +1 -1
  284. package/dist/rfc64/private-catalog-subscription-readiness-v1.d.ts +33 -0
  285. package/dist/rfc64/private-catalog-subscription-readiness-v1.d.ts.map +1 -0
  286. package/dist/rfc64/private-catalog-subscription-readiness-v1.js +97 -0
  287. package/dist/rfc64/private-catalog-subscription-readiness-v1.js.map +1 -0
  288. package/dist/rfc64/private-read-roster-v1.d.ts +14 -0
  289. package/dist/rfc64/private-read-roster-v1.d.ts.map +1 -0
  290. package/dist/rfc64/private-read-roster-v1.js +57 -0
  291. package/dist/rfc64/private-read-roster-v1.js.map +1 -0
  292. package/dist/rfc64/registered-private-empty-vm-proof-v1.d.ts +20 -0
  293. package/dist/rfc64/registered-private-empty-vm-proof-v1.d.ts.map +1 -0
  294. package/dist/rfc64/registered-private-empty-vm-proof-v1.js +26 -0
  295. package/dist/rfc64/registered-private-empty-vm-proof-v1.js.map +1 -0
  296. package/dist/rfc64/swm-inventory-shadow-runtime-v1.d.ts +3 -1
  297. package/dist/rfc64/swm-inventory-shadow-runtime-v1.d.ts.map +1 -1
  298. package/dist/rfc64/swm-inventory-shadow-runtime-v1.js +34 -0
  299. package/dist/rfc64/swm-inventory-shadow-runtime-v1.js.map +1 -1
  300. package/dist/sync/auth/pipe-request-tail.d.ts +5 -1
  301. package/dist/sync/auth/pipe-request-tail.d.ts.map +1 -1
  302. package/dist/sync/auth/pipe-request-tail.js +13 -3
  303. package/dist/sync/auth/pipe-request-tail.js.map +1 -1
  304. package/dist/sync/auth/request-build.d.ts +3 -0
  305. package/dist/sync/auth/request-build.d.ts.map +1 -1
  306. package/dist/sync/auth/request-build.js +11 -2
  307. package/dist/sync/auth/request-build.js.map +1 -1
  308. package/dist/sync/backpressure.d.ts +27 -16
  309. package/dist/sync/backpressure.d.ts.map +1 -1
  310. package/dist/sync/backpressure.js +66 -40
  311. package/dist/sync/backpressure.js.map +1 -1
  312. package/dist/sync/catchup-policy.d.ts +2 -0
  313. package/dist/sync/catchup-policy.d.ts.map +1 -1
  314. package/dist/sync/catchup-policy.js +2 -0
  315. package/dist/sync/catchup-policy.js.map +1 -1
  316. package/dist/sync/exact-assets.d.ts +7 -5
  317. package/dist/sync/exact-assets.d.ts.map +1 -1
  318. package/dist/sync/exact-assets.js +13 -8
  319. package/dist/sync/exact-assets.js.map +1 -1
  320. package/dist/sync/exact-batch-observation.d.ts +3 -0
  321. package/dist/sync/exact-batch-observation.d.ts.map +1 -0
  322. package/dist/sync/exact-batch-observation.js +13 -0
  323. package/dist/sync/exact-batch-observation.js.map +1 -0
  324. package/dist/sync/exact-batch-stream-capability.d.ts +24 -0
  325. package/dist/sync/exact-batch-stream-capability.d.ts.map +1 -0
  326. package/dist/sync/exact-batch-stream-capability.js +88 -0
  327. package/dist/sync/exact-batch-stream-capability.js.map +1 -0
  328. package/dist/sync/exact-batch-stream-contract.d.ts +78 -0
  329. package/dist/sync/exact-batch-stream-contract.d.ts.map +1 -0
  330. package/dist/sync/exact-batch-stream-contract.js +206 -0
  331. package/dist/sync/exact-batch-stream-contract.js.map +1 -0
  332. package/dist/sync/peer-connection.d.ts.map +1 -1
  333. package/dist/sync/peer-connection.js +11 -2
  334. package/dist/sync/peer-connection.js.map +1 -1
  335. package/dist/sync/priority-admission-queue.d.ts +17 -0
  336. package/dist/sync/priority-admission-queue.d.ts.map +1 -1
  337. package/dist/sync/priority-admission-queue.js +58 -37
  338. package/dist/sync/priority-admission-queue.js.map +1 -1
  339. package/dist/sync/requester/admission-boundary.d.ts +12 -0
  340. package/dist/sync/requester/admission-boundary.d.ts.map +1 -0
  341. package/dist/sync/requester/admission-boundary.js +22 -0
  342. package/dist/sync/requester/admission-boundary.js.map +1 -0
  343. package/dist/sync/requester/durable-sync.d.ts +2 -1
  344. package/dist/sync/requester/durable-sync.d.ts.map +1 -1
  345. package/dist/sync/requester/durable-sync.js +3 -188
  346. package/dist/sync/requester/durable-sync.js.map +1 -1
  347. package/dist/sync/requester/exact-batch-stream-driver.d.ts +83 -0
  348. package/dist/sync/requester/exact-batch-stream-driver.d.ts.map +1 -0
  349. package/dist/sync/requester/exact-batch-stream-driver.js +299 -0
  350. package/dist/sync/requester/exact-batch-stream-driver.js.map +1 -0
  351. package/dist/sync/requester/exact-batch-stream.d.ts +72 -0
  352. package/dist/sync/requester/exact-batch-stream.d.ts.map +1 -0
  353. package/dist/sync/requester/exact-batch-stream.js +267 -0
  354. package/dist/sync/requester/exact-batch-stream.js.map +1 -0
  355. package/dist/sync/requester/exact-recovery-transport.d.ts +19 -0
  356. package/dist/sync/requester/exact-recovery-transport.d.ts.map +1 -0
  357. package/dist/sync/requester/exact-recovery-transport.js +3 -0
  358. package/dist/sync/requester/exact-recovery-transport.js.map +1 -0
  359. package/dist/sync/requester/finalized-swm-twin-reconciliation.js +12 -3
  360. package/dist/sync/requester/finalized-swm-twin-reconciliation.js.map +1 -1
  361. package/dist/sync/requester/graph-scoped-materialization.d.ts.map +1 -1
  362. package/dist/sync/requester/graph-scoped-materialization.js +58 -24
  363. package/dist/sync/requester/graph-scoped-materialization.js.map +1 -1
  364. package/dist/sync/requester/page-fetch.d.ts +7 -0
  365. package/dist/sync/requester/page-fetch.d.ts.map +1 -1
  366. package/dist/sync/requester/page-fetch.js +103 -49
  367. package/dist/sync/requester/page-fetch.js.map +1 -1
  368. package/dist/sync/requester/shared-memory-sync.d.ts +10 -5
  369. package/dist/sync/requester/shared-memory-sync.d.ts.map +1 -1
  370. package/dist/sync/requester/shared-memory-sync.js +149 -150
  371. package/dist/sync/requester/shared-memory-sync.js.map +1 -1
  372. package/dist/sync/requester/swm-materialization-memo.d.ts +12 -0
  373. package/dist/sync/requester/swm-materialization-memo.d.ts.map +1 -0
  374. package/dist/sync/requester/swm-materialization-memo.js +54 -0
  375. package/dist/sync/requester/swm-materialization-memo.js.map +1 -0
  376. package/dist/sync/requester/swm-recovery.d.ts +3 -29
  377. package/dist/sync/requester/swm-recovery.d.ts.map +1 -1
  378. package/dist/sync/requester/swm-recovery.js +11 -10
  379. package/dist/sync/requester/swm-recovery.js.map +1 -1
  380. package/dist/sync/requester/swm-snapshot-materializer.d.ts.map +1 -1
  381. package/dist/sync/requester/swm-snapshot-materializer.js +18 -0
  382. package/dist/sync/requester/swm-snapshot-materializer.js.map +1 -1
  383. package/dist/sync/requester/swm-target-executor.d.ts +6 -0
  384. package/dist/sync/requester/swm-target-executor.d.ts.map +1 -1
  385. package/dist/sync/requester/swm-target-executor.js +24 -3
  386. package/dist/sync/requester/swm-target-executor.js.map +1 -1
  387. package/dist/sync/requester/verified-asset-preparation.d.ts +9 -0
  388. package/dist/sync/requester/verified-asset-preparation.d.ts.map +1 -0
  389. package/dist/sync/requester/verified-asset-preparation.js +190 -0
  390. package/dist/sync/requester/verified-asset-preparation.js.map +1 -0
  391. package/dist/sync/responder/asset-metadata.d.ts +18 -0
  392. package/dist/sync/responder/asset-metadata.d.ts.map +1 -0
  393. package/dist/sync/responder/asset-metadata.js +27 -0
  394. package/dist/sync/responder/asset-metadata.js.map +1 -0
  395. package/dist/sync/responder/durable-data-request-policy.d.ts +2 -16
  396. package/dist/sync/responder/durable-data-request-policy.d.ts.map +1 -1
  397. package/dist/sync/responder/durable-data-request-policy.js +1 -31
  398. package/dist/sync/responder/durable-data-request-policy.js.map +1 -1
  399. package/dist/sync/responder/exact-asset-export-cache.d.ts +89 -0
  400. package/dist/sync/responder/exact-asset-export-cache.d.ts.map +1 -0
  401. package/dist/sync/responder/exact-asset-export-cache.js +550 -0
  402. package/dist/sync/responder/exact-asset-export-cache.js.map +1 -0
  403. package/dist/sync/responder/exact-batch-stream.d.ts +47 -0
  404. package/dist/sync/responder/exact-batch-stream.d.ts.map +1 -0
  405. package/dist/sync/responder/exact-batch-stream.js +151 -0
  406. package/dist/sync/responder/exact-batch-stream.js.map +1 -0
  407. package/dist/sync/responder/exact-data-session.d.ts +67 -0
  408. package/dist/sync/responder/exact-data-session.d.ts.map +1 -0
  409. package/dist/sync/responder/exact-data-session.js +130 -0
  410. package/dist/sync/responder/exact-data-session.js.map +1 -0
  411. package/dist/sync/responder/exact-graph-cursor-filter.d.ts +9 -0
  412. package/dist/sync/responder/exact-graph-cursor-filter.d.ts.map +1 -0
  413. package/dist/sync/responder/exact-graph-cursor-filter.js +101 -0
  414. package/dist/sync/responder/exact-graph-cursor-filter.js.map +1 -0
  415. package/dist/sync/responder/exact-graph-reader.d.ts +95 -0
  416. package/dist/sync/responder/exact-graph-reader.d.ts.map +1 -0
  417. package/dist/sync/responder/exact-graph-reader.js +623 -0
  418. package/dist/sync/responder/exact-graph-reader.js.map +1 -0
  419. package/dist/sync/responder/exact-page-reader.d.ts +37 -0
  420. package/dist/sync/responder/exact-page-reader.d.ts.map +1 -0
  421. package/dist/sync/responder/exact-page-reader.js +130 -0
  422. package/dist/sync/responder/exact-page-reader.js.map +1 -0
  423. package/dist/sync/responder/graph-plan.d.ts +19 -85
  424. package/dist/sync/responder/graph-plan.d.ts.map +1 -1
  425. package/dist/sync/responder/graph-plan.js +51 -988
  426. package/dist/sync/responder/graph-plan.js.map +1 -1
  427. package/dist/sync/responder/page-framing-policy.d.ts +31 -0
  428. package/dist/sync/responder/page-framing-policy.d.ts.map +1 -0
  429. package/dist/sync/responder/page-framing-policy.js +41 -0
  430. package/dist/sync/responder/page-framing-policy.js.map +1 -0
  431. package/dist/sync/responder/responder-row-page.d.ts +85 -0
  432. package/dist/sync/responder/responder-row-page.d.ts.map +1 -0
  433. package/dist/sync/responder/responder-row-page.js +181 -0
  434. package/dist/sync/responder/responder-row-page.js.map +1 -0
  435. package/dist/sync/responder/row-serialization.d.ts +34 -0
  436. package/dist/sync/responder/row-serialization.d.ts.map +1 -0
  437. package/dist/sync/responder/row-serialization.js +69 -0
  438. package/dist/sync/responder/row-serialization.js.map +1 -0
  439. package/dist/sync/responder/session-plan-memo.d.ts +20 -0
  440. package/dist/sync/responder/session-plan-memo.d.ts.map +1 -0
  441. package/dist/sync/responder/session-plan-memo.js +135 -0
  442. package/dist/sync/responder/session-plan-memo.js.map +1 -0
  443. package/dist/sync/responder/sync-handler.d.ts +18 -1
  444. package/dist/sync/responder/sync-handler.d.ts.map +1 -1
  445. package/dist/sync/responder/sync-handler.js +101 -57
  446. package/dist/sync/responder/sync-handler.js.map +1 -1
  447. package/dist/sync/shared-memory-completion.d.ts +30 -0
  448. package/dist/sync/shared-memory-completion.d.ts.map +1 -1
  449. package/dist/sync/shared-memory-completion.js +13 -0
  450. package/dist/sync/shared-memory-completion.js.map +1 -1
  451. package/dist/sync/wire-compression.d.ts +47 -0
  452. package/dist/sync/wire-compression.d.ts.map +1 -0
  453. package/dist/sync/wire-compression.js +111 -0
  454. package/dist/sync/wire-compression.js.map +1 -0
  455. package/dist/unscoped-query-consistency.d.ts +12 -0
  456. package/dist/unscoped-query-consistency.d.ts.map +1 -1
  457. package/dist/unscoped-query-consistency.js +17 -1
  458. package/dist/unscoped-query-consistency.js.map +1 -1
  459. package/dist/vm-promotion-audit.d.ts +2 -2
  460. package/dist/vm-promotion-audit.d.ts.map +1 -1
  461. package/dist/vm-promotion-audit.js +26 -9
  462. package/dist/vm-promotion-audit.js.map +1 -1
  463. package/dist/vm-reconcile-dispatcher.d.ts +42 -1
  464. package/dist/vm-reconcile-dispatcher.d.ts.map +1 -1
  465. package/dist/vm-reconcile-dispatcher.js +103 -4
  466. package/dist/vm-reconcile-dispatcher.js.map +1 -1
  467. package/dist/vm-reconcile-peer-topology.d.ts +7 -9
  468. package/dist/vm-reconcile-peer-topology.d.ts.map +1 -1
  469. package/dist/vm-reconcile-peer-topology.js +1 -37
  470. package/dist/vm-reconcile-peer-topology.js.map +1 -1
  471. package/dist/vm-recovery-candidate-order.d.ts +16 -0
  472. package/dist/vm-recovery-candidate-order.d.ts.map +1 -0
  473. package/dist/vm-recovery-candidate-order.js +24 -0
  474. package/dist/vm-recovery-candidate-order.js.map +1 -0
  475. package/dist/vm-recovery-core-transport-preference.d.ts +69 -0
  476. package/dist/vm-recovery-core-transport-preference.d.ts.map +1 -0
  477. package/dist/vm-recovery-core-transport-preference.js +131 -0
  478. package/dist/vm-recovery-core-transport-preference.js.map +1 -0
  479. package/dist/vm-recovery-experiment-policy.d.ts +24 -0
  480. package/dist/vm-recovery-experiment-policy.d.ts.map +1 -0
  481. package/dist/vm-recovery-experiment-policy.js +18 -0
  482. package/dist/vm-recovery-experiment-policy.js.map +1 -0
  483. package/dist/vm-recovery-footprint.d.ts +54 -0
  484. package/dist/vm-recovery-footprint.d.ts.map +1 -1
  485. package/dist/vm-recovery-footprint.js +88 -24
  486. package/dist/vm-recovery-footprint.js.map +1 -1
  487. package/dist/vm-recovery-microbatch.d.ts +2 -0
  488. package/dist/vm-recovery-microbatch.d.ts.map +1 -1
  489. package/dist/vm-recovery-microbatch.js +7 -3
  490. package/dist/vm-recovery-microbatch.js.map +1 -1
  491. package/dist/vm-recovery-pass-authority.d.ts +84 -0
  492. package/dist/vm-recovery-pass-authority.d.ts.map +1 -0
  493. package/dist/vm-recovery-pass-authority.js +126 -0
  494. package/dist/vm-recovery-pass-authority.js.map +1 -0
  495. package/dist/vm-recovery-phase-timing.d.ts +75 -0
  496. package/dist/vm-recovery-phase-timing.d.ts.map +1 -0
  497. package/dist/vm-recovery-phase-timing.js +236 -0
  498. package/dist/vm-recovery-phase-timing.js.map +1 -0
  499. package/dist/vm-recovery-preparation.d.ts +143 -0
  500. package/dist/vm-recovery-preparation.d.ts.map +1 -0
  501. package/dist/vm-recovery-preparation.js +433 -0
  502. package/dist/vm-recovery-preparation.js.map +1 -0
  503. package/dist/vm-recovery-provider-policy.d.ts +13 -1
  504. package/dist/vm-recovery-provider-policy.d.ts.map +1 -1
  505. package/dist/vm-recovery-provider-policy.js +37 -5
  506. package/dist/vm-recovery-provider-policy.js.map +1 -1
  507. package/dist/vm-recovery-stream-profile.d.ts +15 -0
  508. package/dist/vm-recovery-stream-profile.d.ts.map +1 -0
  509. package/dist/vm-recovery-stream-profile.js +53 -0
  510. package/dist/vm-recovery-stream-profile.js.map +1 -0
  511. package/dist/vm-recovery-stream-setback-policy.d.ts +54 -0
  512. package/dist/vm-recovery-stream-setback-policy.d.ts.map +1 -0
  513. package/dist/vm-recovery-stream-setback-policy.js +85 -0
  514. package/dist/vm-recovery-stream-setback-policy.js.map +1 -0
  515. package/dist/vm-recovery-timing-observer.d.ts +27 -0
  516. package/dist/vm-recovery-timing-observer.d.ts.map +1 -0
  517. package/dist/vm-recovery-timing-observer.js +49 -0
  518. package/dist/vm-recovery-timing-observer.js.map +1 -0
  519. package/dist/vm-recovery-transport-budget-policy.d.ts +32 -0
  520. package/dist/vm-recovery-transport-budget-policy.d.ts.map +1 -0
  521. package/dist/vm-recovery-transport-budget-policy.js +55 -0
  522. package/dist/vm-recovery-transport-budget-policy.js.map +1 -0
  523. package/dist/vm-recovery-transport-plan.d.ts +83 -0
  524. package/dist/vm-recovery-transport-plan.d.ts.map +1 -0
  525. package/dist/vm-recovery-transport-plan.js +117 -0
  526. package/dist/vm-recovery-transport-plan.js.map +1 -0
  527. package/package.json +17 -9
  528. package/dist/finalization-annotations.d.ts +0 -5
  529. package/dist/finalization-annotations.d.ts.map +0 -1
  530. package/dist/finalization-annotations.js +0 -6
  531. package/dist/finalization-annotations.js.map +0 -1
  532. package/dist/swm-ka-bound.d.ts +0 -21
  533. package/dist/swm-ka-bound.d.ts.map +0 -1
  534. package/dist/swm-ka-bound.js +0 -36
  535. package/dist/swm-ka-bound.js.map +0 -1
@@ -7,16 +7,17 @@
7
7
  * 1:1 move; methods take `this: DKGAgent` so cross-calls resolve against the
8
8
  * composed class.
9
9
  */
10
+ import { collectProjectedDelegatees } from './internal/workspace-projected-delegatees.js';
10
11
  import { createHash, randomUUID } from 'node:crypto';
11
- import { PROTOCOL_SWM_SENDER_KEY, encodeGossipEnvelope, computeGossipSigningPayload, GOSSIP_ENVELOPE_VERSION, GOSSIP_TYPE_WORKSPACE_PUBLISH, createOperationContext, logKaLifecycleEvent, WORKSPACE_AGENT_ENCRYPTION_KEY_ALGORITHM_X25519, WORKSPACE_RECIPIENT_ENCRYPTION_KEY_PURPOSE, decodeWorkspaceEncryptionKey, SWM_SENDER_KEY_PACKAGE_ACK_TYPE, SWM_SENDER_KEY_PACKAGE_ACK_RETRYABLE_REASON_CODES, SWM_SENDER_KEY_PACKAGE_VERSION, computeSwmSenderKeyMembershipHash, computeSwmSenderKeyPackageAAD, decodeWorkspacePublishRequest, decodeSwmSenderKeyPackage, decodeSwmSenderKeyPackageAck, decryptSwmSenderKeyMessage, decryptSwmSenderKeyPackage, encodeSwmSenderKeyMessage, encodeSwmSenderKeyPackage, encodeSwmSenderKeyPackageAck, encryptSwmSenderKeyMessage, encryptSwmSenderKeyPackage, generateEd25519Keypair, generateSwmSenderChainKey, generateSwmSenderEpochId, ratchetSwmSenderChainKey, uint64ForProto, SWM_SENDER_KEY_SKIPPED_MESSAGE_CACHE_LIMIT, } from '@origintrail-official/dkg-core';
12
+ import { PROTOCOL_SWM_SENDER_KEY, encodeGossipEnvelope, computeGossipSigningPayload, GOSSIP_ENVELOPE_VERSION, GOSSIP_TYPE_WORKSPACE_PUBLISH, createOperationContext, logKaLifecycleEvent, WORKSPACE_AGENT_ENCRYPTION_KEY_ALGORITHM_X25519, WORKSPACE_RECIPIENT_ENCRYPTION_KEY_PURPOSE, decodeWorkspaceEncryptionKey, SWM_SENDER_KEY_PACKAGE_ACK_TYPE, SWM_SENDER_KEY_PACKAGE_ACK_RETRYABLE_REASON_CODES, SWM_SENDER_KEY_PACKAGE_VERSION, computeSwmSenderKeyMembershipHash, computeSwmSenderKeyPackageAAD, decodeWorkspacePublishRequest, decodeSwmSenderKeyPackage, decodeSwmSenderKeyPackageAck, decryptSwmSenderKeyMessage, decryptSwmSenderKeyPackage, encodeSwmSenderKeyMessage, encodeSwmSenderKeyPackage, encodeSwmSenderKeyPackageAck, encryptSwmSenderKeyMessage, encryptSwmSenderKeyPackage, generateEd25519Keypair, generateSwmSenderChainKey, generateSwmSenderEpochId, ratchetSwmSenderChainKey, uint64ForProto, SWM_SENDER_KEY_SKIPPED_MESSAGE_CACHE_LIMIT, tryCanonicalPeerIdString, } from '@origintrail-official/dkg-core';
12
13
  import { createRpcTimeoutError, withRpcRequestContext } from '@origintrail-official/dkg-chain';
13
14
  import { resolveWorkspaceAgentRecipients, resolveWorkspaceAgentRecipientKeys, isWorkspaceAgentEncryptionKeyMissingError, WorkspaceAgentEncryptionKeyMissingError, } from '@origintrail-official/dkg-publisher';
14
15
  import { ethers } from 'ethers';
15
16
  import { resolveActivePublicContextGraphChainProof as resolveStrictActivePublicChainProof, } from './active-public-context-graph-chain-proof.js';
16
17
  import { resolveContextGraphAgentGateAuthorityDecision, } from './internal/context-graph-authority/context-graph-agent-gate-authority.js';
17
18
  import { resolveLiveOnChainAccessPolicyState as resolveLiveAccessPolicyState, } from './internal/context-graph-authority/context-graph-access-policy.js';
18
- import { createContextGraphAuthorityError, isRetryableContextGraphAuthorityUnavailableReason, } from './internal/context-graph-authority/context-graph-authority.js';
19
- import { resolveSwmRegisteredAuthorityDecision, resolveSwmTransportAuthorityDecision, } from './internal/context-graph-authority/swm-transport-authority.js';
19
+ import { createContextGraphAuthorityError, isContextGraphAuthorityUnavailableMarker, isRetryableContextGraphAuthorityUnavailableReason, } from './internal/context-graph-authority/context-graph-authority.js';
20
+ import { resolveSwmMemberRecoveryAuthorityDecision, resolveSwmRegisteredAuthorityDecision, resolveSwmTransportAuthorityDecision, } from './internal/context-graph-authority/swm-transport-authority.js';
20
21
  import { FANOUT_RESPONSE_REJECTED, FANOUT_RESPONSE_RETRYABLE, } from './swm/substrate-fanout.js';
21
22
  import { SyncTargetSupersededError } from './sync/error-tags.js';
22
23
  import { resolveAssetUalFromKaIdentity } from './ka-identity.js';
@@ -24,43 +25,12 @@ import { SWM_RECIPIENT_KEY_FETCH_WAIT_MS, TIMEOUT_SENTINEL, CHAIN_POLICY_READ_TI
24
25
  import { chainAuthorityReadBudgetsOf } from './chain-authority-read-budgets.js';
25
26
  import { isBoundedOperationTimeoutError, runBoundedOperation, } from './bounded-operation.js';
26
27
  import { StaleSenderKeyTargetError, SwmSenderKeySetupRejectionError, } from './dkg-agent-types.js';
27
- import { swmSenderStateKey, swmReceiverStateKey, serializeSwmSenderSendState, serializeSwmSenderReceiveState, serializePendingSenderKeyEntry, deserializeSwmSenderSendState, deserializeSwmSenderReceiveState, deserializePendingSenderKeyEntry, } from './dkg-agent-swm-state.js';
28
+ import { computeSwmSenderKeyRecipientRouteHash, swmSenderStateKey, swmReceiverStateKey, serializeSwmSenderSendState, serializeSwmSenderReceiveState, serializePendingSenderKeyEntry, deserializeSwmSenderSendState, deserializeSwmSenderReceiveState, deserializePendingSenderKeyEntry, } from './dkg-agent-swm-state.js';
28
29
  import { DKGAgentBase } from './dkg-agent-base.js';
29
30
  import { isCanonicalPositiveContextGraphId, localContextGraphIdMatchesCommittedNameHash, } from './context-graph-binding-state.js';
30
31
  import { CONTEXT_GRAPH_AUTHORITY_RPC_SITES as CG_AUTH_RPC_SITES, withRpcUsageSite, } from '@origintrail-official/dkg-chain';
31
32
  const KA_LIFECYCLE_ASSET_UAL_RESOLVE_TIMEOUT_MS = 50;
32
- function delegationIsCurrentlyActive(expiresAtValues, nowMs) {
33
- if (expiresAtValues.length === 0)
34
- return true;
35
- return expiresAtValues.some((value) => {
36
- const expiresAt = Number(value);
37
- return !Number.isFinite(expiresAt) || expiresAt <= 0 || expiresAt >= nowMs;
38
- });
39
- }
40
- function collectProjectedDelegatees(meta, field, normalizeValue) {
41
- const members = new Set([...meta.allowedAgents, ...meta.participantAgents].map((agent) => agent.toLowerCase()));
42
- const revoked = new Set(meta.revokedAgents.map((agent) => agent.toLowerCase()));
43
- const out = new Map();
44
- const nowMs = Date.now();
45
- for (const delegation of meta.delegations) {
46
- if (!delegationIsCurrentlyActive(delegation.expiresAtValues, nowMs))
47
- continue;
48
- for (const rawAgent of delegation.agents) {
49
- const agent = rawAgent.toLowerCase();
50
- if (!agent || !members.has(agent) || revoked.has(agent))
51
- continue;
52
- const values = out.get(agent) ?? [];
53
- for (const rawValue of delegation[field]) {
54
- const value = normalizeValue(rawValue);
55
- if (value && !values.includes(value))
56
- values.push(value);
57
- }
58
- if (values.length > 0)
59
- out.set(agent, values);
60
- }
61
- }
62
- return out;
63
- }
33
+ const SWM_RECIPIENT_AUTHORITY_STABILITY_ATTEMPTS = 3;
64
34
  function mapContextGraphSlotBindingOutcome(outcome, mode) {
65
35
  if (outcome.kind === 'match')
66
36
  return true;
@@ -186,6 +156,49 @@ function withMemberKeyHint(error) {
186
156
  + (one ? 'its profile has' : 'their profiles have')
187
157
  + ' reached this node (profiles are re-published about every 20 minutes).');
188
158
  }
159
+ function hasExactRecipientAgentRoster(resolution, participantAgents) {
160
+ try {
161
+ const resolvedAgents = new Set(resolution.recipients.map(({ agentAddress }) => ethers.getAddress(agentAddress).toLowerCase()));
162
+ const currentAgents = new Set(participantAgents.map((address) => ethers.getAddress(address).toLowerCase()));
163
+ return currentAgents.size === resolvedAgents.size
164
+ && [...currentAgents].every((address) => resolvedAgents.has(address));
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ }
170
+ function hasExactWorkspaceRecipientSet(left, right) {
171
+ const canonical = (recipient) => [
172
+ ethers.getAddress(recipient.agentAddress).toLowerCase(),
173
+ recipient.recipientKeyId,
174
+ recipient.peerId ?? '',
175
+ ].join('\u0000');
176
+ try {
177
+ const leftRecipients = left.recipients.map(canonical).sort();
178
+ const rightRecipients = right.recipients.map(canonical).sort();
179
+ return leftRecipients.length === rightRecipients.length
180
+ && leftRecipients.every((recipient, index) => recipient === rightRecipients[index]);
181
+ }
182
+ catch {
183
+ return false;
184
+ }
185
+ }
186
+ function sameStringSet(left, right) {
187
+ const leftSet = new Set(left);
188
+ const rightSet = new Set(right);
189
+ return leftSet.size === rightSet.size && [...leftSet].every((value) => rightSet.has(value));
190
+ }
191
+ function pendingSenderKeyEntryMatchesDrainAuthority(entry, authority) {
192
+ return entry.contextGraphId === authority.contextGraphId
193
+ && (authority.allSubgraphs === true
194
+ || (entry.subGraphName ?? undefined) === (authority.subGraphName ?? undefined))
195
+ && entry.recipientAgentAddress === authority.recipientAgentAddress
196
+ && entry.recipientKeyId === authority.recipientKeyId
197
+ && entry.recipientPeerId === authority.recipientPeerId
198
+ && (authority.senderAgentAddress === undefined
199
+ || entry.senderAgentAddress === authority.senderAgentAddress)
200
+ && (authority.epochId === undefined || entry.epochId === authority.epochId);
201
+ }
189
202
  export class WorkspaceCryptoMethods extends DKGAgentBase {
190
203
  getWorkspaceGossipSigningAgent() {
191
204
  const defaultAddress = this.defaultAgentAddress?.toLowerCase();
@@ -241,8 +254,8 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
241
254
  async resolveContextGraphAgentGateAuthority(contextGraphId, options = {}) {
242
255
  return withRpcUsageSite(CG_AUTH_RPC_SITES.gate, () => resolveContextGraphAgentGateAuthorityDecision({
243
256
  contextGraphId,
244
- getRegisteredAuthority: () => this.resolveSwmRegisteredAuthority(contextGraphId, { signal: options.signal }),
245
- resolveRfc64PrivateRoster: () => this.resolveRfc64PrivateReadRosterV1(contextGraphId),
257
+ getTransportAuthority: () => this.resolveSwmTransportAuthority(contextGraphId, { signal: options.signal }),
258
+ readMetadataRevision: () => this.contextGraphMetaProjection.readAuthorityFactsRevision,
246
259
  getLegacyMeta: () => this.getCgMeta(contextGraphId, { signal: options.signal }),
247
260
  getSubscriptionAgents: () => (this.subscribedContextGraphs.get(contextGraphId)?.participantAgents ?? []),
248
261
  }));
@@ -257,32 +270,89 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
257
270
  return [];
258
271
  }
259
272
  /**
260
- * R9 (SECURITY) — FRESH, `_meta`-only member-recovery gate.
273
+ * R9 (SECURITY) — authoritative member-recovery gate.
261
274
  *
262
- * Resolves `allowedAgents ∪ participantAgents` minus `revokedAgents` from the
263
- * CG `_meta` projection (store-backed, write-invalidated), with the
264
- * network-influenced `subscribedContextGraphs` subscription cache
265
- * DELIBERATELY OMITTED — that cache is poisonable, and folding it in is
266
- * exactly what `member-recovery-auth.ts` forbids.
275
+ * Registered and active accepted-private graphs use their exact authority
276
+ * roster. Only a legacy unregistered graph (including accepted public, whose
277
+ * local gate controls recovery rather than reads) resolves the effective
278
+ * store-backed metadata projection of
279
+ * `allowedAgents ∪ participantAgents` minus `revokedAgents` from the CG
280
+ * metadata. The network-influenced `subscribedContextGraphs`
281
+ * subscription cache is DELIBERATELY OMITTED — that cache is poisonable, and
282
+ * folding it in is exactly what `member-recovery-auth.ts` forbids.
267
283
  *
268
284
  * Unlike {@link getContextGraphAgentGateAddresses} (which also feeds normal
269
285
  * sync admission and may fold in the subscription cache only for graphs that
270
286
  * are not registered on-chain), this read is used ONLY for `request.recovery`
271
287
  * and is passed straight to
272
288
  * `isMemberRecoveryAuthorized`, which hard-denies on null/empty. Returns
273
- * `null` when the CG has no `_meta` agent gate at all (⇒ hard-deny).
289
+ * `null` when the CG has no projected agent gate at all (⇒ hard-deny).
274
290
  */
275
291
  async getMemberRecoveryGate(contextGraphId, options = {}) {
276
- const registeredAuthority = await withRpcUsageSite(CG_AUTH_RPC_SITES.recoveryGate, () => this.resolveSwmRegisteredAuthority(contextGraphId, { signal: options.signal }));
277
- if (registeredAuthority.kind === 'private')
278
- return registeredAuthority.participantAgents;
279
- if (registeredAuthority.kind !== 'unregistered')
292
+ const recoveryAuthority = await withRpcUsageSite(CG_AUTH_RPC_SITES.recoveryGate, () => resolveSwmMemberRecoveryAuthorityDecision(this, contextGraphId, { signal: options.signal }));
293
+ if (recoveryAuthority.kind === 'private-roster') {
294
+ return [...recoveryAuthority.participantAgents];
295
+ }
296
+ if (recoveryAuthority.kind !== 'legacy-unregistered')
297
+ return null;
298
+ const metadataRevision = this.contextGraphMetaProjection
299
+ .readContextGraphAuthorityFactsRevision(contextGraphId);
300
+ const metadataGate = await this.getLocalMetadataMemberRecoveryGate(contextGraphId, options);
301
+ // Metadata is another async boundary. Re-resolve the authoritative state
302
+ // before returning it so a private policy activation/rotation or a chain
303
+ // registration that commits during the store read takes precedence over
304
+ // the now-stale local projection.
305
+ const currentRecoveryAuthority = await withRpcUsageSite(CG_AUTH_RPC_SITES.recoveryGate, () => resolveSwmMemberRecoveryAuthorityDecision(this, contextGraphId, { signal: options.signal }));
306
+ if (currentRecoveryAuthority.kind === 'private-roster') {
307
+ return [...currentRecoveryAuthority.participantAgents];
308
+ }
309
+ if (currentRecoveryAuthority.kind !== 'legacy-unregistered')
280
310
  return null;
311
+ // A revocation or other authority-fact mutation during the metadata read
312
+ // invalidates the captured roster even if registration remained absent.
313
+ // Recovery is retryable, so fail closed instead of serving that snapshot.
314
+ return this.contextGraphMetaProjection
315
+ .readContextGraphAuthorityFactsRevision(contextGraphId) === metadataRevision
316
+ ? metadataGate
317
+ : null;
318
+ }
319
+ /**
320
+ * Authority source used while constructing or mutating a private roster.
321
+ * Registered private graphs retain their finalized chain participant set;
322
+ * unregistered graphs use the effective store-backed metadata projection.
323
+ * This deliberately ignores an accepted-private overlay roster so rotation
324
+ * does not ask the roster it is replacing to define its own successor.
325
+ */
326
+ async getMemberRecoveryRosterSource(contextGraphId, options = {}) {
327
+ const registered = await this.resolveSwmRegisteredAuthority(contextGraphId, { signal: options.signal });
328
+ if (registered.kind === 'private')
329
+ return [...registered.participantAgents];
330
+ if (registered.kind !== 'unregistered')
331
+ return null;
332
+ const metadataRevision = this.contextGraphMetaProjection
333
+ .readContextGraphAuthorityFactsRevision(contextGraphId);
334
+ const metadataGate = await this.getLocalMetadataMemberRecoveryGate(contextGraphId, options);
335
+ const currentRegistered = await this.resolveSwmRegisteredAuthority(contextGraphId, { signal: options.signal });
336
+ if (currentRegistered.kind === 'private') {
337
+ return [...currentRegistered.participantAgents];
338
+ }
339
+ if (currentRegistered.kind !== 'unregistered')
340
+ return null;
341
+ return this.contextGraphMetaProjection
342
+ .readContextGraphAuthorityFactsRevision(contextGraphId) === metadataRevision
343
+ ? metadataGate
344
+ : null;
345
+ }
346
+ /**
347
+ * Fresh effective store-backed metadata member set. This deliberately stays
348
+ * separate from the transport-authoritative recovery decision.
349
+ */
350
+ async getLocalMetadataMemberRecoveryGate(contextGraphId, options = {}) {
281
351
  const seen = new Set();
282
352
  const agents = [];
283
353
  const meta = await this.getCgMeta(contextGraphId, { signal: options.signal });
284
354
  if (meta.allowedAgents.length === 0 && meta.participantAgents.length === 0) {
285
- return null; // no _meta agent gate ⇒ hard-deny at the recovery gate
355
+ return null; // no projected agent gate ⇒ hard-deny at the recovery gate
286
356
  }
287
357
  const revoked = new Set(meta.revokedAgents.map((addr) => addr.toLowerCase()));
288
358
  const add = (value) => {
@@ -600,6 +670,56 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
600
670
  resolveSwmTransportAuthority(contextGraphId, options = {}) {
601
671
  return resolveSwmTransportAuthorityDecision(this, contextGraphId, options);
602
672
  }
673
+ /**
674
+ * Resolve the transport peer gate that applies to inbound SWM traffic.
675
+ *
676
+ * Ordinary graphs retain the legacy merged metadata projection. A graph
677
+ * carrying a durable local join approval must instead use the peer roster
678
+ * from the same source-qualified proof that authorized its private replica;
679
+ * falling back to the merged projection there would let an unrelated
680
+ * AGENTS/ONTOLOGY row widen the inbound gate. Registration (or a proven
681
+ * public policy) supersedes that participant proof and keeps the established
682
+ * registered/public behaviour.
683
+ */
684
+ async resolveApprovedPrivateReplicaSwmAllowedPeersOverride(contextGraphId) {
685
+ if (!this.localApprovedAgentByCG.has(contextGraphId)) {
686
+ return undefined;
687
+ }
688
+ const transport = await this.resolveSwmTransportAuthority(contextGraphId, {
689
+ authorityReadMode: 'finalized-index-or-live',
690
+ });
691
+ switch (transport.kind) {
692
+ case 'approved-private-replica':
693
+ // An empty proved list is an authoritative open peer gate. Keep it
694
+ // distinct from `undefined` (this override does not apply) so the
695
+ // SharedMemoryHandler projection wrapper never falls back to a merged
696
+ // secondary-source row.
697
+ return [...new Set(transport.allowedPeers)];
698
+ case 'plaintext':
699
+ case 'private-roster':
700
+ return undefined;
701
+ case 'unavailable':
702
+ throw createContextGraphAuthorityError(`Context graph "${contextGraphId}" SWM peer gate authority is unavailable (${transport.reason})`, transport);
703
+ case 'legacy-unregistered':
704
+ // The approval may have been removed while the authority read was in
705
+ // flight. Only that transition restores ordinary legacy behaviour.
706
+ if (!this.localApprovedAgentByCG.has(contextGraphId)) {
707
+ return undefined;
708
+ }
709
+ throw createContextGraphAuthorityError(`Context graph "${contextGraphId}" still has a local join approval but no approved private replica authority`, {
710
+ reason: 'finalized-name-absence-unaccepted',
711
+ detail: 'approved private replica authority did not survive current SWM authority resolution',
712
+ });
713
+ }
714
+ }
715
+ /** List/null adapter for SWM consumers that do not consume meta records. */
716
+ async resolveSwmAllowedPeersForCurrentAuthority(contextGraphId) {
717
+ const approvedReplicaOverride = await this.resolveApprovedPrivateReplicaSwmAllowedPeersOverride(contextGraphId);
718
+ if (approvedReplicaOverride === undefined) {
719
+ return this.getContextGraphAllowedPeers(contextGraphId);
720
+ }
721
+ return approvedReplicaOverride.length > 0 ? approvedReplicaOverride : null;
722
+ }
603
723
  /**
604
724
  * Whether SWM on this graph is public-readable, so plaintext may be both
605
725
  * sent and accepted. One predicate for both ends of the wire: the sender's
@@ -893,9 +1013,16 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
893
1013
  * a public CG that never encrypts. Private graphs resolve recipients from
894
1014
  * the live roster; unavailable registered authority fails closed. An
895
1015
  * unregistered graph whose accepted owner-signed policy is public is
896
- * treated like an on-chain public one.
1016
+ * treated like an on-chain public one. An approved private replica retains
1017
+ * the full effective metadata roster, while any explicit graph peer
1018
+ * allowlist restricts the recipient keys available to every roster member.
897
1019
  */
898
1020
  async resolveWorkspaceRecipientsGated(input) {
1021
+ // Hydrate durable sender-key state before resolving recipients. The
1022
+ // publisher invokes the encryptor synchronously after this resolver, so
1023
+ // this keeps state I/O out of the authority-fenced interval between the
1024
+ // final recipient recheck and sender-key epoch selection/creation.
1025
+ await this.loadSwmSenderKeyState();
899
1026
  return this.resolveWorkspaceAgentRecipientsForCurrentAuthority(input);
900
1027
  }
901
1028
  /**
@@ -924,9 +1051,53 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
924
1051
  if (transport.kind === 'plaintext') {
925
1052
  return { requiresEncryption: false, recipients: [] };
926
1053
  }
1054
+ let recipientAuthorityRevision = null;
927
1055
  let resolveKeys;
928
1056
  if (transport.kind === 'legacy-unregistered') {
929
- resolveKeys = () => resolveWorkspaceAgentRecipients(this.store, input);
1057
+ resolveKeys = async () => {
1058
+ const attemptRevision = this.contextGraphMetaProjection.readAuthorityFactsRevision;
1059
+ const resolution = await resolveWorkspaceAgentRecipients(this.store, input);
1060
+ recipientAuthorityRevision = attemptRevision;
1061
+ return resolution;
1062
+ };
1063
+ }
1064
+ else if (transport.kind === 'approved-private-replica') {
1065
+ // Resolve the complete effective metadata roster first. Participant
1066
+ // approval authorizes this receiver; it is not a replacement roster.
1067
+ // The peer gate comes from the same source-qualified, revision-fenced
1068
+ // proof as the transport classification, never the merged projection.
1069
+ resolveKeys = async () => {
1070
+ const attemptRevision = this.contextGraphMetaProjection.readAuthorityFactsRevision;
1071
+ const resolution = await resolveWorkspaceAgentRecipients(this.store, input);
1072
+ if (!resolution.requiresEncryption) {
1073
+ throw new Error(`Approved private replica "${input.contextGraphId}" resolved a plaintext SWM roster`);
1074
+ }
1075
+ if (transport.allowedPeers.length === 0) {
1076
+ recipientAuthorityRevision = attemptRevision;
1077
+ return resolution;
1078
+ }
1079
+ const allowedPeerSet = new Set(transport.allowedPeers);
1080
+ const effectiveAgentAddresses = new Set(resolution.recipients.map(({ agentAddress }) => agentAddress.toLowerCase()));
1081
+ const authorizedRecipients = resolution.recipients.filter((recipient) => (recipient.peerId !== undefined && allowedPeerSet.has(recipient.peerId)));
1082
+ const authorizedAgentAddresses = new Set(authorizedRecipients.map(({ agentAddress }) => agentAddress.toLowerCase()));
1083
+ for (const agentAddress of effectiveAgentAddresses) {
1084
+ if (!authorizedAgentAddresses.has(agentAddress)) {
1085
+ throw new Error(`Approved private replica "${input.contextGraphId}" requires encrypted SWM gossip but `
1086
+ + `effective DKG agent ${ethers.getAddress(agentAddress)} has no recipient key `
1087
+ + 'advertised by a peer in the context graph allowlist');
1088
+ }
1089
+ }
1090
+ const [firstRecipient, ...remainingRecipients] = authorizedRecipients;
1091
+ if (!firstRecipient) {
1092
+ throw new Error(`Approved private replica "${input.contextGraphId}" requires encrypted SWM gossip but has no allowed DKG agent recipients`);
1093
+ }
1094
+ const authorizedResolution = {
1095
+ requiresEncryption: true,
1096
+ recipients: [firstRecipient, ...remainingRecipients],
1097
+ };
1098
+ recipientAuthorityRevision = attemptRevision;
1099
+ return authorizedResolution;
1100
+ };
930
1101
  }
931
1102
  else {
932
1103
  if (transport.kind === 'unavailable') {
@@ -937,14 +1108,20 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
937
1108
  if (participantAgents.length === 0) {
938
1109
  throw new Error(`Registered context graph "${input.contextGraphId}" requires encrypted SWM gossip but its authoritative chain roster is empty or unavailable`);
939
1110
  }
940
- const allowedPeers = await this.getContextGraphAllowedPeers(input.contextGraphId);
941
- const allowedPeerSet = allowedPeers === null ? null : new Set(allowedPeers);
942
1111
  // Resolve only the live chain-authorized addresses. Filtering a completed
943
1112
  // local resolution afterward is too late: stale removed members can have
944
1113
  // malformed/missing key metadata that makes the local resolver throw
945
1114
  // before the chain intersection is reached, blocking every post-revoke
946
1115
  // write until the local cleanup retry succeeds.
947
1116
  resolveKeys = async () => {
1117
+ // The registered/accepted private roster, local peer gate, and verified
1118
+ // recipient-key routes are conjunctive authorities. Capture their shared
1119
+ // revision before every attempt so a phonebook-hydration retry owns a new
1120
+ // snapshot, while the final transport recheck cannot return a route
1121
+ // removed or revoked meanwhile.
1122
+ const attemptRevision = this.contextGraphMetaProjection.readAuthorityFactsRevision;
1123
+ const allowedPeers = await this.getContextGraphAllowedPeers(input.contextGraphId);
1124
+ const allowedPeerSet = allowedPeers === null ? null : new Set(allowedPeers);
948
1125
  const recipients = [];
949
1126
  // Every member without a key is named at once, so one phonebook fetch
950
1127
  // can cover them all (#2849). Any other key failure still stops here.
@@ -976,6 +1153,10 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
976
1153
  if (!firstRecipient) {
977
1154
  throw new Error(`Registered context graph "${input.contextGraphId}" requires encrypted SWM gossip but has no chain-authorized DKG agent recipients`);
978
1155
  }
1156
+ // Publish only the snapshot owned by the attempt that actually
1157
+ // completed. A failed pre-hydration attempt must not pin its revision
1158
+ // across the phonebook write and the successful retry.
1159
+ recipientAuthorityRevision = attemptRevision;
979
1160
  return {
980
1161
  requiresEncryption: true,
981
1162
  recipients: [firstRecipient, ...remainingRecipients],
@@ -987,31 +1168,105 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
987
1168
  // every member whose key is missing, then the keys are resolved once more.
988
1169
  // Every member still needs a key, so a share that cannot get one stays
989
1170
  // closed.
990
- try {
991
- return await resolveKeys();
992
- }
993
- catch (error) {
994
- if (!isWorkspaceAgentEncryptionKeyMissingError(error))
995
- throw error;
996
- if (typeof this.ensureAgentsInOnDemandPhonebook !== 'function')
997
- throw withMemberKeyHint(error);
998
- await this.ensureAgentsInOnDemandPhonebook(error.agentAddresses.map((address) => address.toLowerCase()), AbortSignal.timeout(SWM_RECIPIENT_KEY_FETCH_WAIT_MS));
999
- }
1000
- try {
1001
- return await resolveKeys();
1002
- }
1003
- catch (error) {
1004
- throw isWorkspaceAgentEncryptionKeyMissingError(error) ? withMemberKeyHint(error) : error;
1171
+ const resolveWithPhonebookHydration = async () => {
1172
+ try {
1173
+ return await resolveKeys();
1174
+ }
1175
+ catch (error) {
1176
+ if (!isWorkspaceAgentEncryptionKeyMissingError(error))
1177
+ throw error;
1178
+ if (typeof this.ensureAgentsInOnDemandPhonebook !== 'function') {
1179
+ throw withMemberKeyHint(error);
1180
+ }
1181
+ await this.ensureAgentsInOnDemandPhonebook(error.agentAddresses.map((address) => address.toLowerCase()), AbortSignal.timeout(SWM_RECIPIENT_KEY_FETCH_WAIT_MS));
1182
+ }
1183
+ try {
1184
+ return await resolveKeys();
1185
+ }
1186
+ catch (error) {
1187
+ throw isWorkspaceAgentEncryptionKeyMissingError(error) ? withMemberKeyHint(error) : error;
1188
+ }
1189
+ };
1190
+ let resolution = await resolveWithPhonebookHydration();
1191
+ if (transport.kind === 'private-roster'
1192
+ || transport.kind === 'approved-private-replica'
1193
+ || transport.kind === 'legacy-unregistered') {
1194
+ // Key and phonebook lookup are asynchronous. Re-read the paired
1195
+ // transport decision after them so an accepted-private (or registered)
1196
+ // roster rotation cannot return keys for a member removed meanwhile.
1197
+ // The store revision is deliberately conservative because recipient
1198
+ // facts can live in any named graph. Unrelated graph activity therefore
1199
+ // triggers a bounded optimistic retry, but a retry is accepted only when
1200
+ // both the exact (agent,key,peer) set and transport authority are stable.
1201
+ for (let attempt = 0; attempt < SWM_RECIPIENT_AUTHORITY_STABILITY_ATTEMPTS; attempt += 1) {
1202
+ const currentTransport = await withRpcUsageSite(CG_AUTH_RPC_SITES.recipients, () => this.resolveSwmTransportAuthority(input.contextGraphId, {
1203
+ authorityReadMode: 'finalized-index-or-live',
1204
+ requireLiveRosterForPrivate: true,
1205
+ }));
1206
+ if (currentTransport.kind === 'unavailable') {
1207
+ throw createContextGraphAuthorityError(`Context graph "${input.contextGraphId}" recipient authority is unavailable (${currentTransport.reason})`, currentTransport);
1208
+ }
1209
+ const transportStayedCurrent = transport.kind === 'private-roster'
1210
+ ? currentTransport.kind === 'private-roster'
1211
+ && resolution.requiresEncryption
1212
+ && hasExactRecipientAgentRoster(resolution, currentTransport.participantAgents)
1213
+ : transport.kind === 'approved-private-replica'
1214
+ ? currentTransport.kind === 'approved-private-replica'
1215
+ && sameStringSet(transport.allowedPeers, currentTransport.allowedPeers)
1216
+ : currentTransport.kind === 'legacy-unregistered';
1217
+ const revisionStayedCurrent = recipientAuthorityRevision
1218
+ === this.contextGraphMetaProjection.readAuthorityFactsRevision;
1219
+ if (transportStayedCurrent && revisionStayedCurrent)
1220
+ break;
1221
+ const hasAnotherAttempt = attempt + 1 < SWM_RECIPIENT_AUTHORITY_STABILITY_ATTEMPTS;
1222
+ if (!transportStayedCurrent || !hasAnotherAttempt) {
1223
+ throw createContextGraphAuthorityError(`Context graph "${input.contextGraphId}" private authority changed while recipient keys were resolving`, {
1224
+ reason: 'chain-participant-authority-unavailable',
1225
+ detail: 'retry recipient resolution against the current private authority',
1226
+ });
1227
+ }
1228
+ const retriedResolution = await resolveWithPhonebookHydration();
1229
+ const recipientSnapshotStayedCurrent = resolution.requiresEncryption
1230
+ ? retriedResolution.requiresEncryption
1231
+ && hasExactWorkspaceRecipientSet(resolution, retriedResolution)
1232
+ : !retriedResolution.requiresEncryption;
1233
+ if (!recipientSnapshotStayedCurrent) {
1234
+ throw createContextGraphAuthorityError(`Context graph "${input.contextGraphId}" private authority changed while recipient keys were resolving`, {
1235
+ reason: 'chain-participant-authority-unavailable',
1236
+ detail: 'recipient routes changed while retrying against current private authority',
1237
+ });
1238
+ }
1239
+ resolution = retriedResolution;
1240
+ }
1005
1241
  }
1242
+ return resolution;
1006
1243
  }
1007
1244
  async encryptWorkspacePayloadWithSenderKey(input) {
1008
- await this.loadSwmSenderKeyState();
1245
+ let resolution = input.resolution;
1246
+ if (!this.swmSenderKeyStateLoaded) {
1247
+ await this.loadSwmSenderKeyState();
1248
+ // Recipient authority may have changed while durable state was loading.
1249
+ // Resolve once against the now-hydrated state and proceed only when the
1250
+ // exact agent/key/peer snapshot is unchanged. The production publisher
1251
+ // avoids this compatibility reread by hydrating in
1252
+ // resolveWorkspaceRecipientsGated above.
1253
+ const currentResolution = await this.resolveWorkspaceAgentRecipientsForCurrentAuthority({
1254
+ contextGraphId: input.contextGraphId,
1255
+ });
1256
+ if (!currentResolution.requiresEncryption
1257
+ || !hasExactWorkspaceRecipientSet(resolution, currentResolution)) {
1258
+ throw createContextGraphAuthorityError(`Context graph "${input.contextGraphId}" authority changed while sender-key state was loading`, {
1259
+ reason: 'chain-participant-authority-unavailable',
1260
+ detail: 'retry recipient resolution before creating or reusing a sender-key epoch',
1261
+ });
1262
+ }
1263
+ resolution = currentResolution;
1264
+ }
1009
1265
  const ctx = createOperationContext('share', input.operationId);
1010
1266
  const sender = this.getLocalSigningAgentForAddress(input.senderAgentAddress);
1011
1267
  if (!sender) {
1012
1268
  throw new Error(`Cannot create SWM Sender Key epoch: no local custodial signing key for agent ${input.senderAgentAddress}`);
1013
1269
  }
1014
- const resolution = input.resolution;
1015
1270
  const senderAddress = ethers.getAddress(sender.agentAddress);
1016
1271
  const recipientSet = new Set(resolution.recipients.map((recipient) => recipient.agentAddress.toLowerCase()));
1017
1272
  if (!recipientSet.has(senderAddress.toLowerCase())) {
@@ -1030,9 +1285,16 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1030
1285
  recipientKeyId: recipient.recipientKeyId,
1031
1286
  })),
1032
1287
  });
1288
+ const recipientRouteHash = computeSwmSenderKeyRecipientRouteHash({
1289
+ contextGraphId: input.contextGraphId,
1290
+ subGraphName: input.subGraphName,
1291
+ recipients: resolution.recipients,
1292
+ });
1033
1293
  const stateKey = swmSenderStateKey(input.contextGraphId, input.subGraphName, senderAddress);
1034
1294
  let state = this.swmSenderKeySendStates.get(stateKey);
1035
- if (!state || state.membershipHash !== membershipHash) {
1295
+ if (!state
1296
+ || state.membershipHash !== membershipHash
1297
+ || state.recipientRouteHash !== recipientRouteHash) {
1036
1298
  const pruned = this.prunePendingSenderKeysForEpochRotation({
1037
1299
  contextGraphId: input.contextGraphId,
1038
1300
  subGraphName: input.subGraphName,
@@ -1055,7 +1317,12 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1055
1317
  await this.saveSwmSenderKeyState();
1056
1318
  }
1057
1319
  else {
1058
- await this.drainPendingSenderKeyForRecipients(resolution.recipients, ctx);
1320
+ await this.drainPendingSenderKeyForRecipients(resolution.recipients, ctx, {
1321
+ contextGraphId: input.contextGraphId,
1322
+ subGraphName: input.subGraphName,
1323
+ senderAgentAddress: state.senderAgentAddress,
1324
+ epochId: state.epochId,
1325
+ });
1059
1326
  }
1060
1327
  const encrypted = await encryptSwmSenderKeyMessage({
1061
1328
  chainKey: state.chainKey,
@@ -1090,6 +1357,11 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1090
1357
  senderAgentAddress,
1091
1358
  epochId,
1092
1359
  membershipHash: input.membershipHash,
1360
+ recipientRouteHash: computeSwmSenderKeyRecipientRouteHash({
1361
+ contextGraphId: input.contextGraphId,
1362
+ subGraphName: input.subGraphName,
1363
+ recipients: input.recipients,
1364
+ }),
1093
1365
  chainKey,
1094
1366
  nextMessageIndex: 0,
1095
1367
  senderSigningSecretKey: senderSigningKeypair.secretKey,
@@ -1105,7 +1377,18 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1105
1377
  senderPrivateKey: input.sender.privateKey,
1106
1378
  });
1107
1379
  const packageBytes = encodeSwmSenderKeyPackage(pkg);
1108
- if (this.hasLocalAgent(recipientAgentAddress)) {
1380
+ const recipientIsLocal = this.hasLocalAgent(recipientAgentAddress);
1381
+ let targetsLocalPeer = recipient.peerId === undefined;
1382
+ if (recipientIsLocal && recipient.peerId !== undefined) {
1383
+ const localPeerId = this.node.peerId.toString();
1384
+ const canonicalRecipientPeerId = tryCanonicalPeerIdString(recipient.peerId);
1385
+ const canonicalLocalPeerId = tryCanonicalPeerIdString(localPeerId);
1386
+ targetsLocalPeer = recipient.peerId === localPeerId
1387
+ || (canonicalRecipientPeerId !== null
1388
+ && canonicalLocalPeerId !== null
1389
+ && canonicalRecipientPeerId === canonicalLocalPeerId);
1390
+ }
1391
+ if (recipientIsLocal && targetsLocalPeer) {
1109
1392
  try {
1110
1393
  await this.acceptSwmSenderKeyPackage(pkg, this.node.peerId.toString(), input.ctx);
1111
1394
  return { kind: 'success', agentAddress: recipientAgentAddress };
@@ -1142,7 +1425,7 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1142
1425
  });
1143
1426
  pendingSenderKeyQueued = true;
1144
1427
  this.log.warn(input.ctx, `SWM sender-key setup for ${recipientAgentAddress} keyId=${recipient.recipientKeyId} ` +
1145
- `queued (no advertised peerId) — will deliver when recipient connects`);
1428
+ `queued (no advertised peerId) — will deliver after a verified peer route is learned`);
1146
1429
  return { kind: 'success', agentAddress: recipientAgentAddress };
1147
1430
  }
1148
1431
  this.log.info(input.ctx, `SWM sender-key setup send: senderAgent=${senderAgentAddress} recipientAgent=${recipientAgentAddress} ` +
@@ -1183,6 +1466,7 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1183
1466
  senderAgentAddress: senderAgentAddress.toLowerCase(),
1184
1467
  recipientAgentAddress: recipientAgentAddress.toLowerCase(),
1185
1468
  recipientKeyId: recipient.recipientKeyId,
1469
+ recipientPeerId: recipient.peerId,
1186
1470
  epochId: state.epochId,
1187
1471
  contextGraphId: state.contextGraphId,
1188
1472
  subGraphName: state.subGraphName,
@@ -1204,6 +1488,7 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1204
1488
  senderAgentAddress: senderAgentAddress.toLowerCase(),
1205
1489
  recipientAgentAddress: recipientAgentAddress.toLowerCase(),
1206
1490
  recipientKeyId: recipient.recipientKeyId,
1491
+ recipientPeerId: recipient.peerId,
1207
1492
  epochId: state.epochId,
1208
1493
  contextGraphId: state.contextGraphId,
1209
1494
  subGraphName: state.subGraphName,
@@ -1222,6 +1507,7 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1222
1507
  senderAgentAddress: senderAgentAddress.toLowerCase(),
1223
1508
  recipientAgentAddress: recipientAgentAddress.toLowerCase(),
1224
1509
  recipientKeyId: recipient.recipientKeyId,
1510
+ recipientPeerId: recipient.peerId,
1225
1511
  epochId: state.epochId,
1226
1512
  contextGraphId: state.contextGraphId,
1227
1513
  subGraphName: state.subGraphName,
@@ -1241,6 +1527,7 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1241
1527
  senderAgentAddress: senderAgentAddress.toLowerCase(),
1242
1528
  recipientAgentAddress: recipientAgentAddress.toLowerCase(),
1243
1529
  recipientKeyId: recipient.recipientKeyId,
1530
+ recipientPeerId: recipient.peerId,
1244
1531
  epochId: state.epochId,
1245
1532
  contextGraphId: state.contextGraphId,
1246
1533
  subGraphName: state.subGraphName,
@@ -1349,26 +1636,34 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1349
1636
  /**
1350
1637
  * PR-2 (SWM-fanout plan): enqueue a sender-key package whose recipient
1351
1638
  * has no advertised `dkg:peerId` (so we can't even ask the messenger
1352
- * to queue it). Older epochs for the same `(sender, recipient)` pair
1353
- * are evicted — a newer epoch supersedes them by definition.
1639
+ * to queue it). Older epochs for the same `(sender, recipient, context
1640
+ * graph, subgraph)` scope are evicted — a newer epoch supersedes them by
1641
+ * definition. Other graph/subgraph obligations remain independent.
1354
1642
  *
1355
- * Per-key dedup: `(senderAgentAddress, recipientKeyId, epochId)`
1356
- * matches an existing row, we replace it (idempotent re-enqueue).
1643
+ * Per-route dedup: `(senderAgentAddress, recipientKeyId,
1644
+ * recipientPeerId, epochId)` matches an existing row, we replace it
1645
+ * (idempotent re-enqueue). Distinct peers advertising the same key retain
1646
+ * independent delivery obligations.
1357
1647
  */
1358
1648
  enqueuePendingSenderKey(entry) {
1359
1649
  const recipientKey = entry.recipientAgentAddress.toLowerCase();
1360
1650
  const existing = this.pendingSenderKeyByAgent.get(recipientKey) ?? [];
1361
- // Drop older epochs for the same (sender, recipient) pair; the newer
1362
- // epoch's membership-hash supersedes them. Keep entries for OTHER
1363
- // senders / recipients unchanged.
1651
+ // Drop older epochs only within the same sender + graph/subgraph scope;
1652
+ // the newer epoch's membership hash supersedes those rows. The map key
1653
+ // already scopes recipient agent. Keep every other scope unchanged.
1364
1654
  const filtered = existing.filter((e) => {
1365
1655
  if (e.senderAgentAddress !== entry.senderAgentAddress)
1366
1656
  return true;
1657
+ if (e.contextGraphId !== entry.contextGraphId
1658
+ || (e.subGraphName ?? undefined) !== (entry.subGraphName ?? undefined)) {
1659
+ return true;
1660
+ }
1367
1661
  if (e.epochId === entry.epochId) {
1368
- // Same epoch: dedupe by recipientKeyId — caller may re-enqueue
1369
- // on retry. Replace by dropping the old slot; the new one is
1370
- // appended below.
1371
- return e.recipientKeyId !== entry.recipientKeyId;
1662
+ // Same epoch: dedupe only the exact key + peer route. A successful
1663
+ // ACK from one peer must never overwrite a retry owed to another peer
1664
+ // that advertises the same agent/key pair.
1665
+ return e.recipientKeyId !== entry.recipientKeyId
1666
+ || e.recipientPeerId !== entry.recipientPeerId;
1372
1667
  }
1373
1668
  return false;
1374
1669
  });
@@ -1397,17 +1692,21 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1397
1692
  return removed;
1398
1693
  }
1399
1694
  async drainPendingSenderKeyQueueForPeer(input) {
1400
- const recipientAgentAddress = input.recipientAgentAddress.toLowerCase();
1695
+ const authority = {
1696
+ ...input.authority,
1697
+ recipientAgentAddress: input.authority.recipientAgentAddress.toLowerCase(),
1698
+ senderAgentAddress: input.authority.senderAgentAddress?.toLowerCase(),
1699
+ };
1700
+ const recipientAgentAddress = authority.recipientAgentAddress;
1401
1701
  const existingDrain = this.pendingSenderKeyDrainByAgent.get(recipientAgentAddress);
1402
1702
  if (existingDrain) {
1403
1703
  await existingDrain;
1404
1704
  if (!this.pendingSenderKeyByAgent.has(recipientAgentAddress))
1405
1705
  return 0;
1406
- return this.drainPendingSenderKeyQueueForPeer(input);
1706
+ return this.drainPendingSenderKeyQueueForPeer({ authority, ctx: input.ctx });
1407
1707
  }
1408
1708
  const drain = this.drainPendingSenderKeyQueueForPeerLocked({
1409
- peerId: input.peerId,
1410
- recipientAgentAddress,
1709
+ authority,
1411
1710
  ctx: input.ctx,
1412
1711
  }).finally(() => {
1413
1712
  if (this.pendingSenderKeyDrainByAgent.get(recipientAgentAddress) === drain) {
@@ -1418,19 +1717,75 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1418
1717
  return drain;
1419
1718
  }
1420
1719
  async drainPendingSenderKeyQueueForPeerLocked(input) {
1421
- const recipientAgentAddress = input.recipientAgentAddress;
1720
+ const { authority } = input;
1721
+ const recipientAgentAddress = authority.recipientAgentAddress;
1422
1722
  const queue = this.pendingSenderKeyByAgent.get(recipientAgentAddress);
1423
1723
  if (!queue || queue.length === 0)
1424
1724
  return 0;
1425
1725
  let drained = 0;
1426
- const remaining = [];
1726
+ // `sendReliable` yields. Enqueue/epoch rotation can replace the live array
1727
+ // while this drain is in flight, so record outcomes against the exact
1728
+ // snapshot objects instead of later overwriting the whole map entry. At
1729
+ // commit, only original objects still present in the live queue are
1730
+ // transformed; concurrently inserted replacements/new scopes survive and
1731
+ // originals concurrently superseded by a new epoch are not resurrected.
1732
+ const outcomes = new Map();
1733
+ const commitOutcomes = () => {
1734
+ const live = this.pendingSenderKeyByAgent.get(recipientAgentAddress) ?? [];
1735
+ const reconciled = [];
1736
+ for (const liveEntry of live) {
1737
+ if (!outcomes.has(liveEntry)) {
1738
+ reconciled.push(liveEntry);
1739
+ continue;
1740
+ }
1741
+ const replacement = outcomes.get(liveEntry);
1742
+ if (replacement)
1743
+ reconciled.push(replacement);
1744
+ }
1745
+ if (reconciled.length === 0) {
1746
+ this.pendingSenderKeyByAgent.delete(recipientAgentAddress);
1747
+ }
1748
+ else {
1749
+ this.pendingSenderKeyByAgent.set(recipientAgentAddress, reconciled);
1750
+ }
1751
+ };
1427
1752
  for (let i = 0; i < queue.length; i += 1) {
1428
1753
  const entry = queue[i];
1754
+ // A row may only be consumed by the caller's exact current-authority
1755
+ // route and graph scope. Legacy/no-peer rows are expanded into explicit
1756
+ // peer-bound obligations before reaching this drain. Foreground callers
1757
+ // additionally bind the current sender + epoch; connection-open callers
1758
+ // explicitly authorize every subgraph for one resolved CG.
1759
+ if (!pendingSenderKeyEntryMatchesDrainAuthority(entry, authority)) {
1760
+ outcomes.set(entry, entry);
1761
+ continue;
1762
+ }
1763
+ // Authority can change while this route waits for the per-agent drain
1764
+ // lock, or while a preceding row is in flight. Re-resolve immediately
1765
+ // before every send so a stale caller snapshot can never consume a row.
1766
+ let routeStillCurrent = false;
1767
+ try {
1768
+ const resolution = await this.resolveWorkspaceAgentRecipientsForCurrentAuthority({
1769
+ contextGraphId: entry.contextGraphId,
1770
+ });
1771
+ routeStillCurrent = resolution.requiresEncryption
1772
+ && resolution.recipients.some((recipient) => (recipient.agentAddress.toLowerCase() === entry.recipientAgentAddress
1773
+ && recipient.recipientKeyId === entry.recipientKeyId
1774
+ && recipient.peerId === entry.recipientPeerId));
1775
+ }
1776
+ catch {
1777
+ // Fail closed. The queued obligation remains available for a later
1778
+ // current-authority retry.
1779
+ }
1780
+ if (!routeStillCurrent) {
1781
+ outcomes.set(entry, entry);
1782
+ continue;
1783
+ }
1429
1784
  try {
1430
- const sendResult = await this.messenger.sendReliable(input.peerId, PROTOCOL_SWM_SENDER_KEY, entry.packageBytes, { messageId: this.swmSenderKeyPendingMessageId(entry) });
1785
+ const sendResult = await this.messenger.sendReliable(authority.recipientPeerId, PROTOCOL_SWM_SENDER_KEY, entry.packageBytes, { messageId: this.swmSenderKeyPendingMessageId(entry) });
1431
1786
  if (!sendResult.delivered) {
1432
1787
  if (sendResult.queued || ('inFlight' in sendResult && sendResult.inFlight)) {
1433
- remaining.push(entry);
1788
+ outcomes.set(entry, entry);
1434
1789
  continue;
1435
1790
  }
1436
1791
  throw new Error(`Unexpected undelivered Sender Key retry result: ${sendResult.error}`);
@@ -1442,65 +1797,134 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1442
1797
  catch {
1443
1798
  // Malformed/legacy ACK: no positive acceptance yet. Keep the
1444
1799
  // row queued so a mixed-version rollout cannot strand the recipient.
1445
- remaining.push(this.rotateSwmSenderKeyPendingMessageId(entry));
1800
+ outcomes.set(entry, this.rotateSwmSenderKeyPendingMessageId(entry));
1446
1801
  continue;
1447
1802
  }
1448
1803
  if (ack.version !== SWM_SENDER_KEY_PACKAGE_VERSION ||
1449
1804
  ack.type !== SWM_SENDER_KEY_PACKAGE_ACK_TYPE) {
1450
1805
  // Malformed/legacy ACK: no positive acceptance yet. Keep the
1451
1806
  // row queued so a mixed-version rollout cannot strand the recipient.
1452
- remaining.push(this.rotateSwmSenderKeyPendingMessageId(entry));
1807
+ outcomes.set(entry, this.rotateSwmSenderKeyPendingMessageId(entry));
1453
1808
  continue;
1454
1809
  }
1455
1810
  if (ack.accepted) {
1456
1811
  drained += 1;
1812
+ outcomes.set(entry, null);
1457
1813
  }
1458
1814
  else if (this.isRetryableSwmSenderKeySetupAckReason(ack.reasonCode)) {
1459
- remaining.push(this.rotateSwmSenderKeyPendingMessageId(entry));
1815
+ outcomes.set(entry, this.rotateSwmSenderKeyPendingMessageId(entry));
1460
1816
  }
1461
1817
  else {
1462
1818
  const reason = ack.reason ?? 'unknown reason';
1463
1819
  const reasonCode = ack.reasonCode ?? 'legacy-unknown';
1464
1820
  this.log.warn(input.ctx ?? SWM_SENDER_KEY_PENDING_DRAIN_LOG_CTX, `SWM sender-key pending retry for ${entry.recipientAgentAddress} keyId=${entry.recipientKeyId} ` +
1465
- `peerId=${input.peerId} contextGraph=${entry.contextGraphId}${entry.subGraphName ? `/${entry.subGraphName}` : ''} ` +
1821
+ `peerId=${authority.recipientPeerId} contextGraph=${entry.contextGraphId}${entry.subGraphName ? `/${entry.subGraphName}` : ''} ` +
1466
1822
  `dropped after terminal rejection (${reasonCode}): ${reason}`);
1467
1823
  // Terminal rejection: keep it out of the queue, but do not
1468
1824
  // report it as a successful drain.
1825
+ outcomes.set(entry, null);
1469
1826
  }
1470
1827
  }
1471
1828
  catch (err) {
1472
- remaining.push(...queue.slice(i));
1473
- if (remaining.length === 0) {
1474
- this.pendingSenderKeyByAgent.delete(recipientAgentAddress);
1475
- }
1476
- else {
1477
- this.pendingSenderKeyByAgent.set(recipientAgentAddress, remaining);
1829
+ outcomes.set(entry, entry);
1830
+ for (const unprocessed of queue.slice(i + 1)) {
1831
+ outcomes.set(unprocessed, unprocessed);
1478
1832
  }
1833
+ commitOutcomes();
1479
1834
  await this.saveSwmSenderKeyState();
1480
1835
  const message = err instanceof Error ? err.message : String(err);
1481
1836
  this.log.warn(input.ctx ?? SWM_SENDER_KEY_PENDING_DRAIN_LOG_CTX, `SWM sender-key pending retry for ${entry.recipientAgentAddress} keyId=${entry.recipientKeyId} ` +
1482
- `peerId=${input.peerId} contextGraph=${entry.contextGraphId}${entry.subGraphName ? `/${entry.subGraphName}` : ''} ` +
1837
+ `peerId=${authority.recipientPeerId} contextGraph=${entry.contextGraphId}${entry.subGraphName ? `/${entry.subGraphName}` : ''} ` +
1483
1838
  `failed before the Messenger substrate queued a retry: ${message}`);
1484
1839
  throw err;
1485
1840
  }
1486
1841
  }
1487
- if (remaining.length === 0) {
1488
- this.pendingSenderKeyByAgent.delete(recipientAgentAddress);
1489
- }
1490
- else {
1491
- this.pendingSenderKeyByAgent.set(recipientAgentAddress, remaining);
1492
- }
1842
+ commitOutcomes();
1493
1843
  await this.saveSwmSenderKeyState();
1494
1844
  return drained;
1495
1845
  }
1496
1846
  /**
1497
- * Drain queued sender-key packages whose recipient agent is one of
1498
- * the agent addresses advertised by `peerId`. Returns the number of
1499
- * rows successfully delivered (acked) and removed.
1847
+ * Upgrade legacy/no-peer pending rows into one durable obligation per exact
1848
+ * `(agent, key, peer)` route in a current authority-fenced recipient
1849
+ * snapshot. The replacement is synchronous so drains can never observe a
1850
+ * partially expanded multi-peer obligation.
1851
+ */
1852
+ expandUnboundPendingSenderKeyRoutes(input) {
1853
+ const peersByAgentAndKey = new Map();
1854
+ for (const recipient of input.recipients) {
1855
+ if (!recipient.peerId)
1856
+ continue;
1857
+ const routeKey = `${recipient.agentAddress.toLowerCase()}\0${recipient.recipientKeyId}`;
1858
+ const peers = peersByAgentAndKey.get(routeKey) ?? new Set();
1859
+ peers.add(recipient.peerId);
1860
+ peersByAgentAndKey.set(routeKey, peers);
1861
+ }
1862
+ if (peersByAgentAndKey.size === 0)
1863
+ return 0;
1864
+ const boundRouteIdentity = (entry, peerId) => JSON.stringify([
1865
+ entry.senderAgentAddress,
1866
+ entry.recipientKeyId,
1867
+ peerId,
1868
+ entry.epochId,
1869
+ entry.contextGraphId,
1870
+ entry.subGraphName ?? null,
1871
+ ]);
1872
+ const senderAgentAddress = input.senderAgentAddress?.toLowerCase();
1873
+ let expanded = 0;
1874
+ for (const [recipientAgentAddress, queue] of this.pendingSenderKeyByAgent.entries()) {
1875
+ const existingBoundRoutes = new Set(queue
1876
+ .flatMap((entry) => entry.recipientPeerId === undefined
1877
+ ? []
1878
+ : [boundRouteIdentity(entry, entry.recipientPeerId)]));
1879
+ let changed = false;
1880
+ const replacement = [];
1881
+ for (const entry of queue) {
1882
+ const matchesScope = entry.contextGraphId === input.contextGraphId
1883
+ && (input.allSubgraphs
1884
+ || (entry.subGraphName ?? undefined) === (input.subGraphName ?? undefined))
1885
+ && (senderAgentAddress === undefined || entry.senderAgentAddress === senderAgentAddress)
1886
+ && (input.epochId === undefined || entry.epochId === input.epochId);
1887
+ if (entry.recipientPeerId !== undefined || !matchesScope) {
1888
+ replacement.push(entry);
1889
+ continue;
1890
+ }
1891
+ const peers = peersByAgentAndKey.get(`${entry.recipientAgentAddress}\0${entry.recipientKeyId}`);
1892
+ if (!peers || peers.size === 0) {
1893
+ replacement.push(entry);
1894
+ continue;
1895
+ }
1896
+ changed = true;
1897
+ expanded += 1;
1898
+ for (const peerId of peers) {
1899
+ const identity = boundRouteIdentity(entry, peerId);
1900
+ if (existingBoundRoutes.has(identity))
1901
+ continue;
1902
+ replacement.push({ ...entry, recipientPeerId: peerId });
1903
+ existingBoundRoutes.add(identity);
1904
+ }
1905
+ }
1906
+ if (changed) {
1907
+ if (replacement.length === 0) {
1908
+ this.pendingSenderKeyByAgent.delete(recipientAgentAddress);
1909
+ }
1910
+ else {
1911
+ this.pendingSenderKeyByAgent.set(recipientAgentAddress, replacement);
1912
+ }
1913
+ }
1914
+ }
1915
+ return expanded;
1916
+ }
1917
+ /**
1918
+ * Drain queued sender-key packages whose exact current recipient route is
1919
+ * the newly connected `peerId`. Returns the number of rows successfully
1920
+ * delivered (acked) and removed.
1500
1921
  *
1501
1922
  * Fired from the `connection:open` listener — see line 2382 — so the
1502
1923
  * cost lives on the cold path of "we just connected to a new peer",
1503
- * not on every share. Each successful `sendReliable` with
1924
+ * not on every share. Every context graph represented by either a bound or
1925
+ * legacy row is resolved first; bound rows never bypass that authority read.
1926
+ * Legacy/no-peer rows are expanded only from that graph's current,
1927
+ * authority-fenced recipient snapshot. Each successful `sendReliable` with
1504
1928
  * `delivered=true && ack.accepted=true` deletes the row and counts as
1505
1929
  * drained; soft (`delivered=false`) and explicitly retryable delivered
1506
1930
  * rejections leave it queued for the next attempt; terminal delivered
@@ -1511,21 +1935,80 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1511
1935
  if (this.pendingSenderKeyByAgent.size === 0)
1512
1936
  return 0;
1513
1937
  let drained = 0;
1514
- let agentAddresses = [];
1515
- try {
1516
- const profile = await this.discovery.findAgentByPeerId(peerId);
1517
- if (profile?.agentAddress) {
1518
- agentAddresses = [profile.agentAddress.toLowerCase()];
1938
+ const pendingContextGraphIds = new Set([...this.pendingSenderKeyByAgent.values()]
1939
+ .flatMap((queue) => queue
1940
+ .filter((entry) => entry.recipientPeerId === undefined || entry.recipientPeerId === peerId)
1941
+ .map((entry) => entry.contextGraphId)));
1942
+ let expandedRows = 0;
1943
+ for (const contextGraphId of pendingContextGraphIds) {
1944
+ let resolution;
1945
+ try {
1946
+ resolution = await this.resolveWorkspaceAgentRecipientsForCurrentAuthority({
1947
+ contextGraphId,
1948
+ });
1949
+ }
1950
+ catch {
1951
+ // Authority/key resolution failure is benign on connection-open. No
1952
+ // row for this CG — including already bound rows — may bypass it.
1953
+ continue;
1954
+ }
1955
+ if (!resolution.requiresEncryption)
1956
+ continue;
1957
+ expandedRows += this.expandUnboundPendingSenderKeyRoutes({
1958
+ recipients: resolution.recipients,
1959
+ contextGraphId,
1960
+ allSubgraphs: true,
1961
+ });
1962
+ const routes = new Map();
1963
+ for (const recipient of resolution.recipients) {
1964
+ if (recipient.peerId !== peerId)
1965
+ continue;
1966
+ const recipientAgentAddress = recipient.agentAddress.toLowerCase();
1967
+ const authority = {
1968
+ contextGraphId,
1969
+ allSubgraphs: true,
1970
+ recipientAgentAddress,
1971
+ recipientKeyId: recipient.recipientKeyId,
1972
+ recipientPeerId: peerId,
1973
+ };
1974
+ const queue = this.pendingSenderKeyByAgent.get(recipientAgentAddress);
1975
+ if (!queue?.some((entry) => (pendingSenderKeyEntryMatchesDrainAuthority(entry, authority))))
1976
+ continue;
1977
+ routes.set(`${recipientAgentAddress}\0${recipient.recipientKeyId}\0${peerId}`, authority);
1978
+ }
1979
+ for (const authority of routes.values()) {
1980
+ drained += await this.drainPendingSenderKeyQueueForPeer({ authority, ctx });
1519
1981
  }
1520
1982
  }
1521
- catch {
1522
- // Resolution failure is benign — we'll try again on the next
1523
- // connection:open burst. Don't propagate.
1983
+ if (expandedRows > 0) {
1984
+ // A connection can teach us routes for another peer. Persist that legacy
1985
+ // migration even when this peer had no exact authorized row to drain.
1986
+ await this.saveSwmSenderKeyState();
1524
1987
  }
1525
- if (agentAddresses.length === 0)
1988
+ return drained;
1989
+ }
1990
+ /**
1991
+ * A delivered Sender Key setup can receive a retryable authority denial.
1992
+ * That leaves a durable pending row but does not create a Messenger outbox
1993
+ * retry, and an already-connected recipient may never produce another
1994
+ * connection:open event. Retry only peer-bound rows on the ordinary outbox
1995
+ * cadence. The drain below re-resolves current graph authority and the exact
1996
+ * recipient key/peer route before sending any package.
1997
+ */
1998
+ async drainPendingSenderKeysForConnectedPeers() {
1999
+ await this.loadSwmSenderKeyState();
2000
+ if (this.pendingSenderKeyByAgent.size === 0 || !this.node.isStarted)
1526
2001
  return 0;
1527
- for (const recipientAgentAddress of agentAddresses) {
1528
- drained += await this.drainPendingSenderKeyQueueForPeer({ peerId, recipientAgentAddress, ctx });
2002
+ const pendingPeers = new Set([...this.pendingSenderKeyByAgent.values()]
2003
+ .flatMap((queue) => queue.flatMap((entry) => entry.recipientPeerId === undefined ? [] : [entry.recipientPeerId])));
2004
+ if (pendingPeers.size === 0)
2005
+ return 0;
2006
+ let drained = 0;
2007
+ for (const peer of this.node.libp2p.getPeers()) {
2008
+ const peerId = peer.toString();
2009
+ if (!pendingPeers.has(peerId))
2010
+ continue;
2011
+ drained += await this.drainPendingSenderKeyForPeer(peerId);
1529
2012
  }
1530
2013
  return drained;
1531
2014
  }
@@ -1535,25 +2018,41 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1535
2018
  * connections where no fresh connection:open event will fire after the
1536
2019
  * remote membership/key view converges.
1537
2020
  */
1538
- async drainPendingSenderKeyForRecipients(recipients, ctx) {
2021
+ async drainPendingSenderKeyForRecipients(recipients, ctx, scope) {
1539
2022
  if (this.pendingSenderKeyByAgent.size === 0)
1540
2023
  return 0;
1541
- const peerByAgent = new Map();
2024
+ const senderAgentAddress = scope.senderAgentAddress.toLowerCase();
2025
+ this.expandUnboundPendingSenderKeyRoutes({
2026
+ recipients,
2027
+ contextGraphId: scope.contextGraphId,
2028
+ subGraphName: scope.subGraphName,
2029
+ senderAgentAddress,
2030
+ epochId: scope.epochId,
2031
+ });
2032
+ const routes = new Map();
1542
2033
  for (const recipient of recipients) {
1543
2034
  if (!recipient.peerId)
1544
2035
  continue;
1545
2036
  const recipientAgentAddress = recipient.agentAddress.toLowerCase();
1546
2037
  if (!this.pendingSenderKeyByAgent.has(recipientAgentAddress))
1547
2038
  continue;
1548
- if (!peerByAgent.has(recipientAgentAddress)) {
1549
- peerByAgent.set(recipientAgentAddress, recipient.peerId);
1550
- }
2039
+ const authority = {
2040
+ contextGraphId: scope.contextGraphId,
2041
+ subGraphName: scope.subGraphName,
2042
+ senderAgentAddress,
2043
+ epochId: scope.epochId,
2044
+ recipientAgentAddress,
2045
+ recipientKeyId: recipient.recipientKeyId,
2046
+ recipientPeerId: recipient.peerId,
2047
+ };
2048
+ const routeKey = `${recipientAgentAddress}\0${recipient.recipientKeyId}\0${recipient.peerId}`;
2049
+ routes.set(routeKey, authority);
1551
2050
  }
1552
- if (peerByAgent.size === 0)
2051
+ if (routes.size === 0)
1553
2052
  return 0;
1554
2053
  let drained = 0;
1555
- for (const [recipientAgentAddress, peerId] of peerByAgent.entries()) {
1556
- drained += await this.drainPendingSenderKeyQueueForPeer({ peerId, recipientAgentAddress, ctx });
2054
+ for (const authority of routes.values()) {
2055
+ drained += await this.drainPendingSenderKeyQueueForPeer({ authority, ctx });
1557
2056
  }
1558
2057
  if (drained > 0 && ctx) {
1559
2058
  this.log.info(ctx, `SWM sender-key pending retry drained ${drained} queued package(s) during publish`);
@@ -1819,7 +2318,17 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
1819
2318
  if (!agentGateSet.has(recipientAgentAddress.toLowerCase())) {
1820
2319
  throw new SwmSenderKeySetupRejectionError('recipient-not-allowed', `Recipient agent ${recipientAgentAddress} is not allowed for context graph "${pkg.contextGraphId}"`);
1821
2320
  }
1822
- const allowedPeers = await this.getContextGraphAllowedPeers(pkg.contextGraphId);
2321
+ let allowedPeers;
2322
+ try {
2323
+ allowedPeers = await this.resolveSwmAllowedPeersForCurrentAuthority(pkg.contextGraphId);
2324
+ }
2325
+ catch (error) {
2326
+ if (!isContextGraphAuthorityUnavailableMarker(error))
2327
+ throw error;
2328
+ throw new SwmSenderKeySetupRejectionError(isRetryableContextGraphAuthorityUnavailableReason(error.reason)
2329
+ ? 'agent-gate-pending'
2330
+ : 'agent-gate-unavailable', `Context graph "${pkg.contextGraphId}" SWM peer gate authority is unavailable (${error.reason})`);
2331
+ }
1823
2332
  if (allowedPeers !== null && !allowedPeers.includes(fromPeerId)) {
1824
2333
  throw new SwmSenderKeySetupRejectionError('sender-not-allowed', `Sender peer ${fromPeerId} is not allowed for context graph "${pkg.contextGraphId}"`);
1825
2334
  }
@@ -2148,23 +2657,32 @@ export class WorkspaceCryptoMethods extends DKGAgentBase {
2148
2657
  const path = this.swmSenderKeyStatePath();
2149
2658
  if (!path)
2150
2659
  return;
2151
- const { mkdir, writeFile, chmod } = await import('node:fs/promises');
2152
- const { dirname } = await import('node:path');
2153
- await mkdir(dirname(path), { recursive: true });
2154
- const payload = {
2155
- version: 1,
2156
- send: [...this.swmSenderKeySendStates.values()].map(serializeSwmSenderSendState),
2157
- receive: [...this.swmSenderKeyReceiveStates.values()].map(serializeSwmSenderReceiveState),
2158
- pending: [...this.pendingSenderKeyByAgent.values()]
2159
- .flatMap((queue) => queue.map(serializePendingSenderKeyEntry)),
2160
- };
2161
- await writeFile(path, JSON.stringify(payload, null, 2), { mode: 0o600 });
2162
- try {
2163
- await chmod(path, 0o600);
2164
- }
2165
- catch {
2166
- // Best-effort on platforms/filesystems that do not support chmod.
2167
- }
2660
+ const save = this.swmSenderKeyStateSaveQueue.then(async () => {
2661
+ const { mkdir, writeFile, chmod } = await import('node:fs/promises');
2662
+ const { dirname } = await import('node:path');
2663
+ await mkdir(dirname(path), { recursive: true });
2664
+ // Capture inside the serialized section. A caller queued behind an
2665
+ // in-flight write must persist the state current when its turn begins,
2666
+ // not a stale snapshot captured while the older write was outstanding.
2667
+ const payload = {
2668
+ version: 1,
2669
+ send: [...this.swmSenderKeySendStates.values()].map(serializeSwmSenderSendState),
2670
+ receive: [...this.swmSenderKeyReceiveStates.values()].map(serializeSwmSenderReceiveState),
2671
+ pending: [...this.pendingSenderKeyByAgent.values()]
2672
+ .flatMap((queue) => queue.map(serializePendingSenderKeyEntry)),
2673
+ };
2674
+ await writeFile(path, JSON.stringify(payload, null, 2), { mode: 0o600 });
2675
+ try {
2676
+ await chmod(path, 0o600);
2677
+ }
2678
+ catch {
2679
+ // Best-effort on platforms/filesystems that do not support chmod.
2680
+ }
2681
+ });
2682
+ // A failed save is still reported to its caller, but must not poison the
2683
+ // queue and suppress every later persistence attempt.
2684
+ this.swmSenderKeyStateSaveQueue = save.catch(() => undefined);
2685
+ await save;
2168
2686
  }
2169
2687
  async resolveWorkspaceGossipSigningAgent(contextGraphId) {
2170
2688
  const authority = await withRpcUsageSite(CG_AUTH_RPC_SITES.signer, () => this.resolveContextGraphAgentGateAuthority(contextGraphId));