@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
@@ -6,11 +6,15 @@
6
6
  * the catchup/enable/stats entrypoints. Bodies are a 1:1 move; methods take
7
7
  * `this: DKGAgent` so cross-calls resolve against the composed class.
8
8
  */
9
+ import { orderVmRecoveryCandidates } from './vm-recovery-candidate-order.js';
9
10
  import { createHash } from 'node:crypto';
11
+ import { createSwmHostModeHandler } from './internal/gossip/host-mode-handler.js';
10
12
  import { Buffer } from 'node:buffer';
13
+ import { VmRecoveryCoreTransportPreferencePolicy } from './vm-recovery-core-transport-preference.js';
14
+ import { VmRecoveryPassAuthority } from './vm-recovery-pass-authority.js';
11
15
  import { performance } from 'node:perf_hooks';
12
- import { PROTOCOL_GET_CIPHERTEXT_CHUNK, PROTOCOL_SWM_HOST_CATCHUP, contextGraphWorkspaceTopic, contextGraphWorkspaceGraphUri, contextGraphWorkspaceMetaGraphUri, contextGraphDataUri, contextGraphMetaUri, contextGraphLayerUri, MemoryLayer, GOSSIP_TYPE_WORKSPACE_PUBLISH, decodeGossipEnvelope, decodeEncryptedWorkspacePayload, ENCRYPTED_WORKSPACE_ENVELOPE_TYPE, decodeSwmSenderKeyMessage, SWM_SENDER_KEY_MESSAGE_TYPE, SYSTEM_CONTEXT_GRAPHS, createOperationContext, getMetrics, sparqlString, isSafeIri, assertSafeIri, TRUST_LEVEL_PREDICATE, LEGACY_TRUST_LEVEL_PREDICATE, GOSSIP_TYPE_WORKSPACE_PUBLISH_CHUNKED, ciphertextChunkStoreGraph, ciphertextChunkStoreSubject, CIPHERTEXT_CHUNK_PREDICATE, SUBSCRIPTION_SOURCES, } from '@origintrail-official/dkg-core';
13
- import { GraphManager, isStoreSchedulerBusyError, withDefaultStoreWorkPriority, asChangelogReader, asGraphWriteRevisionSource, tryUpdateWithTouchedGraphs } from '@origintrail-official/dkg-storage';
16
+ import { PROTOCOL_GET_CIPHERTEXT_CHUNK, PROTOCOL_SWM_HOST_CATCHUP, contextGraphWorkspaceTopic, contextGraphDataUri, contextGraphMetaUri, contextGraphLayerUri, MemoryLayer, GOSSIP_TYPE_WORKSPACE_PUBLISH, decodeGossipEnvelope, decodeEncryptedWorkspacePayload, ENCRYPTED_WORKSPACE_ENVELOPE_TYPE, decodeSwmSenderKeyMessage, SWM_SENDER_KEY_MESSAGE_TYPE, SYSTEM_CONTEXT_GRAPHS, createOperationContext, sparqlString, isSafeIri, assertSafeIri, TRUST_LEVEL_PREDICATE, LEGACY_TRUST_LEVEL_PREDICATE, GOSSIP_TYPE_WORKSPACE_PUBLISH_CHUNKED, ciphertextChunkStoreGraph, ciphertextChunkStoreSubject, CIPHERTEXT_CHUNK_PREDICATE, SUBSCRIPTION_SOURCES, } from '@origintrail-official/dkg-core';
17
+ import { GraphManager, isStoreSchedulerBusyError, withDefaultStoreWorkPriority, tryUpdateWithTouchedGraphs } from '@origintrail-official/dkg-storage';
14
18
  import { readMaterializedVersion, shouldApplyMaterialization, withMaterializationLock, isKnowledgeAssetWorkspaceHeadCorruptError, readConfirmedGraphKnowledgeAssetMetadataEnvelope, resolveKnowledgeAssetWorkspaceHead, } from '@origintrail-official/dkg-publisher';
15
19
  import { ethers } from 'ethers';
16
20
  import { isCoreHostedPublicCgRecorded, resolveCoreHostedPublicCgLocalId, } from './core-hosted-public-cg-record-decision.js';
@@ -24,11 +28,16 @@ import { decodeCiphertextChunkCatchupRequest, encodeCiphertextChunkCatchupReques
24
28
  import { orderCatchupPeers } from './p2p/peer-selection.js';
25
29
  import { reconcileContextGraph, VmReconcileSchedulingRuntime, } from './chain-reconciler.js';
26
30
  import { ContextGraphOnChainIdUnresolvedError, VmReconcileUnavailableError, VmReconcileQueueClosedError, } from './vm-reconcile-service.js';
31
+ import { isUnansweredVmReconcileReadAuthority, VmReconcileReadAuthorityUnansweredError, } from './internal/vm-reconcile-read-authority.js';
27
32
  import { createCursorState } from './reconcile-cursor.js';
28
- import { planVmRecoveryMicrobatch, } from './vm-recovery-microbatch.js';
29
- import { enrichVmRecoveryFootprints } from './vm-recovery-footprint.js';
33
+ import { resolveVmRecoveryExperimentPolicy } from './vm-recovery-experiment-policy.js';
34
+ import { VmRecoveryTimingObserver } from './vm-recovery-timing-observer.js';
35
+ import { noteVmReconcilePassEnd, observeVmRecoveryTiming, vmReconcilePassGapMs, } from './vm-recovery-phase-timing.js';
36
+ import { existingVmRecoveryPreparation, vmRecoveryPreparationFor, } from './vm-recovery-preparation.js';
37
+ import { planVmRecoveryTransport, VmRecoveryTransportPreparation, VM_EXACT_MICROBATCH_LIMITS } from './vm-recovery-transport-plan.js';
38
+ import { EXACT_BATCH_STREAM_PROTOCOL } from './sync/exact-batch-stream-contract.js';
39
+ import { exactBatchStreamUnsupported } from './sync/exact-batch-stream-capability.js';
30
40
  import { VmRecoveryProviderPolicy, } from './vm-recovery-provider-policy.js';
31
- import { encodeExactAssetUals, MAX_EXACT_SYNC_ASSETS, } from './sync/exact-assets.js';
32
41
  import { MAX_CONTEXT_GRAPH_ASSET_FETCH_PEERS, ExactAssetFetchLifecycleClosedError, ExactAssetVersionBehindError, exactAssetFetchPeerWindow, runExactAssetFetch, } from './sync/exact-asset-fetch.js';
33
42
  import { isBoundedOperationTimeoutError, runBoundedOperation } from './bounded-operation.js';
34
43
  /** Graph-scoped KA metadata marker; its presence names the metadata graph. */
@@ -76,12 +85,13 @@ function advanceRsHealCursor(cursorMap, cursorKey, bindings, batchSize, maxEntri
76
85
  cursorMap.delete(oldest);
77
86
  }
78
87
  }
79
- import { canReuseVmReconcilePeerTopology, createVmReconcileCleanMissPeerIds, createVmReconcilePeerTopology, encodeLegacyVmReconcilePeerTopologyKey, isVmReconcilePeerTopology, parseLegacyVmReconcilePeerTopologyKey, parseVmReconcileCleanMissPeerIds, UNREADABLE_VM_RECONCILE_PEER_TOPOLOGY, } from './vm-reconcile-peer-topology.js';
80
- import { TIMEOUT_SENTINEL, } from './dkg-agent-constants.js';
88
+ import { SYNC_MIN_GRAPH_BUDGET_MS, TIMEOUT_SENTINEL, } from './dkg-agent-constants.js';
81
89
  import { chainAuthorityReadBudgetsOf } from './chain-authority-read-budgets.js';
82
90
  import { ContextGraphNotFoundError, } from './dkg-agent-types.js';
83
91
  import { DKGAgentBase } from './dkg-agent-base.js';
84
- import { resolveVmReconcilerEnabled } from './sync/backpressure.js';
92
+ import { resolveExactBatchStreamEnabled, resolveVmReconcilerEnabled } from './sync/backpressure.js';
93
+ import { mapWithConcurrencyDrained } from './map-with-concurrency.js';
94
+ import { VM_RECOVERY_SYNC_PRIORITY } from './sync/catchup-policy.js';
85
95
  import { finalizedContextGraphSnapshotMismatchV1 } from './internal/context-graph-authority/finalized-context-graph-binding.js';
86
96
  import { CONTEXT_GRAPH_AUTHORITY_RPC_SITES as CG_AUTH_RPC_SITES, withOwnedRpcRequestContext, withRpcUsageSite, } from '@origintrail-official/dkg-chain';
87
97
  const DEFAULT_HOST_MODE_RECONCILE_BATCH_SIZE = 32;
@@ -108,26 +118,6 @@ function vmReconcileDeploymentMatches(value, expected) {
108
118
  * amplify into a per-message `eth_call` (Branimir review #1239 follow-on).
109
119
  */
110
120
  const HOST_MODE_PUBLISH_POLICY_MAX_CACHE_AGE_MS = 5_000;
111
- // Exact VM data responses are page-only and capped at 64 rows per page. Keep
112
- // one recovery microbatch near 64 non-empty pages so a single large graph does
113
- // not monopolize the global sync admission. This is a soft scheduling/fairness
114
- // cap, not a wire or correctness limit: an individually larger KA is still
115
- // admitted alone and remains bounded by the exact executor's hard guards.
116
- const VM_EXACT_MICROBATCH_PAGE_FAIRNESS_LEAVES = 4096n;
117
- const VM_EXACT_MICROBATCH_LIMITS = Object.freeze({
118
- // Executor capability: the current exact-sync envelope accepts at most ten.
119
- maxAssets: MAX_EXACT_SYNC_ASSETS,
120
- // Soft recovery-window targets. A single public KA may exceed either; it is
121
- // still admitted alone while the exact executor's own hard byte/quad guards
122
- // remain authoritative.
123
- targetBytes: 24n * 1024n * 1024n,
124
- targetLeaves: VM_EXACT_MICROBATCH_PAGE_FAIRNESS_LEAVES,
125
- fixedBytesPerAsset: 64n * 1024n,
126
- bytesPerLeafOverhead: 128n,
127
- byteSizeMultiplierBps: 11500n,
128
- // Hard exact-selector cap, evaluated with the executor's real encoder.
129
- maxSelectorBytes: 16 * 1024,
130
- });
131
121
  /**
132
122
  * Recovery-target root of an ordinal whose KA the node holds nowhere locally:
133
123
  * the sweep queues it without reading the chain root. Exact recovery fetches
@@ -322,6 +312,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
322
312
  * reserved, or when the node is already subscribed in member mode.
323
313
  */
324
314
  async reconcileSwmHostModeSubscription(contextGraphId, source = SUBSCRIPTION_SOURCES.RECONCILER) {
315
+ const session = this.gossipSession;
316
+ const live = session.live();
317
+ if (live === null)
318
+ return;
325
319
  if (!this.swmHostModeStore)
326
320
  return;
327
321
  if (Object.values(SYSTEM_CONTEXT_GRAPHS).includes(contextGraphId))
@@ -357,7 +351,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
357
351
  await this.isCuratedForHostMode(contextGraphId)) {
358
352
  // A manually hosted CG can become curated later. Upgrade the cached
359
353
  // classification so the existing handler starts stripping immediately.
360
- this.swmHostModeCurated.set(hostKey, true);
354
+ if (!session.active || this.gossipSession !== session)
355
+ return;
356
+ session.swmHostModeCurated.set(hostKey, true);
361
357
  }
362
358
  await this.maybeMarkRegisteredForHostMode(contextGraphId);
363
359
  return;
@@ -392,7 +388,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
392
388
  // critically, the host-only-core case where there's no local
393
389
  // `_meta` and `isPrivateContextGraph` alone returns false.
394
390
  const curated = await this.isCuratedForHostMode(contextGraphId);
395
- if (!curated)
391
+ if (!curated || !session.active || this.gossipSession !== session)
396
392
  return;
397
393
  // OT-RFC-49 WS-A — the private-ciphertext strip. With `stripCiphertext`
398
394
  // ON (default), a core declines ALL host-mode custody for a curated CG:
@@ -512,15 +508,19 @@ export class SwmHostModeMethods extends DKGAgentBase {
512
508
  * so the host-mode lifecycle is in one place.
513
509
  */
514
510
  wireSwmHostModeHandler(contextGraphId, source = SUBSCRIPTION_SOURCES.RECONCILER, curated = true) {
511
+ const session = this.gossipSession;
512
+ const live = session.live();
513
+ if (live === null)
514
+ return;
515
515
  if (!this.rfc64LegacySwmGossipAllowedForContextGraph(contextGraphId)) {
516
516
  const hostKey = this.canonicalSwmHostModeKey(contextGraphId);
517
- const hadRuntimeHostState = this.swmHostModeHandlers.has(hostKey)
518
- || this.swmHostModeSubscribed.has(hostKey)
519
- || this.swmHostModeCurated.has(hostKey);
517
+ const hadRuntimeHostState = session.swmHostModeHandlers.has(hostKey)
518
+ || session.swmHostModeSubscribed.has(hostKey)
519
+ || session.swmHostModeCurated.has(hostKey);
520
520
  this.unwireSwmHostModeHandler(contextGraphId);
521
- const deletedStaleHandler = this.swmHostModeHandlers.delete(hostKey);
522
- const deletedStaleSubscription = this.swmHostModeSubscribed.delete(hostKey);
523
- const deletedStaleClassification = this.swmHostModeCurated.delete(hostKey);
521
+ const deletedStaleHandler = session.swmHostModeHandlers.delete(hostKey);
522
+ const deletedStaleSubscription = session.swmHostModeSubscribed.delete(hostKey);
523
+ const deletedStaleClassification = session.swmHostModeCurated.delete(hostKey);
524
524
  if (deletedStaleHandler || deletedStaleSubscription || deletedStaleClassification) {
525
525
  // Heal partially restored/stale bookkeeping even when its handler
526
526
  // reference is absent, which makes unwireSwmHostModeHandler a no-op.
@@ -548,7 +548,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
548
548
  // genuine no-op (instead of silently wiring a second handler on
549
549
  // the same topic).
550
550
  const wireCgId = this.canonicalSwmHostModeKey(contextGraphId);
551
- if (this.swmHostModeHandlers.has(wireCgId)) {
551
+ if (session.swmHostModeHandlers.has(wireCgId)) {
552
552
  // Idempotent re-entry — preserve the original source. The first
553
553
  // discovery path to wire the handler wins the provenance label;
554
554
  // a later path covering the same CG is "also true" but the
@@ -556,44 +556,12 @@ export class SwmHostModeMethods extends DKGAgentBase {
556
556
  return;
557
557
  }
558
558
  const swmTopic = contextGraphWorkspaceTopic(wireCgId);
559
- this.swmHostModeSubscribed.set(wireCgId, source);
560
- this.swmHostModeCurated.set(wireCgId, curated);
561
- this.gossip.subscribe(swmTopic);
562
- const handler = (_topic, data, from) => {
563
- // Fail closed when the classification is absent. Only an explicitly
564
- // non-curated manual subscription retains the public host-mode hatch.
565
- if (this.swmHostModeStripCiphertext() &&
566
- this.swmHostModeCurated.get(wireCgId) !== false) {
567
- this.log.debug(createOperationContext('share'), `Dropping host-mode envelope on cg=${contextGraphId} from=${from}: ` +
568
- `private-ciphertext strip is ON for a curated CG (OT-RFC-49 WS-A)`);
569
- return;
570
- }
571
- // OT-RFC-38 LU-11: peek envelope type and dispatch. Chunked
572
- // envelopes (`type='share-write-chunked'`) take the V2 chunk
573
- // persistence path; everything else flows through the legacy
574
- // host-mode store unchanged. Failed decode falls through to
575
- // `ingestSwmHostModeEnvelope` which is also defensive — the
576
- // dispatch here is best-effort, not a security boundary.
577
- let envelopeType;
578
- try {
579
- const peek = decodeGossipEnvelope(data);
580
- envelopeType = peek?.type;
581
- }
582
- catch { /* drop into legacy path */ }
583
- if (envelopeType === GOSSIP_TYPE_WORKSPACE_PUBLISH_CHUNKED) {
584
- this.ingestSwmCiphertextChunkEnvelope(contextGraphId, data, from).catch((err) => {
585
- const msg = err instanceof Error ? err.message : String(err);
586
- this.log.warn(createOperationContext('system'), `LU-11: chunked SWM ingest failed for "${contextGraphId}": ${msg}`);
587
- });
588
- return;
589
- }
590
- this.ingestSwmHostModeEnvelope(contextGraphId, data, from).catch((err) => {
591
- const msg = err instanceof Error ? err.message : String(err);
592
- this.log.warn(createOperationContext('system'), `Host-mode SWM ingest failed for "${contextGraphId}": ${msg}`);
593
- });
594
- };
595
- this.swmHostModeHandlers.set(wireCgId, handler);
596
- this.gossip.onMessage(swmTopic, handler);
559
+ session.swmHostModeSubscribed.set(wireCgId, source);
560
+ session.swmHostModeCurated.set(wireCgId, curated);
561
+ live.manager.subscribe(swmTopic);
562
+ const handler = createSwmHostModeHandler(this, this.log, session, contextGraphId, wireCgId);
563
+ session.swmHostModeHandlers.set(wireCgId, handler);
564
+ live.manager.onMessage(swmTopic, handler);
597
565
  // B3: persist the host-mode designation so a restart re-engages
598
566
  // this handler before the chain-event poller catches up.
599
567
  // Codex PR #620 R2: chain wire/unwire writes through a per-CG
@@ -1632,7 +1600,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
1632
1600
  }
1633
1601
  if (!authOk) {
1634
1602
  try {
1635
- const allowedPeers = await this.getContextGraphAllowedPeers(req.contextGraphId);
1603
+ const allowedPeers = await this.resolveSwmAllowedPeersForCurrentAuthority(req.contextGraphId);
1636
1604
  if (allowedPeers !== null) {
1637
1605
  anyAuthorityFound = true;
1638
1606
  if (allowedPeers.includes(fromPeerId))
@@ -3246,12 +3214,18 @@ export class SwmHostModeMethods extends DKGAgentBase {
3246
3214
  this.log.debug(createOperationContext('system'), `VM reconcile for "${localCgId}" stopped: superseded by cleartext adoption of "${supersedingId}"`);
3247
3215
  return;
3248
3216
  }
3217
+ // Reported where the graph was parked (executeVmReconcileForCg): it
3218
+ // is asked again shortly and does not wait for the sweep.
3219
+ if (err instanceof VmReconcileReadAuthorityUnansweredError)
3220
+ return;
3249
3221
  this.log.warn(createOperationContext('system'), `VM reconcile for "${localCgId}" failed; retrying on the periodic sweep: ${err instanceof Error ? err.message : String(err)}`);
3250
3222
  }, {
3251
3223
  concurrency: DKGAgentBase.VM_RECONCILE_CONCURRENCY,
3252
3224
  maxPending: DKGAgentBase.VM_RECONCILE_QUEUE_MAX_PENDING,
3253
3225
  maxForegroundBurst: DKGAgentBase.VM_RECONCILE_MAX_FOREGROUND_BURST,
3254
- }, DKGAgentBase.VM_RECONCILE_UNBOUND_BATCH_SIZE);
3226
+ discoveryBatchSize: DKGAgentBase.VM_RECONCILE_UNBOUND_BATCH_SIZE,
3227
+ periodicBoundBatchSize: DKGAgentBase.VM_RECONCILE_PERIODIC_BOUND_BATCH_SIZE,
3228
+ });
3255
3229
  this.vmReconcileScheduling = scheduling;
3256
3230
  }
3257
3231
  return scheduling;
@@ -3276,7 +3250,37 @@ export class SwmHostModeMethods extends DKGAgentBase {
3276
3250
  ? work()
3277
3251
  : withOwnedRpcRequestContext({ requestClass: 'background', ...(lifecycleSignal ? { signal: lifecycleSignal } : {}) }, () => withDefaultStoreWorkPriority('background', work));
3278
3252
  const physicalRun = runInLane(async () => {
3279
- const target = await this.resolveVmReconcileTarget(localCgId, isLifecycleCurrent, lifecycleSignal);
3253
+ const passStartedAt = performance.now();
3254
+ const passGapMs = vmReconcilePassGapMs(this, localCgId, passStartedAt);
3255
+ const target = await this.resolveVmReconcileTarget(localCgId, isLifecycleCurrent, lifecycleSignal).catch((err) => {
3256
+ if (!(err instanceof VmReconcileReadAuthorityUnansweredError))
3257
+ throw err;
3258
+ // The chain read behind the read-authority check got no answer, which
3259
+ // is the node's RPC budget or its endpoint and not the graph. An
3260
+ // automatic pass leaves the graph waiting to be asked again; without
3261
+ // that its next attempt is the periodic sweep, up to a full interval
3262
+ // after the read would have been answered. It is asked again only
3263
+ // while its fetch could start, the readiness every waiter uses. An
3264
+ // operator's request, and a graph the wait cannot take, end as they
3265
+ // always did.
3266
+ const waiting = source === 'manual'
3267
+ ? undefined
3268
+ : this.vmReconcileScheduling?.deferForReadAuthority(localCgId, {
3269
+ signal: lifecycleSignal,
3270
+ isCurrent: isLifecycleCurrent,
3271
+ canAdmit: () => this.vmRecoverySyncAdmissionAvailable(localCgId),
3272
+ });
3273
+ if (waiting === undefined)
3274
+ throw new ContextGraphNotFoundError(localCgId);
3275
+ const message = `VM reconcile for "${localCgId}" is waiting for read authority `
3276
+ + `(${err.readAuthority}): the chain read got no answer; asking again shortly`;
3277
+ if (waiting === 'parked')
3278
+ this.log.info(createOperationContext('system'), message);
3279
+ else
3280
+ this.log.debug(createOperationContext('system'), message);
3281
+ throw err;
3282
+ });
3283
+ const resolveTargetMs = performance.now() - passStartedAt;
3280
3284
  const isTargetCurrent = () => isLifecycleCurrent()
3281
3285
  && this.isVmReconcileTargetCurrent(localCgId, target, lifecycleGeneration);
3282
3286
  if (!isTargetCurrent())
@@ -3296,10 +3300,12 @@ export class SwmHostModeMethods extends DKGAgentBase {
3296
3300
  };
3297
3301
  let pendingWatermark;
3298
3302
  let result;
3303
+ let engineTimings;
3299
3304
  try {
3300
3305
  result = await reconcileContextGraph(this.createVmReconcileDeps(localCgId, lifecycleGeneration, target, lifecycleSignal, {
3301
3306
  identityCursor: target.cursor,
3302
3307
  persistWatermark: (_lcg, watermark) => { pendingWatermark = watermark; },
3308
+ observePassTimings: (timings) => { engineTimings = timings; },
3303
3309
  }), workingCursor, localCgId, target.onChainCgId);
3304
3310
  }
3305
3311
  catch (err) {
@@ -3319,16 +3325,22 @@ export class SwmHostModeMethods extends DKGAgentBase {
3319
3325
  }
3320
3326
  if (!isTargetCurrent())
3321
3327
  throw new VmReconcileQueueClosedError();
3328
+ let flushMs = 0;
3322
3329
  if (result.reconciled > 0 || pendingWatermark !== undefined) {
3330
+ const flushStartedAt = performance.now();
3323
3331
  await this.store.flush?.({
3324
3332
  priority: 'background',
3325
3333
  source: 'agent.vmReconcile.materialization.flush',
3326
3334
  });
3335
+ flushMs = performance.now() - flushStartedAt;
3327
3336
  if (!isTargetCurrent())
3328
3337
  throw new VmReconcileQueueClosedError();
3329
3338
  }
3339
+ let watermarkMs = 0;
3330
3340
  if (pendingWatermark !== undefined) {
3341
+ const watermarkStartedAt = performance.now();
3331
3342
  await this.persistVmReconcileWatermark(localCgId, pendingWatermark, target);
3343
+ watermarkMs = performance.now() - watermarkStartedAt;
3332
3344
  if (!isTargetCurrent())
3333
3345
  throw new VmReconcileQueueClosedError();
3334
3346
  }
@@ -3337,11 +3349,40 @@ export class SwmHostModeMethods extends DKGAgentBase {
3337
3349
  target.cursor.scanOrdinal = workingCursor.scanOrdinal;
3338
3350
  const response = this.toContextGraphReconcileResult(localCgId, source, target, result);
3339
3351
  this.emitVmReconcileTelemetry(localCgId, target, result, response.status);
3352
+ observeVmRecoveryTiming(() => {
3353
+ const timings = engineTimings;
3354
+ this.log.info(createOperationContext('system'), `VM reconcile pass timing for "${localCgId}": source=${source} `
3355
+ + `gapSincePreviousPassMs=${passGapMs === undefined ? '-' : Math.round(passGapMs)} `
3356
+ + `resolveTargetMs=${Math.round(resolveTargetMs)} `
3357
+ + `headReadMs=${Math.round(timings?.headReadMs ?? 0)} blockReadMs=${Math.round(timings?.blockReadMs ?? 0)} `
3358
+ + `scanMs=${Math.round(timings?.scanMs ?? 0)} recoverMs=${Math.round(timings?.recoverMs ?? 0)} `
3359
+ + `engineMs=${Math.round(timings?.totalMs ?? 0)} flushMs=${Math.round(flushMs)} `
3360
+ + `watermarkMs=${Math.round(watermarkMs)} totalMs=${Math.round(performance.now() - passStartedAt)} `
3361
+ + `processed=${result.processed} reconciled=${result.reconciled} pending=${result.pending} `
3362
+ + `continue=${result.shouldContinueImmediately ? 1 : 0}`);
3363
+ });
3340
3364
  // The reconciler owns the continuation policy: productive slices, stale
3341
3365
  // bindings, and explicit provider rotations continue immediately, while
3342
3366
  // pending-only historical inventory yields to the periodic sweep.
3343
- if (isLifecycleCurrent() && result.shouldContinueImmediately) {
3344
- this.vmReconcileScheduling?.triggerLive(localCgId);
3367
+ const localAdmissionWait = {
3368
+ signal: lifecycleSignal,
3369
+ isCurrent: isTargetCurrent,
3370
+ canAdmit: () => this.vmRecoverySyncAdmissionAvailable(localCgId),
3371
+ };
3372
+ if (isTargetCurrent() && result.localAdmissionDeferred) {
3373
+ this.vmReconcileScheduling?.retryLocalAdmission(localCgId, localAdmissionWait);
3374
+ }
3375
+ else if (isLifecycleCurrent() && result.shouldContinueImmediately) {
3376
+ // A graph that just fetched from peers takes its next turn behind the
3377
+ // graphs the node's sync admission refused while it did. Without that
3378
+ // the freed capacity goes back to whichever pass asks first, which is
3379
+ // this one, and the others repeat their passes to be refused again.
3380
+ const yielded = result.recoveryAttempted === true
3381
+ && !result.staleTarget
3382
+ && isTargetCurrent()
3383
+ && this.vmReconcileScheduling?.yieldLocalAdmissionTurn(localCgId, localAdmissionWait) === true;
3384
+ if (!yielded)
3385
+ this.vmReconcileScheduling?.triggerLive(localCgId);
3345
3386
  }
3346
3387
  else if (isTargetCurrent()) {
3347
3388
  // RS heal is bounded, best-effort maintenance. Run it only after the
@@ -3362,6 +3403,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
3362
3403
  if (!isTargetCurrent())
3363
3404
  throw new VmReconcileQueueClosedError();
3364
3405
  }
3406
+ noteVmReconcilePassEnd(this, localCgId, performance.now());
3365
3407
  return response;
3366
3408
  });
3367
3409
  trackVmReconcilePhysicalRun(this.vmReconcilePhysicalRuns, physicalRun);
@@ -3448,8 +3490,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
3448
3490
  // Central defense for periodic, live-chain, and manual reconciliation.
3449
3491
  // Every dispatcher entry point converges here and must independently prove
3450
3492
  // read authority. Never let a persisted subscription authorize itself.
3493
+ let authorityDecision;
3451
3494
  const authorityRead = withRpcUsageSite(CG_AUTH_RPC_SITES.vmReconcile, () => this.canReadContextGraph(localCgId, {
3452
3495
  allowSubscriptionFallback: false,
3496
+ onReadAuthorityDecision: (decision) => { authorityDecision = decision; },
3453
3497
  }));
3454
3498
  // Cancellation releases the dispatcher worker, but an underlying store/RPC
3455
3499
  // read may ignore it. Keep that physical dependency in the shutdown drain.
@@ -3457,8 +3501,16 @@ export class SwmHostModeMethods extends DKGAgentBase {
3457
3501
  const canRead = await raceVmReconcileAbort(authorityRead, signal).catch(() => false);
3458
3502
  if (!isCurrent())
3459
3503
  throw new VmReconcileQueueClosedError();
3460
- if (!canRead)
3504
+ if (!canRead) {
3505
+ // Still a refusal for this pass. The class only records that the chain
3506
+ // never answered, so scheduling can ask again instead of treating the
3507
+ // graph as unreadable until the next sweep.
3508
+ const decision = authorityDecision;
3509
+ if (isUnansweredVmReconcileReadAuthority(decision)) {
3510
+ throw new VmReconcileReadAuthorityUnansweredError(localCgId, decision);
3511
+ }
3461
3512
  throw new ContextGraphNotFoundError(localCgId);
3513
+ }
3462
3514
  if (this.contextGraphBindingState.currentBindingFor(localCgId, sub) === undefined
3463
3515
  && sub.subscribed) {
3464
3516
  await this.selfPrimeSubscriptionOnChainId(localCgId, sub, undefined, isCurrent, signal);
@@ -3749,7 +3801,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
3749
3801
  reconcileOrdinal: (lcg, ocg, ordinal, headBlock, context) => this.reconcileChainOrdinal(lcg, ocg, ordinal, headBlock, {
3750
3802
  isTargetCurrent,
3751
3803
  revalidateTarget,
3752
- deferActiveFetch: true,
3753
3804
  rememberFinalizedEvidence: context === undefined
3754
3805
  || context.headOrdinal - ordinal <= DKGAgentBase.VM_RECONCILE_BATCH_SIZE,
3755
3806
  }),
@@ -3784,6 +3835,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
3784
3835
  },
3785
3836
  confirmationDepth: DKGAgentBase.VM_RECONCILE_CONFIRMATION_DEPTH,
3786
3837
  log: (msg) => this.log.info(createOperationContext('system'), msg),
3838
+ ...(execution?.observePassTimings ? { observePassTimings: execution.observePassTimings } : {}),
3787
3839
  };
3788
3840
  }
3789
3841
  async persistVmReconcileWatermark(localCgId, watermark, target) {
@@ -4162,425 +4214,29 @@ export class SwmHostModeMethods extends DKGAgentBase {
4162
4214
  return { status: 'skipped', reason: 'failed' };
4163
4215
  }
4164
4216
  }
4165
- async collectVmReconcileSwmCandidateState(localCgId) {
4166
- const candidateNamespaces = await this.collectVmReconcileSwmCandidateNamespacesBestEffort(localCgId);
4167
- return {
4168
- candidateNamespaces: candidateNamespaces.namespaces,
4169
- swmGen: await this.readVmReconcileSwmGen(candidateNamespaces.namespaces),
4170
- peerTopology: await this.vmReconcilePeerTopology(localCgId),
4171
- cleanMissPeerIds: [],
4172
- };
4173
- }
4174
- vmReconcileRootSwmCandidateNamespaces(localCgId) {
4175
- return [{
4176
- metaGraph: contextGraphWorkspaceMetaGraphUri(localCgId),
4177
- dataGraph: contextGraphWorkspaceGraphUri(localCgId),
4178
- }];
4179
- }
4180
- async collectVmReconcileSwmCandidateNamespaces(localCgId) {
4181
- const graphManager = new GraphManager(this.store);
4182
- // `listSubGraphs` finds sub-graphs by their data or `_meta` graphs; a
4183
- // hosted-only core holds only `<cg>/<sub>/_shared_memory*` copies, which
4184
- // its StorageACK ledger names.
4185
- const subGraphs = [...new Set([
4186
- ...await graphManager.listSubGraphs(localCgId),
4187
- ...await this.storageAckLedgerNamespaceSubGraphs(localCgId),
4188
- ])];
4189
- const subGraphNamespaces = subGraphs
4190
- .map((sg) => ({
4191
- metaGraph: graphManager.sharedMemoryMetaUri(localCgId, sg),
4192
- dataGraph: graphManager.sharedMemoryUri(localCgId, sg),
4193
- }))
4194
- .sort((a, b) => `${a.metaGraph}\0${a.dataGraph}`.localeCompare(`${b.metaGraph}\0${b.dataGraph}`));
4195
- return [
4196
- ...this.vmReconcileRootSwmCandidateNamespaces(localCgId),
4197
- ...subGraphNamespaces,
4198
- ];
4199
- }
4200
- async collectVmReconcileSwmCandidateNamespacesBestEffort(localCgId) {
4201
- try {
4202
- return { namespaces: await this.collectVmReconcileSwmCandidateNamespaces(localCgId), complete: true };
4203
- }
4204
- catch {
4205
- return { namespaces: this.vmReconcileRootSwmCandidateNamespaces(localCgId), complete: false };
4206
- }
4207
- }
4208
- vmReconcileSwmNamespaceKey(candidateNamespaces) {
4209
- return candidateNamespaces
4210
- .map((namespace) => `${namespace.metaGraph}\0${namespace.dataGraph}`)
4211
- .sort()
4212
- .join('\n');
4213
- }
4214
- async vmReconcilePeerTopology(localCgId) {
4215
- try {
4216
- const preferredPeerId = await this.resolvePreferredSyncPeerId(localCgId);
4217
- const isPrivateContextGraph = await this.isPrivateContextGraph(localCgId);
4218
- const libp2p = this.node?.libp2p;
4219
- const getConnections = libp2p?.getConnections;
4220
- if (typeof getConnections !== 'function')
4221
- return UNREADABLE_VM_RECONCILE_PEER_TOPOLOGY;
4222
- const peerIds = [...new Map(getConnections.call(libp2p)
4223
- .map((connection) => [connection.remotePeer?.toString(), connection.remotePeer])
4224
- .filter((entry) => typeof entry[0] === 'string' && entry[0].length > 0 && !!entry[1])).keys()].sort();
4225
- const orderedPeers = this.selectCatchupPeers(peerIds.map((peerId) => ({ toString: () => peerId })), preferredPeerId, isPrivateContextGraph);
4226
- return createVmReconcilePeerTopology({
4227
- preferredPeerId: preferredPeerId ?? null,
4228
- privateOnly: isPrivateContextGraph,
4229
- peers: orderedPeers.map((peer) => {
4230
- const peerId = peer.toString();
4231
- return {
4232
- peerId,
4233
- core: this.peerCapabilityRegistry.supportsCore(peerId),
4234
- };
4235
- }),
4236
- });
4237
- }
4238
- catch {
4239
- return UNREADABLE_VM_RECONCILE_PEER_TOPOLOGY;
4240
- }
4241
- }
4242
- async readVmReconcileSwmGen(candidateNamespaces) {
4243
- if (candidateNamespaces.length === 0)
4244
- return 'empty:0';
4245
- try {
4246
- // The changelog cursor is a durable write generation: it survives daemon
4247
- // restart, advances at the store mutation choke point, and is O(1) after
4248
- // its one-time seed. It lets large (> fingerprint cap) stores reuse a
4249
- // proven negative without reconstructing or sampling their SWM content.
4250
- const changelog = asChangelogReader(this.store);
4251
- if (changelog) {
4252
- const head = await changelog.changelogHead({
4253
- priority: 'background',
4254
- source: 'agent.vmReconcile.negativeGeneration',
4255
- });
4256
- return `changelog:${head.era}:${head.seq}`;
4257
- }
4258
- const parts = [];
4259
- const digestRows = (rows) => createHash('sha256').update(rows.join('\n'), 'utf8').digest('hex');
4260
- const maxRows = DKGAgentBase.VM_RECONCILE_SWM_GEN_FINGERPRINT_MAX_ROWS;
4261
- const isTooLarge = (rows) => rows.length > maxRows;
4262
- for (const namespace of candidateNamespaces) {
4263
- const metaGraph = assertSafeIri(namespace.metaGraph);
4264
- const dataGraph = assertSafeIri(namespace.dataGraph);
4265
- const operationRows = await this.store.query(`SELECT ?op ?root ?ts WHERE {
4266
- GRAPH <${metaGraph}> {
4267
- ?op <http://dkg.io/ontology/rootEntity> ?root .
4268
- OPTIONAL { ?op <http://dkg.io/ontology/publishedAt> ?ts . }
4269
- }
4270
- } ORDER BY ?op ?root ?ts LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.operations' });
4271
- if (operationRows.type !== 'bindings')
4272
- return null;
4273
- if (isTooLarge(operationRows.bindings))
4274
- return null;
4275
- const operations = operationRows.bindings
4276
- .map((row) => [
4277
- String(row['op'] ?? ''),
4278
- String(row['root'] ?? ''),
4279
- String(row['ts'] ?? ''),
4280
- ].join('\0'))
4281
- .sort();
4282
- const dataRows = await this.store.query(`SELECT ?s ?p ?o WHERE {
4283
- GRAPH <${dataGraph}> { ?s ?p ?o . }
4284
- } ORDER BY ?s ?p ?o LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.data' });
4285
- if (dataRows.type !== 'bindings')
4286
- return null;
4287
- if (isTooLarge(dataRows.bindings))
4288
- return null;
4289
- const dataTriples = dataRows.bindings
4290
- .map((row) => [
4291
- String(row['s'] ?? ''),
4292
- String(row['p'] ?? ''),
4293
- String(row['o'] ?? ''),
4294
- ].join('\0'))
4295
- .sort();
4296
- const privateRootRows = await this.store.query(`SELECT ?privateEntity ?privateRoot WHERE {
4297
- GRAPH <${metaGraph}> {
4298
- ?privateEntity <http://dkg.io/ontology/privateMerkleRoot> ?privateRoot .
4299
- }
4300
- } ORDER BY ?privateEntity ?privateRoot LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.privateRoots' });
4301
- if (privateRootRows.type !== 'bindings')
4302
- return null;
4303
- if (isTooLarge(privateRootRows.bindings))
4304
- return null;
4305
- const privateRoots = privateRootRows.bindings
4306
- .map((row) => [
4307
- String(row['privateEntity'] ?? ''),
4308
- String(row['privateRoot'] ?? ''),
4309
- ].join('\0'))
4310
- .sort();
4311
- parts.push([
4312
- `meta:${namespace.metaGraph}`,
4313
- `data:${namespace.dataGraph}`,
4314
- `ops:${operations.length}`,
4315
- `opHash:${digestRows(operations)}`,
4316
- `dataTriples:${dataTriples.length}`,
4317
- `dataHash:${digestRows(dataTriples)}`,
4318
- `privateRoots:${privateRoots.length}`,
4319
- `privateRootHash:${digestRows(privateRoots)}`,
4320
- ].join(';'));
4321
- }
4322
- // Catch writes into a newly-created/unregistered namespace that the
4323
- // current namespace enumeration cannot yet name. This process-local term
4324
- // complements (rather than replaces) the content fingerprint: after a
4325
- // restart the fingerprint remains the correctness gate while the counter
4326
- // restarts harmlessly.
4327
- const rootDataGraph = candidateNamespaces[0]?.dataGraph ?? '';
4328
- const swmSuffix = rootDataGraph.indexOf('/_shared_memory');
4329
- const graphPrefix = swmSuffix >= 0 ? `${rootDataGraph.slice(0, swmSuffix)}/` : rootDataGraph;
4330
- const writeRevision = asGraphWriteRevisionSource(this.store)?.getWriteRevision(graphPrefix);
4331
- // A remote mutation can still commit after this fingerprint's reads.
4332
- // Never turn such an in-flight/indeterminate observation into a stable
4333
- // negative-cache key.
4334
- if (writeRevision && !writeRevision.stable)
4335
- return null;
4336
- if (writeRevision)
4337
- parts.push(`writeGen:${writeRevision.generation}`);
4338
- return parts.join('|');
4339
- }
4340
- catch {
4341
- // Probe failures are not a stable SWM generation. Callers must not cache
4342
- // or preserve a negative-cache gate from this result.
4343
- }
4344
- return null;
4345
- }
4346
- vmReconcileSwmGenHasOperations(swmGen) {
4347
- return swmGen.split('|').some((part) => {
4348
- const match = /(?:^|;)ops:(\d+)(?:;|$)/.exec(part);
4349
- return match ? Number(match[1]) > 0 : false;
4350
- });
4351
- }
4352
- vmReconcileSwmGenSupportsDurableNegative(swmGen) {
4353
- // A changelog cursor covers every descendant graph mutation. The fallback
4354
- // fingerprint covers only the bare SWM bucket, so an operation whose data
4355
- // later lands in a per-KA child graph must fail open after restart.
4356
- return swmGen.startsWith('changelog:') || !this.vmReconcileSwmGenHasOperations(swmGen);
4357
- }
4358
- vmReconcileSwmGenContainsSnapshot(cachedSwmGen, currentSwmGen) {
4359
- return cachedSwmGen === currentSwmGen || cachedSwmGen.split('|').includes(currentSwmGen);
4360
- }
4361
- vmReconcileWorkspaceOperationPattern(candidateMetaGraphs) {
4362
- const branches = [];
4363
- for (const graph of candidateMetaGraphs) {
4364
- try {
4365
- branches.push(`{ GRAPH <${assertSafeIri(graph)}> {
4366
- ?op <http://dkg.io/ontology/rootEntity> ?root .
4367
- OPTIONAL { ?op <http://dkg.io/ontology/publishedAt> ?ts . }
4368
- } }`);
4369
- }
4370
- catch {
4371
- // Skip unsafe graph names instead of building a malformed query.
4372
- }
4373
- }
4374
- return branches.join(' UNION ');
4375
- }
4376
- deleteVmReconcileNegativeCacheEntry(cacheKey) {
4377
- const existing = this.vmReconcileNegativeCache.get(cacheKey);
4378
- this.markVmReconcileNegativeCacheHydrated(cacheKey, existing?.localCgId ?? cacheKey.slice(0, Math.max(0, cacheKey.indexOf('\0'))));
4379
- if (existing) {
4380
- this.vmReconcileNegativeCache.delete(cacheKey);
4381
- const keys = this.vmReconcileNegativeCacheKeysByCg.get(existing.localCgId);
4382
- if (keys) {
4383
- keys.delete(cacheKey);
4384
- if (keys.size === 0)
4385
- this.vmReconcileNegativeCacheKeysByCg.delete(existing.localCgId);
4386
- }
4387
- }
4388
- void this.config.contextGraphSubscriptionStore?.deleteVmReconcileNegative?.(cacheKey).catch(() => {
4389
- // The in-memory invalidation remains authoritative for this process.
4390
- });
4391
- }
4392
- indexVmReconcileNegativeCacheEntry(localCgId, cacheKey) {
4393
- let keys = this.vmReconcileNegativeCacheKeysByCg.get(localCgId);
4394
- if (!keys) {
4395
- keys = new Set();
4396
- this.vmReconcileNegativeCacheKeysByCg.set(localCgId, keys);
4397
- }
4398
- keys.add(cacheKey);
4399
- }
4400
- markVmReconcileNegativeCacheHydrated(cacheKey, localCgId) {
4401
- // Access order keeps actively reused keys resident while old one-shot
4402
- // misses fall out. Eviction is fail-open: it only permits another durable
4403
- // lookup if the same key is encountered later.
4404
- this.vmReconcileNegativeCacheHydrated.delete(cacheKey);
4405
- this.vmReconcileNegativeCacheHydrated.set(cacheKey, localCgId);
4406
- while (this.vmReconcileNegativeCacheHydrated.size
4407
- > DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
4408
- const oldestKey = this.vmReconcileNegativeCacheHydrated.keys().next().value;
4409
- if (oldestKey === undefined)
4410
- break;
4411
- this.vmReconcileNegativeCacheHydrated.delete(oldestKey);
4412
- }
4413
- }
4414
- async shouldDeferVmReconcileByNegativeCache(cacheKey, localCgId) {
4415
- let cached = this.vmReconcileNegativeCache.get(cacheKey);
4416
- const durableStateAlreadyConsulted = this.vmReconcileNegativeCacheHydrated.has(cacheKey);
4417
- this.markVmReconcileNegativeCacheHydrated(cacheKey, localCgId);
4418
- if (!cached && !durableStateAlreadyConsulted) {
4419
- try {
4420
- const durable = await this.config.contextGraphSubscriptionStore
4421
- ?.loadVmReconcileNegative?.(cacheKey);
4422
- const durablePeerTopology = durable
4423
- ? isVmReconcilePeerTopology(durable.peerTopology)
4424
- ? durable.peerTopology
4425
- : parseLegacyVmReconcilePeerTopologyKey(durable.peerTopologyKey)
4426
- : null;
4427
- const durableCleanMissPeerIds = durablePeerTopology
4428
- ? parseVmReconcileCleanMissPeerIds(durable?.cleanMissPeerIds ?? [], durablePeerTopology)
4429
- : null;
4430
- if (durable &&
4431
- durable.cacheKey === cacheKey &&
4432
- durable.localCgId === localCgId &&
4433
- Number.isInteger(durable.failures) && durable.failures > 0 &&
4434
- Number.isFinite(durable.nextRetryAt) &&
4435
- typeof durable.swmGen === 'string' &&
4436
- Array.isArray(durable.candidateNamespaces) &&
4437
- durable.candidateNamespaces.every((item) => typeof item?.metaGraph === 'string' && typeof item?.dataGraph === 'string') &&
4438
- durablePeerTopology !== null &&
4439
- durableCleanMissPeerIds !== null) {
4440
- if (!this.vmReconcileSwmGenSupportsDurableNegative(durable.swmGen)) {
4441
- await this.config.contextGraphSubscriptionStore
4442
- ?.deleteVmReconcileNegative?.(cacheKey);
4443
- }
4444
- else {
4445
- cached = {
4446
- localCgId: durable.localCgId,
4447
- failures: durable.failures,
4448
- nextRetryAt: durable.nextRetryAt,
4449
- swmGen: durable.swmGen,
4450
- candidateNamespaces: durable.candidateNamespaces,
4451
- peerTopology: durablePeerTopology,
4452
- cleanMissPeerIds: durableCleanMissPeerIds,
4453
- };
4454
- this.vmReconcileNegativeCache.set(cacheKey, cached);
4455
- this.indexVmReconcileNegativeCacheEntry(localCgId, cacheKey);
4456
- }
4457
- }
4458
- }
4459
- catch {
4460
- // Persistence is an accelerator only. Fail open to an authoritative scan.
4461
- }
4462
- }
4463
- if (!cached)
4464
- return false;
4465
- if (Date.now() >= cached.nextRetryAt)
4466
- return false;
4467
- try {
4468
- try {
4469
- await this.primeCatchupConnections();
4470
- }
4471
- catch {
4472
- // Best effort only; an unchanged connection view can still honor the
4473
- // cached miss until the backoff expires.
4474
- }
4475
- const currentPeerTopology = await this.vmReconcilePeerTopology(localCgId);
4476
- if (!canReuseVmReconcilePeerTopology({
4477
- topology: cached.peerTopology,
4478
- cleanMissPeerIds: cached.cleanMissPeerIds,
4479
- }, currentPeerTopology)) {
4480
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
4481
- this.clearVmReconcileActiveFetchCooldown(localCgId);
4482
- return false;
4483
- }
4484
- const currentNamespaces = await this.collectVmReconcileSwmCandidateNamespacesBestEffort(localCgId);
4485
- const currentNamespaceKey = this.vmReconcileSwmNamespaceKey(currentNamespaces.namespaces);
4486
- const cachedNamespaceKey = this.vmReconcileSwmNamespaceKey(cached.candidateNamespaces);
4487
- if (currentNamespaceKey !== cachedNamespaceKey) {
4488
- if (!currentNamespaces.complete) {
4489
- const currentSwmGen = await this.readVmReconcileSwmGen(currentNamespaces.namespaces);
4490
- if (currentSwmGen === null) {
4491
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
4492
- return false;
4493
- }
4494
- if (!this.vmReconcileSwmGenContainsSnapshot(cached.swmGen, currentSwmGen)) {
4495
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
4496
- return false;
4497
- }
4498
- return true;
4499
- }
4500
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
4501
- return false;
4502
- }
4503
- const pattern = this.vmReconcileWorkspaceOperationPattern(currentNamespaces.namespaces.map((namespace) => namespace.metaGraph));
4504
- if (!pattern)
4505
- return true;
4506
- const currentSwmGen = await this.readVmReconcileSwmGen(currentNamespaces.namespaces);
4507
- if (currentSwmGen === null) {
4508
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
4509
- return false;
4510
- }
4511
- if (currentSwmGen !== cached.swmGen) {
4512
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
4513
- return false;
4514
- }
4515
- }
4516
- catch {
4517
- // Unexpected validation failures leave the existing backoff in place;
4518
- // expected generation probe failures return null and clear the gate above.
4519
- }
4520
- return true;
4521
- }
4522
- recordVmReconcileNegativeCache(cacheKey, localCgId, state) {
4523
- if (state.swmGen === null) {
4524
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
4525
- return;
4526
- }
4527
- this.pruneVmReconcileState();
4528
- const previous = this.vmReconcileNegativeCache.get(cacheKey);
4529
- const failures = (previous?.failures ?? 0) + 1;
4530
- const exponentialBackoff = Math.min(DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS, DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_BASE_MS * 2 ** Math.max(0, failures - 1));
4531
- const jitterSample = createHash('sha256')
4532
- .update(`${this.node.peerId.toString()}\0${cacheKey}\0${failures}`)
4533
- .digest()
4534
- .readUInt32BE(0) / 0x1_0000_0000;
4535
- const backoff = Math.min(DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS, Math.max(1, Math.round(exponentialBackoff * (0.8 + jitterSample * 0.4))));
4536
- getMetrics().storeRetryAttemptsTotal.add(1, {
4537
- scope: 'vm_reconcile',
4538
- reason: 'no_swm',
4539
- attempt: Math.min(failures, 16),
4217
+ initializeVmReconcilePublicCoreTransportPreferencePolicy() {
4218
+ this.vmReconcilePublicCoreTransportPreferencePolicy ??= new VmRecoveryCoreTransportPreferencePolicy({
4219
+ now: () => this.vmReconcileRotationNow(),
4220
+ connectionKey: peerId => this.getSyncReconcilerConnectionKey(peerId),
4221
+ supportsCore: peerId => this.peerCapabilityRegistry.supportsCore(peerId),
4222
+ holderReuseEnabled: resolveExactBatchStreamEnabled,
4223
+ captureScope: (localCgId, candidatePeerIds) => typeof this.chain.deploymentId === 'string' ? {
4224
+ deploymentId: this.chain.deploymentId,
4225
+ lifecycleGeneration: this.vmReconcileLifecycleGeneration,
4226
+ bindingGeneration: this.contextGraphBindingState.capture(localCgId),
4227
+ selectedBindingGeneration: this.selectedVmReconcileCursors.get(localCgId)?.bindingGeneration,
4228
+ candidatePeerIds: [...candidatePeerIds],
4229
+ } : undefined,
4230
+ scopeIsCurrent: (localCgId, scope) => this.chain.deploymentId === scope.deploymentId
4231
+ && this.vmReconcileLifecycleGeneration === scope.lifecycleGeneration
4232
+ && !this.vmReconcileRotationClosed
4233
+ && this.contextGraphBindingState.capture(localCgId) === scope.bindingGeneration
4234
+ && this.selectedVmReconcileCursors.get(localCgId)?.bindingGeneration === scope.selectedBindingGeneration
4235
+ && this.vmReconcilePeerMembershipMatches(new Set(scope.candidatePeerIds), this.vmReconcileObservedCandidatePeerIds(localCgId)),
4236
+ }, {
4237
+ ttlMs: DKGAgentBase.VM_RECONCILE_PUBLIC_CORE_TRANSPORT_TTL_MS,
4238
+ maxEntries: DKGAgentBase.VM_RECONCILE_CG_STATE_MAX_ENTRIES,
4540
4239
  });
4541
- if (previous) {
4542
- // Replace in place without racing an asynchronous durable DELETE against
4543
- // the SAVE below. The record key/local CG are unchanged here.
4544
- this.vmReconcileNegativeCache.delete(cacheKey);
4545
- const keys = this.vmReconcileNegativeCacheKeysByCg.get(previous.localCgId);
4546
- keys?.delete(cacheKey);
4547
- if (keys?.size === 0)
4548
- this.vmReconcileNegativeCacheKeysByCg.delete(previous.localCgId);
4549
- }
4550
- const record = {
4551
- localCgId,
4552
- failures,
4553
- nextRetryAt: Date.now() + backoff,
4554
- swmGen: state.swmGen,
4555
- candidateNamespaces: state.candidateNamespaces,
4556
- peerTopology: state.peerTopology,
4557
- cleanMissPeerIds: state.cleanMissPeerIds,
4558
- };
4559
- this.vmReconcileNegativeCache.set(cacheKey, record);
4560
- this.markVmReconcileNegativeCacheHydrated(cacheKey, localCgId);
4561
- this.indexVmReconcileNegativeCacheEntry(localCgId, cacheKey);
4562
- const durableStore = this.config.contextGraphSubscriptionStore;
4563
- if (this.vmReconcileSwmGenSupportsDurableNegative(record.swmGen)) {
4564
- void durableStore?.saveVmReconcileNegative?.({
4565
- cacheKey,
4566
- localCgId: record.localCgId,
4567
- failures: record.failures,
4568
- nextRetryAt: record.nextRetryAt,
4569
- swmGen: record.swmGen,
4570
- candidateNamespaces: record.candidateNamespaces,
4571
- peerTopologyKey: encodeLegacyVmReconcilePeerTopologyKey(record.peerTopology),
4572
- peerTopology: record.peerTopology,
4573
- cleanMissPeerIds: record.cleanMissPeerIds,
4574
- }).catch(() => {
4575
- // Persistence is an accelerator only; the process-local gate still works.
4576
- });
4577
- }
4578
- else {
4579
- void durableStore?.deleteVmReconcileNegative?.(cacheKey).catch(() => {
4580
- // Fail open after restart even if best-effort cleanup cannot complete.
4581
- });
4582
- }
4583
- this.pruneVmReconcileState();
4584
4240
  }
4585
4241
  vmReconcileRotationNow() {
4586
4242
  return performance.now();
@@ -4861,11 +4517,26 @@ export class SwmHostModeMethods extends DKGAgentBase {
4861
4517
  * mutable transition so partial responses revoke affinity consistently.
4862
4518
  * Unavailable peers and the global considered-peer cap remain authoritative.
4863
4519
  */
4864
- selectVmReconcileExactCandidate(record, fallbackCandidatePeerIds, policy) {
4520
+ selectVmReconcileExactCandidate(record, fallbackCandidatePeerIds, policy, binding) {
4865
4521
  const uncreditedCandidateOrder = record
4866
4522
  ? this.vmReconcileUncreditedCandidateOrder(record)
4867
4523
  : fallbackCandidatePeerIds;
4868
- return policy.selectNextCandidate(uncreditedCandidateOrder, DKGAgentBase.VM_RECONCILE_EXACT_PEER_MAX);
4524
+ const preferredPeerId = binding
4525
+ ? this.vmReconcilePublicCoreTransportPreferencePolicy.preferredPeer(binding.localCgId, binding.onChainCgId, uncreditedCandidateOrder)
4526
+ : undefined;
4527
+ const now = this.vmReconcileRotationNow();
4528
+ const { order, heldOffPeerIds } = orderVmRecoveryCandidates({
4529
+ candidatePeerIds: uncreditedCandidateOrder,
4530
+ preferredPeerId,
4531
+ streamPeerIds: binding?.experimentalStreamPeerIds,
4532
+ lastAttemptedPeerId: record?.lastAttemptedPeerId,
4533
+ hasSetback: (peerId) => binding !== undefined
4534
+ && record?.streamSetbackPeerIds?.has(peerId) === true
4535
+ && this.vmReconcileStreamSetbackPolicy.inSetbackStreak(binding.localCgId, peerId, now),
4536
+ isHeldOff: (peerId) => binding !== undefined
4537
+ && this.vmReconcileStreamSetbackPolicy.heldOff(binding.localCgId, peerId, now),
4538
+ });
4539
+ return policy.selectNextCandidate(order, DKGAgentBase.VM_RECONCILE_EXACT_PEER_MAX, heldOffPeerIds);
4869
4540
  }
4870
4541
  findVmReconcileRotationReplacement(requestingCgId) {
4871
4542
  if (this.vmReconcileRotationState.size < DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
@@ -4936,7 +4607,11 @@ export class SwmHostModeMethods extends DKGAgentBase {
4936
4607
  if (key.startsWith(prefix))
4937
4608
  this.vmReconcileRotationState.delete(key);
4938
4609
  }
4610
+ this.vmReconcileTransportBudgetPolicy.forgetContextGraph(localCgId);
4611
+ this.vmReconcileStreamSetbackPolicy.forgetContextGraph(localCgId);
4939
4612
  this.vmReconcileRotationAdmissionCursorByCg.delete(localCgId);
4613
+ this.vmReconcilePublicCoreTransportPreferencePolicy?.forgetContextGraph(localCgId);
4614
+ existingVmRecoveryPreparation(this)?.discard(localCgId);
4940
4615
  }
4941
4616
  closeVmReconcileRotationState() {
4942
4617
  this.vmReconcileLifecycleController?.abort();
@@ -4947,11 +4622,22 @@ export class SwmHostModeMethods extends DKGAgentBase {
4947
4622
  // without running the base constructor. Shutdown must remain best-effort
4948
4623
  // for that supported test seam and never mask later teardown failures.
4949
4624
  this.vmReconcileRotationState?.clear();
4625
+ this.vmReconcileTransportBudgetPolicy?.clear();
4626
+ this.vmReconcileStreamSetbackPolicy?.clear();
4950
4627
  this.vmReconcileRotationAdmissionCursorByCg?.clear();
4951
4628
  this.vmReconcileCuratorPeersByCg?.clear();
4952
4629
  this.vmReconcileCuratorPageCursorByCg?.clear();
4953
4630
  this.vmReconcileExactPeerCapabilities?.clear();
4631
+ this.vmReconcilePublicCoreTransportPreferencePolicy?.clear();
4954
4632
  this.vmRefreshQueue?.clear();
4633
+ // Cancel speculative sizing reads and let shutdown wait for them to settle.
4634
+ const preparation = existingVmRecoveryPreparation(this);
4635
+ if (preparation && this.vmReconcilePhysicalRuns) {
4636
+ trackVmReconcilePhysicalRun(this.vmReconcilePhysicalRuns, preparation.close());
4637
+ }
4638
+ else {
4639
+ void preparation?.close();
4640
+ }
4955
4641
  }
4956
4642
  openVmReconcileRotationState() {
4957
4643
  if (!this.vmReconcileLifecycleController || this.vmReconcileLifecycleController.signal.aborted) {
@@ -4978,6 +4664,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
4978
4664
  return;
4979
4665
  if (peerId !== undefined) {
4980
4666
  capturedRecord.attemptedPeerIds.add(peerId);
4667
+ // This attempt gave a verdict: an earlier setback with the peer is history.
4668
+ capturedRecord.streamSetbackPeerIds?.delete(peerId);
4981
4669
  if (disposition === 'clean-absent')
4982
4670
  capturedRecord.cleanAbsentPeerIds.add(peerId);
4983
4671
  // Preserve fairly accumulated proof progress while other targets share
@@ -5002,6 +4690,23 @@ export class SwmHostModeMethods extends DKGAgentBase {
5002
4690
  }
5003
4691
  this.touchVmReconcileRotationRecord(slotKey, capturedRecord);
5004
4692
  }
4693
+ /**
4694
+ * Hand a target its turn at `peerId` back after an attempt that ended
4695
+ * without a verdict on the peer's data. The peer is uncredited again, so the
4696
+ * cycle cannot complete, and so cannot back off, without asking it. Removing
4697
+ * a mark is safe against any roster, which is why membership is not checked.
4698
+ * The setback is noted on the target, for candidate selection to order by.
4699
+ */
4700
+ releaseVmReconcileRotationAttempt(target, peerId, capturedRecord) {
4701
+ if (this.vmReconcileRotationClosed)
4702
+ return;
4703
+ const slotKey = this.vmReconcileRotationSlotKey(target);
4704
+ if (this.vmReconcileRotationState.get(slotKey) !== capturedRecord)
4705
+ return;
4706
+ capturedRecord.attemptedPeerIds.delete(peerId);
4707
+ (capturedRecord.streamSetbackPeerIds ??= new Set()).add(peerId);
4708
+ this.touchVmReconcileRotationRecord(slotKey, capturedRecord);
4709
+ }
5005
4710
  creditVmReconcileCleanAbsence(target, peerId, expectedCandidatePeerIds, capturedRecord) {
5006
4711
  this.settleVmReconcileRotationAttempt(target, peerId, 'clean-absent', expectedCandidatePeerIds, capturedRecord);
5007
4712
  }
@@ -5032,26 +4737,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5032
4737
  this.installVmReconcileActiveFetchCooldown(localCgId, now);
5033
4738
  return true;
5034
4739
  }
5035
- vmReconcileActiveFetchHadUsableResponse(result) {
5036
- if ((result.peersSucceeded ?? 0) > 0)
5037
- return true;
5038
- if ((result.sharedMemorySynced ?? 0) > 0)
5039
- return true;
5040
- const shared = result.diagnostics?.sharedMemory;
5041
- if (!shared)
5042
- return false;
5043
- return (shared.insertedDataTriples ?? 0) > 0
5044
- || (shared.insertedMetaTriples ?? 0) > 0
5045
- || (shared.checkpointAdvances ?? 0) > 0
5046
- || ((shared.completedPhases ?? 0) > 0 && (shared.resumedPhases ?? 0) > 0);
5047
- }
5048
4740
  pruneVmReconcileState(now = Date.now()) {
5049
- while (this.vmReconcileNegativeCache.size > DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
5050
- const oldestKey = this.vmReconcileNegativeCache.keys().next().value;
5051
- if (oldestKey === undefined)
5052
- break;
5053
- this.deleteVmReconcileNegativeCacheEntry(oldestKey);
5054
- }
5055
4741
  for (const [localCgId, cooldown] of this.vmReconcileFetchCooldowns) {
5056
4742
  if (now - cooldown.startedAt >= DKGAgentBase.VM_RECONCILE_SWEEP_INTERVAL_MS) {
5057
4743
  this.clearVmReconcileActiveFetchCooldown(localCgId);
@@ -5110,22 +4796,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
5110
4796
  });
5111
4797
  }
5112
4798
  forceClearVmReconcileStateForContextGraph(localCgId, options = {}) {
5113
- const negativeCacheKeys = this.vmReconcileNegativeCacheKeysByCg.get(localCgId);
5114
- if (negativeCacheKeys) {
5115
- for (const cacheKey of Array.from(negativeCacheKeys)) {
5116
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
5117
- }
5118
- }
5119
- for (const [cacheKey, hydratedLocalCgId] of this.vmReconcileNegativeCacheHydrated) {
5120
- if (hydratedLocalCgId === localCgId) {
5121
- this.vmReconcileNegativeCacheHydrated.delete(cacheKey);
5122
- }
5123
- }
5124
- void this.config.contextGraphSubscriptionStore
5125
- ?.deleteVmReconcileNegativesForContextGraph?.(localCgId)
5126
- .catch(() => {
5127
- // Best-effort durable cleanup; generation checks still reject stale rows.
5128
- });
5129
4799
  this.reconcileCursors.delete(localCgId);
5130
4800
  if (options.includeSelectedCursor !== false
5131
4801
  && this.selectedVmReconcileCursors.delete(localCgId)) {
@@ -5214,97 +4884,120 @@ export class SwmHostModeMethods extends DKGAgentBase {
5214
4884
  }
5215
4885
  pruneVmReconcileCacheKeySiblings(cacheKey) {
5216
4886
  const prefix = this.vmReconcileCacheKeyPrefix(cacheKey);
5217
- for (const key of this.vmReconcileNegativeCache.keys()) {
5218
- if (key !== cacheKey && key.startsWith(prefix)) {
5219
- this.deleteVmReconcileNegativeCacheEntry(key);
5220
- }
5221
- }
5222
4887
  this.recentReconciledUals.deleteByPrefix(prefix, cacheKey);
5223
4888
  }
5224
- vmReconcileConnectedPeerCount() {
5225
- try {
5226
- const libp2p = this.node?.libp2p;
5227
- const getConnections = libp2p?.getConnections;
5228
- if (typeof getConnections !== 'function')
5229
- return 0;
5230
- const uniquePeers = new Set(getConnections.call(libp2p)
5231
- .map((connection) => connection.remotePeer?.toString())
5232
- .filter((peerId) => typeof peerId === 'string' && peerId.length > 0));
5233
- return uniquePeers.size;
5234
- }
5235
- catch {
5236
- return 0;
5237
- }
5238
- }
5239
4889
  /**
5240
- * Execute one already-admitted provider attempt. The caller owns
5241
- * roster/admission, packing, and provider affinity; this executor owns the
5242
- * exact request or cached legacy fallback, per-UAL chain revalidation, and
5243
- * rotation settlement.
4890
+ * Execute one provider after connection/network readiness. The caller owns
4891
+ * roster, packing, and provider affinity; local sync admission starts the
4892
+ * physical attempt before rotation marks are installed. This executor owns
4893
+ * exact or cached legacy work, per-UAL chain revalidation, and settlement.
5244
4894
  * Returns immutable evidence for the caller to merge. A stale lifecycle is
5245
4895
  * discriminated by whether the physical attempt had already been admitted;
5246
4896
  * only the pre-admission variant guarantees zero attempt side effects.
5247
4897
  */
5248
4898
  async executeVmRecoveryBatch(input) {
5249
- const { localCgId, onChainCgId, peerId, attempts, unavailablePeerIds, headBlock, signal, isRecoveryCurrent, revalidateTarget, ctx, } = input;
4899
+ const { localCgId, onChainCgId, peerId, attempts, unavailablePeerIds, headBlock, signal, isRecoveryCurrent, revalidateTarget, ctx, exactRecoveryTransportMode = 'stream-preferred', legacyAttemptTimeoutMs, registeredPublicEvidence, phases, } = input;
5250
4900
  const unavailablePeerIdSet = new Set(unavailablePeerIds);
5251
4901
  // The caller's last guard can race an unsubscribe/rebind before this
5252
4902
  // executor begins. Do not retain attempt evidence or emit a fetch event
5253
4903
  // until this exact lifecycle has been re-proved at the ownership boundary.
5254
4904
  if (!isRecoveryCurrent())
5255
4905
  return { kind: 'not-started-stale' };
5256
- const useCachedLegacyFallback = this.vmReconcileExactFilterUnsupported(peerId);
4906
+ const useCachedLegacyFallback = exactRecoveryTransportMode !== 'stream-required'
4907
+ && this.vmReconcileExactFilterUnsupported(peerId);
5257
4908
  const handledOrdinals = [];
5258
4909
  const attemptedOrdinals = [];
5259
4910
  const outcomes = [];
5260
- for (const attempt of attempts) {
5261
- const batchTarget = attempt.entry.target;
5262
- handledOrdinals.push(batchTarget.ordinal);
5263
- attemptedOrdinals.push(batchTarget.ordinal);
5264
- if (attempt.installedRecord) {
5265
- attempt.installedRecord.lastAttemptedPeerId = peerId;
5266
- attempt.installedRecord.attemptedPeerIds.add(peerId);
5267
- attempt.installedRecord.collectionDeadlineAt = this.vmReconcileRotationNow()
5268
- + DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS;
5269
- this.touchVmReconcileRotationRecord(this.vmReconcileRotationSlotKey(batchTarget), attempt.installedRecord);
4911
+ let workStarted = false;
4912
+ const onWorkStarted = () => {
4913
+ if (workStarted || !isRecoveryCurrent())
4914
+ return;
4915
+ workStarted = true;
4916
+ for (const attempt of attempts) {
4917
+ const batchTarget = attempt.entry.target;
4918
+ this.vmReconcileTransportBudgetPolicy.recordAdmitted(batchTarget, peerId);
4919
+ handledOrdinals.push(batchTarget.ordinal);
4920
+ attemptedOrdinals.push(batchTarget.ordinal);
4921
+ const record = attempt.installedRecord;
4922
+ const slotKey = this.vmReconcileRotationSlotKey(batchTarget);
4923
+ if (record && this.vmReconcileRotationState.get(slotKey) === record
4924
+ && record.fingerprint === this.vmReconcileRotationFingerprint(batchTarget)) {
4925
+ record.lastAttemptedPeerId = peerId;
4926
+ record.attemptedPeerIds.add(peerId);
4927
+ record.collectionDeadlineAt = this.vmReconcileRotationNow()
4928
+ + DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS;
4929
+ this.touchVmReconcileRotationRecord(slotKey, record);
4930
+ }
4931
+ this.emitReplication({
4932
+ contextGraphId: localCgId,
4933
+ onChainCgId: onChainCgId.toString(),
4934
+ action: 'fetch',
4935
+ ordinal: batchTarget.ordinal,
4936
+ kaId: batchTarget.kaId,
4937
+ ual: batchTarget.ual,
4938
+ detail: useCachedLegacyFallback
4939
+ ? 'legacy-sync'
4940
+ : attempts.length > 1 ? 'exact-asset-batch' : 'exact-asset',
4941
+ });
5270
4942
  }
5271
- this.emitReplication({
5272
- contextGraphId: localCgId,
5273
- onChainCgId: onChainCgId.toString(),
5274
- action: 'fetch',
5275
- ordinal: batchTarget.ordinal,
5276
- kaId: batchTarget.kaId,
5277
- ual: batchTarget.ual,
5278
- detail: useCachedLegacyFallback
5279
- ? 'legacy-sync'
5280
- : attempts.length > 1 ? 'exact-asset-batch' : 'exact-asset',
5281
- });
5282
- }
4943
+ };
5283
4944
  let disposition = 'incomplete';
4945
+ let streamOutcome;
4946
+ let localAdmissionDeferred = false;
4947
+ const legacyAttemptStartedAt = Date.now();
4948
+ const remainingLegacyAttemptMs = () => legacyAttemptTimeoutMs === undefined
4949
+ ? undefined
4950
+ : Math.max(0, legacyAttemptTimeoutMs - (Date.now() - legacyAttemptStartedAt));
5284
4951
  const runLegacyFallback = async () => {
4952
+ const remainingMs = remainingLegacyAttemptMs();
4953
+ // Both the exact-filter probe and a full-scan fallback belong to one
4954
+ // provider turn. Do not grant a fresh long deadline after the probe has
4955
+ // already spent the bounded legacy window.
4956
+ if (remainingMs !== undefined && remainingMs < SYNC_MIN_GRAPH_BUDGET_MS) {
4957
+ this.log.info(ctx, `VM legacy fallback for "${localCgId}" from ${peerId.slice(-8)} skipped: attempt budget exhausted`);
4958
+ return;
4959
+ }
5285
4960
  try {
5286
4961
  const fallback = await this.runLegacyDurableSyncDetailed(ctx, peerId, [localCgId], undefined, undefined, undefined, {
5287
4962
  stopOnBackoffWorthyFailure: true,
5288
- priority: 1_000,
4963
+ priority: VM_RECOVERY_SYNC_PRIORITY,
5289
4964
  source: 'vm-recovery',
4965
+ onWorkStarted,
5290
4966
  signal,
5291
4967
  isCurrent: isRecoveryCurrent,
4968
+ ...(remainingMs === undefined ? {} : { totalTimeoutMs: remainingMs }),
5292
4969
  });
4970
+ if (fallback.admission === 'work-started')
4971
+ onWorkStarted();
4972
+ // An admitted exact request retains its attempt through a refused
4973
+ // fallback. Only the original never-started operation can defer.
4974
+ localAdmissionDeferred = !workStarted && fallback.admission === 'local-admission-deferred';
5293
4975
  this.log.info(ctx, `VM legacy fallback for "${localCgId}" from ${peerId.slice(-8)}: fetched=${fallback.result.fetchedDataTriples + fallback.result.fetchedMetaTriples} inserted=${fallback.result.insertedTriples} failed=${fallback.result.failedPeers + fallback.result.failedPhases}`);
5294
4976
  }
5295
4977
  catch (fallbackError) {
5296
4978
  this.log.info(ctx, `VM legacy fallback for "${localCgId}" from ${peerId.slice(-8)} failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
5297
4979
  }
5298
4980
  };
4981
+ const exchangeInterval = phases?.begin();
5299
4982
  try {
5300
4983
  if (useCachedLegacyFallback) {
5301
4984
  await runLegacyFallback();
5302
4985
  }
5303
4986
  else {
5304
- const detailed = await this.syncExactKnowledgeAssetsFromPeerDetailed(peerId, localCgId, attempts.map(({ entry }) => entry.target.ual), { signal, isCurrent: isRecoveryCurrent });
4987
+ const detailed = await this.syncExactKnowledgeAssetsFromPeerDetailed(peerId, localCgId, attempts.map(({ entry }) => entry.target.ual), {
4988
+ signal, isCurrent: isRecoveryCurrent, onWorkStarted, exactRecoveryTransportMode,
4989
+ ...(legacyAttemptTimeoutMs === undefined ? {} : { totalTimeoutMs: legacyAttemptTimeoutMs }),
4990
+ ...(registeredPublicEvidence ? { registeredPublicEvidence } : {}),
4991
+ });
5305
4992
  const { result } = detailed;
5306
4993
  disposition = detailed.disposition;
5307
- if (detailed.responderCapability === 'legacy-filter-unsupported') {
4994
+ streamOutcome = detailed.streamOutcome;
4995
+ // A fresh capability response is physical peer evidence even when
4996
+ // its later ordinary fallback cannot acquire local capacity.
4997
+ if (detailed.admission === 'work-started' || detailed.responderCapability !== undefined)
4998
+ onWorkStarted();
4999
+ localAdmissionDeferred = !workStarted && detailed.admission === 'local-admission-deferred';
5000
+ if (exactRecoveryTransportMode !== 'stream-required' && detailed.responderCapability === 'legacy-filter-unsupported') {
5308
5001
  this.rememberVmReconcileExactFilterUnsupported(peerId);
5309
5002
  if (isRecoveryCurrent() && disposition === 'incomplete') {
5310
5003
  await runLegacyFallback();
@@ -5316,18 +5009,44 @@ export class SwmHostModeMethods extends DKGAgentBase {
5316
5009
  catch (error) {
5317
5010
  this.log.info(ctx, `VM exact fetch for "${localCgId}" from ${peerId.slice(-8)} failed: ${error instanceof Error ? error.message : String(error)}`);
5318
5011
  }
5012
+ if (phases && exchangeInterval)
5013
+ phases.chargeSince('exchange', exchangeInterval.startedAt, exchangeInterval.before);
5319
5014
  if (!isRecoveryCurrent())
5320
- return { kind: 'stale-after-attempt' };
5015
+ return { kind: workStarted ? 'stale-after-attempt' : 'not-started-stale' };
5016
+ if (localAdmissionDeferred)
5017
+ return { kind: 'local-admission-deferred' };
5018
+ // A stream that answered busy or broke says nothing about the peer's data.
5019
+ // The policy decides whether the targets still pending keep their turn at it.
5020
+ let providerTurnKept = false;
5021
+ if (workStarted && streamOutcome !== undefined) {
5022
+ if (streamOutcome === 'complete') {
5023
+ this.vmReconcileStreamSetbackPolicy.recordServed(localCgId, peerId, this.vmReconcileRotationNow());
5024
+ }
5025
+ else {
5026
+ const setback = this.vmReconcileStreamSetbackPolicy.recordSetback(localCgId, peerId, streamOutcome, this.vmReconcileRotationNow());
5027
+ providerTurnKept = setback.keepsTurn;
5028
+ observeVmRecoveryTiming(() => this.log.info(ctx, `VM exact recovery stream setback for "${localCgId}" from ${peerId.slice(-8)}: `
5029
+ + `kind=${streamOutcome} assets=${attempts.length} keepsTurn=${setback.keepsTurn ? 1 : 0} `
5030
+ + `holdOffMs=${setback.holdOffMs}`));
5031
+ }
5032
+ }
5321
5033
  const perUalDispositions = new Map();
5322
- for (const attempt of attempts) {
5034
+ // The batch's targets are re-verified side by side, with the scan's own
5035
+ // bound, so their chain reads can leave in one request. Like the scan, a
5036
+ // target that throws stops the ones not yet started and the batch rejects
5037
+ // only once the ones in flight have finished: nothing here outlives the
5038
+ // pass. They are settled in order below: each step still sees the rotation
5039
+ // state its predecessor left.
5040
+ const revalidateBatch = () => mapWithConcurrencyDrained(attempts, DKGAgentBase.VM_RECONCILE_ORDINAL_CONCURRENCY, (attempt) => this.reconcileChainOrdinal(localCgId, onChainCgId, attempt.entry.target.ordinal, headBlock, {
5041
+ isTargetCurrent: isRecoveryCurrent,
5042
+ revalidateTarget,
5043
+ }));
5044
+ const revalidated = await (phases ? phases.measure('post-fetch', revalidateBatch) : revalidateBatch());
5045
+ if (!isRecoveryCurrent())
5046
+ return { kind: 'stale-after-attempt' };
5047
+ for (const [attemptIndex, attempt] of attempts.entries()) {
5323
5048
  const batchTarget = attempt.entry.target;
5324
- const outcome = await this.reconcileChainOrdinal(localCgId, onChainCgId, batchTarget.ordinal, headBlock, {
5325
- isTargetCurrent: isRecoveryCurrent,
5326
- revalidateTarget,
5327
- deferActiveFetch: true,
5328
- });
5329
- if (!isRecoveryCurrent())
5330
- return { kind: 'stale-after-attempt' };
5049
+ const outcome = revalidated[attemptIndex];
5331
5050
  outcomes.push([batchTarget.ordinal, outcome]);
5332
5051
  const perTargetDisposition = (outcome.status === 'reconciled' || outcome.status === 'already')
5333
5052
  ? 'found'
@@ -5336,6 +5055,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
5336
5055
  : 'incomplete';
5337
5056
  perUalDispositions.set(batchTarget.ual, perTargetDisposition);
5338
5057
  if (outcome.status === 'pending' && outcome.recovery) {
5058
+ if (!workStarted)
5059
+ continue;
5339
5060
  const batchRecord = attempt.installedRecord;
5340
5061
  if (!batchRecord || this.vmReconcileRotationClosed)
5341
5062
  continue;
@@ -5343,10 +5064,14 @@ export class SwmHostModeMethods extends DKGAgentBase {
5343
5064
  !== batchRecord)
5344
5065
  continue;
5345
5066
  const candidateMembershipAfter = this.vmReconcileObservedCandidatePeerIds(localCgId);
5067
+ if (providerTurnKept) {
5068
+ this.releaseVmReconcileRotationAttempt(batchTarget, peerId, batchRecord);
5069
+ }
5346
5070
  if (!this.vmReconcilePeerMembershipMatches(batchRecord.candidatePeerIds, candidateMembershipAfter)) {
5347
5071
  this.prepareVmReconcileRotationTarget(outcome.recovery, candidateMembershipAfter, this.vmReconcileRotationNow());
5348
5072
  }
5349
- else if (this.vmReconcileRecoveryTargetMatches(batchTarget, outcome.recovery)) {
5073
+ else if (!providerTurnKept
5074
+ && this.vmReconcileRecoveryTargetMatches(batchTarget, outcome.recovery)) {
5350
5075
  this.settleVmReconcileRotationAttempt(batchTarget, peerId, perTargetDisposition, attempt.candidatePeerIds, batchRecord, unavailablePeerIdSet);
5351
5076
  }
5352
5077
  }
@@ -5361,6 +5086,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5361
5086
  attemptedOrdinals,
5362
5087
  providerDisposition: disposition,
5363
5088
  perUalDispositions: [...perUalDispositions],
5089
+ providerTurnKept,
5364
5090
  };
5365
5091
  }
5366
5092
  /**
@@ -5376,6 +5102,26 @@ export class SwmHostModeMethods extends DKGAgentBase {
5376
5102
  && this.vmReconcileLifecycleGeneration === rotationGeneration
5377
5103
  && isTargetCurrent();
5378
5104
  const ctx = createOperationContext('system');
5105
+ // Advisory sizing preparation (opt-in, default off). Created lazily and
5106
+ // owned by this host; every hint is bound to this exact recovery operation.
5107
+ const readUpdateContextForPreparation = this.chain.getKnowledgeAssetUpdateContext;
5108
+ const experimentPolicy = resolveVmRecoveryExperimentPolicy(this.config.vmRecoveryPrefetchEnabled, existingVmRecoveryPreparation(this)?.limits);
5109
+ const preparation = experimentPolicy.prefetchEnabled
5110
+ ? vmRecoveryPreparationFor(this, typeof readUpdateContextForPreparation === 'function'
5111
+ ? { readUpdateContext: (id, readOptions) => readUpdateContextForPreparation.call(this.chain, id, readOptions) }
5112
+ : null)
5113
+ : undefined;
5114
+ const preparationScope = {
5115
+ localCgId,
5116
+ onChainCgId,
5117
+ generation: rotationGeneration,
5118
+ ...(signal ? { signal } : {}),
5119
+ isCurrent: isRecoveryCurrent,
5120
+ };
5121
+ const transportPreparation = preparation
5122
+ ? new VmRecoveryTransportPreparation(preparation, preparationScope) : undefined;
5123
+ const timing = new VmRecoveryTimingObserver(localCgId, (message) => this.log.info(ctx, message));
5124
+ const phases = timing.phases;
5379
5125
  const noRecovery = (continuationOrdinal, cooldownOnly = false) => ({
5380
5126
  outcomes: new Map(),
5381
5127
  attemptedOrdinals: [],
@@ -5508,7 +5254,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5508
5254
  ...(this.vmReconcileCuratorPeersByCg.get(localCgId) ?? []),
5509
5255
  ];
5510
5256
  const curatorPageCursor = this.vmReconcileCuratorPageCursorByCg.get(localCgId);
5511
- const curatorResolution = await this.resolveCuratorPeerIdsForCg(localCgId, {
5257
+ const curatorResolution = await phases.measure('roster', () => this.resolveCuratorPeerIdsForCg(localCgId, {
5512
5258
  maxPeerIds: DKGAgentBase.VM_RECONCILE_EXACT_ROSTER_MAX,
5513
5259
  // Once overflow is proven, expose exactly one new ordered peer per pass.
5514
5260
  // One target can spend only one peer attempt, so advancing by more would
@@ -5523,7 +5269,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5523
5269
  curatorIsLocal: false,
5524
5270
  legacyTripleResolved: false,
5525
5271
  lookupFailed: true,
5526
- }));
5272
+ })));
5527
5273
  if (!isRecoveryCurrent())
5528
5274
  return staleRecovery();
5529
5275
  const allResolvedCuratorPeerIds = [...new Set(curatorResolution.peerIds
@@ -5569,7 +5315,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5569
5315
  let legacyPreferredPeerId;
5570
5316
  if (resolutionSucceeded && !curatorResolution.curatorIsLocal
5571
5317
  && resolvedCuratorPeerIds.length === 0) {
5572
- legacyPreferredPeerId = await this.resolvePreferredSyncPeerId(localCgId);
5318
+ legacyPreferredPeerId = await phases.measure('roster', () => this.resolvePreferredSyncPeerId(localCgId));
5573
5319
  }
5574
5320
  if (!isRecoveryCurrent())
5575
5321
  return staleRecovery();
@@ -5625,6 +5371,79 @@ export class SwmHostModeMethods extends DKGAgentBase {
5625
5371
  // Curator resolution may connect a missing peer, but it must not substitute
5626
5372
  // a different ranking algorithm and invalidate an otherwise stable roster.
5627
5373
  const orderedPeerIds = this.vmReconcileObservedCandidatePeerIds(localCgId);
5374
+ // Only an explicit experimental run may reorder these existing capped
5375
+ // transport candidates. The proof roster and every target's independent
5376
+ // attempted/absence credits stay unchanged. Identify does not prove data.
5377
+ const experimentalStreamPeerIds = new Set();
5378
+ // Observation only: why this pass did or did not choose the stream wire.
5379
+ let streamAdvertisedCount = 0;
5380
+ const advertisedStreamPeers = new Set();
5381
+ // The registered-public answer that gates the stream wire. Retry eligibility, the wire
5382
+ // choice and the log label all derive from this one typed observation.
5383
+ const passAuthority = new VmRecoveryPassAuthority(undefined, undefined, experimentPolicy.authorityRetry);
5384
+ const readRegisteredPublicAuthority = async () => {
5385
+ await phases.measure('authority', () => passAuthority.read(() => this.resolveRegisteredContextGraphAuthority(localCgId, {
5386
+ authorityReadMode: 'finalized-index-or-live', signal,
5387
+ })));
5388
+ if (passAuthority.isPublic && isRecoveryCurrent()) {
5389
+ for (const advertisedPeerId of advertisedStreamPeers)
5390
+ experimentalStreamPeerIds.add(advertisedPeerId);
5391
+ }
5392
+ };
5393
+ const prepareThisPassTargets = () => {
5394
+ if (!experimentPolicy.prefetchEnabled || orderedPeerIds.length === 0)
5395
+ return;
5396
+ // The planner sizes this pass's own targets as soon as a provider is chosen.
5397
+ // Start that early, but never ahead of the registered-authority read that gates
5398
+ // the wire: a miss there costs far more than the sizing it would overlap. The
5399
+ // first target keeps its own live read (a probe sizes its single asset itself),
5400
+ // so preparing never adds a read the unprepared path would not make. Pure
5401
+ // metadata: nothing is marked handled or attempted.
5402
+ transportPreparation?.preparePass(currentTargets
5403
+ .slice(1)
5404
+ .map((candidate) => ({ kaId: candidate.kaId })));
5405
+ };
5406
+ if (resolveExactBatchStreamEnabled()) {
5407
+ const transportInterval = phases.begin();
5408
+ const advertised = await Promise.all(orderedPeerIds
5409
+ .filter((peerId) => connectedByPeerId.has(peerId)
5410
+ && this.peerCapabilityRegistry.supportsCore(peerId)
5411
+ && !exactBatchStreamUnsupported(this, peerId, this.getSyncReconcilerConnectionKey(peerId), Date.now(), this.captureExperimentalExactBatchRefusalScope(localCgId)))
5412
+ .map(async (peerId) => {
5413
+ try {
5414
+ return (await this.getPeerProtocols(peerId)).includes(EXACT_BATCH_STREAM_PROTOCOL) ? peerId : undefined;
5415
+ }
5416
+ catch {
5417
+ return undefined;
5418
+ }
5419
+ }));
5420
+ phases.chargeSince('transport', transportInterval.startedAt, transportInterval.before);
5421
+ streamAdvertisedCount = advertised.filter((peerId) => peerId !== undefined).length;
5422
+ if (streamAdvertisedCount > 0) {
5423
+ for (const advertisedPeerId of advertised) {
5424
+ if (advertisedPeerId !== undefined)
5425
+ advertisedStreamPeers.add(advertisedPeerId);
5426
+ }
5427
+ await readRegisteredPublicAuthority();
5428
+ if (!isRecoveryCurrent())
5429
+ return staleRecovery();
5430
+ prepareThisPassTargets();
5431
+ if (experimentPolicy.authorityRetry.kind === 'spaced' && passAuthority.missed) {
5432
+ // A miss here is usually the shared local request budget, not the graph: the
5433
+ // read's detached resolution keeps warming the projection cache after its
5434
+ // deadline. Give it a short, bounded moment and ask once more before the
5435
+ // pass commits to the legacy singleton wire for every provider it tries.
5436
+ const retry = await phases.measure('authority', () => passAuthority.waitForMissRetry(signal));
5437
+ if (!isRecoveryCurrent())
5438
+ return staleRecovery();
5439
+ if (retry)
5440
+ await readRegisteredPublicAuthority();
5441
+ if (!isRecoveryCurrent())
5442
+ return staleRecovery();
5443
+ }
5444
+ }
5445
+ }
5446
+ prepareThisPassTargets();
5628
5447
  // Curator preparation may have grown or shrunk the connected candidate
5629
5448
  // set. Re-evaluate every target against that observed change. Any roster
5630
5449
  // change breaks backoff and starts a fresh proof cycle.
@@ -5690,6 +5509,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5690
5509
  const providerPolicy = new VmRecoveryProviderPolicy();
5691
5510
  const handledBatchOrdinals = new Set();
5692
5511
  let recoveryWorkRan = false;
5512
+ let localAdmissionDeferred = false;
5693
5513
  for (let eligibleIndex = 0; eligibleIndex < eligible.length; eligibleIndex += 1) {
5694
5514
  if (!isRecoveryCurrent())
5695
5515
  break;
@@ -5697,6 +5517,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5697
5517
  const { target } = entry;
5698
5518
  if (handledBatchOrdinals.has(target.ordinal))
5699
5519
  continue;
5520
+ const batchTiming = timing.beginBatch();
5700
5521
  const record = entry.prepared.record;
5701
5522
  const installedRecord = record
5702
5523
  && this.vmReconcileRotationState.get(this.vmReconcileRotationSlotKey(target)) === record
@@ -5711,21 +5532,27 @@ export class SwmHostModeMethods extends DKGAgentBase {
5711
5532
  // uncredited, but consume this target's turn so another missing KA gets
5712
5533
  // the next physical peer slot in the same bounded pass.
5713
5534
  let peerId;
5714
- const candidatePeerId = this.selectVmReconcileExactCandidate(installedRecord, orderedPeerIds, providerPolicy);
5535
+ if (advertisedStreamPeers.size > 0
5536
+ && passAuthority.retryDue()) {
5537
+ // One pass can try several providers over minutes, and every wire choice below
5538
+ // follows this single registered-public observation. A transient miss at pass
5539
+ // start (the read shares a saturated local request budget; its detached
5540
+ // resolution keeps warming the projection cache) would otherwise pin every
5541
+ // later provider of the pass to the legacy singleton wire. Read it again, at
5542
+ // most once per interval, before choosing the next provider.
5543
+ await readRegisteredPublicAuthority();
5544
+ if (!isRecoveryCurrent())
5545
+ return staleRecovery();
5546
+ }
5547
+ const candidatePeerId = this.selectVmReconcileExactCandidate(installedRecord, orderedPeerIds, providerPolicy, { localCgId, onChainCgId: expectedOnChainCgId, experimentalStreamPeerIds });
5548
+ // One policy capture owns every later transition and token fence.
5549
+ const preferenceAttempt = this.vmReconcilePublicCoreTransportPreferencePolicy.capture(localCgId, expectedOnChainCgId, orderedPeerIds);
5715
5550
  if (candidatePeerId) {
5716
- attemptedOrdinals.add(target.ordinal);
5717
- if (installedRecord) {
5718
- installedRecord.lastAttemptedPeerId = candidatePeerId;
5719
- installedRecord.attemptedPeerIds.add(candidatePeerId);
5720
- installedRecord.collectionDeadlineAt = this.vmReconcileRotationNow()
5721
- + DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS;
5722
- this.touchVmReconcileRotationRecord(this.vmReconcileRotationSlotKey(target), installedRecord);
5723
- }
5724
5551
  let connectedPeer = connectedByPeerId.get(candidatePeerId);
5725
5552
  if (!connectedPeer) {
5726
- await this.ensurePeerConnected(candidatePeerId, { signal }).catch((error) => {
5553
+ await phases.measure('peer-ready', () => this.ensurePeerConnected(candidatePeerId, { signal }).catch((error) => {
5727
5554
  this.log.info(ctx, `VM exact fetch could not connect candidate peer ${candidatePeerId.slice(-8)}: ${error instanceof Error ? error.message : String(error)}`);
5728
- });
5555
+ }));
5729
5556
  if (!isRecoveryCurrent())
5730
5557
  return staleRecovery();
5731
5558
  const connection = this.node.libp2p.getConnections()
@@ -5736,7 +5563,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5736
5563
  }
5737
5564
  recoveryWorkRan = true;
5738
5565
  const protocolReady = connectedPeer
5739
- ? await this.waitForSyncProtocol(connectedPeer, signal)
5566
+ ? await phases.measure('peer-ready', () => this.waitForSyncProtocol(connectedPeer, signal))
5740
5567
  : false;
5741
5568
  if (!isRecoveryCurrent())
5742
5569
  return staleRecovery();
@@ -5747,7 +5574,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5747
5574
  // Network boundary: a merely-connected peer is not necessarily
5748
5575
  // admitted to this DKG network. Never send an authenticated exact
5749
5576
  // request to an unverified or rejected peer.
5750
- const peerAdmitted = await this.ensurePeerAdmittedForRecovery(candidatePeerId, ctx, 'VM exact fetch', signal);
5577
+ const peerAdmitted = await phases.measure('peer-ready', () => this.ensurePeerAdmittedForRecovery(candidatePeerId, ctx, 'VM exact fetch', signal));
5751
5578
  if (!isRecoveryCurrent())
5752
5579
  return staleRecovery();
5753
5580
  if (!peerAdmitted)
@@ -5757,25 +5584,41 @@ export class SwmHostModeMethods extends DKGAgentBase {
5757
5584
  }
5758
5585
  }
5759
5586
  if (!peerId) {
5587
+ if (candidatePeerId) {
5588
+ this.vmReconcilePublicCoreTransportPreferencePolicy.revoke(preferenceAttempt, candidatePeerId);
5589
+ }
5590
+ // A failed connection/protocol/network-admission probe still consumes
5591
+ // its bounded provider turn. A ready peer is marked only by the exact
5592
+ // executor after local sync capacity admits its physical work.
5593
+ if (candidatePeerId) {
5594
+ attemptedOrdinals.add(target.ordinal);
5595
+ if (installedRecord) {
5596
+ installedRecord.lastAttemptedPeerId = candidatePeerId;
5597
+ installedRecord.attemptedPeerIds.add(candidatePeerId);
5598
+ installedRecord.collectionDeadlineAt = this.vmReconcileRotationNow()
5599
+ + DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS;
5600
+ this.touchVmReconcileRotationRecord(this.vmReconcileRotationSlotKey(target), installedRecord);
5601
+ }
5602
+ }
5760
5603
  if (installedRecord) {
5761
5604
  this.settleVmReconcileRotationAttempt(target, undefined, 'incomplete', candidatePeerIds, installedRecord, providerPolicy.unavailablePeerIds());
5762
5605
  }
5763
5606
  continue;
5764
5607
  }
5608
+ const carriedHolder = this.vmReconcilePublicCoreTransportPreferencePolicy.canReuse(preferenceAttempt, peerId, candidatePeerIds);
5609
+ if (carriedHolder)
5610
+ providerPolicy.seedProvenHolder(peerId);
5765
5611
  const providerAttempt = providerPolicy.beginAttempt(peerId);
5766
5612
  if (!providerAttempt)
5767
5613
  continue;
5768
- let batchAttempts = [{
5769
- entry,
5770
- installedRecord,
5771
- candidatePeerIds,
5772
- }];
5773
- // The first exact request to a peer remains a single-KA probe. Once that
5774
- // probe has proved the peer is a holder, pack a stable compatible prefix
5775
- // by authoritative size hints. Unknown footprints stay singleton; the
5776
- // exact-sync protocol's ten-UAL cap remains the hard upper bound.
5777
- if (providerAttempt.kind === 'proven-holder-reuse') {
5778
- const compatible = [];
5614
+ const admittedConnectionKey = this.getSyncReconcilerConnectionKey(peerId);
5615
+ // Choose the admitted singleton probe or rotation-compatible holder
5616
+ // prefix before sizing; one pipeline owns its selection and wire mode.
5617
+ const candidateAttempts = [];
5618
+ if (providerAttempt.kind === 'probe') {
5619
+ candidateAttempts.push({ entry, installedRecord, candidatePeerIds });
5620
+ }
5621
+ else {
5779
5622
  for (let candidateIndex = eligibleIndex; candidateIndex < eligible.length; candidateIndex += 1) {
5780
5623
  const candidateEntry = eligible[candidateIndex];
5781
5624
  const candidateTarget = candidateEntry.target;
@@ -5786,8 +5629,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
5786
5629
  && this.vmReconcileRotationState.get(this.vmReconcileRotationSlotKey(candidateTarget)) === candidateRecord
5787
5630
  ? candidateRecord
5788
5631
  : undefined;
5789
- // The current target was selected and marked attempted before the
5790
- // connection/admission boundary. Later candidates must still prove
5632
+ // The current target already passed connection/network admission.
5633
+ // Later candidates must still prove
5791
5634
  // this peer remains uncredited in their independent rotation record.
5792
5635
  const peerEligible = candidateIndex === eligibleIndex
5793
5636
  || (candidateInstalledRecord
@@ -5795,7 +5638,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5795
5638
  : orderedPeerIds.includes(peerId));
5796
5639
  if (!peerEligible)
5797
5640
  break;
5798
- compatible.push({
5641
+ candidateAttempts.push({
5799
5642
  entry: candidateEntry,
5800
5643
  installedRecord: candidateInstalledRecord,
5801
5644
  candidatePeerIds: candidateInstalledRecord
@@ -5803,53 +5646,85 @@ export class SwmHostModeMethods extends DKGAgentBase {
5803
5646
  : orderedPeerIds,
5804
5647
  });
5805
5648
  }
5806
- // Only candidates that survived rotation/admission are sized. Keeping
5807
- // this hint local to the planner avoids broadening the canonical chain
5808
- // ordinal target and does not waste bounded RPC reads on ineligible
5809
- // prefixes. Missing/failed hints remain conservative singletons.
5810
- const readVmRecoveryUpdateContext = this.chain.getKnowledgeAssetUpdateContext;
5811
- const sizedCandidates = await enrichVmRecoveryFootprints(compatible.map((attempt) => ({
5812
- attempt,
5813
- kaId: attempt.entry.target.kaId,
5814
- })), onChainCgId, {
5815
- resolvePublicAccess: async (contextGraphId) => (await withRpcUsageSite(CG_AUTH_RPC_SITES.vmSizing,
5816
- // Sizing a recovery batch: this decides how much to READ, not
5817
- // who may. A bound stale by one index tick changes a batch
5818
- // size, and the next pass corrects it. Inert unless the
5819
- // operator enables `chain.boundedAuthorityReads`.
5820
- () => this.readLiveOnChainAccessPolicy(contextGraphId.toString(), ctx, { freshness: 'bounded' }))) === 0,
5821
- sizing: typeof readVmRecoveryUpdateContext === 'function'
5822
- ? {
5823
- readUpdateContext: (kaId, readOptions) => readVmRecoveryUpdateContext.call(this.chain, kaId, readOptions),
5824
- }
5825
- : null,
5826
- }, {
5827
- maxContextReads: MAX_EXACT_SYNC_ASSETS,
5828
- signal,
5829
- isCurrent: isRecoveryCurrent,
5830
- });
5831
- if (!isRecoveryCurrent())
5832
- return noRecovery();
5833
- const plannableTargets = sizedCandidates.map(({ attempt, recoveryFootprint }) => ({
5834
- attempt,
5835
- recoveryFootprint,
5836
- }));
5837
- const plan = planVmRecoveryMicrobatch(plannableTargets, VM_EXACT_MICROBATCH_LIMITS, (plannedTargets) => Buffer.byteLength(encodeExactAssetUals(plannedTargets.map(({ attempt }) => attempt.entry.target.ual)), 'utf8'));
5838
- if (plan.targets.length === 0) {
5839
- this.log.warn(ctx, `VM exact recovery selector for "${localCgId}" exceeds the executor cap; `
5840
- + `ordinal=${target.ordinal} selectorCap=${VM_EXACT_MICROBATCH_LIMITS.maxSelectorBytes}`);
5841
- if (installedRecord) {
5842
- this.settleVmReconcileRotationAttempt(target, undefined, 'incomplete', candidatePeerIds, installedRecord, providerPolicy.unavailablePeerIds());
5843
- }
5844
- providerPolicy.finishAttempt(providerAttempt, 'incomplete', new Map([[target.ual, 'incomplete']]));
5845
- continue;
5649
+ }
5650
+ const streamEligibleProvider = passAuthority.isPublic && experimentalStreamPeerIds.has(peerId)
5651
+ && !exactBatchStreamUnsupported(this, peerId, admittedConnectionKey, Date.now(), this.captureExperimentalExactBatchRefusalScope(localCgId));
5652
+ let transportPlan;
5653
+ transportPlan = await phases.measure('sizing', () => planVmRecoveryTransport({
5654
+ candidates: candidateAttempts.map(attempt => ({
5655
+ attempt, kaId: attempt.entry.target.kaId, assetUal: attempt.entry.target.ual,
5656
+ })),
5657
+ providerAttemptKind: providerAttempt.kind, onChainCgId,
5658
+ streamEligible: streamEligibleProvider, registeredPublicAccess: passAuthority.isPublic,
5659
+ legacyAttemptTimeoutMs: this.vmReconcileTransportBudgetPolicy.timeoutFor({
5660
+ target, peerId, providerAttemptKind: providerAttempt.kind,
5661
+ registeredPublicAccess: passAuthority.isPublic,
5662
+ competingStreamAvailable: experimentalStreamPeerIds.size > 0
5663
+ && !experimentalStreamPeerIds.has(peerId),
5664
+ streamEligible: streamEligibleProvider,
5665
+ }),
5666
+ signal, isCurrent: isRecoveryCurrent,
5667
+ observeSizing: batchTiming.observeSizing,
5668
+ // Start reads in candidate order with a small bound so each read's
5669
+ // deadline measures its own round trip, not its wait in the local
5670
+ // RPC governor behind earlier candidates.
5671
+ ...experimentPolicy.sizing,
5672
+ ...(experimentPolicy.prefetchEnabled ? {
5673
+ probeRemainder: eligible.slice(eligibleIndex + 1)
5674
+ .filter(candidate => !handledBatchOrdinals.has(candidate.target.ordinal))
5675
+ .map(candidate => ({ kaId: candidate.target.kaId })),
5676
+ } : {}),
5677
+ }, {
5678
+ createSizingReader: () => {
5679
+ const readContext = this.chain.getKnowledgeAssetUpdateContext;
5680
+ return typeof readContext === 'function'
5681
+ ? { readUpdateContext: (kaId, readOptions) => readContext.call(this.chain, kaId, readOptions) }
5682
+ : null;
5683
+ },
5684
+ resolvePublicAccess: async (contextGraphId) => (await withRpcUsageSite(CG_AUTH_RPC_SITES.vmSizing,
5685
+ // This bounded observation controls soft sizing only. Canonical
5686
+ // root/version/binding authority is still checked per asset.
5687
+ () => this.readLiveOnChainAccessPolicy(contextGraphId.toString(), ctx, { freshness: 'bounded' }))) === 0,
5688
+ preparation: transportPreparation,
5689
+ }));
5690
+ if (!isRecoveryCurrent())
5691
+ return providerAttempt.kind === 'probe' ? staleRecovery() : noRecovery();
5692
+ const { attempts: batchAttempts, transportMode: exactRecoveryTransportMode, publicAccessEvidence: publicRecoveryAccessVerified, packing, legacyAttemptTimeoutMs, } = transportPlan;
5693
+ if (batchAttempts.length === 0) {
5694
+ this.vmReconcilePublicCoreTransportPreferencePolicy.revoke(preferenceAttempt, peerId);
5695
+ this.log.warn(ctx, `VM exact recovery selector for "${localCgId}" exceeds the executor cap; `
5696
+ + `ordinal=${target.ordinal} selectorCap=${VM_EXACT_MICROBATCH_LIMITS.maxSelectorBytes}`);
5697
+ if (installedRecord) {
5698
+ this.settleVmReconcileRotationAttempt(target, undefined, 'incomplete', candidatePeerIds, installedRecord, providerPolicy.unavailablePeerIds());
5846
5699
  }
5847
- batchAttempts = plan.targets.map(({ attempt }) => attempt);
5700
+ providerPolicy.finishAttempt(providerAttempt, 'incomplete', new Map([[target.ual, 'incomplete']]));
5701
+ continue;
5702
+ }
5703
+ observeVmRecoveryTiming(() => this.log.info(ctx, `VM exact recovery transport for "${localCgId}" from ${peerId.slice(-8)}: kind=${providerAttempt.kind} `
5704
+ + `transport=${exactRecoveryTransportMode} assets=${batchAttempts.length} `
5705
+ + `streamAdvertised=${streamAdvertisedCount} streamPeers=${experimentalStreamPeerIds.size} `
5706
+ + `registeredAuthority=${passAuthority.label}`));
5707
+ if (packing !== undefined) {
5848
5708
  this.log.info(ctx, `VM exact recovery plan for "${localCgId}" from ${peerId.slice(-8)}: `
5849
- + `assets=${batchAttempts.length} estimatedBytes=${plan.estimatedBytes} `
5850
- + `estimatedLeaves=${plan.estimatedLeaves} `
5851
- + `completeFootprints=${plan.completeFootprints}`);
5709
+ + `assets=${batchAttempts.length} estimatedBytes=${packing.estimatedBytes} `
5710
+ + `estimatedLeaves=${packing.estimatedLeaves} `
5711
+ + `completeFootprints=${packing.completeFootprints} transport=${exactRecoveryTransportMode}`);
5712
+ }
5713
+ if (carriedHolder && !this.vmReconcilePublicCoreTransportPreferencePolicy.revalidateReuse(preferenceAttempt, peerId, admittedConnectionKey)) {
5714
+ // Sizing outlived the captured holder proof; the next slice must probe.
5715
+ if (activeFetchCooldownOwner !== undefined) {
5716
+ this.clearVmReconcileActiveFetchCooldown(localCgId, activeFetchCooldownOwner);
5717
+ }
5718
+ return {
5719
+ outcomes, attemptedOrdinals: [...attemptedOrdinals],
5720
+ continuationOrdinal: target.ordinal, hasImmediateRecoveryWork: true, cooldownOnly: false,
5721
+ };
5852
5722
  }
5723
+ // Only while preparation is on: the exchange may rely on this pass's own fresh positive
5724
+ // answer instead of reading it again. The handle dies with the exchange.
5725
+ const registeredPublicEvidence = experimentPolicy.sharePassAuthorityEvidence
5726
+ ? passAuthority.evidence({ contextGraphId: localCgId, signal, isCurrent: isRecoveryCurrent })
5727
+ : undefined;
5853
5728
  const execution = await this.executeVmRecoveryBatch({
5854
5729
  localCgId,
5855
5730
  onChainCgId,
@@ -5861,16 +5736,52 @@ export class SwmHostModeMethods extends DKGAgentBase {
5861
5736
  isRecoveryCurrent,
5862
5737
  revalidateTarget,
5863
5738
  ctx,
5864
- });
5865
- if (execution.kind !== 'completed')
5739
+ exactRecoveryTransportMode,
5740
+ ...(legacyAttemptTimeoutMs === undefined ? {} : { legacyAttemptTimeoutMs }),
5741
+ ...(registeredPublicEvidence ? { registeredPublicEvidence } : {}),
5742
+ phases,
5743
+ }).finally(() => registeredPublicEvidence?.revoke());
5744
+ if (execution.kind === 'local-admission-deferred') {
5745
+ // Capacity is node-local, so trying other providers would burn their
5746
+ // turns under the same refusal. Retain all peer evidence and yield the
5747
+ // whole slice for the scheduler's bounded local retry.
5748
+ localAdmissionDeferred = true;
5749
+ break;
5750
+ }
5751
+ if (execution.kind !== 'completed' || !isRecoveryCurrent())
5866
5752
  return staleRecovery();
5753
+ batchTiming.complete({ peerId, assets: batchAttempts.length,
5754
+ candidates: eligible.length - eligibleIndex, kind: providerAttempt.kind,
5755
+ transport: exactRecoveryTransportMode, streamAdvertised: streamAdvertisedCount,
5756
+ streamPeers: experimentalStreamPeerIds.size, registeredAuthority: passAuthority.label });
5867
5757
  for (const [ordinal, outcome] of execution.outcomes)
5868
5758
  outcomes.set(ordinal, outcome);
5869
5759
  for (const ordinal of execution.handledOrdinals)
5870
5760
  handledBatchOrdinals.add(ordinal);
5871
5761
  for (const ordinal of execution.attemptedOrdinals)
5872
5762
  attemptedOrdinals.add(ordinal);
5873
- providerPolicy.finishAttempt(providerAttempt, execution.providerDisposition, new Map(execution.perUalDispositions));
5763
+ if (execution.providerTurnKept) {
5764
+ // Busy or a broken stream: the peer is not spent for this slice. It is
5765
+ // asked again, with a fresh probe, once its hold-off has passed.
5766
+ providerPolicy.releaseAttempt(providerAttempt);
5767
+ }
5768
+ else {
5769
+ providerPolicy.finishAttempt(providerAttempt, execution.providerDisposition, new Map(execution.perUalDispositions));
5770
+ }
5771
+ const completelyVerified = execution.providerDisposition === 'found'
5772
+ && execution.perUalDispositions.length === batchAttempts.length
5773
+ && execution.perUalDispositions.every(([, disposition]) => disposition === 'found');
5774
+ const preferenceSettlement = this.vmReconcilePublicCoreTransportPreferencePolicy.settle(preferenceAttempt, {
5775
+ peerId, connectionKey: admittedConnectionKey, completelyVerified,
5776
+ publicAccessVerified: publicRecoveryAccessVerified,
5777
+ reusedHolder: providerAttempt.kind === 'proven-holder-reuse', carriedHolder,
5778
+ });
5779
+ if (preferenceSettlement.yield) {
5780
+ this.log.info(ctx, `VM exact recovery yield: reason=verified-core-turn assets=${batchAttempts.length} `
5781
+ + `publicSizingAccess=${publicRecoveryAccessVerified === undefined ? 'unknown' : publicRecoveryAccessVerified} `
5782
+ + `holderCreditPresent=${preferenceSettlement.holderCreditPresent}`);
5783
+ break;
5784
+ }
5874
5785
  }
5875
5786
  const eligibleOrdinals = new Set(eligible.map(({ target }) => target.ordinal));
5876
5787
  const unattemptedContinuationOrdinal = currentTargets.find((target) => eligibleOrdinals.has(target.ordinal) && !attemptedOrdinals.has(target.ordinal))?.ordinal;
@@ -5901,6 +5812,19 @@ export class SwmHostModeMethods extends DKGAgentBase {
5901
5812
  // negative backoff applies exactly as before.
5902
5813
  if (!isRecoveryCurrent())
5903
5814
  return staleRecovery();
5815
+ if (localAdmissionDeferred) {
5816
+ if (activeFetchCooldownOwner !== undefined) {
5817
+ this.clearVmReconcileActiveFetchCooldown(localCgId, activeFetchCooldownOwner);
5818
+ }
5819
+ return {
5820
+ outcomes,
5821
+ attemptedOrdinals: [...attemptedOrdinals],
5822
+ continuationOrdinal: unattemptedContinuationOrdinal,
5823
+ hasImmediateRecoveryWork: false,
5824
+ cooldownOnly: false,
5825
+ localAdmissionDeferred: true,
5826
+ };
5827
+ }
5904
5828
  const recoveredAny = [...outcomes.values()]
5905
5829
  .some((outcome) => outcome.status === 'reconciled' || outcome.status === 'already');
5906
5830
  if (!recoveryWorkRan
@@ -5917,6 +5841,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5917
5841
  this.installVmReconcileActiveFetchCooldown(localCgId, Date.now());
5918
5842
  }
5919
5843
  }
5844
+ timing.finish(eligible.length, preparation ? () => preparation.stats() : undefined);
5920
5845
  return {
5921
5846
  outcomes,
5922
5847
  attemptedOrdinals: [...attemptedOrdinals],
@@ -5934,7 +5859,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5934
5859
  * latest on-chain merkle root + publisher, build the UAL, and ask the
5935
5860
  * finalization handler to promote the matching local SWM snapshot to VM
5936
5861
  * (verifying the CG binding from chain). When no local SWM matches, run an
5937
- * active core-first catch-up fetch and retry once. A successful result is
5862
+ * queue the exact target for bounded batch recovery. A successful result is
5938
5863
  * validated against one coherent pinned version snapshot before it earns a
5939
5864
  * same-finalized-block shortcut; a new block always reads again. `headBlock`
5940
5865
  * remains the independent cursor observation for the reorg-depth gate. See
@@ -5942,7 +5867,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
5942
5867
  *
5943
5868
  * Local first: before any root, publisher or version read, the handler
5944
5869
  * classifies what the store holds for the KA. A confirmed VM copy with
5945
- * nothing else to promote settles as `already`, and in the deferred mode an
5870
+ * nothing else to promote settles as `already`, and an
5946
5871
  * asset held nowhere locally goes straight to the exact-recovery queue, both
5947
5872
  * without chain reads. Only local state whose outcome depends on the root
5948
5873
  * takes the chain-backed path below.
@@ -6016,11 +5941,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
6016
5941
  });
6017
5942
  return { status: 'already', blockNumber: headBlock ?? 0 };
6018
5943
  }
6019
- if (localCandidate.kind === 'none' && options.deferActiveFetch) {
5944
+ if (localCandidate.kind === 'none') {
6020
5945
  // Nothing local can match any root, so the chain-backed path could only
6021
5946
  // answer `no-swm`. Queue the same exact-recovery target it would; the
6022
5947
  // recovery batch keeps its rotation backoff, cooldown and peer gating.
6023
- // The inline mode keys its negative cache by the root and keeps reading it.
6024
5948
  if (!(await targetMayMaterialize()))
6025
5949
  return { status: 'skip' };
6026
5950
  this.emitReplication({
@@ -6082,18 +6006,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
6082
6006
  }
6083
6007
  merkleRoot = await this.chain.getLatestMerkleRoot(kaId);
6084
6008
  cacheKey = this.vmReconcileCacheKey(localCgId, ual, merkleRoot);
6085
- if (!options.deferActiveFetch && await this.shouldDeferVmReconcileByNegativeCache(cacheKey, localCgId)) {
6086
- this.emitReplication({
6087
- contextGraphId: localCgId,
6088
- onChainCgId: onChainCgId.toString(),
6089
- action: 'defer',
6090
- ordinal,
6091
- kaId: kaId.toString(),
6092
- ual,
6093
- detail: 'negative-cache',
6094
- });
6095
- return { status: 'pending' };
6096
- }
6097
6009
  publisherAddress = (this.chain.getLatestMerkleRootPublisher
6098
6010
  ? await this.chain.getLatestMerkleRootPublisher(kaId)
6099
6011
  : '') ?? '';
@@ -6140,119 +6052,33 @@ export class SwmHostModeMethods extends DKGAgentBase {
6140
6052
  return undefined;
6141
6053
  }
6142
6054
  };
6143
- let swmState;
6144
- let activeFetchRan = false;
6145
- let activeFetchHadUsableResponse = false;
6146
- const cleanMissPeerIds = new Set();
6147
6055
  if (!(await targetMayMaterialize()))
6148
6056
  return { status: 'skip' };
6149
- let outcome = await reconcileKnowledgeAsset();
6057
+ const outcome = await reconcileKnowledgeAsset();
6150
6058
  if (outcome === undefined)
6151
6059
  return { status: 'pending' };
6152
6060
  if (outcome === 'no-swm' || outcome === 'verified-vm-metadata-pending') {
6153
- if (options.deferActiveFetch) {
6154
- this.emitReplication({
6155
- contextGraphId: localCgId,
6061
+ this.emitReplication({
6062
+ contextGraphId: localCgId,
6063
+ onChainCgId: onChainCgId.toString(),
6064
+ action: 'defer',
6065
+ ordinal,
6066
+ kaId: kaId.toString(),
6067
+ ual,
6068
+ detail: outcome,
6069
+ });
6070
+ return {
6071
+ status: 'pending',
6072
+ recovery: {
6073
+ localCgId,
6156
6074
  onChainCgId: onChainCgId.toString(),
6157
- action: 'defer',
6158
6075
  ordinal,
6159
- kaId: kaId.toString(),
6160
6076
  ual,
6161
- detail: outcome,
6162
- });
6163
- return {
6164
- status: 'pending',
6165
- recovery: {
6166
- localCgId,
6167
- onChainCgId: onChainCgId.toString(),
6168
- ordinal,
6169
- ual,
6170
- merkleRoot: Array.from(merkleRoot, (byte) => byte.toString(16).padStart(2, '0')).join(''),
6171
- kaId: kaId.toString(),
6172
- reason: outcome,
6173
- },
6174
- };
6175
- }
6176
- if (outcome === 'no-swm') {
6177
- swmState = await this.collectVmReconcileSwmCandidateState(localCgId);
6178
- }
6179
- // Active fetch: pull the missing snapshot core-first (selectCatchupPeers
6180
- // already prioritises known cores + the preferred sync peer), then retry.
6181
- // Metadata-pending exact VM content needs the same recovery: a durable
6182
- // sync can supply the missing provenance-bearing assertion metadata even
6183
- // when no content triples need to move.
6184
- const batchAllowsFetch = options.acquireActiveFetchPermit?.() ?? true;
6185
- const cooldownAllowsFetch = batchAllowsFetch
6186
- && this.shouldRunVmReconcileActiveFetch(localCgId);
6187
- if (batchAllowsFetch && cooldownAllowsFetch) {
6188
- activeFetchRan = true;
6189
- this.emitReplication({
6190
- contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
6191
- action: 'fetch', ordinal, kaId: kaId.toString(), ual,
6192
- });
6193
- let maxAttempts = 1;
6194
- const fixedMaxAttempts = options.maxPeerAttempts === undefined
6195
- ? undefined
6196
- : Math.max(1, Math.floor(options.maxPeerAttempts));
6197
- if (fixedMaxAttempts !== undefined)
6198
- maxAttempts = fixedMaxAttempts;
6199
- for (let attempt = 0; attempt < maxAttempts
6200
- && (outcome === 'no-swm' || outcome === 'verified-vm-metadata-pending'); attempt += 1) {
6201
- if (options.isTargetCurrent && !options.isTargetCurrent()) {
6202
- break;
6203
- }
6204
- try {
6205
- const recovery = await this.syncVmRecoveryFromConnectedPeers(localCgId, {
6206
- includeSharedMemory: true,
6207
- maxPeers: 1,
6208
- peerRotationKey: localCgId,
6209
- // This is recovery, not routine background catch-up. Without the
6210
- // override it would enter through the default-background path and
6211
- // be reported as `catchup-background`, merging repair traffic into
6212
- // the background lane. Attribution only — mode, priority, peer
6213
- // selection and the coalescing key are all unchanged.
6214
- sourceOverride: 'vm-recovery',
6215
- });
6216
- const fetchResult = recovery.catchup;
6217
- if (fixedMaxAttempts === undefined) {
6218
- maxAttempts = Math.max(maxAttempts, fetchResult.totalPeers ?? fetchResult.connectedPeers ?? 0, this.vmReconcileConnectedPeerCount());
6219
- }
6220
- if ((fetchResult.peersTried ?? 0) === 0 && (fetchResult.syncCapablePeers ?? 0) === 0) {
6221
- continue;
6222
- }
6223
- if (!this.vmReconcileActiveFetchHadUsableResponse(fetchResult)) {
6224
- continue;
6225
- }
6226
- activeFetchHadUsableResponse = true;
6227
- for (const peerId of recovery.cleanMissPeerIds) {
6228
- cleanMissPeerIds.add(peerId);
6229
- }
6230
- }
6231
- catch (err) {
6232
- this.log.info(ctx, `Phase B: active fetch for "${localCgId}" (ordinal ${ordinal}) failed: ${err instanceof Error ? err.message : String(err)}`);
6233
- if (fixedMaxAttempts === undefined) {
6234
- maxAttempts = Math.max(maxAttempts, this.vmReconcileConnectedPeerCount());
6235
- }
6236
- continue;
6237
- }
6238
- if (options.isTargetCurrent && !options.isTargetCurrent()) {
6239
- break;
6240
- }
6241
- if (!(await targetMayMaterialize()))
6242
- return { status: 'skip' };
6243
- outcome = await reconcileKnowledgeAsset();
6244
- if (outcome === undefined)
6245
- return { status: 'pending' };
6246
- }
6247
- if (outcome === 'no-swm') {
6248
- swmState = await this.collectVmReconcileSwmCandidateState(localCgId);
6249
- swmState.cleanMissPeerIds = createVmReconcileCleanMissPeerIds(swmState.peerTopology, [...cleanMissPeerIds]);
6250
- }
6251
- }
6252
- else {
6253
- const reason = batchAllowsFetch ? 'per-CG cooldown' : 'per-batch fetch budget';
6254
- this.log.info(ctx, `Phase B: active fetch for "${localCgId}" (ordinal ${ordinal}) skipped by ${reason}`);
6255
- }
6077
+ merkleRoot: Array.from(merkleRoot, (byte) => byte.toString(16).padStart(2, '0')).join(''),
6078
+ kaId: kaId.toString(),
6079
+ reason: outcome,
6080
+ },
6081
+ };
6256
6082
  }
6257
6083
  if (options.isTargetCurrent && !options.isTargetCurrent()) {
6258
6084
  return { status: 'skip' };
@@ -6268,7 +6094,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
6268
6094
  case 'promoted':
6269
6095
  this.clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal);
6270
6096
  this.pruneVmReconcileCacheKeySiblings(cacheKey);
6271
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
6272
6097
  this.recentReconciledUals.add(cacheKey);
6273
6098
  await this.confirmAndRememberVmReconcileFinalizedSlot(localCgId, onChainCgId, ordinal, rememberSlotBlock, kaId, merkleRoot, publisherAddress);
6274
6099
  this.emitReplication({
@@ -6285,7 +6110,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
6285
6110
  contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
6286
6111
  action: 'already', ordinal, kaId: kaId.toString(), ual,
6287
6112
  });
6288
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
6289
6113
  return { status: 'already', blockNumber: completionBlock };
6290
6114
  case 'stale-target':
6291
6115
  this.clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal);
@@ -6296,20 +6120,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
6296
6120
  contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
6297
6121
  action: 'already', ordinal, kaId: kaId.toString(), ual,
6298
6122
  });
6299
- this.deleteVmReconcileNegativeCacheEntry(cacheKey);
6300
6123
  return { status: 'already', blockNumber: completionBlock };
6301
- case 'no-swm':
6302
- if (activeFetchRan && !activeFetchHadUsableResponse) {
6303
- this.clearVmReconcileActiveFetchCooldown(localCgId);
6304
- }
6305
- else {
6306
- this.recordVmReconcileNegativeCache(cacheKey, localCgId, swmState ?? await this.collectVmReconcileSwmCandidateState(localCgId));
6307
- }
6308
- this.emitReplication({
6309
- contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
6310
- action: 'defer', ordinal, kaId: kaId.toString(), ual, detail: activeFetchRan && !activeFetchHadUsableResponse ? 'network-unavailable' : outcome,
6311
- });
6312
- return { status: 'pending' };
6313
6124
  case 'receipt-revalidation-pending':
6314
6125
  case 'unverified':
6315
6126
  default:
@@ -6529,7 +6340,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
6529
6340
  // on nodes that have persisted the CG's `allowedPeers`; host-only
6530
6341
  // cores never see it.
6531
6342
  try {
6532
- const allowedPeers = await this.getContextGraphAllowedPeers(req.contextGraphId);
6343
+ const allowedPeers = await this.resolveSwmAllowedPeersForCurrentAuthority(req.contextGraphId);
6533
6344
  if (allowedPeers !== null) {
6534
6345
  anyAuthoritySourceFound = true;
6535
6346
  if (allowedPeers.includes(fromPeerId)) {
@@ -6859,6 +6670,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
6859
6670
  * two would race / duplicate every apply (Codex PR #610 R4).
6860
6671
  */
6861
6672
  async enableSwmHostModeFor(contextGraphId) {
6673
+ const session = this.gossipSession;
6674
+ if (!session.active)
6675
+ return { subscribed: false, alreadySubscribed: false, hostingEnabled: false };
6862
6676
  if (!this.swmHostModeStore) {
6863
6677
  return { subscribed: false, alreadySubscribed: false, hostingEnabled: false };
6864
6678
  }
@@ -6882,6 +6696,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
6882
6696
  }
6883
6697
  const hostKey = this.canonicalSwmHostModeKey(contextGraphId);
6884
6698
  const curated = await this.isCuratedForHostMode(contextGraphId);
6699
+ if (!session.active || this.gossipSession !== session) {
6700
+ return { subscribed: false, alreadySubscribed: false, hostingEnabled: true };
6701
+ }
6885
6702
  if (this.swmHostModeSubscribed.has(hostKey)) {
6886
6703
  // Idempotent re-entry: even when the subscription is already
6887
6704
  // active, re-probe registration state. This handles the