@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
@@ -1,68 +1,18 @@
1
- import { SWM_SNAPSHOT_CONTENT_DIGEST_PREDICATE, SWM_SNAPSHOT_MERKLE_ROOT_PREDICATE, } from './finalization-annotations.js';
2
- import { decodeFinalizationMessage, contextGraphWorkspaceGraphUri, contextGraphWorkspaceMetaGraphUri, contextGraphDataUri, contextGraphMetaUri, contextGraphOnChainIdBindingQuery, contextGraphSubGraphUri, validateSubGraphName, validateContextGraphId, DKGEvent, Logger, createOperationContext, assertSafeIri, isSafeIri, GRAPH_KA_CONTENT_SCOPE_VERSION, MemoryLayer, createGraphKnowledgeAssetScope, knowledgeAssetLayerGraphUri, DKG_ENTITY, DKG_ROOT_ENTITY_LEGACY, ENTITY_PRED_ALT, getMetrics, sparqlString, } from '@origintrail-official/dkg-core';
3
- import { deleteByPatternWithoutCount, GraphManager, loadSelectedSharedMemoryQuads, loadSharedMemorySliceWithKaBoundFallback, asGraphWriteRevisionSource, resolveGraphScopedOrLegacyMetadata, resolveSharedMemoryScopeGraphs, tryReplaceGraphAtomically, tryReplaceGraphAndSubjectAtomically, StoreSchedulerBusyError, } from '@origintrail-official/dkg-storage';
1
+ import { decodeFinalizationMessage, contextGraphMetaUri, validateSubGraphName, validateContextGraphId, DKGEvent, Logger, createOperationContext, assertSafeIri, GRAPH_KA_CONTENT_SCOPE_VERSION, MemoryLayer, createGraphKnowledgeAssetScope, knowledgeAssetLayerGraphUri, sparqlString, } from '@origintrail-official/dkg-core';
2
+ import { GraphManager, resolveGraphScopedOrLegacyMetadata, resolveSharedMemoryScopeGraphs, tryReplaceGraphAtomically, tryReplaceGraphAndSubjectAtomically, StoreSchedulerBusyError, } from '@origintrail-official/dkg-storage';
4
3
  import { resolvePublicFinalizedMaterializationAuthority, } from '@origintrail-official/dkg-chain';
5
- import { computeFlatKCRootV10 as computeFlatKCRoot, skolemizeByEntity, generatedPrivateCatalogFloorQuads, generatedPrivateCatalogTripleKeys, generateConfirmedFullMetadata, generateGraphKnowledgeAssetMetadata, buildDeterministicTokenRows, compareRootIris, getTentativeStatusQuad, insertBoundedAgentRegistryMeta, generateSubGraphRegistration, splitTrustedGeneratedCatalogRootMap, compareMaterializedVersion, readMaterializedVersion, shouldApplyMaterialization, writeMaterializedVersion, materializedVersionQuad, withMaterializationLock, workspacePublicQuadsDigest, KnowledgeAssetWorkspaceHeadCorruptError, isKnowledgeAssetWorkspaceHeadCorruptError, resolveKnowledgeAssetWorkspaceHead, } from '@origintrail-official/dkg-publisher';
4
+ import { generateGraphKnowledgeAssetMetadata, compareMaterializedVersion, readMaterializedVersion, writeMaterializedVersion, materializedVersionQuad, withMaterializationLock, workspacePublicQuadsDigest, KnowledgeAssetWorkspaceHeadCorruptError, isKnowledgeAssetWorkspaceHeadCorruptError, resolveKnowledgeAssetWorkspaceHead, } from '@origintrail-official/dkg-publisher';
6
5
  const DKG_NS = 'http://dkg.io/ontology/';
7
6
  const PROV_NS = 'http://www.w3.org/ns/prov#';
8
7
  const CHAIN_FINALIZED_RECONCILE_PEER_ID = 'chain-finalized-reconcile-v1';
9
- // Slow-query / canary tags for the finalization SWM slice (#1549). A healthy fleet
10
- // sees `.fallbackUnbounded` at ~0 relative to `.bounded`; a spike means the bound is
11
- // mis-derived or recurrence is common, and `DKG_DISABLE_SWM_KA_BOUND=1` is the lever.
12
- const SWM_SLICE_SOURCE = 'agent.finalization.sharedMemorySlice';
13
- const SWM_SLICE_SOURCE_BOUNDED = `${SWM_SLICE_SOURCE}.bounded`;
14
- const SWM_SLICE_SOURCE_WIDENED = `${SWM_SLICE_SOURCE}.fallbackUnbounded`;
15
8
  import { ethers } from 'ethers';
16
- import { createHash } from 'node:crypto';
17
- import { deriveSwmKaGraphBound } from './swm-ka-bound.js';
18
9
  import { FinalizationLifecycleLogger, finalizationLifecycleDecision, } from './finalization-lifecycle-logger.js';
19
10
  import { FinalizationRecoveryCapacityError, FinalizationRecovery, } from './finalization-recovery.js';
20
11
  import { FinalizationRecoveryWorker, } from './finalization-recovery-worker.js';
21
12
  import { recoverReceiptBackedGraphScopedEvidence } from './receipt-backed-graph-scoped-evidence.js';
22
13
  import { resolveConfirmedGraphScopedVm, resolveLocallyConfirmedGraphScopedVm, } from './confirmed-graph-scoped-vm-resolver.js';
23
14
  import { verifyExactGraphContent, } from './exact-graph-content-verifier.js';
24
- import { protobufScalarToBigInt, protobufScalarToNumber } from './protobuf-scalars.js';
25
15
  import { createDurableFinalizationRecoveryEligibility, } from './finalization-recovery-eligibility.js';
26
- /**
27
- * Predicate for the durable per-root keep-root-copy signal the publisher
28
- * persists into SWM workspace meta at publish time (the chain-driven
29
- * reconcile path's equivalent of the gossip envelope's `keepRootCopyOnLabel`).
30
- * Shared with `DKGAgent` so the write and read sites can't drift.
31
- */
32
- export const KEEP_ROOT_COPY_PREDICATE = `${DKG_NS}keepRootCopyOnLabel`;
33
- /**
34
- * Reader-maintained memo (#1609): the flat-KC merkle root a WorkspaceOperation's
35
- * SWM snapshot hashes to, paired with `SWM_SNAPSHOT_CONTENT_DIGEST_PREDICATE` (a
36
- * cheap digest of the exact content that produced it), stamped onto the op subject
37
- * (`urn:dkg:share:<cg>:<id>`) the first time `findSwmSnapshotInNamespace` computes
38
- * it. Lets a chain-reconcile lookup (a) resolve a *present* KA's op by root
39
- * directly (fast path), and (b) skip the expensive `computeFlatKCRoot` recompute
40
- * for an op whose content is unchanged (the digest still matches) — the recompute
41
- * that dominates beacon reconcile load when a KA published elsewhere forces a full
42
- * O(#WorkspaceOperations) scan to conclude "not here".
43
- *
44
- * This memo is a bridge, NOT the durable fix (see OT-RFC-60): a WorkspaceOperation
45
- * is assembled incrementally (data quads and private roots arrive over time via
46
- * *entity-keyed* writes that do NOT rewrite the op subject), so the stamp can go
47
- * stale without a structural invalidation. Correctness therefore does NOT rely on
48
- * the stamp being fresh:
49
- * - `verifyMerkleMatch` stays authoritative on the fast path — a stale stamp can
50
- * only ever cause a *missed* promotion, never a wrong one.
51
- * - the fallback scan re-reads EVERY op (it never excludes on stamp-presence) and
52
- * trusts the memoized root only when the content digest still matches; any
53
- * content change flips the digest → full recompute → the op is re-evaluated and
54
- * re-stamped. An op can never be stranded by a stale stamp.
55
- * The durable fix (OT-RFC-60) makes the root a write-maintained, indexed property
56
- * stamped once when an op becomes complete-and-immutable, removing both the
57
- * recompute AND the staleness by construction.
58
- *
59
- * Deliberately invisible to the sibling VM-reconcile negative cache: its
60
- * `readVmReconcileSwmGen` / `vmReconcileWorkspaceOperationPattern` fingerprints
61
- * select only `rootEntity`/`publishedAt` on the op subject (plus the separate data
62
- * graph), so stamping these predicates into the meta graph does not perturb the
63
- * generation signal the negative cache keys on — the two mechanisms don't fight.
64
- */
65
- export { SWM_SNAPSHOT_CONTENT_DIGEST_PREDICATE, SWM_SNAPSHOT_MERKLE_ROOT_PREDICATE, } from './finalization-annotations.js';
66
16
  function stripOptionalLiteral(value) {
67
17
  if (!value)
68
18
  return undefined;
@@ -101,45 +51,6 @@ function resolveGraphScopedAccessEnvelope(head, requestedAccess) {
101
51
  function normalizedHex(value) {
102
52
  return value.replace(/^0x/i, '').toLowerCase();
103
53
  }
104
- /**
105
- * Ops kill-switch for the #1549 bounded SWM read. Set `DKG_DISABLE_SWM_KA_BOUND=1`
106
- * to fall back to the unbounded read with no redeploy. Read here at the
107
- * orchestration boundary so `deriveSwmKaGraphBound` (in `swm-ka-bound.ts`) stays a
108
- * deterministic identity transform.
109
- */
110
- function swmKaBoundDisabled() {
111
- return process.env.DKG_DISABLE_SWM_KA_BOUND === '1';
112
- }
113
- /**
114
- * TTL cap on the in-memory negative reconcile memo (#1609) — the longest a
115
- * "no local SWM snapshot" verdict may be trusted without a fresh scan even
116
- * when no write-generation change was observed. Bounds the exposure to any
117
- * SWM writer invisible to the adapter's write-generation counter (e.g. a
118
- * second process mutating a shared oxigraph-server) to "≤ TTL late", never
119
- * "never". Read per call (like `swmKaBoundDisabled`) so ops and tests can
120
- * retune it without a redeploy.
121
- */
122
- const VM_RECONCILE_NEGATIVE_TTL_MS_DEFAULT = 600_000;
123
- function vmReconcileNegativeTtlMs() {
124
- const raw = Number(process.env['DKG_VM_RECONCILE_NEGATIVE_TTL_MS']);
125
- return Number.isFinite(raw) && raw > 0 ? raw : VM_RECONCILE_NEGATIVE_TTL_MS_DEFAULT;
126
- }
127
- /** LRU cap for the negative reconcile memo — bounds memory across CGs × roots. */
128
- const VM_RECONCILE_NEGATIVE_MEMO_MAX_ENTRIES = 4096;
129
- const FINALIZATION_SWM_PAGE_ROWS_DEFAULT = 1_000;
130
- const FINALIZATION_SWM_MAX_ROWS_DEFAULT = 250_000;
131
- const FINALIZATION_SWM_MAX_BYTES_DEFAULT = 128 * 1024 * 1024;
132
- function positiveIntegerEnv(name, fallback) {
133
- const parsed = Number(process.env[name]);
134
- return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
135
- }
136
- function finalizationSwmResultBudget() {
137
- return {
138
- pageRows: positiveIntegerEnv('DKG_FINALIZATION_SWM_PAGE_ROWS', FINALIZATION_SWM_PAGE_ROWS_DEFAULT),
139
- maxRows: positiveIntegerEnv('DKG_FINALIZATION_SWM_MAX_ROWS', FINALIZATION_SWM_MAX_ROWS_DEFAULT),
140
- maxBytesEstimate: positiveIntegerEnv('DKG_FINALIZATION_SWM_MAX_BYTES', FINALIZATION_SWM_MAX_BYTES_DEFAULT),
141
- };
142
- }
143
54
  function isLegacyFinalizationEventBus(value) {
144
55
  return value !== undefined
145
56
  && typeof value.emit === 'function';
@@ -172,32 +83,15 @@ export class FinalizationHandler {
172
83
  lifecycle;
173
84
  processedUals = new Set();
174
85
  // Forward-prevention for the cgId-resolution race (RS heal): chain-authoritative
175
- // Legacy batch-id or graph-scoped KA-id -> cgId bindings, cached
176
- // POSITIVE-ONLY. A 0/miss is NEVER cached: caching a miss before the on-chain
177
- // KA->CG binding lands would pin finalization to the legacy fallback forever.
86
+ // KA-id -> cgId bindings, cached POSITIVE-ONLY. A 0/miss is NEVER cached:
87
+ // caching a miss before the on-chain KA->CG binding lands would pin
88
+ // finalization to the local fallback forever.
178
89
  chainCgIdByLookupId = new Map();
179
- // #1609 (2026-07-11/12 testnet incident): the write-generation source backing
180
- // the negative reconcile memo below. `null` when the store's adapter doesn't
181
- // track write generations — the memo is then DISABLED and every reconcile
182
- // scans (fail-open), matching pre-memo behavior.
183
- graphWriteGen;
184
- // Negative memo for `findSwmSnapshotForMerkleRoot`: "this (cg, namespace,
185
- // root) had NO matching local SWM snapshot at write generation G". Unlike
186
- // `chainCgIdByLookupId` above, caching the negative here is sound BECAUSE it
187
- // is generation-gated: the verdict is only replayed while the store proves
188
- // no local write has touched the CG since the scan. LRU, in-memory only —
189
- // a restart clears it (fail-open).
190
- negativeSnapshotMemo = new Map();
191
- /** Legacy WorkspaceOperation presence per (cg, namespace), gated the same way. */
192
- legacySwmOperationsMemo = new Map();
193
- /** Equivalent finalization/reconcile reads share one promise until it settles. */
194
- scanSingleFlights = new Map();
195
90
  recoveryWorker;
196
91
  finalizationRecoveryEligibility;
197
92
  constructor(store, chain, optionsOrEventBus, legacyResolveContextGraphOnChainId, legacyMarkContextGraphMetaDirtyFromQuads, legacyLifecycleLogOptions) {
198
93
  const options = normalizeFinalizationHandlerOptions(optionsOrEventBus, legacyResolveContextGraphOnChainId, legacyMarkContextGraphMetaDirtyFromQuads, legacyLifecycleLogOptions);
199
94
  this.store = store;
200
- this.graphWriteGen = asGraphWriteRevisionSource(store);
201
95
  this.chain = chain;
202
96
  this.eventBus = options.eventBus;
203
97
  this.resolveContextGraphOnChainId = options.resolveContextGraphOnChainId;
@@ -256,7 +150,6 @@ export class FinalizationHandler {
256
150
  });
257
151
  if (liveAdmission.status === 'invalid')
258
152
  return;
259
- let envelope;
260
153
  let candidate;
261
154
  if (liveAdmission.status === 'admitted') {
262
155
  const liveCandidate = liveAdmission.input.candidate;
@@ -317,22 +210,16 @@ export class FinalizationHandler {
317
210
  throw new Error(`Unhandled finalization recovery result: ${JSON.stringify(exhaustive)}`);
318
211
  }
319
212
  }
320
- envelope = {
321
- rawMessage: data,
322
- msg: liveAdmission.message,
323
- graphAdmission: { ok: true, value: candidate },
324
- };
325
213
  }
326
214
  else {
327
- envelope = this.decodeFinalizationEnvelope(data);
328
- }
329
- if (!envelope)
215
+ this.ignoreUnscopedFinalization(data, contextGraphId);
330
216
  return;
331
- // Compatibility path for legacy envelopes and deployments without the
332
- // durable recovery inbox.
217
+ }
218
+ // A deployment without the durable recovery inbox applies the envelope
219
+ // unjournaled.
333
220
  for (let attempt = 0; attempt < 2; attempt += 1) {
334
221
  try {
335
- await this.processFinalizationEnvelope(envelope, contextGraphId, sourcePeerId);
222
+ await this.processUnjournaledFinalization(liveAdmission.input);
336
223
  return;
337
224
  }
338
225
  catch (error) {
@@ -350,13 +237,9 @@ export class FinalizationHandler {
350
237
  }
351
238
  }
352
239
  }
353
- decodeFinalizationEnvelope(rawMessage) {
240
+ decodeFinalizationMessageOrWarn(rawMessage) {
354
241
  try {
355
- const msg = decodeFinalizationMessage(rawMessage);
356
- return {
357
- rawMessage,
358
- msg,
359
- };
242
+ return decodeFinalizationMessage(rawMessage);
360
243
  }
361
244
  catch (error) {
362
245
  const message = error instanceof Error ? error.message : String(error);
@@ -443,276 +326,14 @@ export class FinalizationHandler {
443
326
  return undefined;
444
327
  }
445
328
  }
446
- async processFinalizationEnvelope(envelope, contextGraphId, sourcePeerId) {
447
- let ctx = createOperationContext('gossip');
448
- let resolvedTargetContextGraphId;
449
- const { rawMessage, msg, graphAdmission } = envelope;
329
+ /** Apply a graph-scoped envelope on a deployment without the durable recovery inbox. */
330
+ async processUnjournaledFinalization(input) {
331
+ const { msg } = input.candidate;
332
+ const ctx = msg.operationId
333
+ ? createOperationContext('gossip', msg.operationId)
334
+ : createOperationContext('gossip');
450
335
  try {
451
- resolvedTargetContextGraphId = msg.targetContextGraphId || undefined;
452
- if (msg.operationId) {
453
- ctx = createOperationContext('gossip', msg.operationId);
454
- }
455
- const isGraphScoped = msg.contentScopeVersion === GRAPH_KA_CONTENT_SCOPE_VERSION;
456
- if (isGraphScoped) {
457
- if (!graphAdmission?.ok) {
458
- this.log.warn(ctx, `Finalization: invalid graph-scoped envelope for ${msg.ual || '(missing UAL)'}: `
459
- + `${graphAdmission?.reason ?? 'decode-failed'}`);
460
- return 'deferred';
461
- }
462
- return await this.recovery.processUnjournaled({
463
- rawMessage,
464
- contextGraphId,
465
- ...(sourcePeerId ? { sourcePeerId } : {}),
466
- candidate: graphAdmission.value,
467
- });
468
- }
469
- if (msg.contextGraphId && msg.contextGraphId !== contextGraphId) {
470
- // #1100: same guard as GossipPublishHandler — frames of other gossip
471
- // message types decode "successfully" with garbage in this field, so
472
- // only WARN when the mismatched value is a plausible CG id.
473
- if (!validateContextGraphId(msg.contextGraphId).valid)
474
- return 'deferred';
475
- this.log.warn(ctx, `Finalization: contextGraphId "${msg.contextGraphId.slice(0, 120)}" does not match topic "${contextGraphId}", ignoring`);
476
- return 'deferred';
477
- }
478
- // Deduplicate: skip if we already successfully processed this UAL
479
- const dedupeKey = `${msg.ual}:${msg.txHash}`;
480
- if (this.processedUals.has(dedupeKey)) {
481
- this.log.info(ctx, `Finalization: already processed ${msg.ual}, skipping duplicate`);
482
- return 'already-confirmed';
483
- }
484
- if (msg.contentScopeVersion !== undefined
485
- && msg.contentScopeVersion !== 0) {
486
- this.log.warn(ctx, `Finalization: unsupported content scope version ${msg.contentScopeVersion}, ignoring`);
487
- return 'deferred';
488
- }
489
- if (!msg.ual || !msg.txHash || msg.rootEntities.length === 0) {
490
- this.log.warn(ctx, `Finalization: incomplete message (ual=${msg.ual}, txHash=${msg.txHash}, roots=${msg.rootEntities.length}, scope=${msg.contentScopeVersion ?? 0}), ignoring`);
491
- return 'deferred';
492
- }
493
- const blockNumber = protobufScalarToNumber(msg.blockNumber);
494
- const startKAId = protobufScalarToBigInt(msg.startKAId);
495
- const endKAId = protobufScalarToBigInt(msg.endKAId);
496
- let batchIdForResolve = 0n;
497
- try {
498
- batchIdForResolve = protobufScalarToBigInt(msg.batchId);
499
- }
500
- catch {
501
- batchIdForResolve = 0n;
502
- }
503
- const ctxGraphId = await this.resolveFinalizationContextGraphId(contextGraphId, msg.targetContextGraphId || undefined, batchIdForResolve, ctx);
504
- resolvedTargetContextGraphId = ctxGraphId;
505
- // Validate sub-graph name from gossip — reject invalid names entirely
506
- let subGraphName;
507
- if (msg.subGraphName) {
508
- const sgVal = validateSubGraphName(msg.subGraphName);
509
- if (sgVal.valid) {
510
- subGraphName = msg.subGraphName;
511
- }
512
- else {
513
- this.log.warn(ctx, `Finalization: rejected message with invalid subGraphName "${msg.subGraphName}": ${sgVal.reason}`);
514
- return 'deferred';
515
- }
516
- }
517
- // Dedup guard: skip if this batch was already promoted (e.g. by ChainEventPoller).
518
- // Read-both (review F5): also ASK the label `_meta` — the minimal
519
- // per-cgId partition shape carries no `dkg:status` row.
520
- const targetMetaGraph = ctxGraphId
521
- ? contextGraphMetaUri(contextGraphId, ctxGraphId)
522
- : `did:dkg:context-graph:${contextGraphId}/_meta`;
523
- const alreadyPromoted = await this.isAlreadyConfirmed(msg.ual, targetMetaGraph, `did:dkg:context-graph:${contextGraphId}/_meta`);
524
- if (alreadyPromoted) {
525
- this.markProcessed(dedupeKey);
526
- this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_already_confirmed', {
527
- ...msg,
528
- targetContextGraphId: ctxGraphId ?? msg.targetContextGraphId,
529
- }));
530
- this.log.info(ctx, `Finalization: ${msg.ual} already confirmed in ${ctxGraphId ? `context graph ${ctxGraphId}` : 'context graph'}, skipping`);
531
- return 'already-confirmed';
532
- }
533
- // #1549: read this KA's per-author SWM under-graphs first, widening to today's
534
- // unbounded read on empty-or-mismatch. All of the bound/widen policy — and the
535
- // reasoning for why it is safe — lives in storage's
536
- // `loadSharedMemorySliceWithKaBoundFallback`, which owns the widen.
537
- const { quads: sharedMemoryQuads, matched: merkleMatchedQuads } = await this.loadFinalizationSwmSlice(contextGraphId, msg.rootEntities, subGraphName, swmKaBoundDisabled() ? undefined : deriveSwmKaGraphBound(startKAId, endKAId), msg.kcMerkleRoot, async () => {
538
- const privateRoots = await this.getPrivateRootsFromMeta(contextGraphId, msg.rootEntities, subGraphName);
539
- const allowGeneratedCatalogFloor = await this.allowsGeneratedCatalogFloor(contextGraphId, ctxGraphId);
540
- return (quads) => this.sharedMemoryQuadsMatchingMerkle(contextGraphId, quads, privateRoots, msg.kcMerkleRoot, allowGeneratedCatalogFloor);
541
- });
542
- if (sharedMemoryQuads.length > 0) {
543
- if (merkleMatchedQuads) {
544
- const batchId = protobufScalarToBigInt(msg.batchId);
545
- // PR #845 review #9: derive `txIndex` from the verified receipt
546
- // (via `verifyOnChain`), NOT from gossip-supplied `msg.txIndex`.
547
- // The latter is trust-based; a peer can forge an inflated index
548
- // for a real publish `txHash` and lock out a legitimate
549
- // same-block update on the receiver. The verified value comes
550
- // from the on-chain log we matched (`log.transactionIndex`).
551
- const { verified, authorAddress, txIndex: verifiedTxIndex } = await this.verifyOnChain(msg.txHash, blockNumber, msg.kcMerkleRoot, msg.publisherAddress, startKAId, endKAId, ctx, ctxGraphId, batchId);
552
- if (verified) {
553
- // Codex r5b — drop the rolling-upgrade legacy-publisher
554
- // fallback. Earlier rounds inferred same-graph intent from
555
- // `targetContextGraphId === local-on-chain-id-for(contextGraphId)`,
556
- // but Codex r5b correctly observed that signal is ambiguous:
557
- // it ALSO matches an explicit-remap-to-self publish (one where
558
- // the legacy publisher passed `subContextGraphId === ownCG's
559
- // own on-chain id` to deliberately drop the root copy). Both
560
- // shapes hit `targetContextGraphId === local id` on the wire,
561
- // so the fallback would re-add a root copy that the publisher
562
- // had intentionally removed — a data-isolation regression.
563
- //
564
- // The cure is worse than the disease: trading a hard
565
- // data-isolation bug for a soft query-discoverability gap is
566
- // unacceptable. Without an unambiguous version/intent signal
567
- // on the wire, a legacy publisher's same-graph publish stays
568
- // queryable on receivers via per-cgId partitions but not via
569
- // the bare `<cg>` label until the publisher upgrades to a
570
- // tristate-emitting build and re-emits. New publishers always
571
- // set the tristate (encoded with explicit KEEP/DROP) so the
572
- // gap is bounded by the upgrade window. PR #779 has not
573
- // shipped to any production peer yet, so this is the right
574
- // moment to harden the contract.
575
- const requestedKeepRootCopyOnLabel = msg.keepRootCopyOnLabel === true;
576
- // PR #845 review #9: tiebreaker comes from chain-truth.
577
- // verifyOnChain may not yield a txIndex if the matched event
578
- // shape didn't carry it (e.g. mocks). Fall back to 0 in that
579
- // case — matches pre-#845 ordering.
580
- const finalizationVersion = {
581
- blockNumber,
582
- txIndex: typeof verifiedTxIndex === 'number' ? verifiedTxIndex : 0,
583
- };
584
- // PR #845 review #10: when same-graph dual-write is requested,
585
- // BOTH the per-cgId target meta and the root label meta
586
- // (`<cg>/_meta`) get rewritten. The pre-#845 code only guarded
587
- // the per-cgId target, so a stale finalization could pass that
588
- // check while an update had already stamped a newer version in
589
- // the root label meta — the dual-write then re-inserted the
590
- // old root-label data + meta on top of the update. Acquire the
591
- // label-meta lock too (when dual-writing), and downgrade to
592
- // per-cgId-only if the label projection is newer.
593
- const isDualWrite = requestedKeepRootCopyOnLabel
594
- && !!ctxGraphId
595
- && !subGraphName;
596
- const defaultMeta = `did:dkg:context-graph:${contextGraphId}/_meta`;
597
- // PR #845 review #7: TOCTOU — serialise check + promotion +
598
- // version stamp under the per-KA materialization lock so a
599
- // concurrent stale writer cannot interleave between our
600
- // `shouldApplyMaterialization` and `writeMaterializedVersion`.
601
- const outcome = await this.applyVerifiedFinalization({
602
- contextGraphId,
603
- sharedMemoryQuads: merkleMatchedQuads,
604
- ual: msg.ual,
605
- rootEntities: msg.rootEntities,
606
- publisherAddress: msg.publisherAddress,
607
- txHash: msg.txHash,
608
- blockNumber,
609
- startKAId,
610
- endKAId,
611
- batchId: protobufScalarToBigInt(msg.batchId),
612
- ctxGraphId,
613
- subGraphName,
614
- authorAddress,
615
- finalizationVersion,
616
- targetMetaGraph,
617
- defaultMeta,
618
- isDualWrite,
619
- ctx,
620
- });
621
- if (outcome === 'stale-target') {
622
- this.markProcessed(dedupeKey);
623
- this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_stale_target', {
624
- ...msg,
625
- targetContextGraphId: ctxGraphId ?? msg.targetContextGraphId,
626
- rootEntityCount: msg.rootEntities.length,
627
- swmStatementCount: merkleMatchedQuads.length,
628
- subGraphName,
629
- blockNumber,
630
- batchId,
631
- outcome,
632
- reason: 'newer update already materialized',
633
- }));
634
- this.log.info(ctx, `Finalization: a newer update is already materialised for ${msg.ual}, skipping stale publish promotion`);
635
- return 'already-confirmed';
636
- }
637
- this.markProcessed(dedupeKey);
638
- this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_applied', {
639
- ...msg,
640
- targetContextGraphId: ctxGraphId ?? msg.targetContextGraphId,
641
- rootEntityCount: msg.rootEntities.length,
642
- swmStatementCount: merkleMatchedQuads.length,
643
- subGraphName,
644
- blockNumber,
645
- batchId,
646
- outcome,
647
- retryable: false,
648
- }));
649
- this.log.info(ctx, `Finalization: promoted SWM snapshot to ${ctxGraphId ? `context graph ${ctxGraphId}` : 'canonical'} for ${msg.ual} (tx=${msg.txHash.slice(0, 10)}…)`);
650
- return 'applied';
651
- }
652
- this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_verification_failed', {
653
- ...msg,
654
- targetContextGraphId: ctxGraphId ?? msg.targetContextGraphId,
655
- rootEntityCount: msg.rootEntities.length,
656
- swmStatementCount: merkleMatchedQuads.length,
657
- subGraphName,
658
- blockNumber,
659
- batchId,
660
- outcome: 'deferred',
661
- retryable: true,
662
- reason: 'on-chain verification failed',
663
- level: 'warn',
664
- }));
665
- this.log.info(ctx, `Finalization: on-chain verification failed for ${msg.ual}, will retry via ChainEventPoller`);
666
- return 'deferred';
667
- }
668
- this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_merkle_mismatch', {
669
- ...msg,
670
- targetContextGraphId: ctxGraphId ?? msg.targetContextGraphId,
671
- rootEntityCount: msg.rootEntities.length,
672
- swmStatementCount: sharedMemoryQuads.length,
673
- subGraphName,
674
- blockNumber,
675
- batchId: protobufScalarToBigInt(msg.batchId),
676
- outcome: 'deferred',
677
- retryable: true,
678
- reason: 'shared memory merkle root mismatch',
679
- level: 'warn',
680
- }));
681
- this.log.info(ctx, `Finalization: merkle mismatch for ${msg.ual}, shared memory data differs from published`);
682
- }
683
- else {
684
- this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_no_data', {
685
- ...msg,
686
- targetContextGraphId: ctxGraphId ?? msg.targetContextGraphId,
687
- rootEntityCount: msg.rootEntities.length,
688
- swmStatementCount: 0,
689
- subGraphName,
690
- blockNumber,
691
- batchId: protobufScalarToBigInt(msg.batchId),
692
- outcome: 'deferred',
693
- retryable: true,
694
- reason: 'no shared memory data',
695
- level: 'warn',
696
- }));
697
- this.log.info(ctx, `Finalization: no shared memory data for ${msg.ual}, peer missed SWM sharing`);
698
- }
699
- // Fallback: no matching shared memory data. The data will arrive via
700
- // the regular publish topic broadcast or ChainEventPoller sync.
701
- this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_payload_sync_required', {
702
- ...msg,
703
- targetContextGraphId: ctxGraphId ?? msg.targetContextGraphId,
704
- rootEntityCount: msg.rootEntities.length,
705
- swmStatementCount: sharedMemoryQuads.length,
706
- subGraphName,
707
- blockNumber,
708
- batchId: protobufScalarToBigInt(msg.batchId),
709
- outcome: 'deferred',
710
- retryable: true,
711
- reason: 'no matching SWM snapshot',
712
- level: 'warn',
713
- }));
714
- this.log.info(ctx, `Finalization: ${msg.ual} requires full payload sync (no matching SWM snapshot)`);
715
- return 'deferred';
336
+ return await this.recovery.processUnjournaled(input);
716
337
  }
717
338
  catch (err) {
718
339
  if (err instanceof StoreSchedulerBusyError)
@@ -720,8 +341,7 @@ export class FinalizationHandler {
720
341
  const errMsg = err instanceof Error ? err.message : String(err);
721
342
  this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_failed', {
722
343
  ...msg,
723
- contextGraphId: msg.contextGraphId || contextGraphId,
724
- targetContextGraphId: resolvedTargetContextGraphId ?? msg.targetContextGraphId,
344
+ contextGraphId: msg.contextGraphId || input.contextGraphId,
725
345
  rootEntityCount: msg.rootEntities.length,
726
346
  outcome: 'failed',
727
347
  retryable: true,
@@ -732,6 +352,36 @@ export class FinalizationHandler {
732
352
  return 'deferred';
733
353
  }
734
354
  }
355
+ /**
356
+ * Only graph-scoped finalization is materialized. A message without that
357
+ * scope names root entities instead of the KA's own graph, so this node
358
+ * holds no exact copy to verify it against. Its KA reaches Verified Memory
359
+ * through chain reconcile, which fetches it from peers.
360
+ */
361
+ ignoreUnscopedFinalization(data, contextGraphId) {
362
+ const msg = this.decodeFinalizationMessageOrWarn(data);
363
+ // Frames of other gossip message types decode "successfully" with garbage
364
+ // fields (#1100), so only report what plausibly is a finalization.
365
+ if (!msg
366
+ || !msg.ual
367
+ || !msg.txHash
368
+ || (msg.contextGraphId && !validateContextGraphId(msg.contextGraphId).valid)) {
369
+ return;
370
+ }
371
+ const ctx = msg.operationId
372
+ ? createOperationContext('gossip', msg.operationId)
373
+ : createOperationContext('gossip');
374
+ const reason = `content scope ${msg.contentScopeVersion ?? 0} is not graph-scoped`;
375
+ this.lifecycle.record(ctx, finalizationLifecycleDecision('finalization_unsupported_scope', {
376
+ ...msg,
377
+ contextGraphId: msg.contextGraphId || contextGraphId,
378
+ rootEntityCount: msg.rootEntities.length,
379
+ outcome: 'ignored',
380
+ retryable: false,
381
+ reason,
382
+ }));
383
+ this.log.info(ctx, `Finalization: ignoring ${msg.ual}: ${reason}`);
384
+ }
735
385
  /** Resolve and verify the exact RDF state that a graph-scoped command may apply. */
736
386
  async prepareGraphScopedMaterialization(input) {
737
387
  const { candidate: parsed, contextGraphId, sourcePeerId } = input;
@@ -1130,14 +780,13 @@ export class FinalizationHandler {
1130
780
  }
1131
781
  /**
1132
782
  * Resolve and promote the exact graph-scoped SWM assertion for a chain-known
1133
- * KA. `undefined` means no V2 head exists and the caller may try the legacy
1134
- * root-operation recovery path; every other result is authoritative for V2.
783
+ * KA. `undefined` means no V2 head exists and the caller answers `no-swm`;
784
+ * every other result is authoritative for V2.
1135
785
  */
1136
786
  async reconcileGraphScopedKC(input, ctx) {
1137
787
  const { contextGraphId, onChainCgId, ual, assertionVersion, merkleRoot, publisherAddress, kaId, batchId, versionBlock, authorAddress, versionSnapshot, signal, subGraphName, trustedAssertionEvidence, } = input;
1138
- // Historical UAL shapes can be valid inputs to the legacy root-scoped
1139
- // recovery code but cannot name a V2 per-KA graph. Do not let the strict
1140
- // V2 parser turn those into a terminal "corrupt head" result.
788
+ // Historical UAL shapes cannot name a V2 per-KA graph. Do not let the
789
+ // strict V2 parser turn those into a terminal "corrupt head" result.
1141
790
  try {
1142
791
  createGraphKnowledgeAssetScope(ual, 1);
1143
792
  }
@@ -1166,8 +815,7 @@ export class FinalizationHandler {
1166
815
  // post-fetch check runs. Re-read the immutable confirmed envelope and
1167
816
  // verify the exact VM graph against current chain truth. This remains
1168
817
  // fail-closed: absent, invalid, stale, or root-mismatched VM state is
1169
- // still reported as no-swm, and the legacy all-workspace scan stays
1170
- // disabled for exact fetch callers.
818
+ // still reported as no-swm.
1171
819
  return (await this.reconcileConfirmedGraphScopedVmWithoutWorkspaceHead({
1172
820
  contextGraphId,
1173
821
  ual,
@@ -2050,353 +1698,6 @@ export class FinalizationHandler {
2050
1698
  return false;
2051
1699
  }
2052
1700
  }
2053
- finalizationSwmBucketUri(contextGraphId, subGraphName) {
2054
- return subGraphName
2055
- ? new GraphManager(this.store).sharedMemoryUri(contextGraphId, subGraphName)
2056
- : contextGraphWorkspaceGraphUri(contextGraphId);
2057
- }
2058
- /**
2059
- * Read the finalization SWM slice, preferring this KA's per-author under-graphs
2060
- * and widening to the complete read on empty-or-mismatch (#1549). The widen — and
2061
- * the reasoning for why the bound is a safe pure accelerator (INV-1 is refuted
2062
- * under root recurrence, so the bounded read may miss and must widen before the
2063
- * caller records anything) — lives in storage's
2064
- * `loadSharedMemorySliceWithKaBoundFallback`. This method only resolves the bucket
2065
- * URI and the accept predicate; it is a thin finalization-specific adapter.
2066
- *
2067
- * #1098/#1099: replicas store gossiped SWM shares in the PER-KA graphs
2068
- * `…/_shared_memory/{author}/{number}`, not the bare bucket, so the read must span
2069
- * bucket + per-KA graphs or a replica reports "no shared memory data" and never
2070
- * materialises the KA into VM.
2071
- */
2072
- async loadFinalizationSwmSlice(contextGraphId, rootEntities, subGraphName, kaGraphBound, expectedMerkleRoot, createAccept) {
2073
- const safeRoots = rootEntities.filter(isSafeIri);
2074
- if (safeRoots.length === 0)
2075
- return { quads: [], matched: null };
2076
- const writeRevision = this.graphWriteGen?.getWriteRevision(`${contextGraphDataUri(contextGraphId)}/`);
2077
- const key = [
2078
- 'finalization',
2079
- contextGraphId,
2080
- subGraphName ?? '',
2081
- safeRoots.slice().sort().join('\u0001'),
2082
- kaGraphBound ? `${kaGraphBound.agentAddress}:${kaGraphBound.startNumber}:${kaGraphBound.endNumber}` : '*',
2083
- ethers.hexlify(expectedMerkleRoot),
2084
- writeRevision
2085
- ? `${writeRevision.generation}:${writeRevision.stable ? 'stable' : 'unstable'}`
2086
- : 'unknown',
2087
- ].join('\u0000');
2088
- return this.runScanSingleFlight(key, async () => {
2089
- const { quads, accepted } = await loadSharedMemorySliceWithKaBoundFallback(this.store, this.finalizationSwmBucketUri(contextGraphId, subGraphName), { rootEntities: safeRoots }, kaGraphBound, {
2090
- sources: {
2091
- bounded: SWM_SLICE_SOURCE_BOUNDED,
2092
- widened: SWM_SLICE_SOURCE_WIDENED,
2093
- unbounded: SWM_SLICE_SOURCE,
2094
- },
2095
- createAccept,
2096
- queryOptions: { priority: 'background' },
2097
- resultBudget: finalizationSwmResultBudget(),
2098
- });
2099
- return { quads, matched: accepted };
2100
- });
2101
- }
2102
- /** Complete (unbounded) SWM read for the chain-reconcile backstop. */
2103
- async getSharedMemoryQuadsForRoots(contextGraphId, rootEntities, subGraphName) {
2104
- const safeRoots = rootEntities.filter(isSafeIri);
2105
- if (safeRoots.length === 0)
2106
- return [];
2107
- return loadSelectedSharedMemoryQuads(this.store, this.finalizationSwmBucketUri(contextGraphId, subGraphName), { rootEntities: safeRoots }, {
2108
- querySource: SWM_SLICE_SOURCE,
2109
- queryOptions: { priority: 'background' },
2110
- resultBudget: finalizationSwmResultBudget(),
2111
- });
2112
- }
2113
- runScanSingleFlight(key, work) {
2114
- const scope = key.startsWith('finalization\u0000') ? 'finalization' : 'reconcile';
2115
- const existing = this.scanSingleFlights.get(key);
2116
- if (existing) {
2117
- getMetrics().storeScanSingleFlightJoinsTotal.add(1, { scope });
2118
- return existing;
2119
- }
2120
- getMetrics().storeScanSingleFlightActive.add(1, { scope });
2121
- const running = work().finally(() => {
2122
- if (this.scanSingleFlights.get(key) === running)
2123
- this.scanSingleFlights.delete(key);
2124
- getMetrics().storeScanSingleFlightActive.add(-1, { scope });
2125
- });
2126
- this.scanSingleFlights.set(key, running);
2127
- return running;
2128
- }
2129
- verifyMerkleMatch(sharedMemoryQuads, privateRoots, expectedMerkleRoot) {
2130
- const computedRoot = computeFlatKCRoot(sharedMemoryQuads, privateRoots);
2131
- return ethers.hexlify(computedRoot) === ethers.hexlify(expectedMerkleRoot);
2132
- }
2133
- sharedMemoryQuadsMatchingMerkle(contextGraphId, sharedMemoryQuads, privateRoots, expectedMerkleRoot, allowGeneratedCatalogFloor) {
2134
- if (this.verifyMerkleMatch(sharedMemoryQuads, privateRoots, expectedMerkleRoot)) {
2135
- return sharedMemoryQuads;
2136
- }
2137
- if (!allowGeneratedCatalogFloor)
2138
- return null;
2139
- const withGeneratedCatalog = [
2140
- ...sharedMemoryQuads,
2141
- ...generatedPrivateCatalogFloorQuads(contextGraphId),
2142
- ];
2143
- if (this.verifyMerkleMatch(withGeneratedCatalog, privateRoots, expectedMerkleRoot)) {
2144
- return withGeneratedCatalog;
2145
- }
2146
- return null;
2147
- }
2148
- async storedOnChainContextGraphId(contextGraphId) {
2149
- const result = await this.store.query(contextGraphOnChainIdBindingQuery(contextGraphId), { source: 'agent.finalization.contextGraphOnChainId' });
2150
- if (result.type !== 'bindings' || result.bindings.length === 0)
2151
- return undefined;
2152
- return stripOptionalLiteral(result.bindings[0]?.['id'])?.trim();
2153
- }
2154
- async onChainContextGraphMatchesLocalId(contextGraphId, onChainCgId) {
2155
- if (onChainCgId === undefined || onChainCgId === null)
2156
- return false;
2157
- const normalizedOnChainId = String(onChainCgId).trim();
2158
- const normalizedContextGraphId = contextGraphId.trim();
2159
- if (/^\d+$/.test(normalizedContextGraphId) && normalizedContextGraphId === normalizedOnChainId)
2160
- return true;
2161
- const liveNameHashMatches = async () => {
2162
- if (typeof this.chain?.getContextGraphNameHash !== 'function')
2163
- return false;
2164
- try {
2165
- const nameHash = await this.chain.getContextGraphNameHash(BigInt(normalizedOnChainId));
2166
- return typeof nameHash === 'string' &&
2167
- nameHash.toLowerCase() === ethers.keccak256(ethers.toUtf8Bytes(normalizedContextGraphId)).toLowerCase();
2168
- }
2169
- catch {
2170
- return false;
2171
- }
2172
- };
2173
- let resolvedOnChainId;
2174
- try {
2175
- resolvedOnChainId = await this.resolveContextGraphOnChainId?.(contextGraphId);
2176
- }
2177
- catch {
2178
- resolvedOnChainId = undefined;
2179
- }
2180
- if (sameBigIntLiteral(resolvedOnChainId, normalizedOnChainId)) {
2181
- return typeof this.chain?.getContextGraphNameHash === 'function'
2182
- ? liveNameHashMatches()
2183
- : true;
2184
- }
2185
- const storedOnChainId = await this.storedOnChainContextGraphId(contextGraphId);
2186
- if (sameBigIntLiteral(storedOnChainId, normalizedOnChainId)) {
2187
- return typeof this.chain?.getContextGraphNameHash === 'function'
2188
- ? liveNameHashMatches()
2189
- : true;
2190
- }
2191
- return liveNameHashMatches();
2192
- }
2193
- async allowsGeneratedCatalogFloor(contextGraphId, onChainCgId) {
2194
- if (onChainCgId === undefined || onChainCgId === null)
2195
- return false;
2196
- if (!this.chain || this.chain.chainId === 'none')
2197
- return false;
2198
- const normalizedContextGraphId = contextGraphId.trim();
2199
- const normalizedOnChainId = String(onChainCgId).trim();
2200
- const readFinalizedCreation = this.chain.getContextGraphFinalizedCreation;
2201
- if (typeof readFinalizedCreation === 'function') {
2202
- try {
2203
- const creation = await readFinalizedCreation.call(this.chain, BigInt(normalizedOnChainId));
2204
- if (creation !== undefined) {
2205
- const idMatches = /^\d+$/.test(normalizedContextGraphId)
2206
- ? normalizedContextGraphId === normalizedOnChainId
2207
- : creation.nameHash.toLowerCase() === ethers.keccak256(ethers.toUtf8Bytes(normalizedContextGraphId)).toLowerCase();
2208
- return idMatches && creation.accessPolicy === 1;
2209
- }
2210
- }
2211
- catch {
2212
- // Never mix a partial/failing finalized creation proof with a latest
2213
- // point read: that can splice two forks into a false-private result.
2214
- return false;
2215
- }
2216
- }
2217
- if (typeof this.chain.getContextGraphAccessPolicy !== 'function')
2218
- return false;
2219
- if (!await this.onChainContextGraphMatchesLocalId(contextGraphId, onChainCgId))
2220
- return false;
2221
- try {
2222
- return Number(await this.chain.getContextGraphAccessPolicy(BigInt(onChainCgId))) === 1;
2223
- }
2224
- catch {
2225
- return false;
2226
- }
2227
- }
2228
- async getPrivateRootsFromMeta(contextGraphId, rootEntities, subGraphName) {
2229
- const graphManager = new GraphManager(this.store);
2230
- const wsMetaGraph = subGraphName
2231
- ? graphManager.sharedMemoryMetaUri(contextGraphId, subGraphName)
2232
- : contextGraphWorkspaceMetaGraphUri(contextGraphId);
2233
- const safeRoots = rootEntities.filter(isSafeIri);
2234
- if (safeRoots.length === 0)
2235
- return [];
2236
- const values = safeRoots.map(r => `<${r}>`).join(' ');
2237
- const sparql = `SELECT ?entity ?root WHERE {
2238
- GRAPH <${wsMetaGraph}> {
2239
- VALUES ?entity { ${values} }
2240
- ?entity <${DKG_NS}privateMerkleRoot> ?root .
2241
- }
2242
- }`;
2243
- const roots = [];
2244
- try {
2245
- const result = await this.store.query(sparql, { source: 'agent.finalization.privateRoots' });
2246
- if (result.type === 'bindings') {
2247
- for (const row of result.bindings) {
2248
- const hex = row['root'].replace(/^"(.*)".*$/, '$1').replace(/^0x/, '');
2249
- if (hex.length === 64) {
2250
- const bytes = new Uint8Array(32);
2251
- for (let i = 0; i < 32; i++)
2252
- bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
2253
- roots.push(bytes);
2254
- }
2255
- }
2256
- }
2257
- }
2258
- catch { /* metadata may not exist */ }
2259
- return roots;
2260
- }
2261
- /**
2262
- * Recover the publisher's `keepRootCopyOnLabel` decision for these roots from
2263
- * SWM workspace meta. The publisher persists `<root> dkg:keepRootCopyOnLabel
2264
- * "true"|"false"` at publish time — the durable equivalent of the gossip
2265
- * envelope flag — and it replicates to subscribers alongside the per-root
2266
- * `privateMerkleRoot`. Returns:
2267
- * - `true` — a matched root explicitly kept the root-label copy,
2268
- * - `false` — explicitly dropped (remap / explicit-subCG publish),
2269
- * - `undefined` — no signal persisted (legacy publish); the caller defaults
2270
- * to per-cgId-only so a dropped root copy is never re-added.
2271
- * An explicit `true` wins over `false` across the matched roots: a same-graph
2272
- * publish demands the root copy exist.
2273
- */
2274
- async getKeepRootCopySignal(contextGraphId, rootEntities, subGraphName) {
2275
- const graphManager = new GraphManager(this.store);
2276
- const wsMetaGraph = subGraphName
2277
- ? graphManager.sharedMemoryMetaUri(contextGraphId, subGraphName)
2278
- : contextGraphWorkspaceMetaGraphUri(contextGraphId);
2279
- const safeRoots = rootEntities.filter(isSafeIri);
2280
- if (safeRoots.length === 0)
2281
- return undefined;
2282
- const values = safeRoots.map(r => `<${r}>`).join(' ');
2283
- const sparql = `SELECT ?v WHERE {
2284
- GRAPH <${assertSafeIri(wsMetaGraph)}> {
2285
- VALUES ?root { ${values} }
2286
- ?root <${KEEP_ROOT_COPY_PREDICATE}> ?v .
2287
- }
2288
- }`;
2289
- try {
2290
- const result = await this.store.query(sparql, { source: 'agent.finalization.keepRootCopySignal' });
2291
- if (result.type !== 'bindings' || result.bindings.length === 0)
2292
- return undefined;
2293
- let sawFalse = false;
2294
- for (const row of result.bindings) {
2295
- const v = String(row['v']).replace(/^"(.*)".*$/, '$1');
2296
- if (v === 'true')
2297
- return true;
2298
- if (v === 'false')
2299
- sawFalse = true;
2300
- }
2301
- return sawFalse ? false : undefined;
2302
- }
2303
- catch {
2304
- return undefined;
2305
- }
2306
- }
2307
- async getPublisherPeerIdFromMeta(contextGraphId, rootEntities, subGraphName) {
2308
- const graphManager = new GraphManager(this.store);
2309
- const wsMetaGraph = subGraphName
2310
- ? graphManager.sharedMemoryMetaUri(contextGraphId, subGraphName)
2311
- : contextGraphWorkspaceMetaGraphUri(contextGraphId);
2312
- const safeRoots = rootEntities.filter(isSafeIri);
2313
- if (safeRoots.length === 0)
2314
- return undefined;
2315
- const values = safeRoots.map(r => `<${r}>`).join(' ');
2316
- const PROV = 'http://www.w3.org/ns/prov#';
2317
- // GH #748: prefer the dedicated `dkg:publisherPeerId` field (peer-ID
2318
- // literal); fall back to literal-form `prov:wasAttributedTo` for legacy
2319
- // un-migrated SWM rows. Skip URI form — that's an agent DID, not a
2320
- // peer ID, and downstream dials a libp2p peer with this value.
2321
- //
2322
- // `FILTER(BOUND(?peerId))` guards the LIMIT 1: without it, an op with
2323
- // `rootEntity` but neither peer-ID source produces an unbound `?peerId`
2324
- // that LIMIT could pick. The caller's `wsPeerId || publisherAddress`
2325
- // fallback would then store an EVM address where a libp2p peer ID is
2326
- // expected. With BOUND, only ops carrying a real peer-ID match.
2327
- const sparql = `SELECT ?peerId WHERE {
2328
- GRAPH <${wsMetaGraph}> {
2329
- VALUES ?root { ${values} }
2330
- ?op <${DKG_NS}rootEntity> ?root .
2331
- OPTIONAL { ?op <${DKG_NS}publisherPeerId> ?pidField }
2332
- OPTIONAL { ?op <${PROV}wasAttributedTo> ?attrField . FILTER(isLiteral(?attrField)) }
2333
- BIND(COALESCE(?pidField, ?attrField) AS ?peerId)
2334
- FILTER(BOUND(?peerId))
2335
- }
2336
- } LIMIT 1`;
2337
- try {
2338
- const result = await this.store.query(sparql, { source: 'agent.finalization.publisherPeerId' });
2339
- if (result.type === 'bindings' && result.bindings.length > 0) {
2340
- const raw = result.bindings[0]['peerId'];
2341
- const peerId = raw.replace(/^"(.*)".*$/, '$1');
2342
- if (peerId && peerId !== 'unknown')
2343
- return peerId;
2344
- }
2345
- }
2346
- catch { /* shared memory metadata may not exist */ }
2347
- return undefined;
2348
- }
2349
- /**
2350
- * Shared SWM→VM promotion under the per-KA materialization lock(s).
2351
- *
2352
- * Extracted verbatim from the former `promoteUnderLocks`/`runUnderLocks`
2353
- * inline closure in `handleFinalizationMessage` (PR #845's TOCTOU-safe
2354
- * promotion) so BOTH the gossip path and the chain-driven reconciliation
2355
- * path (`handleChainReconciledKC`) share one implementation. Behavior is
2356
- * identical to the gossip path; the only change is that the captured locals
2357
- * are now explicit params.
2358
- *
2359
- * Serialises check + promotion + version stamp under the per-KA lock so a
2360
- * concurrent stale writer cannot interleave between `shouldApplyMaterialization`
2361
- * and `writeMaterializedVersion`. When dual-writing (same-graph keep-root),
2362
- * both the per-cgId target meta and the root-label meta are guarded; if the
2363
- * root label has a newer projection (an applied update), the dual-write
2364
- * portion is skipped so we don't clobber it.
2365
- */
2366
- async applyVerifiedFinalization(input) {
2367
- const { contextGraphId, sharedMemoryQuads, ual, rootEntities, publisherAddress, txHash, blockNumber, startKAId, endKAId, batchId, ctxGraphId, subGraphName, authorAddress, finalizationVersion, targetMetaGraph, defaultMeta, isDualWrite, ctx, } = input;
2368
- const promoteUnderLocks = async () => {
2369
- if (!(await shouldApplyMaterialization(this.store, targetMetaGraph, ual, finalizationVersion))) {
2370
- return 'stale-target';
2371
- }
2372
- let effectiveKeepRoot = isDualWrite;
2373
- if (isDualWrite) {
2374
- if (!(await shouldApplyMaterialization(this.store, defaultMeta, ual, finalizationVersion))) {
2375
- // Per-cgId is stale-or-equal but ROOT label has a
2376
- // newer projection (an applied update). Skip the
2377
- // dual-write portion so we don't clobber it.
2378
- effectiveKeepRoot = false;
2379
- this.log.info(ctx, `Finalization: root-label projection is newer for ${ual}; downgrading to per-cgId-only promotion`);
2380
- }
2381
- }
2382
- await this.promoteSharedMemoryToCanonical(contextGraphId, sharedMemoryQuads, ual, rootEntities, publisherAddress, txHash, blockNumber, startKAId, endKAId, batchId, ctx, ctxGraphId, subGraphName, authorAddress, effectiveKeepRoot);
2383
- await writeMaterializedVersion(this.store, targetMetaGraph, ual, finalizationVersion);
2384
- if (effectiveKeepRoot) {
2385
- await writeMaterializedVersion(this.store, defaultMeta, ual, finalizationVersion);
2386
- }
2387
- return 'promoted';
2388
- };
2389
- // Nested per-graph locks. Acquired in sorted order to prevent
2390
- // cross-deadlock with any other call site that might one day
2391
- // also lock multiple metas.
2392
- const lockOrder = isDualWrite
2393
- ? [defaultMeta, targetMetaGraph].sort()
2394
- : [targetMetaGraph];
2395
- if (lockOrder.length === 1) {
2396
- return withMaterializationLock(lockOrder[0], ual, promoteUnderLocks);
2397
- }
2398
- return withMaterializationLock(lockOrder[0], ual, () => withMaterializationLock(lockOrder[1], ual, promoteUnderLocks));
2399
- }
2400
1701
  /**
2401
1702
  * Resolve the exact graph namespace from canonical graph-scoped metadata.
2402
1703
  * A named assertion stores its namespace in the root metadata graph, so this
@@ -2454,30 +1755,27 @@ export class FinalizationHandler {
2454
1755
  return null;
2455
1756
  }
2456
1757
  }
2457
- /**
2458
- * Constant-time exact graph-scoped reconciliation. This method never enters
2459
- * the legacy workspace-operation scan.
2460
- */
1758
+ /** Constant-time exact graph-scoped reconciliation. */
2461
1759
  async reconcileExactChainRegisteredKC(rawInput, ctx) {
2462
1760
  const resolvedInput = await this.resolveExactChainReconcileInput(rawInput, ctx);
2463
1761
  if (!resolvedInput) {
2464
- return { outcome: 'no-swm', legacyEligible: false, input: rawInput };
1762
+ return { outcome: 'no-swm', legacyMarkerEligible: false, input: rawInput };
2465
1763
  }
2466
1764
  const input = resolvedInput;
2467
1765
  const { contextGraphId, onChainCgId, ual, merkleRoot, publisherAddress, kaId, batchId, versionBlock, authorAddress, subGraphName, trustedAssertionEvidence, assertionVersion, versionSnapshot, signal, } = input;
2468
1766
  if (!(await this.verifyChainCgBinding(kaId, onChainCgId, ctx))) {
2469
1767
  this.log.info(ctx, `Chain-reconcile: chain CG binding for ${ual} (ka=${kaId}) not confirmed against cg ${onChainCgId}; deferring to sweep retry`);
2470
- return { outcome: 'unverified', legacyEligible: false, input };
1768
+ return { outcome: 'unverified', legacyMarkerEligible: false, input };
2471
1769
  }
2472
1770
  const journalReplay = await this.replayMatchingRecoveryEntries(input, ctx);
2473
1771
  // The recovery index predates exact chain-rootCount evidence. Exact asset
2474
1772
  // inspection must therefore verify the resulting local assertion version
2475
1773
  // below instead of treating a same-root replay as sufficient on its own.
2476
1774
  if (journalReplay === 'recovered' && assertionVersion === undefined) {
2477
- return { outcome: 'already-confirmed', legacyEligible: false, input };
1775
+ return { outcome: 'already-confirmed', legacyMarkerEligible: false, input };
2478
1776
  }
2479
1777
  if (journalReplay === 'retry-pending' && assertionVersion === undefined) {
2480
- return { outcome: 'receipt-revalidation-pending', legacyEligible: false, input };
1778
+ return { outcome: 'receipt-revalidation-pending', legacyMarkerEligible: false, input };
2481
1779
  }
2482
1780
  if (journalReplay === 'invalidated') {
2483
1781
  this.log.info(ctx, `Chain-reconcile: stale finalization evidence for ${ual} was invalidated; `
@@ -2500,14 +1798,14 @@ export class FinalizationHandler {
2500
1798
  trustedAssertionEvidence,
2501
1799
  }, ctx);
2502
1800
  if (graphScopedOutcome !== undefined) {
2503
- return { outcome: graphScopedOutcome, legacyEligible: false, input };
1801
+ return { outcome: graphScopedOutcome, legacyMarkerEligible: false, input };
2504
1802
  }
2505
1803
  if (await this.hasGraphScopedMetadata(contextGraphId, ual)) {
2506
1804
  this.log.info(ctx, `Chain-reconcile: graph-scoped metadata exists for ${ual} but its durable workspace head is missing`);
2507
- return { outcome: 'no-swm', legacyEligible: false, input };
1805
+ return { outcome: 'no-swm', legacyMarkerEligible: false, input };
2508
1806
  }
2509
- this.log.info(ctx, `Chain-reconcile: exact graph-scoped state is absent for ${ual}; legacy scan is not part of the exact operation`);
2510
- return { outcome: 'no-swm', legacyEligible: true, input };
1807
+ this.log.info(ctx, `Chain-reconcile: no local graph-scoped state for ${ual}`);
1808
+ return { outcome: 'no-swm', legacyMarkerEligible: true, input };
2511
1809
  }
2512
1810
  /**
2513
1811
  * Reconcile only the exact graph-scoped state named by one UAL. This is the
@@ -2517,64 +1815,28 @@ export class FinalizationHandler {
2517
1815
  return (await this.reconcileExactChainRegisteredKC(input, ctx)).outcome;
2518
1816
  }
2519
1817
  /**
2520
- * Normal chain reconciliation composes the exact operation with the legacy
2521
- * workspace scan. The scan remains available only through this ordinary path.
1818
+ * Normal chain reconciliation: the exact operation, then the historical
1819
+ * confirmed marker of a KA materialized before per-KA graphs. A KA with no
1820
+ * local graph-scoped state answers `no-swm` and the caller fetches it from
1821
+ * peers; historical workspace operations are never searched for it.
2522
1822
  */
2523
1823
  async handleChainReconciledKC(rawInput, ctx) {
2524
1824
  const exact = await this.reconcileExactChainRegisteredKC(rawInput, ctx);
2525
- if (!exact.legacyEligible)
1825
+ if (!exact.legacyMarkerEligible)
2526
1826
  return exact.outcome;
2527
- const { contextGraphId, onChainCgId, ual, merkleRoot, publisherAddress, kaId, batchId, versionBlock, authorAddress, subGraphName, } = exact.input;
2528
- const ctxGraphId = onChainCgId.length > 0 ? onChainCgId : undefined;
2529
- const targetMetaGraph = ctxGraphId
2530
- ? contextGraphMetaUri(contextGraphId, ctxGraphId)
2531
- : `did:dkg:context-graph:${contextGraphId}/_meta`;
1827
+ const { contextGraphId, onChainCgId, ual } = exact.input;
1828
+ const rootMetaGraph = `did:dkg:context-graph:${contextGraphId}/_meta`;
1829
+ const targetMetaGraph = onChainCgId.length > 0
1830
+ ? contextGraphMetaUri(contextGraphId, onChainCgId)
1831
+ : rootMetaGraph;
2532
1832
  // The ordinary compatibility path may retain the historical confirmed
2533
1833
  // marker shortcut. Exact RFC-64 fetch cannot use this marker because it
2534
1834
  // proves neither the current assertion version nor the current chain root.
2535
- if (await this.isAlreadyConfirmed(ual, targetMetaGraph, `did:dkg:context-graph:${contextGraphId}/_meta`)) {
1835
+ if (await this.isAlreadyConfirmed(ual, targetMetaGraph, rootMetaGraph)) {
2536
1836
  this.log.info(ctx, `Chain-reconcile: legacy ${ual} already confirmed in VM, skipping`);
2537
1837
  return 'already-confirmed';
2538
1838
  }
2539
- // Recover the published roots from the legacy local SWM snapshot. This is
2540
- // the only operation that may scan historical workspace operations.
2541
- const snapshot = await this.findSwmSnapshotForMerkleRoot(contextGraphId, merkleRoot, subGraphName, onChainCgId);
2542
- if (!snapshot) {
2543
- this.log.info(ctx, `Chain-reconcile: no local SWM snapshot matches the published merkleRoot for ${ual}; deferring to sweep retry`);
2544
- return 'no-swm';
2545
- }
2546
- const { rootEntities, sharedMemoryQuads } = snapshot;
2547
- const resolvedSubGraphName = snapshot.subGraphName ?? subGraphName;
2548
- const finalizationVersion = { blockNumber: versionBlock, txIndex: 0 };
2549
- const keepRootCopyOnLabel = await this.getKeepRootCopySignal(contextGraphId, rootEntities, resolvedSubGraphName);
2550
- const isDualWrite = keepRootCopyOnLabel === true && !!ctxGraphId && !resolvedSubGraphName;
2551
- const defaultMeta = `did:dkg:context-graph:${contextGraphId}/_meta`;
2552
- const outcome = await this.applyVerifiedFinalization({
2553
- contextGraphId,
2554
- sharedMemoryQuads,
2555
- ual,
2556
- rootEntities,
2557
- publisherAddress,
2558
- txHash: '',
2559
- blockNumber: versionBlock,
2560
- startKAId: kaId,
2561
- endKAId: kaId,
2562
- batchId,
2563
- ctxGraphId,
2564
- subGraphName: resolvedSubGraphName,
2565
- authorAddress,
2566
- finalizationVersion,
2567
- targetMetaGraph,
2568
- defaultMeta,
2569
- isDualWrite,
2570
- ctx,
2571
- });
2572
- if (outcome === 'stale-target') {
2573
- this.log.info(ctx, `Chain-reconcile: a newer update is already materialised for ${ual}, skipping`);
2574
- return 'stale-target';
2575
- }
2576
- this.log.info(ctx, `Chain-reconcile: promoted SWM snapshot to VM for ${ual} (ka=${kaId}, cg=${onChainCgId})`);
2577
- return 'promoted';
1839
+ return exact.outcome;
2578
1840
  }
2579
1841
  /**
2580
1842
  * Decide from local state alone whether an ordinary chain reconcile of one
@@ -2593,8 +1855,7 @@ export class FinalizationHandler {
2593
1855
  * 4. graph-scoped metadata without a head or confirmed copy;
2594
1856
  * 5. the legacy confirmed marker, which that path answers
2595
1857
  * `already-confirmed` without reading the root;
2596
- * 6. legacy workspace operations, the root-matched scan: with none in the
2597
- * namespaces it searches, nothing local can match any root, so `none`.
1858
+ * 6. nothing else is searched, so everything left is `none`.
2598
1859
  *
2599
1860
  * `confirmed-vm` trusts the root the local copy was confirmed at; a newer
2600
1861
  * on-chain version is not looked for here, which keeps the walk free of
@@ -2671,9 +1932,7 @@ export class FinalizationHandler {
2671
1932
  if (await this.isAlreadyConfirmed(ual, targetMetaGraph, rootMetaGraph)) {
2672
1933
  return { kind: 'confirmed-vm', layout: 'legacy' };
2673
1934
  }
2674
- return await this.hasLegacySwmOperations(contextGraphId, subGraphName)
2675
- ? LOCAL_CANDIDATE_PRESENT
2676
- : { kind: 'none' };
1935
+ return { kind: 'none' };
2677
1936
  }
2678
1937
  /**
2679
1938
  * Whether the headless-VM retirement in the chain-backed path would find an
@@ -2690,53 +1949,6 @@ export class FinalizationHandler {
2690
1949
  const result = await this.store.query(`ASK { ${graphs.map((graph) => `{ GRAPH <${assertSafeIri(graph)}> { ?s ?p ?o } }`).join(' UNION ')} }`, { source: 'agent.finalization.localCandidate.swmTwin' });
2691
1950
  return result.type !== 'boolean' || result.value;
2692
1951
  }
2693
- /**
2694
- * Whether the root-matched legacy scan (`findSwmSnapshotForMerkleRoot`) has
2695
- * any WorkspaceOperation to look at: the given namespace, or the root and
2696
- * every listed sub-graph when none is known. Memoized per CG write
2697
- * generation, like that scan's negative memo, so a quiet graph answers from
2698
- * memory for the whole sweep.
2699
- */
2700
- async hasLegacySwmOperations(contextGraphId, subGraphName) {
2701
- const memoKey = `${contextGraphId}\0${subGraphName ?? ''}`;
2702
- const revision = this.graphWriteGen?.getWriteRevision(`${contextGraphDataUri(contextGraphId)}/`);
2703
- if (revision?.stable) {
2704
- const memo = this.legacySwmOperationsMemo.get(memoKey);
2705
- if (memo
2706
- && memo.writeGen === revision.generation
2707
- && Date.now() - memo.recordedAt < vmReconcileNegativeTtlMs()) {
2708
- return memo.present;
2709
- }
2710
- }
2711
- const graphManager = new GraphManager(this.store);
2712
- const metaGraphs = subGraphName
2713
- ? [graphManager.sharedMemoryMetaUri(contextGraphId, subGraphName)]
2714
- : [
2715
- contextGraphWorkspaceMetaGraphUri(contextGraphId),
2716
- ...(await graphManager.listSubGraphs(contextGraphId))
2717
- .map((name) => graphManager.sharedMemoryMetaUri(contextGraphId, name)),
2718
- ];
2719
- const result = await this.store.query(`ASK { ${metaGraphs
2720
- .map((graph) => `{ GRAPH <${assertSafeIri(graph)}> { ?op <${DKG_NS}rootEntity> ?root } }`)
2721
- .join(' UNION ')} }`, { source: 'agent.finalization.localCandidate.legacySwmOperations' });
2722
- const present = result.type !== 'boolean' || result.value;
2723
- if (revision?.stable) {
2724
- // Recorded at the pre-probe generation: a racing write flips the gate.
2725
- this.legacySwmOperationsMemo.delete(memoKey);
2726
- this.legacySwmOperationsMemo.set(memoKey, {
2727
- writeGen: revision.generation,
2728
- recordedAt: Date.now(),
2729
- present,
2730
- });
2731
- while (this.legacySwmOperationsMemo.size > VM_RECONCILE_NEGATIVE_MEMO_MAX_ENTRIES) {
2732
- const oldest = this.legacySwmOperationsMemo.keys().next().value;
2733
- if (oldest === undefined)
2734
- break;
2735
- this.legacySwmOperationsMemo.delete(oldest);
2736
- }
2737
- }
2738
- return present;
2739
- }
2740
1952
  async verifyChainCgBinding(kaId, onChainCgId, ctx) {
2741
1953
  if (!this.chain || this.chain.chainId === 'none' || typeof this.chain.getKAContextGraphId !== 'function') {
2742
1954
  return false;
@@ -2750,784 +1962,5 @@ export class FinalizationHandler {
2750
1962
  return false;
2751
1963
  }
2752
1964
  }
2753
- /**
2754
- * Find the local SWM snapshot whose KC merkle root matches the chain
2755
- * `merkleRoot`, returning its root entities + shared-memory quads.
2756
- *
2757
- * SWM workspace meta is written at share-time (before publish), so it carries
2758
- * no merkle root to look up by — only `?op a dkg:WorkspaceOperation ;
2759
- * dkg:rootEntity <root>`. We therefore enumerate the candidate operations,
2760
- * gather each one's SWM quads + private roots, recompute the flat-KC root
2761
- * (`computeFlatKCRoot`, the same function the gossip path verifies against),
2762
- * and return the first operation whose computed root equals the chain root.
2763
- * A match is an authoritative merkle verification.
2764
- *
2765
- * Returns `null` when no local operation matches — either the snapshot hasn't
2766
- * been synced yet (the caller's active-fetch missed / is in flight) or this
2767
- * KA belongs to a publish this node never shared. Either way the B.2 sweep
2768
- * retries later.
2769
- *
2770
- * Cost note (#1609): the recompute is memoized (`SWM_SNAPSHOT_MERKLE_ROOT_PREDICATE`
2771
- * + content digest). A *present* KA is resolved by its stamped root directly (fast
2772
- * path, no scan); the fallback still enumerates every op — it must, because ops
2773
- * assemble incrementally — but reuses each op's memoized root whenever its content
2774
- * digest is unchanged, skipping the expensive `computeFlatKCRoot`. So steady-state
2775
- * reconcile pays one bounded read + a cheap digest per op instead of a full merkle
2776
- * recompute per op. This is a bridge; OT-RFC-60 makes the root a write-maintained,
2777
- * indexed property (stamped once at op completion) so the fallback disappears
2778
- * entirely. The generated-catalog-floor variant keeps the exhaustive recompute
2779
- * (its match is over quads+floor, not the op's intrinsic stamped root).
2780
- *
2781
- * READ memo (#1609, 2026-07-11/12 testnet incident): the #1612 digest memo skips
2782
- * only the merkle recompute — a never-match KA (a publish this node never shared,
2783
- * exactly the beacon shape) still paid O(#ops) unbounded SWM CONSTRUCTs on EVERY
2784
- * sweep tick, forever, stalling the event loop and dropping storage-ACK streams
2785
- * on big stores. This wrapper adds a write-generation-gated NEGATIVE memo over
2786
- * the whole scan (floor retry included): a "no match" verdict is replayed
2787
- * without touching the store while (a) the adapter's write generation for the
2788
- * CG's graph prefix is unchanged — new SWM only arrives via local writes
2789
- * (gossip receive, publish share, active fetch), all of which pass the adapter
2790
- * choke points and bump the generation, so the next call rescans — (b) the
2791
- * catalog-floor eligibility is unchanged, and (c) the entry is younger than
2792
- * `vmReconcileNegativeTtlMs()`. A MATCH is never memoized. Stores without the
2793
- * write-generation capability disable the memo entirely (always scan), and a
2794
- * restart clears it — every failure mode degrades to a rescan, never a miss.
2795
- */
2796
- async findSwmSnapshotForMerkleRoot(contextGraphId, merkleRoot, subGraphName, onChainCgId) {
2797
- const allowGeneratedCatalogFloor = await this.allowsGeneratedCatalogFloor(contextGraphId, onChainCgId);
2798
- // Every graph the scan reads — root/sub-graph SWM buckets, their per-KA
2799
- // under-graphs and `_shared_memory_meta` (op rows, `privateMerkleRoot`,
2800
- // stamps) — lives under the CG's URI subtree, so one prefix covers all
2801
- // namespaces of this call. Broader than strictly needed (any CG-local
2802
- // write invalidates) — that only costs an extra rescan.
2803
- const memoKey = `${contextGraphId}\0${subGraphName ?? ''}\0${ethers.hexlify(merkleRoot)}`;
2804
- const swmWritePrefix = `${contextGraphDataUri(contextGraphId)}/`;
2805
- const preScanRevision = this.graphWriteGen?.getWriteRevision(swmWritePrefix);
2806
- const preScanGen = preScanRevision?.generation;
2807
- if (preScanRevision?.stable) {
2808
- const memo = this.negativeSnapshotMemo.get(memoKey);
2809
- if (memo) {
2810
- if (memo.writeGen === preScanGen &&
2811
- memo.allowGeneratedCatalogFloor === allowGeneratedCatalogFloor &&
2812
- Date.now() - memo.recordedAt < vmReconcileNegativeTtlMs()) {
2813
- // Refresh LRU recency; recordedAt stays — the TTL runs from the scan.
2814
- this.negativeSnapshotMemo.delete(memoKey);
2815
- this.negativeSnapshotMemo.set(memoKey, memo);
2816
- return null;
2817
- }
2818
- this.negativeSnapshotMemo.delete(memoKey);
2819
- }
2820
- }
2821
- const hit = await this.runScanSingleFlight(['reconcile', memoKey, String(allowGeneratedCatalogFloor), String(preScanGen ?? 'unknown')].join('\u0000'), () => this.scanForSwmSnapshot(contextGraphId, merkleRoot, subGraphName, allowGeneratedCatalogFloor));
2822
- if (hit)
2823
- return hit;
2824
- if (preScanRevision?.stable) {
2825
- // Record at the PRE-scan generation: any write that raced the scan (or
2826
- // the scan's own best-effort restamps) flips the gate above, so the next
2827
- // call rescans rather than replaying a verdict that may predate the write.
2828
- this.negativeSnapshotMemo.set(memoKey, {
2829
- writeGen: preScanRevision.generation,
2830
- recordedAt: Date.now(),
2831
- allowGeneratedCatalogFloor,
2832
- });
2833
- while (this.negativeSnapshotMemo.size > VM_RECONCILE_NEGATIVE_MEMO_MAX_ENTRIES) {
2834
- const oldest = this.negativeSnapshotMemo.keys().next().value;
2835
- if (oldest === undefined)
2836
- break;
2837
- this.negativeSnapshotMemo.delete(oldest);
2838
- }
2839
- }
2840
- return null;
2841
- }
2842
- /** The authoritative full scan behind {@link findSwmSnapshotForMerkleRoot}. */
2843
- async scanForSwmSnapshot(contextGraphId, merkleRoot, subGraphName, allowGeneratedCatalogFloor) {
2844
- // Caller knows the exact namespace → search only that one.
2845
- if (subGraphName) {
2846
- const hit = await this.findSwmSnapshotInNamespace(contextGraphId, merkleRoot, subGraphName, allowGeneratedCatalogFloor);
2847
- return hit ? { ...hit, subGraphName } : null;
2848
- }
2849
- // No namespace supplied (the chain-driven path never knows it). Try the
2850
- // root workspace first, then fall back to every registered sub-graph —
2851
- // otherwise a KA published into a named sub-graph would stay `no-swm`
2852
- // forever because its SWM snapshot lives under a sub-graph meta graph,
2853
- // not the root workspace meta. Return the namespace we matched in so the
2854
- // caller promotes into the correct data graph.
2855
- const rootHit = await this.findSwmSnapshotInNamespace(contextGraphId, merkleRoot, undefined, allowGeneratedCatalogFloor);
2856
- if (rootHit)
2857
- return { ...rootHit, subGraphName: undefined };
2858
- let subGraphNames = [];
2859
- try {
2860
- subGraphNames = await new GraphManager(this.store).listSubGraphs(contextGraphId);
2861
- }
2862
- catch { /* no sub-graphs / store can't enumerate */ }
2863
- for (const sg of subGraphNames) {
2864
- const hit = await this.findSwmSnapshotInNamespace(contextGraphId, merkleRoot, sg, allowGeneratedCatalogFloor);
2865
- if (hit)
2866
- return { ...hit, subGraphName: sg };
2867
- }
2868
- return null;
2869
- }
2870
- /**
2871
- * Search a single SWM namespace (root workspace when `subGraphName` is
2872
- * undefined, otherwise the named sub-graph's shared-memory meta) for a
2873
- * WorkspaceOperation whose recomputed flat-KC root matches `merkleRoot`.
2874
- */
2875
- async findSwmSnapshotInNamespace(contextGraphId, merkleRoot, subGraphName, allowGeneratedCatalogFloor = false) {
2876
- const graphManager = new GraphManager(this.store);
2877
- const wsMetaGraph = subGraphName
2878
- ? graphManager.sharedMemoryMetaUri(contextGraphId, subGraphName)
2879
- : contextGraphWorkspaceMetaGraphUri(contextGraphId);
2880
- // The generated-catalog-floor variant matches over quads+floor rather than an
2881
- // op's intrinsic root, so it cannot be resolved by the stamped intrinsic root —
2882
- // those CGs (a stable per-CG access policy) keep the exhaustive recompute scan.
2883
- const useStampIndex = !allowGeneratedCatalogFloor;
2884
- // FAST PATH — resolve a *present* KA whose op is stamped with the target root via
2885
- // one indexed lookup (verified authoritatively), instead of recomputing every op's
2886
- // root. On a miss it falls through to the memoized fallback scan below.
2887
- if (useStampIndex) {
2888
- const stamped = await this.findStampedSwmSnapshot(contextGraphId, wsMetaGraph, merkleRoot, subGraphName);
2889
- if (stamped)
2890
- return stamped;
2891
- }
2892
- const opsBySubject = new Map();
2893
- try {
2894
- const memoPatterns = useStampIndex
2895
- ? `OPTIONAL { ?op <${SWM_SNAPSHOT_MERKLE_ROOT_PREDICATE}> ?memoRoot . }
2896
- OPTIONAL { ?op <${SWM_SNAPSHOT_CONTENT_DIGEST_PREDICATE}> ?memoDigest . }`
2897
- : '';
2898
- const result = await this.store.query(`SELECT ?op ?root ?memoRoot ?memoDigest WHERE {
2899
- GRAPH <${assertSafeIri(wsMetaGraph)}> {
2900
- ?op <${DKG_NS}rootEntity> ?root .
2901
- ${memoPatterns}
2902
- }
2903
- }`, { source: 'agent.finalization.swmSnapshotCandidates' });
2904
- if (result.type === 'bindings') {
2905
- for (const row of result.bindings) {
2906
- const op = typeof row['op'] === 'string' ? row['op'].replace(/^<(.*)>$/, '$1') : '';
2907
- const root = typeof row['root'] === 'string' ? row['root'].replace(/^<(.*)>$/, '$1') : '';
2908
- if (!op || !isSafeIri(root))
2909
- continue;
2910
- const memo = opsBySubject.get(op) ?? { roots: [] };
2911
- if (!memo.roots.includes(root))
2912
- memo.roots.push(root);
2913
- if (memo.memoRoot === undefined && typeof row['memoRoot'] === 'string') {
2914
- memo.memoRoot = row['memoRoot'].replace(/^"(.*)".*$/, '$1');
2915
- }
2916
- if (memo.memoDigest === undefined && typeof row['memoDigest'] === 'string') {
2917
- memo.memoDigest = row['memoDigest'].replace(/^"(.*)".*$/, '$1');
2918
- }
2919
- opsBySubject.set(op, memo);
2920
- }
2921
- }
2922
- }
2923
- catch { /* SWM meta may not exist yet */ }
2924
- if (opsBySubject.size === 0)
2925
- return null;
2926
- const opsSorted = [...opsBySubject.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
2927
- // Ops whose content changed (or were never stamped) get a fresh (root, digest)
2928
- // memo written after the loop — best-effort: a store hiccup on the memo must
2929
- // never fail reconcile (correctness comes from the recompute here).
2930
- const restamp = new Map();
2931
- const targetHex = ethers.hexlify(merkleRoot);
2932
- let hit = null;
2933
- for (const [op, memo] of opsSorted) {
2934
- const roots = memo.roots;
2935
- const sharedMemoryQuads = await this.getSharedMemoryQuadsForRoots(contextGraphId, roots, subGraphName);
2936
- if (sharedMemoryQuads.length === 0)
2937
- continue;
2938
- const privateRoots = await this.getPrivateRootsFromMeta(contextGraphId, roots, subGraphName);
2939
- if (useStampIndex) {
2940
- const digest = this.swmContentDigest(sharedMemoryQuads, privateRoots);
2941
- let computedHex;
2942
- if (memo.memoRoot !== undefined && memo.memoDigest === digest) {
2943
- // Content unchanged since the memo was written → reuse the root and skip
2944
- // the expensive computeFlatKCRoot. The digest is a pure function of the
2945
- // same (quads, privateRoots) computeFlatKCRoot consumes, so digest-equal
2946
- // ⇒ root-equal.
2947
- computedHex = memo.memoRoot;
2948
- }
2949
- else {
2950
- computedHex = this.computeOpMerkleRoot(sharedMemoryQuads, privateRoots);
2951
- restamp.set(op, { root: computedHex, digest });
2952
- }
2953
- if (computedHex === targetHex) {
2954
- hit = { rootEntities: roots, sharedMemoryQuads };
2955
- break;
2956
- }
2957
- }
2958
- else {
2959
- const merkleMatchedQuads = this.sharedMemoryQuadsMatchingMerkle(contextGraphId, sharedMemoryQuads, privateRoots, merkleRoot, allowGeneratedCatalogFloor);
2960
- if (merkleMatchedQuads) {
2961
- return { rootEntities: roots, sharedMemoryQuads: merkleMatchedQuads };
2962
- }
2963
- }
2964
- }
2965
- await this.persistSwmStamps(wsMetaGraph, restamp);
2966
- return hit;
2967
- }
2968
- /**
2969
- * Cheap content fingerprint of an op's SWM snapshot — a pure function of the same
2970
- * `(sharedMemoryQuads, privateRoots)` that `computeFlatKCRoot` consumes, but a
2971
- * plain sorted-sha256 rather than the full skolemize + merkle construction. Used
2972
- * only to decide whether a memoized root is still valid (digest-equal ⇒ the
2973
- * content, hence its flat-KC root, is unchanged), never as an authoritative root.
2974
- */
2975
- /**
2976
- * The op's intrinsic flat-KC merkle root (hex) — the expensive recompute the
2977
- * content-digest memo lets us skip for unchanged ops. A named seam so callers and
2978
- * tests can attribute the cost.
2979
- */
2980
- computeOpMerkleRoot(sharedMemoryQuads, privateRoots) {
2981
- return ethers.hexlify(computeFlatKCRoot(sharedMemoryQuads, privateRoots));
2982
- }
2983
- swmContentDigest(sharedMemoryQuads, privateRoots) {
2984
- // Unambiguous encoding (NUL field-sep, NL row-sep) matching the SWM-generation
2985
- // fingerprint convention in `readVmReconcileSwmGen`, so distinct contents cannot
2986
- // collide to the same digest and cause a wrong memoized-root reuse.
2987
- const quadLines = sharedMemoryQuads
2988
- .map((q) => [q.subject, q.predicate, typeof q.object === 'string' ? q.object : String(q.object), q.graph ?? ''].join('\0'))
2989
- .sort();
2990
- const privateLines = privateRoots.map((r) => ethers.hexlify(r)).sort();
2991
- const payload = `${quadLines.join('\n')}\0\0private\0\0${privateLines.join('\n')}`;
2992
- return createHash('sha256').update(payload, 'utf8').digest('hex');
2993
- }
2994
- /**
2995
- * Replace the (root, content-digest) memo for each op — delete-then-insert so an
2996
- * op never accumulates duplicate/stale stamp triples. Best-effort; a failure here
2997
- * leaves the memo absent (next pass recomputes), never wrong.
2998
- */
2999
- async persistSwmStamps(wsMetaGraph, restamp) {
3000
- if (restamp.size === 0)
3001
- return;
3002
- try {
3003
- const quads = [];
3004
- for (const [op, { root, digest }] of restamp) {
3005
- await deleteByPatternWithoutCount(this.store, { graph: wsMetaGraph, subject: op, predicate: SWM_SNAPSHOT_MERKLE_ROOT_PREDICATE });
3006
- await deleteByPatternWithoutCount(this.store, { graph: wsMetaGraph, subject: op, predicate: SWM_SNAPSHOT_CONTENT_DIGEST_PREDICATE });
3007
- quads.push({ subject: op, predicate: SWM_SNAPSHOT_MERKLE_ROOT_PREDICATE, object: `"${root}"`, graph: wsMetaGraph }, { subject: op, predicate: SWM_SNAPSHOT_CONTENT_DIGEST_PREDICATE, object: `"${digest}"`, graph: wsMetaGraph });
3008
- }
3009
- await this.store.insert(quads);
3010
- }
3011
- catch { /* memo is best-effort; the recompute is the source of truth */ }
3012
- }
3013
- /**
3014
- * Fast path for `findSwmSnapshotInNamespace`: resolve the WorkspaceOperation
3015
- * whose stamped intrinsic root equals `merkleRoot` without recomputing every
3016
- * op's root. Re-verifies with `verifyMerkleMatch` (authoritative) so a stale
3017
- * stamp can never promote the wrong snapshot; a stamp that no longer verifies is
3018
- * dropped so the op falls back into the recompute scan this same pass.
3019
- */
3020
- async findStampedSwmSnapshot(contextGraphId, wsMetaGraph, merkleRoot, subGraphName) {
3021
- const targetHex = ethers.hexlify(merkleRoot);
3022
- const rootsByOp = new Map();
3023
- try {
3024
- const result = await this.store.query(`SELECT ?op ?root WHERE {
3025
- GRAPH <${assertSafeIri(wsMetaGraph)}> {
3026
- ?op <${SWM_SNAPSHOT_MERKLE_ROOT_PREDICATE}> "${targetHex}" .
3027
- ?op <${DKG_NS}rootEntity> ?root .
3028
- }
3029
- }`, { source: 'agent.finalization.stampedSwmSnapshot' });
3030
- if (result.type === 'bindings') {
3031
- for (const row of result.bindings) {
3032
- const op = typeof row['op'] === 'string' ? row['op'].replace(/^<(.*)>$/, '$1') : '';
3033
- const root = typeof row['root'] === 'string' ? row['root'].replace(/^<(.*)>$/, '$1') : '';
3034
- if (!op || !isSafeIri(root))
3035
- continue;
3036
- const list = rootsByOp.get(op) ?? [];
3037
- list.push(root);
3038
- rootsByOp.set(op, list);
3039
- }
3040
- }
3041
- }
3042
- catch {
3043
- return null;
3044
- }
3045
- for (const [op, roots] of rootsByOp) {
3046
- const sharedMemoryQuads = await this.getSharedMemoryQuadsForRoots(contextGraphId, roots, subGraphName);
3047
- if (sharedMemoryQuads.length > 0) {
3048
- const privateRoots = await this.getPrivateRootsFromMeta(contextGraphId, roots, subGraphName);
3049
- if (this.verifyMerkleMatch(sharedMemoryQuads, privateRoots, merkleRoot)) {
3050
- return { rootEntities: roots, sharedMemoryQuads };
3051
- }
3052
- }
3053
- // The stamp no longer reflects the op's content — drop the whole (root, digest)
3054
- // memo so the recompute scan re-evaluates and re-stamps it this pass.
3055
- try {
3056
- await deleteByPatternWithoutCount(this.store, { graph: wsMetaGraph, subject: op, predicate: SWM_SNAPSHOT_MERKLE_ROOT_PREDICATE });
3057
- await deleteByPatternWithoutCount(this.store, { graph: wsMetaGraph, subject: op, predicate: SWM_SNAPSHOT_CONTENT_DIGEST_PREDICATE });
3058
- }
3059
- catch { /* best-effort self-heal */ }
3060
- }
3061
- return null;
3062
- }
3063
- async verifyOnChain(txHash, blockNumber, expectedMerkleRoot, expectedPublisher, expectedStartKAId, expectedEndKAId, ctx, ctxGraphId, expectedBatchId) {
3064
- if (!this.chain || this.chain.chainId === 'none')
3065
- return { verified: false };
3066
- if (blockNumber <= 0)
3067
- return { verified: false };
3068
- try {
3069
- // Verify KnowledgeBatchCreated or KCCreated (V10) at the specific block
3070
- const batchFilter = {
3071
- eventTypes: ['KnowledgeBatchCreated', 'KCCreated'],
3072
- fromBlock: blockNumber,
3073
- toBlock: blockNumber,
3074
- };
3075
- let batchVerified = false;
3076
- // Round 5 review §10 — capture the indexed `author` from the matched
3077
- // KCCreated event so the caller can populate `dkg:Publication` /
3078
- // `dkg:authoredBy` provenance on the replica side, mirroring the
3079
- // originator. `address(0)` here is the unattributed-publish sentinel
3080
- // (RFC-001 §3.6) and is correctly preserved.
3081
- let authorAddress;
3082
- // PR #845 review #9: capture the chain-truth `transactionIndex` from
3083
- // the matched event so the materialization-version guard does not
3084
- // have to trust the gossip-supplied `msg.txIndex` (which a peer can
3085
- // inflate to lock out a legitimate same-block update).
3086
- let verifiedTxIndex;
3087
- for await (const event of this.chain.listenForEvents(batchFilter)) {
3088
- if (event.blockNumber !== blockNumber)
3089
- continue;
3090
- if (txHash && (!event.data['txHash'] || event.data['txHash'].toLowerCase() !== txHash.toLowerCase())) {
3091
- continue;
3092
- }
3093
- const eventMerkle = typeof event.data['merkleRoot'] === 'string'
3094
- ? ethers.getBytes(event.data['merkleRoot'])
3095
- : event.data['merkleRoot'];
3096
- const eventPublisher = event.data['publisherAddress'] ?? '';
3097
- const eventStartKAId = BigInt(event.data['startKAId'] ?? '0');
3098
- const eventEndKAId = BigInt(event.data['endKAId'] ?? '0');
3099
- const merkleMatch = ethers.hexlify(eventMerkle) === ethers.hexlify(expectedMerkleRoot);
3100
- const publisherMatch = eventPublisher.toLowerCase() === expectedPublisher.toLowerCase();
3101
- const rangeMatch = eventStartKAId === expectedStartKAId && eventEndKAId === expectedEndKAId;
3102
- if (merkleMatch && publisherMatch && rangeMatch) {
3103
- batchVerified = true;
3104
- const eventAuthor = event.data['author'] ?? '';
3105
- if (eventAuthor)
3106
- authorAddress = eventAuthor;
3107
- const rawTxIdx = event.data['txIndex'];
3108
- if (typeof rawTxIdx === 'number' && Number.isFinite(rawTxIdx) && rawTxIdx >= 0) {
3109
- verifiedTxIndex = rawTxIdx;
3110
- }
3111
- break;
3112
- }
3113
- }
3114
- if (!batchVerified)
3115
- return { verified: false };
3116
- // V10 publish registers the KC to the context graph internally
3117
- // (no separate addBatchToContextGraph tx / ContextGraphExpanded event).
3118
- // Skip the legacy ContextGraphExpanded check — the batch verification
3119
- // above is sufficient for V10.
3120
- if (ctxGraphId) {
3121
- if (typeof this.chain.isV10Ready === 'function' && this.chain.isV10Ready()) {
3122
- return { verified: true, authorAddress, txIndex: verifiedTxIndex };
3123
- }
3124
- try {
3125
- const scanWindow = 256;
3126
- const headBlock = typeof this.chain.getBlockNumber === 'function'
3127
- ? await this.chain.getBlockNumber()
3128
- : blockNumber + scanWindow;
3129
- const cgFilter = {
3130
- eventTypes: ['ContextGraphExpanded'],
3131
- fromBlock: blockNumber,
3132
- toBlock: Math.min(blockNumber + scanWindow, headBlock),
3133
- };
3134
- for await (const event of this.chain.listenForEvents(cgFilter)) {
3135
- const eventCGId = String(event.data['contextGraphId'] ?? '');
3136
- const eventBatchId = BigInt(event.data['batchId'] ?? '0');
3137
- if (eventCGId === ctxGraphId && (expectedBatchId === undefined || eventBatchId === expectedBatchId)) {
3138
- return { verified: true, authorAddress, txIndex: verifiedTxIndex };
3139
- }
3140
- }
3141
- return { verified: false };
3142
- }
3143
- catch {
3144
- return { verified: true, authorAddress, txIndex: verifiedTxIndex };
3145
- }
3146
- }
3147
- return { verified: true, authorAddress, txIndex: verifiedTxIndex };
3148
- }
3149
- catch (err) {
3150
- this.log.info(ctx, `Finalization on-chain verification pending (RPC may be lagging): ${err instanceof Error ? err.message : String(err)}`);
3151
- }
3152
- return { verified: false };
3153
- }
3154
- async promoteSharedMemoryToCanonical(contextGraphId, sharedMemoryQuads, ual, msgRootEntities, publisherAddress, txHash, blockNumber, startKAId, endKAId, batchId, ctx, ctxGraphId, subGraphName,
3155
- /**
3156
- * EIP-712-attested author recovered from `KnowledgeAssetCreated.author`.
3157
- * Round 5 review §10 — when set, the replica emits matching
3158
- * `dkg:Publication` / `dkg:authoredBy` triples so agent-provenance via the
3159
- * `_meta` triplestore is consistent across the originator and every replica.
3160
- * `address(0)` and missing/empty values are skipped (preserves the
3161
- * unattributed-publish path's no-author behaviour from RFC-001 §3.6).
3162
- */
3163
- authorAddress,
3164
- /**
3165
- * PR #779 same-graph signal: when `true` the publisher kept a root-graph
3166
- * copy of the canonical quads, so receivers mirror the dual-write so
3167
- * label-scoped queries resolve. When `false` (or omitted on older
3168
- * publishers) the publisher used the explicit-`subContextGraphId` /
3169
- * remap path and deleted its own root copy on purpose — receivers
3170
- * MUST NOT dual-write or they re-expose the KC under the source CG
3171
- * label and double-count it in unscoped queries.
3172
- */
3173
- keepRootCopyOnLabel) {
3174
- const graphManager = new GraphManager(this.store);
3175
- await graphManager.ensureContextGraph(contextGraphId);
3176
- if (subGraphName) {
3177
- await graphManager.ensureSubGraph(contextGraphId, subGraphName);
3178
- const sgUri = contextGraphSubGraphUri(contextGraphId, subGraphName);
3179
- const metaGraph = `did:dkg:context-graph:${assertSafeIri(contextGraphId)}/_meta`;
3180
- const alreadyRegistered = await this.store.query(`ASK { GRAPH <${metaGraph}> {
3181
- <${assertSafeIri(sgUri)}> a <http://dkg.io/ontology/SubGraph> ;
3182
- <http://schema.org/name> ${JSON.stringify(subGraphName)} ;
3183
- <http://dkg.io/ontology/createdBy> ?createdBy .
3184
- } }`, { source: 'agent.finalization.subGraphRegistration' });
3185
- if (alreadyRegistered.type !== 'boolean' || !alreadyRegistered.value) {
3186
- const regQuads = generateSubGraphRegistration({
3187
- contextGraphId,
3188
- subGraphName,
3189
- createdBy: publisherAddress || 'finalization-discovery',
3190
- timestamp: new Date(),
3191
- });
3192
- await this.store.insert(regQuads);
3193
- this.markContextGraphMetaDirtyFromQuads?.(regQuads);
3194
- this.log.info(ctx, `Finalization: auto-registered sub-graph "${subGraphName}" in context graph "${contextGraphId}"`);
3195
- }
3196
- }
3197
- const dataGraph = subGraphName
3198
- ? contextGraphSubGraphUri(contextGraphId, subGraphName)
3199
- : ctxGraphId
3200
- ? contextGraphDataUri(contextGraphId, ctxGraphId)
3201
- : graphManager.dataGraphUri(contextGraphId);
3202
- // Devnet test #774-followup (v10-rc-validation §5 gossip replication):
3203
- // when `ctxGraphId` is set on a non-sub-graph publish, the canonical
3204
- // data lands in the per-on-chain-id partition
3205
- // `<cg>/context/<ctxGraphId>` only. The publisher path
3206
- // (`dkg-publisher.ts` ~line 1382) intentionally ALSO writes the same
3207
- // quads to the root `<cg>` graph "so `agent.query(label)` (which
3208
- // resolves to `did:dkg:context-graph:<label>` without a
3209
- // `/context/<id>` suffix) still finds the just-published triples"
3210
- // (commit c2abbc9a). Replicas were never updated to mirror that
3211
- // dual-write — so a CG-scoped query against a label on a recipient
3212
- // node finds 0 bindings even though the data is local in
3213
- // `<cg>/context/<ctxGraphId>`. The query engine cannot widen its
3214
- // allow set to `<cg>/context/<num>` without a CG-registry lookup
3215
- // (id-prefix collisions, see PR #776 r6 in dkg-query-engine.ts).
3216
- // Mirroring the publisher's same-graph dual-write fixes the
3217
- // visibility asymmetry without re-introducing that ambiguity.
3218
- //
3219
- // Critical scoping (Codex review on PR #779): the recipient dual-write
3220
- // MUST only fire for same-graph publishes (the publisher kept the
3221
- // root copy too). Explicit-`subContextGraphId` / remap publishes
3222
- // delete the root copy on purpose (`dkg-publisher.ts` ~line 1393),
3223
- // and a recipient that re-adds it would re-expose the KC under the
3224
- // source CG's label on every replica — leaking remap intent and
3225
- // double-counting the same triples in unscoped queries. The
3226
- // publisher signals same-graph vs remap on the wire via
3227
- // `keepRootCopyOnLabel`; missing/false (older publishers, or any
3228
- // remap publish) → no dual-write.
3229
- const rootDataGraphForLabel = (!subGraphName && ctxGraphId && keepRootCopyOnLabel === true)
3230
- ? graphManager.dataGraphUri(contextGraphId)
3231
- : null;
3232
- // Compute canonical quads now, but defer the `store.insert` until AFTER
3233
- // the confirmed-meta write and SWM cleanup (see bottom of this method).
3234
- //
3235
- // Rationale: on a replica the three store mutations (canonical insert,
3236
- // confirmed-meta insert, SWM cleanup) happen as three sequential awaits.
3237
- // Readers polling the canonical graph (e.g. downstream consumers waiting
3238
- // for "KC landed") can observe the canonical insert before the meta flip
3239
- // to `confirmed` or before SWM has been drained, producing a visible
3240
- // "data-without-confirmed-meta" intermediate state that is wrong from a
3241
- // causal-consistency standpoint (the whole point of the finalization
3242
- // gossip is that by the time data is canonical on a replica, the chain
3243
- // proof is durable too). On fast dev machines the window is sub-
3244
- // millisecond and invisible; on slower CI runners it widens to ~70 ms
3245
- // and a poll that happens to land there sees `tentative` instead of
3246
- // `confirmed` (flipped the `Tornado EVM integration: agent`
3247
- // e2e-finalization suite red on every PR-224 run since the
3248
- // sync-refactor merge at `bc65c3ae`).
3249
- //
3250
- // Ordering meta+cleanup BEFORE canonical insert makes the public state
3251
- // transition single-stepped from an observer's point of view: either
3252
- // you don't see the data yet, or you see data + confirmed meta + empty
3253
- // SWM. The only user-visible partial state that remains (confirmed
3254
- // meta + no data) is naturally recovered by `isAlreadyConfirmed` on the
3255
- // next retry path, and it's strictly less broken than the converse.
3256
- const canonicalQuads = sharedMemoryQuads.map(q => ({ ...q, graph: dataGraph }));
3257
- const privateRoots = await this.getPrivateRootsFromMeta(contextGraphId, msgRootEntities, subGraphName);
3258
- const merkleRoot = computeFlatKCRoot(canonicalQuads, privateRoots);
3259
- const partitioned = skolemizeByEntity(canonicalQuads);
3260
- const localRootSet = new Set(partitioned.keys());
3261
- const rootEntities = msgRootEntities.length > 0
3262
- ? msgRootEntities
3263
- : [...partitioned.keys()];
3264
- if (msgRootEntities.length > 0) {
3265
- const msgSet = new Set(msgRootEntities);
3266
- const extraInMsg = msgRootEntities.filter(r => !localRootSet.has(r));
3267
- let generatedCatalogRootSet = new Set();
3268
- try {
3269
- generatedCatalogRootSet = new Set(splitTrustedGeneratedCatalogRootMap(partitioned, generatedPrivateCatalogTripleKeys(contextGraphId)).generatedCatalogRootEntities);
3270
- }
3271
- catch {
3272
- generatedCatalogRootSet = new Set();
3273
- }
3274
- const missingInMsg = [...localRootSet].filter(r => !msgSet.has(r) && !generatedCatalogRootSet.has(r));
3275
- if (extraInMsg.length > 0 || missingInMsg.length > 0) {
3276
- this.log.warn(ctx, `Finalization: root entity set mismatch — extra in msg: [${extraInMsg.join(', ')}], missing: [${missingInMsg.join(', ')}]`);
3277
- }
3278
- }
3279
- const kaMetadata = [];
3280
- // GH #936 — assign per-root tokenIds over a CANONICAL (lexicographic) root
3281
- // order, NOT the SPARQL/gossip binding order. oxigraph binding order is
3282
- // store-history-dependent, so two replicas reconciling the same KC from
3283
- // chain would otherwise mint divergent root→tokenId maps. These tokenIds are
3284
- // local compatibility labels (the on-chain KA count is 1, no on-chain
3285
- // dependency — see dkg-publisher.ts), so a content-derived sort makes the
3286
- // map a pure function of the root SET: identical on every replica and on
3287
- // both the gossip and chain-reconcile promotion paths.
3288
- const orderedRoots = [...rootEntities].sort(compareRootIris);
3289
- for (let tokenIdx = 0; tokenIdx < orderedRoots.length; tokenIdx++) {
3290
- const rootEntity = orderedRoots[tokenIdx];
3291
- const entityQuads = partitioned.get(rootEntity) ?? [];
3292
- if (entityQuads.length === 0)
3293
- continue;
3294
- kaMetadata.push({
3295
- rootEntity,
3296
- kcUal: ual,
3297
- tokenId: BigInt(tokenIdx + 1),
3298
- publicTripleCount: entityQuads.length,
3299
- privateTripleCount: 0,
3300
- privateMerkleRoot: undefined,
3301
- });
3302
- }
3303
- const wsPeerId = await this.getPublisherPeerIdFromMeta(contextGraphId, msgRootEntities, subGraphName);
3304
- // Round 5 review §10 — propagate the on-chain-attested author into the
3305
- // confirmed `_meta` block so replicas emit a `prov:wasAttributedTo`
3306
- // matching the originator's. We treat `address(0)` (the
3307
- // unattributed-publish sentinel) as "no author" by skipping the field,
3308
- // so the legacy no-author behaviour is preserved verbatim. (The former
3309
- // `dkg:Publication` / `dkg:authoredBy` mirror was dropped — RFC
3310
- // ka-metadata-trim Phase 1, zero readers.)
3311
- const isUnattributed = !authorAddress
3312
- || authorAddress === '0x0000000000000000000000000000000000000000'
3313
- || authorAddress.toLowerCase() === '0x0000000000000000000000000000000000000000';
3314
- const kcMeta = {
3315
- ual,
3316
- contextGraphId,
3317
- merkleRoot,
3318
- publisherPeerId: wsPeerId || publisherAddress,
3319
- timestamp: new Date(),
3320
- subGraphName,
3321
- ...(isUnattributed
3322
- ? {}
3323
- : { authorAddress }),
3324
- };
3325
- let blockTimestamp = Math.floor(Date.now() / 1000);
3326
- if (this.chain && typeof this.chain.getBlockTimestamp === 'function') {
3327
- try {
3328
- blockTimestamp = await this.chain.getBlockTimestamp(blockNumber);
3329
- }
3330
- catch {
3331
- this.log.info(ctx, `Could not fetch block timestamp for block ${blockNumber}, using local time`);
3332
- }
3333
- }
3334
- const provenance = {
3335
- txHash,
3336
- blockNumber,
3337
- blockTimestamp,
3338
- publisherAddress,
3339
- batchId,
3340
- chainId: this.chain?.chainId ?? 'unknown',
3341
- };
3342
- // Remove any existing tentative status for this UAL before inserting
3343
- // confirmed metadata. Two graph locations can carry it on this replica:
3344
- // 1. Root `<cg>/_meta` — gossip-publish-handler ALWAYS writes
3345
- // `generateTentativeMetadata(...)` here (via `getTentativeStatusQuad`,
3346
- // which hardcodes the root `_meta` graph). This applies to every
3347
- // gossip-replicated KC regardless of `ctxGraphId` / dual-write mode.
3348
- // 2. Per-cgId `<cg>/context/<id>/_meta` — older code paths (and any
3349
- // future writer that respects the same partition split as canonical
3350
- // data) may park a tentative quad here when `ctxGraphId` is set.
3351
- //
3352
- // Codex r5 on PR #779: the previous form mutated `tentativeQuad.graph`
3353
- // to the per-cgId URI when `ctxGraphId` was set and deleted ONLY that
3354
- // copy. The root tentative survived. With the same-graph dual-write
3355
- // path (`keepRootCopyOnLabel === true`) we then re-inserted confirmed
3356
- // `_meta` into root `<cg>/_meta`, leaving `tentative` AND `confirmed`
3357
- // status quads coexisting on the same UAL in the root meta graph —
3358
- // label-scoped status reads were non-deterministic. Even on the
3359
- // `keepRootCopyOnLabel === false` path the leftover root tentative was
3360
- // wrong (the publisher had moved/dropped its root copy on remap).
3361
- //
3362
- // Fix: always queue the root tentative for deletion AND, when ctxGraphId
3363
- // is set, also queue the per-cgId variant. Single `store.delete` call so
3364
- // all stale tentative copies are reaped in one shot — `delete` no-ops on
3365
- // missing quads, so it's safe to enumerate both regardless of which
3366
- // writer actually populated them.
3367
- const rootTentativeQuad = getTentativeStatusQuad(ual, contextGraphId);
3368
- const tentativesToDelete = [rootTentativeQuad];
3369
- if (ctxGraphId) {
3370
- tentativesToDelete.push({
3371
- ...rootTentativeQuad,
3372
- graph: contextGraphMetaUri(contextGraphId, ctxGraphId),
3373
- });
3374
- }
3375
- try {
3376
- await this.store.delete(tentativesToDelete);
3377
- }
3378
- catch { /* tentative status may not exist */ }
3379
- let metaQuads = generateConfirmedFullMetadata(kcMeta, kaMetadata, provenance);
3380
- // GH #936 — append the SHARED deterministic per-root token rows (no-op for
3381
- // single-root). This is the SAME helper the publisher uses on the originator
3382
- // path, so a locally-published and a chain-reconciled multi-root KC expose
3383
- // an identical, queryable rootEntity→tokenId map. graph = the default
3384
- // `<cg>/_meta` so the ctxGraphId remap below routes them to the per-cgId
3385
- // `_meta` (and dual-writes a root copy when keepRootCopyOnLabel).
3386
- metaQuads.push(...buildDeterministicTokenRows(ual, kaMetadata, `did:dkg:context-graph:${contextGraphId}/_meta`));
3387
- if (ctxGraphId) {
3388
- const defaultMeta = `did:dkg:context-graph:${contextGraphId}/_meta`;
3389
- const targetMeta = contextGraphMetaUri(contextGraphId, ctxGraphId);
3390
- // Codex r3 on PR #779: the publisher's same-graph path keeps the
3391
- // confirmed `_meta` triples in BOTH the root `<cg>/_meta` and
3392
- // the per-cgId `<cg>/context/<cgId>/_meta` graphs (see the
3393
- // matching dual-write in `dkg-publisher.ts` ~line 1419, comment
3394
- // "on remap publishes the original copy at `<NAME>/_meta` is
3395
- // also moved; on same-graph publishes we leave the default copy
3396
- // in place"). Replicas were only writing the per-cgId copy, so
3397
- // label-only `_meta` reads (status / UAL / authoredBy lookups
3398
- // that don't know the on-chain id) diverged between publisher
3399
- // and recipients. Mirror the publisher's same-graph dual-write
3400
- // here too — gated on the same `keepRootCopyOnLabel` signal as
3401
- // the data-graph dual-write below, and folded into a single
3402
- // `store.insert` for the same retry-safety reason (`_meta`
3403
- // tentative→confirmed flip is supposed to be the durable point
3404
- // post-this-call; splitting the writes would re-introduce a
3405
- // partial-state window).
3406
- if (keepRootCopyOnLabel === true) {
3407
- const rootCopies = metaQuads
3408
- .filter((q) => q.graph === defaultMeta)
3409
- .map((q) => ({ ...q }));
3410
- const perCgIdCopies = metaQuads.map((q) => q.graph === defaultMeta ? { ...q, graph: targetMeta } : q);
3411
- metaQuads = [...perCgIdCopies, ...rootCopies];
3412
- }
3413
- else {
3414
- metaQuads = metaQuads.map((q) => q.graph === defaultMeta ? { ...q, graph: targetMeta } : q);
3415
- }
3416
- }
3417
- // #1233 follow-up — bound agents/_meta: this confirmed-metadata restatement
3418
- // is load-bearing (prior-root cleanup + the tentative→confirmed flip), so it
3419
- // cannot be skipped for the agents CG. INSERT then PRUNE (insert-first) via
3420
- // the helper: the just-inserted UAL is `recordUal` so the prune protects it,
3421
- // and a post-insert prune failure is swallowed (warned) inside the helper so
3422
- // it can never abort this promotion. agents/_meta
3423
- // stays O(agents), not O(agents × heartbeats); no-op prune for every other CG
3424
- // (so it just inserts). For the agents CG `ctxGraphId` is always undefined
3425
- // (never on-chain), so `metaQuads` land in the default `<cg>/_meta` graph —
3426
- // the graph bounded here.
3427
- // NOTE: the agents CG is always-tentative and never confirms on-chain, so in
3428
- // practice this promotion is not reached for it (see report); this is a
3429
- // defensive, lifecycle-preserving bound that keeps the invariant robust.
3430
- // The bound derives its lock + prune roots from `metaQuads` itself, so the
3431
- // dropped zero-public-triple roots (the `continue` above) are naturally excluded
3432
- // from the prune — it can only evict superseded records for roots THIS record
3433
- // covers, never zero a root the record omits. (Previously this call passed a
3434
- // separate rootEntities list, which risked exactly that drift.)
3435
- await insertBoundedAgentRegistryMeta({
3436
- store: this.store,
3437
- contextGraphId,
3438
- metaGraph: `did:dkg:context-graph:${contextGraphId}/_meta`,
3439
- recordUal: ual,
3440
- metadataQuads: metaQuads,
3441
- });
3442
- // Clean up promoted shared memory entries
3443
- const sharedMemoryGraph = subGraphName
3444
- ? graphManager.sharedMemoryUri(contextGraphId, subGraphName)
3445
- : contextGraphWorkspaceGraphUri(contextGraphId);
3446
- const swmMetaGraph = subGraphName
3447
- ? graphManager.sharedMemoryMetaUri(contextGraphId, subGraphName)
3448
- : contextGraphWorkspaceMetaGraphUri(contextGraphId);
3449
- // #1099: replicas hold the gossiped SWM copy in PER-KA graphs
3450
- // `…/_shared_memory/{author}/{number}` (workspace-handler.ts ~line 987),
3451
- // but this cleanup only drained the bare bucket. The stale per-KA copy
3452
- // survived every publish, kept being served to late subscribers via the
3453
- // PROTOCOL_SYNC SWM responder (which reads the whole `…/_shared_memory/`
3454
- // prefix), and re-poisoned even the publisher's own SWM on resync —
3455
- // publisher and replicas permanently disagreed about SWM content.
3456
- // Mirror DKGPublisher's `swmGraphsUnder`: drain the bucket AND every
3457
- // graph under its `/` prefix.
3458
- const allGraphs = await this.store.listGraphs();
3459
- const swmGraphsForClear = allGraphs.filter((g) => g === sharedMemoryGraph || g.startsWith(`${sharedMemoryGraph}/`));
3460
- for (const rootEntity of rootEntities) {
3461
- for (const g of swmGraphsForClear) {
3462
- await deleteByPatternWithoutCount(this.store, { graph: g, subject: rootEntity });
3463
- await this.store.deleteBySubjectPrefix(g, rootEntity + '/.well-known/genid/');
3464
- await deleteByPatternWithoutCount(this.store, {
3465
- graph: g, subject: rootEntity, predicate: 'http://dkg.io/ontology/workspaceOwner',
3466
- });
3467
- }
3468
- await deleteByPatternWithoutCount(this.store, {
3469
- graph: swmMetaGraph, subject: rootEntity, predicate: 'http://dkg.io/ontology/workspaceOwner',
3470
- });
3471
- await this.deleteMetaForRoot(swmMetaGraph, rootEntity);
3472
- }
3473
- // Canonical insert LAST — see the comment above `const canonicalQuads`.
3474
- // By the time any reader observes these quads in the canonical graph,
3475
- // `_meta` already carries `confirmed` status + chain provenance and the
3476
- // matching SWM entries have been drained.
3477
- //
3478
- // Codex r2 on PR #779: when same-graph dual-write is in play, both
3479
- // copies (per-cgId partition + root label graph) MUST land in a single
3480
- // `store.insert` so a crash or store-write failure between them
3481
- // cannot leave the replica with the per-cgId copy but no root copy.
3482
- // The previous split form would be skipped on retry by
3483
- // `isAlreadyConfirmed()` (`_meta` already confirmed) and the root
3484
- // copy would never be back-filled — a permanent label-scoped query
3485
- // miss on that replica. Folding both into one insert call ties their
3486
- // durability to the same store-write transaction (Oxigraph's `load`
3487
- // and SPARQL-backend bulk insert are both atomic at the call level).
3488
- const allCanonicalQuads = rootDataGraphForLabel
3489
- ? [
3490
- ...canonicalQuads,
3491
- ...canonicalQuads.map(q => ({ ...q, graph: rootDataGraphForLabel })),
3492
- ]
3493
- : canonicalQuads;
3494
- await this.store.insert(allCanonicalQuads);
3495
- this.log.info(ctx, `Promoted ${canonicalQuads.length} quads from shared memory to canonical for ${ual}`);
3496
- this.eventBus?.emit(DKGEvent.MEMORY_GRAPH_CHANGED, {
3497
- contextGraphId,
3498
- layers: ['swm', 'vm'],
3499
- subGraphName,
3500
- operation: 'verifiable_memory_finalized',
3501
- source: 'finalization',
3502
- counts: {
3503
- roots: rootEntities.length,
3504
- triples: canonicalQuads.length,
3505
- },
3506
- });
3507
- }
3508
- async deleteMetaForRoot(metaGraph, rootEntity) {
3509
- const result = await this.store.query(`SELECT DISTINCT ?op WHERE { GRAPH <${assertSafeIri(metaGraph)}> { ?op ${ENTITY_PRED_ALT} <${assertSafeIri(rootEntity)}> } }`, { source: 'agent.finalization.rootMetadataOperations' });
3510
- if (result.type !== 'bindings')
3511
- return;
3512
- for (const row of result.bindings) {
3513
- const op = row['op'];
3514
- if (!op)
3515
- continue;
3516
- // OT-RFC-43 §10.1 — dual-write migration: remove BOTH the legacy
3517
- // dkg:rootEntity and the new dkg:entity for this op/entity pair.
3518
- await this.store.delete([
3519
- { subject: op, predicate: DKG_ROOT_ENTITY_LEGACY, object: rootEntity, graph: metaGraph },
3520
- { subject: op, predicate: DKG_ENTITY, object: rootEntity, graph: metaGraph },
3521
- ]);
3522
- const remaining = await this.store.query(`SELECT (COUNT(DISTINCT ?r) AS ?c) WHERE { GRAPH <${assertSafeIri(metaGraph)}> { <${assertSafeIri(op)}> ${ENTITY_PRED_ALT} ?r } }`, { source: 'agent.finalization.remainingOperationRoots' });
3523
- const rawCount = remaining.type === 'bindings' && remaining.bindings[0]?.['c'];
3524
- const countVal = typeof rawCount === 'string'
3525
- ? Number(rawCount.replace(/^"/, '').replace(/"(\^\^<[^>]+>)?$/, ''))
3526
- : NaN;
3527
- if (countVal === 0) {
3528
- await deleteByPatternWithoutCount(this.store, { graph: metaGraph, subject: op });
3529
- }
3530
- }
3531
- }
3532
1965
  }
3533
1966
  //# sourceMappingURL=finalization-handler.js.map