@origintrail-official/dkg-node-ui 10.0.5 → 10.0.7

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 (312) hide show
  1. package/dist/db.d.ts +166 -1
  2. package/dist/db.d.ts.map +1 -1
  3. package/dist/db.js +765 -32
  4. package/dist/db.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist-ui/assets/{3d-force-graph-DRDhpplH.js → 3d-force-graph-oy_vn0Oy.js} +1 -1
  10. package/dist-ui/assets/{AgentHub-DdUAxJoy.js → AgentHub-BdodC7bc.js} +1 -1
  11. package/dist-ui/assets/{AgentProfilePage-yI8fA4vv.js → AgentProfilePage-DsjmgCNC.js} +1 -1
  12. package/dist-ui/assets/{ApproveWalletsModal-9w2edv7e.js → ApproveWalletsModal-CoK9PC14.js} +18 -18
  13. package/dist-ui/assets/{ConvictionDetailView-CcQMya-y.js → ConvictionDetailView-DnpWB1CW.js} +2 -2
  14. package/dist-ui/assets/{Network-DpAKVzwi.js → Network-B1hFeJc_.js} +1 -1
  15. package/dist-ui/assets/{OnChainProvenanceCard-DMOm3jLz.js → OnChainProvenanceCard-5HtdkdH8.js} +1 -1
  16. package/dist-ui/assets/{Operations-DKJFp6hM.js → Operations-CD56ktgV.js} +1 -1
  17. package/dist-ui/assets/{PublishingConviction-1CPHxNwp.js → PublishingConviction-CsGM0rzZ.js} +2 -2
  18. package/dist-ui/assets/{Settings-DaYMlGHD.js → Settings-FtB9ej6P.js} +1 -1
  19. package/dist-ui/assets/{ccip-hCQYqfNz.js → ccip-C3eQxfL2.js} +1 -1
  20. package/dist-ui/assets/{index-DGpnb_b1.js → index-B0ozkBOt.js} +137 -137
  21. package/dist-ui/assets/{index-Bh_8bMB0.js → index-zKvsH-2o.js} +3 -3
  22. package/dist-ui/assets/{jsonld-32FQRO67-C3B_LjZ-.js → jsonld-32FQRO67-Ckj0IC7a.js} +2 -2
  23. package/dist-ui/assets/{jsonld-CoPRWQuM.js → jsonld-D76K907R.js} +1 -1
  24. package/dist-ui/assets/{renderer-3d-2EVDZII7-Bx6o7Gge.js → renderer-3d-2EVDZII7-C0_bWCJC.js} +2 -2
  25. package/dist-ui/assets/shikiHighlighter-FYa041eg.js +12 -0
  26. package/dist-ui/assets/{turtle-BsS91CYL.js → sparql-DVZY-KUA.js} +1 -1
  27. package/dist-ui/assets/{java-CylS5w8V.js → xml-B8PEiB0p.js} +1 -1
  28. package/dist-ui/index.html +1 -1
  29. package/package.json +4 -4
  30. package/dist-ui/assets/abap-BdImnpbu.js +0 -1
  31. package/dist-ui/assets/actionscript-3-CoDkCxhg.js +0 -1
  32. package/dist-ui/assets/ada-bCR0ucgS.js +0 -1
  33. package/dist-ui/assets/andromeeda-C4gqWexZ.js +0 -1
  34. package/dist-ui/assets/angular-html-DgL0RwuR.js +0 -1
  35. package/dist-ui/assets/angular-ts-DtEQuh-b.js +0 -1
  36. package/dist-ui/assets/apache-Pmp26Uib.js +0 -1
  37. package/dist-ui/assets/apex-D8_7TLub.js +0 -1
  38. package/dist-ui/assets/apl-CORt7UWP.js +0 -1
  39. package/dist-ui/assets/applescript-Co6uUVPk.js +0 -1
  40. package/dist-ui/assets/ara-BRHolxvo.js +0 -1
  41. package/dist-ui/assets/asciidoc-Ve4PFQV2.js +0 -1
  42. package/dist-ui/assets/asm-D_Q5rh1f.js +0 -1
  43. package/dist-ui/assets/astro-HNnZUWAn.js +0 -1
  44. package/dist-ui/assets/aurora-x-D-2ljcwZ.js +0 -1
  45. package/dist-ui/assets/awk-DMzUqQB5.js +0 -1
  46. package/dist-ui/assets/ayu-dark-DYE7WIF3.js +0 -1
  47. package/dist-ui/assets/ayu-light-BA47KaF1.js +0 -1
  48. package/dist-ui/assets/ayu-mirage-32ctXXKs.js +0 -1
  49. package/dist-ui/assets/ballerina-BFfxhgS-.js +0 -1
  50. package/dist-ui/assets/bat-BkioyH1T.js +0 -1
  51. package/dist-ui/assets/beancount-k_qm7-4y.js +0 -1
  52. package/dist-ui/assets/berry-uYugtg8r.js +0 -1
  53. package/dist-ui/assets/bibtex-CHM0blh-.js +0 -1
  54. package/dist-ui/assets/bicep-Bmn6On1c.js +0 -1
  55. package/dist-ui/assets/bird2-BIv1doCn.js +0 -1
  56. package/dist-ui/assets/blade-BjGOyj-B.js +0 -1
  57. package/dist-ui/assets/bsl-BO_Y6i37.js +0 -1
  58. package/dist-ui/assets/c-BIGW1oBm.js +0 -1
  59. package/dist-ui/assets/c3-eo99z4R2.js +0 -1
  60. package/dist-ui/assets/cadence-Bv_4Rxtq.js +0 -1
  61. package/dist-ui/assets/cairo-KRGpt6FW.js +0 -1
  62. package/dist-ui/assets/catppuccin-frappe-DFWUc33u.js +0 -1
  63. package/dist-ui/assets/catppuccin-latte-C9dUb6Cb.js +0 -1
  64. package/dist-ui/assets/catppuccin-macchiato-DQyhUUbL.js +0 -1
  65. package/dist-ui/assets/catppuccin-mocha-D87Tk5Gz.js +0 -1
  66. package/dist-ui/assets/clarity-D53aC0YG.js +0 -1
  67. package/dist-ui/assets/clojure-P80f7IUj.js +0 -1
  68. package/dist-ui/assets/cmake-D1j8_8rp.js +0 -1
  69. package/dist-ui/assets/cobol-nBiQ_Alo.js +0 -1
  70. package/dist-ui/assets/codeowners-Bp6g37R7.js +0 -1
  71. package/dist-ui/assets/codeql-DsOJ9woJ.js +0 -1
  72. package/dist-ui/assets/coffee-Ch7k5sss.js +0 -1
  73. package/dist-ui/assets/common-lisp-Cg-RD9OK.js +0 -1
  74. package/dist-ui/assets/coq-DkFqJrB1.js +0 -1
  75. package/dist-ui/assets/cpp-CofmeUqb.js +0 -1
  76. package/dist-ui/assets/crystal-DNxU26gB.js +0 -1
  77. package/dist-ui/assets/csharp-COcwbKMJ.js +0 -1
  78. package/dist-ui/assets/csv-fuZLfV_i.js +0 -1
  79. package/dist-ui/assets/cue-D82EKSYY.js +0 -1
  80. package/dist-ui/assets/cypher-COkxafJQ.js +0 -1
  81. package/dist-ui/assets/d-85-TOEBH.js +0 -1
  82. package/dist-ui/assets/dark-plus-C3mMm8J8.js +0 -1
  83. package/dist-ui/assets/dart-bE4Kk8sk.js +0 -1
  84. package/dist-ui/assets/dax-CEL-wOlO.js +0 -1
  85. package/dist-ui/assets/desktop-BmXAJ9_W.js +0 -1
  86. package/dist-ui/assets/dotenv-Da5cRb03.js +0 -1
  87. package/dist-ui/assets/dracula-BzJJZx-M.js +0 -1
  88. package/dist-ui/assets/dracula-soft-BXkSAIEj.js +0 -1
  89. package/dist-ui/assets/dream-maker-BtqSS_iP.js +0 -1
  90. package/dist-ui/assets/edge-FbVlp4U3.js +0 -1
  91. package/dist-ui/assets/elixir-CkH2-t6x.js +0 -1
  92. package/dist-ui/assets/elm-DbKCFpqz.js +0 -1
  93. package/dist-ui/assets/emacs-lisp-CXvaQtF9.js +0 -1
  94. package/dist-ui/assets/erb-BYCe7drp.js +0 -1
  95. package/dist-ui/assets/erlang-DsQrWhSR.js +0 -1
  96. package/dist-ui/assets/everforest-dark-BgDCqdQA.js +0 -1
  97. package/dist-ui/assets/everforest-light-C8M2exoo.js +0 -1
  98. package/dist-ui/assets/fennel-BYunw83y.js +0 -1
  99. package/dist-ui/assets/fish-BvzEVeQv.js +0 -1
  100. package/dist-ui/assets/fluent-C4IJs8-o.js +0 -1
  101. package/dist-ui/assets/fortran-fixed-form-CkoXwp7k.js +0 -1
  102. package/dist-ui/assets/fortran-free-form-BxgE0vQu.js +0 -1
  103. package/dist-ui/assets/fsharp-CXgrBDvD.js +0 -1
  104. package/dist-ui/assets/gdresource-BOOCDP_w.js +0 -1
  105. package/dist-ui/assets/gdscript-C5YyOfLZ.js +0 -1
  106. package/dist-ui/assets/gdshader-DkwncUOv.js +0 -1
  107. package/dist-ui/assets/genie-D0YGMca9.js +0 -1
  108. package/dist-ui/assets/gherkin-DyxjwDmM.js +0 -1
  109. package/dist-ui/assets/git-commit-F4YmCXRG.js +0 -1
  110. package/dist-ui/assets/git-rebase-r7XF79zn.js +0 -1
  111. package/dist-ui/assets/github-dark-DHJKELXO.js +0 -1
  112. package/dist-ui/assets/github-dark-default-Cuk6v7N8.js +0 -1
  113. package/dist-ui/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
  114. package/dist-ui/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
  115. package/dist-ui/assets/github-light-DAi9KRSo.js +0 -1
  116. package/dist-ui/assets/github-light-default-D7oLnXFd.js +0 -1
  117. package/dist-ui/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
  118. package/dist-ui/assets/gleam-BspZqrRM.js +0 -1
  119. package/dist-ui/assets/glimmer-js-ByusRIyA.js +0 -1
  120. package/dist-ui/assets/glimmer-ts-BfAWNZQY.js +0 -1
  121. package/dist-ui/assets/glsl-DplSGwfg.js +0 -1
  122. package/dist-ui/assets/gn-n2N0HUVH.js +0 -1
  123. package/dist-ui/assets/gnuplot-DdkO51Og.js +0 -1
  124. package/dist-ui/assets/graphql-ChdNCCLP.js +0 -1
  125. package/dist-ui/assets/groovy-gcz8RCvz.js +0 -1
  126. package/dist-ui/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
  127. package/dist-ui/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
  128. package/dist-ui/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
  129. package/dist-ui/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
  130. package/dist-ui/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
  131. package/dist-ui/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
  132. package/dist-ui/assets/hack-i7_Ulhet.js +0 -1
  133. package/dist-ui/assets/haml-D5jkg6IW.js +0 -1
  134. package/dist-ui/assets/handlebars-BpdQsYii.js +0 -1
  135. package/dist-ui/assets/haskell-Df6bDoY_.js +0 -1
  136. package/dist-ui/assets/haxe-CzTSHFRz.js +0 -1
  137. package/dist-ui/assets/hcl-BWvSN4gD.js +0 -1
  138. package/dist-ui/assets/hjson-D5-asLiD.js +0 -1
  139. package/dist-ui/assets/hlsl-D3lLCCz7.js +0 -1
  140. package/dist-ui/assets/horizon-BUw7H-hv.js +0 -1
  141. package/dist-ui/assets/horizon-bright-CUuTKBJd.js +0 -1
  142. package/dist-ui/assets/houston-DnULxvSX.js +0 -1
  143. package/dist-ui/assets/html-derivative-DlHx6ybY.js +0 -1
  144. package/dist-ui/assets/http-jrhK8wxY.js +0 -1
  145. package/dist-ui/assets/hurl-irOxFIW8.js +0 -1
  146. package/dist-ui/assets/hxml-Bvhsp5Yf.js +0 -1
  147. package/dist-ui/assets/hy-DFXneXwc.js +0 -1
  148. package/dist-ui/assets/imba-DGztddWO.js +0 -1
  149. package/dist-ui/assets/index-CVwCoJrM.js +0 -153
  150. package/dist-ui/assets/ini-BEwlwnbL.js +0 -1
  151. package/dist-ui/assets/jinja-f2NsQr07.js +0 -1
  152. package/dist-ui/assets/jison-wvAkD_A8.js +0 -1
  153. package/dist-ui/assets/json5-C9tS-k6U.js +0 -1
  154. package/dist-ui/assets/jsonc-Des-eS-w.js +0 -1
  155. package/dist-ui/assets/jsonl-DcaNXYhu.js +0 -1
  156. package/dist-ui/assets/jsonnet-DFQXde-d.js +0 -1
  157. package/dist-ui/assets/jssm-C2t-YnRu.js +0 -1
  158. package/dist-ui/assets/julia-CxzCAyBv.js +0 -1
  159. package/dist-ui/assets/just-VxiPbLrw.js +0 -1
  160. package/dist-ui/assets/kanagawa-dragon-CkXjmgJE.js +0 -1
  161. package/dist-ui/assets/kanagawa-lotus-CfQXZHmo.js +0 -1
  162. package/dist-ui/assets/kanagawa-wave-DWedfzmr.js +0 -1
  163. package/dist-ui/assets/kdl-DV7GczEv.js +0 -1
  164. package/dist-ui/assets/kotlin-BdnUsdx6.js +0 -1
  165. package/dist-ui/assets/kusto-wEQ09or8.js +0 -1
  166. package/dist-ui/assets/laserwave-DUszq2jm.js +0 -1
  167. package/dist-ui/assets/latex-CWtU0Tv5.js +0 -1
  168. package/dist-ui/assets/lean-BZvkOJ9d.js +0 -1
  169. package/dist-ui/assets/less-B1dDrJ26.js +0 -1
  170. package/dist-ui/assets/light-plus-B7mTdjB0.js +0 -1
  171. package/dist-ui/assets/liquid-C0sCDyMI.js +0 -1
  172. package/dist-ui/assets/llvm-DjAJT7YJ.js +0 -1
  173. package/dist-ui/assets/log-2UxHyX5q.js +0 -1
  174. package/dist-ui/assets/logo-BtOb2qkB.js +0 -1
  175. package/dist-ui/assets/lua-BaeVxFsk.js +0 -1
  176. package/dist-ui/assets/luau-C-HG3fhB.js +0 -1
  177. package/dist-ui/assets/make-CHLpvVh8.js +0 -1
  178. package/dist-ui/assets/marko-DjSrsDqO.js +0 -1
  179. package/dist-ui/assets/material-theme-D5KoaKCx.js +0 -1
  180. package/dist-ui/assets/material-theme-darker-BfHTSMKl.js +0 -1
  181. package/dist-ui/assets/material-theme-lighter-B0m2ddpp.js +0 -1
  182. package/dist-ui/assets/material-theme-ocean-CyktbL80.js +0 -1
  183. package/dist-ui/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
  184. package/dist-ui/assets/matlab-D7o27uSR.js +0 -1
  185. package/dist-ui/assets/mdc-DTYItulj.js +0 -1
  186. package/dist-ui/assets/mdx-Cmh6b_Ma.js +0 -1
  187. package/dist-ui/assets/mermaid-mWjccvbQ.js +0 -1
  188. package/dist-ui/assets/min-dark-CafNBF8u.js +0 -1
  189. package/dist-ui/assets/min-light-CTRr51gU.js +0 -1
  190. package/dist-ui/assets/mipsasm-CKIfxQSi.js +0 -1
  191. package/dist-ui/assets/mojo-rZm6bMo-.js +0 -1
  192. package/dist-ui/assets/monokai-D4h5O-jR.js +0 -1
  193. package/dist-ui/assets/moonbit-_H4v1dQx.js +0 -1
  194. package/dist-ui/assets/move-IF9eRakj.js +0 -1
  195. package/dist-ui/assets/narrat-DRg8JJMk.js +0 -1
  196. package/dist-ui/assets/nextflow-C-mBbutL.js +0 -1
  197. package/dist-ui/assets/nextflow-groovy-vE_lwT2v.js +0 -1
  198. package/dist-ui/assets/nginx-BpAMiNFr.js +0 -1
  199. package/dist-ui/assets/night-owl-C39BiMTA.js +0 -1
  200. package/dist-ui/assets/night-owl-light-CMTm3GFP.js +0 -1
  201. package/dist-ui/assets/nim-BIad80T-.js +0 -1
  202. package/dist-ui/assets/nix-CwoSXNpI.js +0 -1
  203. package/dist-ui/assets/nord-Ddv68eIx.js +0 -1
  204. package/dist-ui/assets/nushell-Cz2AlsmD.js +0 -1
  205. package/dist-ui/assets/objective-c-DXmwc3jG.js +0 -1
  206. package/dist-ui/assets/objective-cpp-CLxacb5B.js +0 -1
  207. package/dist-ui/assets/ocaml-C0hk2d4L.js +0 -1
  208. package/dist-ui/assets/odin-BBf5iR-q.js +0 -1
  209. package/dist-ui/assets/one-dark-pro-DVMEJ2y_.js +0 -1
  210. package/dist-ui/assets/one-light-C3Wv6jpd.js +0 -1
  211. package/dist-ui/assets/openscad-C4EeE6gA.js +0 -1
  212. package/dist-ui/assets/pascal-D93ZcfNL.js +0 -1
  213. package/dist-ui/assets/perl-NvoQZIq0.js +0 -1
  214. package/dist-ui/assets/php-R6g_5hLQ.js +0 -1
  215. package/dist-ui/assets/pkl-u5AG7uiY.js +0 -1
  216. package/dist-ui/assets/plastic-3e1v2bzS.js +0 -1
  217. package/dist-ui/assets/plsql-ChMvpjG-.js +0 -1
  218. package/dist-ui/assets/po-BTJTHyun.js +0 -1
  219. package/dist-ui/assets/poimandres-CS3Unz2-.js +0 -1
  220. package/dist-ui/assets/polar-C0HS_06l.js +0 -1
  221. package/dist-ui/assets/postcss-CXtECtnM.js +0 -1
  222. package/dist-ui/assets/powerquery-CEu0bR-o.js +0 -1
  223. package/dist-ui/assets/powershell-Dpen1YoG.js +0 -1
  224. package/dist-ui/assets/prisma-Dd19v3D-.js +0 -1
  225. package/dist-ui/assets/prolog-CbFg5uaA.js +0 -1
  226. package/dist-ui/assets/proto-C7zT0LnQ.js +0 -1
  227. package/dist-ui/assets/pug-DKIMFp6K.js +0 -1
  228. package/dist-ui/assets/puppet-BMWR74SV.js +0 -1
  229. package/dist-ui/assets/purescript-CklMAg4u.js +0 -1
  230. package/dist-ui/assets/qml-3beO22l8.js +0 -1
  231. package/dist-ui/assets/qmldir-C8lEn-DE.js +0 -1
  232. package/dist-ui/assets/qss-IeuSbFQv.js +0 -1
  233. package/dist-ui/assets/r-Dspwwk_N.js +0 -1
  234. package/dist-ui/assets/racket-BqYA7rlc.js +0 -1
  235. package/dist-ui/assets/raku-DXvB9xmW.js +0 -1
  236. package/dist-ui/assets/razor-BDqjjVU7.js +0 -1
  237. package/dist-ui/assets/red-bN70gL4F.js +0 -1
  238. package/dist-ui/assets/reg-C-SQnVFl.js +0 -1
  239. package/dist-ui/assets/regexp-CDVJQ6XC.js +0 -1
  240. package/dist-ui/assets/rel-C3B-1QV4.js +0 -1
  241. package/dist-ui/assets/riscv-BM1_JUlF.js +0 -1
  242. package/dist-ui/assets/ron-D8l8udqQ.js +0 -1
  243. package/dist-ui/assets/rose-pine-dawn-DHQR4-dF.js +0 -1
  244. package/dist-ui/assets/rose-pine-moon-D4_iv3hh.js +0 -1
  245. package/dist-ui/assets/rose-pine-qdsjHGoJ.js +0 -1
  246. package/dist-ui/assets/rosmsg-BJDFO7_C.js +0 -1
  247. package/dist-ui/assets/rst-CRjBmOyv.js +0 -1
  248. package/dist-ui/assets/ruby-Wjq7vjNf.js +0 -1
  249. package/dist-ui/assets/sas-cz2c8ADy.js +0 -1
  250. package/dist-ui/assets/sass-Cj5Yp3dK.js +0 -1
  251. package/dist-ui/assets/scala-C151Ov-r.js +0 -1
  252. package/dist-ui/assets/scheme-C98Dy4si.js +0 -1
  253. package/dist-ui/assets/scss-D5BDwBP9.js +0 -1
  254. package/dist-ui/assets/sdbl-DVxCFoDh.js +0 -1
  255. package/dist-ui/assets/shaderlab-Dg9Lc6iA.js +0 -1
  256. package/dist-ui/assets/shellsession-BADoaaVG.js +0 -1
  257. package/dist-ui/assets/slack-dark-BthQWCQV.js +0 -1
  258. package/dist-ui/assets/slack-ochin-DqwNpetd.js +0 -1
  259. package/dist-ui/assets/smalltalk-BERRCDM3.js +0 -1
  260. package/dist-ui/assets/snazzy-light-Bw305WKR.js +0 -1
  261. package/dist-ui/assets/solarized-dark-DXbdFlpD.js +0 -1
  262. package/dist-ui/assets/solarized-light-L9t79GZl.js +0 -1
  263. package/dist-ui/assets/soy-8wufbnw4.js +0 -1
  264. package/dist-ui/assets/sparql-rVzFXLq3.js +0 -1
  265. package/dist-ui/assets/splunk-BtCnVYZw.js +0 -1
  266. package/dist-ui/assets/ssh-config-_ykCGR6B.js +0 -1
  267. package/dist-ui/assets/stata-BH5u7GGu.js +0 -1
  268. package/dist-ui/assets/stylus-BEDo0Tqx.js +0 -1
  269. package/dist-ui/assets/surrealql-Bq5Q-fJD.js +0 -1
  270. package/dist-ui/assets/svelte-Cy7k_4gC.js +0 -1
  271. package/dist-ui/assets/swift-D82vCrfD.js +0 -1
  272. package/dist-ui/assets/synthwave-84-CbfX1IO0.js +0 -1
  273. package/dist-ui/assets/system-verilog-CnnmHF94.js +0 -1
  274. package/dist-ui/assets/systemd-4A_iFExJ.js +0 -1
  275. package/dist-ui/assets/talonscript-CkByrt1z.js +0 -1
  276. package/dist-ui/assets/tasl-QIJgUcNo.js +0 -1
  277. package/dist-ui/assets/tcl-dwOrl1Do.js +0 -1
  278. package/dist-ui/assets/templ-DhtptRzy.js +0 -1
  279. package/dist-ui/assets/terraform-BETggiCN.js +0 -1
  280. package/dist-ui/assets/tex-idrVyKtj.js +0 -1
  281. package/dist-ui/assets/tokyo-night-hegEt444.js +0 -1
  282. package/dist-ui/assets/ts-tags-DQrlYJgV.js +0 -1
  283. package/dist-ui/assets/tsv-B_m7g4N7.js +0 -1
  284. package/dist-ui/assets/twig-xg9kU7Mw.js +0 -1
  285. package/dist-ui/assets/typespec-CAFt9gP4.js +0 -1
  286. package/dist-ui/assets/typst-DHCkPAjA.js +0 -1
  287. package/dist-ui/assets/v-BcVCzyr7.js +0 -1
  288. package/dist-ui/assets/vala-CsfeWuGM.js +0 -1
  289. package/dist-ui/assets/vb-D17OF-Vu.js +0 -1
  290. package/dist-ui/assets/verilog-BQ8w6xss.js +0 -1
  291. package/dist-ui/assets/vesper-DU1UobuO.js +0 -1
  292. package/dist-ui/assets/vhdl-CeAyd5Ju.js +0 -1
  293. package/dist-ui/assets/viml-CJc9bBzg.js +0 -1
  294. package/dist-ui/assets/vitesse-black-Bkuqu6BP.js +0 -1
  295. package/dist-ui/assets/vitesse-dark-D0r3Knsf.js +0 -1
  296. package/dist-ui/assets/vitesse-light-CVO1_9PV.js +0 -1
  297. package/dist-ui/assets/vue-D2xRrEX4.js +0 -1
  298. package/dist-ui/assets/vue-html-AaS7Mt5G.js +0 -1
  299. package/dist-ui/assets/vue-vine-BoDAl6tE.js +0 -1
  300. package/dist-ui/assets/vyper-CDx5xZoG.js +0 -1
  301. package/dist-ui/assets/wasm-MzD3tlZU.js +0 -1
  302. package/dist-ui/assets/wenyan-BV7otONQ.js +0 -1
  303. package/dist-ui/assets/wgsl-Dx-B1_4e.js +0 -1
  304. package/dist-ui/assets/wikitext-BhOHFoWU.js +0 -1
  305. package/dist-ui/assets/wit-5i3qLPDT.js +0 -1
  306. package/dist-ui/assets/wolfram-lXgVvXCa.js +0 -1
  307. package/dist-ui/assets/xml-sdJ4AIDG.js +0 -1
  308. package/dist-ui/assets/xsl-CtQFsRM5.js +0 -1
  309. package/dist-ui/assets/zenscript-DVFEvuxE.js +0 -1
  310. package/dist-ui/assets/zig-VOosw3JB.js +0 -1
  311. /package/dist-ui/assets/{shellscript-Yzrsuije.js → bash-Yzrsuije.js} +0 -0
  312. /package/dist-ui/assets/{docker-BcOcwvcX.js → dockerfile-BcOcwvcX.js} +0 -0
package/dist/db.js CHANGED
@@ -1,20 +1,24 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import { join } from 'node:path';
3
- import { RESPONSE_CACHE_BYTES, } from '@origintrail-official/dkg-core';
3
+ import { RESPONSE_CACHE_BYTES, parseContextGraphJoinPolicyRecord, } from '@origintrail-official/dkg-core';
4
4
  export { SqliteChainEventCursorStore, SqliteContextGraphRegistryScanCursorStore, } from './chain-cursor-stores.js';
5
- const SCHEMA_VERSION = 22;
5
+ const SCHEMA_VERSION = 29;
6
6
  // Default operator retention. Lowered from 90 → 14 days on V15 (2026-05) after
7
7
  // a production incident in which the `logs` table + its FTS5 shadow tables
8
8
  // grew to ~9 GB on a 12-day-old node and corrupted the SQLite page (header
9
9
  // hash mismatch on boot). 90 days had been chosen for "metrics history",
10
10
  // but logs were the dominant grower (~1M rows/12d) and pruning was a no-op
11
- // on any DB younger than 90 days. 14 days is still long enough for any
12
- // realistic operator-driven post-mortem while bounding worst-case growth
13
- // of the (now FTS5-less) logs table to ~150 MB. Operators who want longer
11
+ // on any DB younger than 90 days. 14 days is still long enough for a useful
12
+ // operator-driven post-mortem, but the mainnet sync storm proved that time
13
+ // retention alone cannot bound worst-case growth. Operators who want longer
14
14
  // retention can override via `setRetentionDays()`; the setting is persisted
15
- // in the `settings` table and re-read on next boot.
15
+ // in the `settings` table and re-read on next boot. Time alone is not a hard
16
+ // size bound during a log storm, so routine info/debug rows also have a count
17
+ // ceiling. Warning/error rows keep the full operator-selected time window.
16
18
  const DEFAULT_RETENTION_DAYS = 14;
17
19
  const LEGACY_IMPLICIT_RETENTION_DAYS = 90;
20
+ const DEFAULT_ROUTINE_LOG_ROW_CAP = 1_000_000;
21
+ const DEFAULT_LOG_VOLUME_PRUNE_BATCH_ROWS = 25_000;
18
22
  const LOGS_VACUUM_DELETE_THRESHOLD = 10_000;
19
23
  // SQLite reports reusable-but-not-yet-reclaimed pages via freelist_count.
20
24
  // With the default 4 KiB page size this is roughly 4 MiB, large enough
@@ -100,10 +104,14 @@ export class DashboardDB {
100
104
  dataDir;
101
105
  retentionDays;
102
106
  explicitRetentionDays;
107
+ routineLogRowCap;
108
+ logVolumePruneBatchRows;
103
109
  constructor(opts) {
104
110
  this.dataDir = opts.dataDir;
105
111
  this.explicitRetentionDays = opts.retentionDays !== undefined;
106
112
  this.retentionDays = opts.retentionDays ?? DEFAULT_RETENTION_DAYS;
113
+ this.routineLogRowCap = Math.max(0, Math.floor(opts.routineLogRowCap ?? DEFAULT_ROUTINE_LOG_ROW_CAP));
114
+ this.logVolumePruneBatchRows = Math.max(1, Math.floor(opts.logVolumePruneBatchRows ?? DEFAULT_LOG_VOLUME_PRUNE_BATCH_ROWS));
107
115
  this._memoTtlMs = resolveCacheTtlMs(opts.cacheTtlMs);
108
116
  const dbPath = join(opts.dataDir, 'node-ui.db');
109
117
  this.db = new Database(dbPath);
@@ -130,8 +138,49 @@ export class DashboardDB {
130
138
  migrate() {
131
139
  const version = this.db.pragma('user_version', { simple: true });
132
140
  const upgradedExistingDb = version > 0 && version < SCHEMA_VERSION;
133
- if (version >= SCHEMA_VERSION)
141
+ const ensureJoinPolicyAuditCapTrigger = () => this.db.exec(`
142
+ CREATE TRIGGER IF NOT EXISTS cap_cg_join_policy_audit_rows
143
+ AFTER INSERT ON context_graph_join_policy_audit
144
+ BEGIN
145
+ DELETE FROM context_graph_join_policy_audit
146
+ WHERE id <= NEW.id - 100000
147
+ AND event_type NOT IN (
148
+ 'join_admission_committed',
149
+ 'join_policy_changed'
150
+ );
151
+ END;
152
+ `);
153
+ const ensureJoinApprovalRepairMarker = () => {
154
+ const table = this.db.prepare(`
155
+ SELECT 1 AS found FROM sqlite_master
156
+ WHERE type = 'table' AND name = 'context_graph_join_approval_ledger'
157
+ `).get();
158
+ if (!table)
159
+ return;
160
+ const columns = this.db.pragma('table_info(context_graph_join_approval_ledger)');
161
+ if (!columns.some((column) => column.name === 'repair_pending')) {
162
+ this.db.exec(`
163
+ ALTER TABLE context_graph_join_approval_ledger
164
+ ADD COLUMN repair_pending INTEGER NOT NULL DEFAULT 0
165
+ CHECK (repair_pending IN (0, 1));
166
+ `);
167
+ }
168
+ this.db.exec(`
169
+ CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_repair
170
+ ON context_graph_join_approval_ledger(
171
+ context_graph_id, request_digest, repair_pending, reserved_at DESC
172
+ );
173
+ `);
174
+ };
175
+ if (version > SCHEMA_VERSION)
134
176
  return;
177
+ if (version === SCHEMA_VERSION) {
178
+ // Repair restored/development databases that carry the current version
179
+ // but lost an idempotent schema adjunct.
180
+ ensureJoinApprovalRepairMarker();
181
+ ensureJoinPolicyAuditCapTrigger();
182
+ return;
183
+ }
135
184
  if (version < 1) {
136
185
  this.db.exec(`
137
186
  CREATE TABLE IF NOT EXISTS metric_snapshots (
@@ -483,12 +532,11 @@ export class DashboardDB {
483
532
  // (i.e. the `ReliableEnvelope` proto output, not the raw
484
533
  // application payload), so retries replay byte-identical
485
534
  // frames without re-encoding.
486
- // - `idx_outbox_next_attempt` indexes the periodic-tick query
487
- // (`SELECT ... WHERE next_attempt_at <= ?`); the
488
- // opportunistic-flush query
489
- // (`SELECT ... WHERE peer_id = ?`) uses an implicit scan
490
- // on the PK prefix, which is fast enough at expected
491
- // queue sizes (~tens of entries per peer).
535
+ // - `idx_outbox_next_attempt` indexes the sole automatic retry query
536
+ // (`SELECT ... WHERE next_attempt_at <= ?`). The peer-scoped query is
537
+ // retained only for diagnostics/DHT bookkeeping and MUST NOT become a
538
+ // connection-triggered drain; it uses the PK prefix at expected queue
539
+ // sizes (~tens of entries per peer).
492
540
  //
493
541
  // No data migration: pure additive. The chat-specific
494
542
  // `idx_chat_msgid` from V11 stays in place — PR-3 will drop
@@ -744,6 +792,214 @@ export class DashboardDB {
744
792
  this.db.exec(`
745
793
  CREATE INDEX IF NOT EXISTS idx_runtime_cursors_namespace_scope
746
794
  ON runtime_cursors(namespace, scope);
795
+ `);
796
+ }
797
+ if (version < 23) {
798
+ // OT-RFC-59 SC5: durable per-(peer, contextGraph) changelog cursor. NEVER
799
+ // pruned (see SqliteChangelogCursorStore) — a TTL would defeat the O(delta)
800
+ // cross-restart catch-up this table exists for.
801
+ this.db.exec(`
802
+ CREATE TABLE IF NOT EXISTS changelog_cursors (
803
+ peer_id TEXT NOT NULL,
804
+ context_graph_id TEXT NOT NULL,
805
+ era TEXT NOT NULL,
806
+ seq INTEGER NOT NULL CHECK (seq >= 0),
807
+ updated_at INTEGER NOT NULL,
808
+ PRIMARY KEY (peer_id, context_graph_id)
809
+ );
810
+ `);
811
+ }
812
+ if (version < 24) {
813
+ // OT-RFC-59 §6 P0 durable era guard: the write-side ChangelogStore's
814
+ // restore-detection needs the (era, highSeq) high-water in a store that a
815
+ // `store.nq` restore does NOT roll back with the RDF store. node-ui.db
816
+ // survives such a restore, so a rollback under the same era rotates the era
817
+ // (forcing peers to full-resync instead of silently skipping). Single row.
818
+ this.db.exec(`
819
+ CREATE TABLE IF NOT EXISTS changelog_era (
820
+ id INTEGER PRIMARY KEY CHECK (id = 1),
821
+ era TEXT NOT NULL,
822
+ high_seq INTEGER NOT NULL CHECK (high_seq >= 0),
823
+ updated_at INTEGER NOT NULL
824
+ );
825
+ `);
826
+ }
827
+ if (version < 25) {
828
+ // Durable security audit for private-CG open enrollment. V25/V26 also
829
+ // used this table as admission state; V27 projects those legacy rows into
830
+ // the typed operational ledger below.
831
+ this.db.exec(`
832
+ CREATE TABLE IF NOT EXISTS context_graph_join_policy_audit (
833
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
834
+ ts INTEGER NOT NULL,
835
+ context_graph_id TEXT NOT NULL,
836
+ event_type TEXT NOT NULL,
837
+ actor TEXT,
838
+ agent_address TEXT,
839
+ outcome TEXT NOT NULL,
840
+ reason TEXT,
841
+ request_digest TEXT,
842
+ policy_version INTEGER,
843
+ details TEXT
844
+ );
845
+ CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_ts
846
+ ON context_graph_join_policy_audit(ts);
847
+ CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_cg_ts
848
+ ON context_graph_join_policy_audit(context_graph_id, ts);
849
+ CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_event_ts
850
+ ON context_graph_join_policy_audit(event_type, ts);
851
+ `);
852
+ }
853
+ if (version < 26) {
854
+ // Bound flood-generated decision noise without letting that same flood
855
+ // erase the two records needed to reconstruct a private-CG admission:
856
+ // who changed the policy, and who was automatically admitted under it.
857
+ // V26 additionally retained live reservations because they still enforced
858
+ // the rolling one-hour ceilings. V27 removes that dependency below.
859
+ //
860
+ // V25 already installed this trigger, so DROP before CREATE is required:
861
+ // CREATE TRIGGER IF NOT EXISTS would silently preserve the old predicate
862
+ // on an upgraded database.
863
+ this.db.exec('DROP TRIGGER IF EXISTS cap_cg_join_policy_audit_rows;');
864
+ }
865
+ if (version < 27) {
866
+ // Operational admission state is deliberately separate from the audit
867
+ // stream. Audit retention and event naming must never alter rate-limit,
868
+ // epoch, or commit-idempotency behaviour.
869
+ this.db.exec(`
870
+ CREATE TABLE IF NOT EXISTS context_graph_join_approval_ledger (
871
+ context_graph_id TEXT NOT NULL,
872
+ request_digest TEXT NOT NULL,
873
+ policy_epoch INTEGER NOT NULL,
874
+ reserved_at INTEGER NOT NULL,
875
+ state TEXT NOT NULL CHECK (state IN ('reserved', 'committed')),
876
+ committed_at INTEGER,
877
+ actor TEXT NOT NULL,
878
+ agent_address TEXT NOT NULL,
879
+ policy_version INTEGER NOT NULL,
880
+ PRIMARY KEY (context_graph_id, request_digest, policy_epoch),
881
+ CHECK (length(context_graph_id) > 0),
882
+ CHECK (length(request_digest) > 0),
883
+ CHECK (typeof(policy_epoch) = 'integer'),
884
+ CHECK (typeof(reserved_at) = 'integer'),
885
+ CHECK (committed_at IS NULL OR typeof(committed_at) = 'integer'),
886
+ CHECK (typeof(policy_version) = 'integer'),
887
+ CHECK (
888
+ (state = 'reserved' AND committed_at IS NULL)
889
+ OR (state = 'committed' AND committed_at IS NOT NULL)
890
+ )
891
+ );
892
+ CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_reserved_at
893
+ ON context_graph_join_approval_ledger(reserved_at);
894
+ CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_cg_reserved_at
895
+ ON context_graph_join_approval_ledger(context_graph_id, reserved_at);
896
+ CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_digest_reserved_at
897
+ ON context_graph_join_approval_ledger(context_graph_id, request_digest, reserved_at DESC);
898
+ CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_committed_at
899
+ ON context_graph_join_approval_ledger(committed_at);
900
+ `);
901
+ // Best-effort one-time projection for V25/V26 databases. Invalid legacy
902
+ // JSON cannot abort startup; only rows with a typed numeric epoch can be
903
+ // authoritative operational state. For duplicate audit reservations,
904
+ // retain the newest row for each (CG, digest, epoch).
905
+ this.db.exec(`
906
+ WITH raw_reservations AS (
907
+ SELECT
908
+ id,
909
+ context_graph_id,
910
+ request_digest,
911
+ CASE
912
+ WHEN json_valid(details)
913
+ AND json_type(details, '$.policyEpoch') IN ('integer', 'real')
914
+ THEN CAST(json_extract(details, '$.policyEpoch') AS INTEGER)
915
+ ELSE NULL
916
+ END AS policy_epoch,
917
+ ts AS reserved_at,
918
+ COALESCE(actor, '') AS actor,
919
+ COALESCE(agent_address, '') AS agent_address,
920
+ COALESCE(policy_version, 1) AS policy_version
921
+ FROM context_graph_join_policy_audit
922
+ WHERE event_type = 'join_auto_reservation'
923
+ AND outcome = 'reserved'
924
+ AND request_digest IS NOT NULL
925
+ ),
926
+ ranked_reservations AS (
927
+ SELECT *, ROW_NUMBER() OVER (
928
+ PARTITION BY context_graph_id, request_digest, policy_epoch
929
+ ORDER BY reserved_at DESC, id DESC
930
+ ) AS rank
931
+ FROM raw_reservations
932
+ WHERE policy_epoch IS NOT NULL
933
+ ),
934
+ raw_commits AS (
935
+ SELECT
936
+ context_graph_id,
937
+ request_digest,
938
+ CASE
939
+ WHEN json_valid(details)
940
+ AND json_type(details, '$.policyEpoch') IN ('integer', 'real')
941
+ THEN CAST(json_extract(details, '$.policyEpoch') AS INTEGER)
942
+ ELSE NULL
943
+ END AS policy_epoch,
944
+ MIN(ts) AS committed_at
945
+ FROM context_graph_join_policy_audit
946
+ WHERE event_type = 'join_admission_committed'
947
+ AND outcome = 'approved'
948
+ AND request_digest IS NOT NULL
949
+ GROUP BY context_graph_id, request_digest, policy_epoch
950
+ )
951
+ INSERT OR IGNORE INTO context_graph_join_approval_ledger (
952
+ context_graph_id, request_digest, policy_epoch, reserved_at,
953
+ state, committed_at, actor, agent_address, policy_version
954
+ )
955
+ SELECT
956
+ reservation.context_graph_id,
957
+ reservation.request_digest,
958
+ reservation.policy_epoch,
959
+ reservation.reserved_at,
960
+ CASE WHEN commit_row.committed_at IS NULL THEN 'reserved' ELSE 'committed' END,
961
+ commit_row.committed_at,
962
+ reservation.actor,
963
+ reservation.agent_address,
964
+ reservation.policy_version
965
+ FROM ranked_reservations AS reservation
966
+ LEFT JOIN raw_commits AS commit_row
967
+ ON commit_row.context_graph_id = reservation.context_graph_id
968
+ AND commit_row.request_digest = reservation.request_digest
969
+ AND commit_row.policy_epoch = reservation.policy_epoch
970
+ WHERE reservation.rank = 1;
971
+ `);
972
+ // V26's trigger protected live reservation audit rows because they were
973
+ // operational state. The ledger makes that coupling obsolete, so replace
974
+ // the trigger while preserving the flood-resistant proof exemptions.
975
+ this.db.exec('DROP TRIGGER IF EXISTS cap_cg_join_policy_audit_rows;');
976
+ }
977
+ if (version < 28) {
978
+ // A reservation alone does not prove that admission crossed its
979
+ // membership boundary. Persist that distinction so an exact retry can
980
+ // finish moderation/audit after delegation expiry or daemon restart.
981
+ ensureJoinApprovalRepairMarker();
982
+ }
983
+ // Keep this repair outside the version gate. Restored/development DBs can
984
+ // carry the current user_version while missing a trigger; recreating it is
985
+ // idempotent and keeps the audit bound fail-closed on every open.
986
+ ensureJoinPolicyAuditCapTrigger();
987
+ if (version < 29) {
988
+ this.db.exec(`
989
+ CREATE TABLE IF NOT EXISTS vm_reconcile_negative_cache (
990
+ cache_key TEXT PRIMARY KEY,
991
+ context_graph_id TEXT NOT NULL,
992
+ failures INTEGER NOT NULL,
993
+ next_retry_at INTEGER NOT NULL,
994
+ swm_gen TEXT NOT NULL,
995
+ candidate_namespaces TEXT NOT NULL,
996
+ peer_topology_key TEXT NOT NULL,
997
+ updated_at INTEGER NOT NULL
998
+ );
999
+ CREATE INDEX IF NOT EXISTS idx_vm_reconcile_negative_cg
1000
+ ON vm_reconcile_negative_cache(context_graph_id);
1001
+ CREATE INDEX IF NOT EXISTS idx_vm_reconcile_negative_retry
1002
+ ON vm_reconcile_negative_cache(next_retry_at);
747
1003
  `);
748
1004
  }
749
1005
  this.db.pragma(`user_version = ${SCHEMA_VERSION}`);
@@ -778,7 +1034,21 @@ export class DashboardDB {
778
1034
  this.db.exec(`DELETE FROM chat_persistence_jobs WHERE updated_at < ${cutoff} AND status IN ('stored', 'failed')`);
779
1035
  this.db.exec(`DELETE FROM notifications WHERE ts < ${cutoff}`);
780
1036
  this.db.exec(`DELETE FROM replication_events WHERE ts < ${cutoff}`);
1037
+ this.db.exec(`DELETE FROM context_graph_join_policy_audit WHERE ts < ${cutoff}`);
1038
+ // The admission ledger has its own typed lifetime. It intentionally does
1039
+ // not depend on whether the corresponding audit rows still exist. Preserve
1040
+ // the historical retention envelope for durable commit idempotency while
1041
+ // allowing fully expired reservation/commit state to age out.
1042
+ this.db.prepare(`
1043
+ DELETE FROM context_graph_join_approval_ledger
1044
+ WHERE reserved_at < ?
1045
+ AND (committed_at IS NULL OR committed_at < ?)
1046
+ `).run(cutoff, cutoff);
781
1047
  this.db.prepare(`DELETE FROM sync_checkpoints WHERE expires_at < ?`).run(Date.now());
1048
+ // Reconcile negatives are accelerators, never historical records. Once the
1049
+ // retry window elapses they must not survive indefinitely or accumulate one
1050
+ // row per previously-seen KA across restarts.
1051
+ this.db.prepare(`DELETE FROM vm_reconcile_negative_cache WHERE next_retry_at < ?`).run(Date.now());
782
1052
  // Universal Messenger idempotency table. Shorter TTL than the
783
1053
  // operator retention: no realistic dedup window extends beyond
784
1054
  // a day. The protocol_outbox table is intentionally not pruned
@@ -786,20 +1056,12 @@ export class DashboardDB {
786
1056
  // SqliteProtocolOutboxStore.dropExpired().
787
1057
  const messengerCutoff = Date.now() - 24 * 60 * 60 * 1000;
788
1058
  this.db.exec(`DELETE FROM message_idempotency WHERE ts < ${messengerCutoff}`);
789
- // Reclaim free pages from the file. Without this, the SQLite file
790
- // size only ever grows DELETE / DROP just marks pages reusable,
791
- // it does not return them to the OS. Vacuum when prune removed a
792
- // meaningful number of log rows, or when a previous migration/drop
793
- // left a large freelist behind without deleting any retained logs.
794
- const freePages = Number(this.db.pragma('freelist_count', { simple: true }) ?? 0);
795
- if (logsDeleted > LOGS_VACUUM_DELETE_THRESHOLD || freePages > VACUUM_FREE_PAGE_THRESHOLD) {
796
- try {
797
- this.db.exec(`VACUUM`);
798
- }
799
- catch {
800
- // VACUUM requires an exclusive lock. If another connection is
801
- // holding the DB open we skip and retry on the next prune.
802
- }
1059
+ // Avoid rebuilding an oversized DB at startup while millions of routine
1060
+ // rows are still live. The daemon trims that backlog in bounded batches;
1061
+ // the final batch performs one compacting VACUUM. Databases already under
1062
+ // the cap retain the established time-prune reclamation behaviour.
1063
+ if (!this.hasRoutineLogOverflow()) {
1064
+ this.reclaimFreePagesIfNeeded(logsDeleted > LOGS_VACUUM_DELETE_THRESHOLD);
803
1065
  }
804
1066
  // Return the WAL file itself to the OS. journal_size_limit bounds it
805
1067
  // in steady state, but a TRUNCATE checkpoint here shrinks it promptly
@@ -807,6 +1069,96 @@ export class DashboardDB {
807
1069
  // which rewrites the whole DB through the WAL and momentarily grows
808
1070
  // it. Runs unconditionally — independent of the VACUUM gate — because
809
1071
  // an idle node still wants its -wal reclaimed.
1072
+ this.truncateWal('prune');
1073
+ }
1074
+ /**
1075
+ * Remove one bounded batch of the oldest routine (non-warning/error) logs.
1076
+ * A count cap complements time retention: a high-rate sync storm can create
1077
+ * millions of rows inside a single day, long before a 14-day cutoff applies.
1078
+ *
1079
+ * Deletion is deliberately incremental so an upgrade does not block node
1080
+ * startup on a multi-GB transaction. Once the backlog reaches the cap, one
1081
+ * VACUUM returns the accumulated free pages to the OS and the file shrinks.
1082
+ */
1083
+ pruneLogVolumeBatch() {
1084
+ const overflowCutoff = this.routineLogOverflowCutoff();
1085
+ if (overflowCutoff === null) {
1086
+ const reclaim = this.reclaimFreePagesIfNeeded(false);
1087
+ if (!reclaim.reclaimPending)
1088
+ this.truncateWal('log-volume prune');
1089
+ return {
1090
+ deleted: 0,
1091
+ status: reclaim.reclaimPending
1092
+ ? 'reclaim-pending'
1093
+ : reclaim.compacted
1094
+ ? 'done-compacted'
1095
+ : 'done',
1096
+ };
1097
+ }
1098
+ const deleted = this.db.prepare(`
1099
+ DELETE FROM logs
1100
+ WHERE id IN (
1101
+ SELECT id
1102
+ FROM logs
1103
+ WHERE id <= @cutoff
1104
+ AND level NOT IN ('warn', 'error')
1105
+ ORDER BY id ASC
1106
+ LIMIT @batchRows
1107
+ )
1108
+ `).run({
1109
+ cutoff: overflowCutoff,
1110
+ batchRows: this.logVolumePruneBatchRows,
1111
+ }).changes;
1112
+ // If the batch filled, conservatively schedule another tick. An exact-size
1113
+ // final batch costs one extra cheap probe before compaction, which is safer
1114
+ // than running a second million-row count after every deletion.
1115
+ const hasMore = deleted === this.logVolumePruneBatchRows;
1116
+ const reclaim = hasMore
1117
+ ? { compacted: false, reclaimPending: false }
1118
+ : this.reclaimFreePagesIfNeeded(deleted > LOGS_VACUUM_DELETE_THRESHOLD);
1119
+ if (!hasMore && !reclaim.reclaimPending)
1120
+ this.truncateWal('log-volume prune');
1121
+ return {
1122
+ deleted,
1123
+ status: hasMore
1124
+ ? 'more'
1125
+ : reclaim.reclaimPending
1126
+ ? 'reclaim-pending'
1127
+ : reclaim.compacted
1128
+ ? 'done-compacted'
1129
+ : 'done',
1130
+ };
1131
+ }
1132
+ routineLogOverflowCutoff() {
1133
+ const row = this.db.prepare(`
1134
+ SELECT id
1135
+ FROM logs
1136
+ WHERE level NOT IN ('warn', 'error')
1137
+ ORDER BY id DESC
1138
+ LIMIT 1 OFFSET ?
1139
+ `).get(this.routineLogRowCap);
1140
+ return row?.id ?? null;
1141
+ }
1142
+ hasRoutineLogOverflow() {
1143
+ return this.routineLogOverflowCutoff() !== null;
1144
+ }
1145
+ reclaimFreePagesIfNeeded(force) {
1146
+ // DELETE / DROP only marks pages reusable; VACUUM is what returns them to
1147
+ // the OS. A failed exclusive-lock/disk attempt remains pending for a later
1148
+ // background retry and never prevents the node from running.
1149
+ const freePages = Number(this.db.pragma('freelist_count', { simple: true }) ?? 0);
1150
+ if (!force && freePages <= VACUUM_FREE_PAGE_THRESHOLD) {
1151
+ return { compacted: false, reclaimPending: false };
1152
+ }
1153
+ try {
1154
+ this.db.exec(`VACUUM`);
1155
+ return { compacted: true, reclaimPending: false };
1156
+ }
1157
+ catch {
1158
+ return { compacted: false, reclaimPending: true };
1159
+ }
1160
+ }
1161
+ truncateWal(reason) {
810
1162
  try {
811
1163
  // wal_checkpoint signals reader contention through its result row
812
1164
  // (`busy = 1`), NOT by throwing — so a busy checkpoint leaves the
@@ -816,7 +1168,7 @@ export class DashboardDB {
816
1168
  // rather than indistinguishable from a real one.
817
1169
  const [checkpoint] = this.db.pragma('wal_checkpoint(TRUNCATE)');
818
1170
  if (checkpoint?.busy) {
819
- console.warn(`[DashboardDB] wal_checkpoint(TRUNCATE) busy — WAL not reclaimed this prune ` +
1171
+ console.warn(`[DashboardDB] wal_checkpoint(TRUNCATE) busy — WAL not reclaimed during ${reason} ` +
820
1172
  `(log=${checkpoint.log}, checkpointed=${checkpoint.checkpointed}); retried next prune`);
821
1173
  }
822
1174
  }
@@ -827,7 +1179,7 @@ export class DashboardDB {
827
1179
  // better-sqlite3 change turns lock or I/O errors into throws, the
828
1180
  // stalled WAL reclaim is visible in the daemon log instead of only
829
1181
  // showing up later as unexplained disk growth. Never block prune.
830
- console.warn(`[DashboardDB] wal_checkpoint(TRUNCATE) failed — WAL not reclaimed this prune: ` +
1182
+ console.warn(`[DashboardDB] wal_checkpoint(TRUNCATE) failed — WAL not reclaimed during ${reason}: ` +
831
1183
  `${err instanceof Error ? err.message : String(err)}`);
832
1184
  }
833
1185
  }
@@ -950,7 +1302,302 @@ export class DashboardDB {
950
1302
  return this.stmt('getContextGraphSubscription', 'SELECT * FROM context_graph_subscriptions WHERE context_graph_id = ?').get(contextGraphId);
951
1303
  }
952
1304
  deleteContextGraphSubscription(contextGraphId) {
953
- this.stmt('deleteContextGraphSubscription', 'DELETE FROM context_graph_subscriptions WHERE context_graph_id = ?').run(contextGraphId);
1305
+ const remove = this.db.transaction(() => {
1306
+ this.stmt('deleteContextGraphSubscription', 'DELETE FROM context_graph_subscriptions WHERE context_graph_id = ?').run(contextGraphId);
1307
+ this.stmt('deleteContextGraphReadinessProvenance', 'DELETE FROM settings WHERE key = ?')
1308
+ .run(this.contextGraphReadinessProvenanceKey(contextGraphId));
1309
+ });
1310
+ remove();
1311
+ }
1312
+ /**
1313
+ * CLI-owned, durable provenance for context-graph readiness flags.
1314
+ *
1315
+ * Subscription rows predate per-plane proof and can therefore contain the
1316
+ * v10.0.6 false-ready shape (`synced=1`, `shared_memory_synced=1`) after an
1317
+ * unrelated peer returned an empty response. Keep the proof out of the
1318
+ * agent-owned subscription upsert so older/custom agent stores remain
1319
+ * source-compatible and routine subscription persistence cannot overwrite a
1320
+ * proof established by the daemon's catch-up classifier.
1321
+ */
1322
+ getContextGraphReadinessProvenance(contextGraphId) {
1323
+ const row = this.stmt('getContextGraphReadinessProvenance', 'SELECT value FROM settings WHERE key = ?').get(this.contextGraphReadinessProvenanceKey(contextGraphId));
1324
+ if (!row)
1325
+ return null;
1326
+ try {
1327
+ const parsed = JSON.parse(row.value);
1328
+ if (!Number.isInteger(parsed.version) || (parsed.version ?? 0) < 1)
1329
+ return null;
1330
+ return {
1331
+ version: parsed.version,
1332
+ durableVerified: parsed.durableVerified === true,
1333
+ sharedMemoryVerified: parsed.sharedMemoryVerified === true,
1334
+ updatedAt: typeof parsed.updatedAt === 'number' && Number.isFinite(parsed.updatedAt)
1335
+ ? parsed.updatedAt
1336
+ : 0,
1337
+ };
1338
+ }
1339
+ catch {
1340
+ return null;
1341
+ }
1342
+ }
1343
+ setContextGraphReadinessProvenance(contextGraphId, provenance) {
1344
+ const record = {
1345
+ version: provenance.version,
1346
+ durableVerified: provenance.durableVerified,
1347
+ sharedMemoryVerified: provenance.sharedMemoryVerified,
1348
+ updatedAt: provenance.updatedAt ?? Date.now(),
1349
+ };
1350
+ this.stmt('setContextGraphReadinessProvenance', 'INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)').run(this.contextGraphReadinessProvenanceKey(contextGraphId), JSON.stringify(record));
1351
+ }
1352
+ contextGraphReadinessProvenanceKey(contextGraphId) {
1353
+ return `contextGraphReadiness:${contextGraphId}`;
1354
+ }
1355
+ // --- Private context-graph join policy + audit ---
1356
+ getContextGraphJoinPolicy(contextGraphId) {
1357
+ const row = this.stmt('getContextGraphJoinPolicy', 'SELECT value FROM settings WHERE key = ?').get(this.contextGraphJoinPolicyKey(contextGraphId));
1358
+ if (!row)
1359
+ return null;
1360
+ try {
1361
+ return parseContextGraphJoinPolicyRecord(JSON.parse(row.value), contextGraphId);
1362
+ }
1363
+ catch {
1364
+ // Corrupt policy state is indistinguishable from no policy. The agent's
1365
+ // default is manual, so this is intentionally fail-closed.
1366
+ return null;
1367
+ }
1368
+ }
1369
+ setContextGraphJoinPolicy(record) {
1370
+ this.stmt('setContextGraphJoinPolicy', 'INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)').run(this.contextGraphJoinPolicyKey(record.contextGraphId), JSON.stringify(record));
1371
+ }
1372
+ setContextGraphJoinPolicyWithAudit(record, event) {
1373
+ const transition = this.db.transaction(() => {
1374
+ this.setContextGraphJoinPolicy(record);
1375
+ this.appendContextGraphJoinPolicyAudit(event);
1376
+ });
1377
+ transition();
1378
+ }
1379
+ appendContextGraphJoinPolicyAudit(event) {
1380
+ this.stmt('appendContextGraphJoinPolicyAudit', `
1381
+ INSERT INTO context_graph_join_policy_audit (
1382
+ ts, context_graph_id, event_type, actor, agent_address, outcome,
1383
+ reason, request_digest, policy_version, details
1384
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1385
+ `).run(event.timestamp, event.contextGraphId, event.eventType, event.actor ?? null, event.agentAddress ?? null, event.outcome, event.reason ?? null, event.requestDigest ?? null, event.policyVersion ?? null, event.details ? JSON.stringify(event.details) : null);
1386
+ }
1387
+ reserveContextGraphAutomaticApproval(input) {
1388
+ const reserve = this.db.transaction(() => {
1389
+ const since = input.timestamp - 60 * 60 * 1000;
1390
+ const cgRow = this.stmt('countContextGraphAutomaticApprovalReservations', `
1391
+ SELECT COUNT(*) AS count
1392
+ FROM context_graph_join_approval_ledger
1393
+ WHERE context_graph_id = ?
1394
+ AND reserved_at >= ?
1395
+ `).get(input.contextGraphId, since);
1396
+ const nodeRow = this.stmt('countNodeAutomaticApprovalReservations', `
1397
+ SELECT COUNT(*) AS count
1398
+ FROM context_graph_join_approval_ledger
1399
+ WHERE reserved_at >= ?
1400
+ `).get(since);
1401
+ const contextGraphCount = Number(cgRow?.count ?? 0);
1402
+ const nodeCount = Number(nodeRow?.count ?? 0);
1403
+ const existing = this.stmt('findContextGraphAutomaticApprovalReservation', `
1404
+ SELECT 1 AS found
1405
+ FROM context_graph_join_approval_ledger
1406
+ WHERE context_graph_id = ?
1407
+ AND request_digest = ?
1408
+ AND policy_epoch = ?
1409
+ AND reserved_at >= ?
1410
+ LIMIT 1
1411
+ `).get(input.contextGraphId, input.requestDigest, input.policyEpoch, since);
1412
+ if (existing) {
1413
+ return {
1414
+ allowed: true,
1415
+ contextGraphApprovalsLastHour: contextGraphCount,
1416
+ nodeApprovalsLastHour: nodeCount,
1417
+ };
1418
+ }
1419
+ if (contextGraphCount >= input.contextGraphLimit) {
1420
+ return {
1421
+ allowed: false,
1422
+ contextGraphApprovalsLastHour: contextGraphCount,
1423
+ nodeApprovalsLastHour: nodeCount,
1424
+ reason: 'context-graph-rate-limit',
1425
+ };
1426
+ }
1427
+ if (nodeCount >= input.nodeLimit) {
1428
+ return {
1429
+ allowed: false,
1430
+ contextGraphApprovalsLastHour: contextGraphCount,
1431
+ nodeApprovalsLastHour: nodeCount,
1432
+ reason: 'node-rate-limit',
1433
+ };
1434
+ }
1435
+ // The typed ledger row and its audit projection share one transaction:
1436
+ // failures cannot consume quota without an audit event or emit an audit
1437
+ // reservation without durable operational state. An expired replay of
1438
+ // the same digest+epoch refreshes its reservation timestamp and consumes
1439
+ // the rolling-window quota again, matching the former append-only model.
1440
+ this.stmt('upsertContextGraphAutomaticApprovalReservation', `
1441
+ INSERT INTO context_graph_join_approval_ledger (
1442
+ context_graph_id, request_digest, policy_epoch, reserved_at,
1443
+ state, committed_at, actor, agent_address, policy_version
1444
+ ) VALUES (?, ?, ?, ?, 'reserved', NULL, ?, ?, ?)
1445
+ ON CONFLICT(context_graph_id, request_digest, policy_epoch) DO UPDATE SET
1446
+ reserved_at = excluded.reserved_at,
1447
+ actor = excluded.actor,
1448
+ agent_address = excluded.agent_address,
1449
+ policy_version = excluded.policy_version
1450
+ `).run(input.contextGraphId, input.requestDigest, input.policyEpoch, input.timestamp, input.actor, input.agentAddress, input.policyVersion);
1451
+ this.appendContextGraphJoinPolicyAudit({
1452
+ timestamp: input.timestamp,
1453
+ contextGraphId: input.contextGraphId,
1454
+ eventType: 'join_auto_reservation',
1455
+ actor: input.actor,
1456
+ agentAddress: input.agentAddress,
1457
+ outcome: 'reserved',
1458
+ requestDigest: input.requestDigest,
1459
+ policyVersion: input.policyVersion,
1460
+ details: {
1461
+ contextGraphLimit: input.contextGraphLimit,
1462
+ nodeLimit: input.nodeLimit,
1463
+ policyEpoch: input.policyEpoch,
1464
+ },
1465
+ });
1466
+ return {
1467
+ allowed: true,
1468
+ contextGraphApprovalsLastHour: contextGraphCount + 1,
1469
+ nodeApprovalsLastHour: nodeCount + 1,
1470
+ };
1471
+ });
1472
+ return reserve();
1473
+ }
1474
+ markContextGraphAutomaticApprovalRepairPending(input) {
1475
+ const marked = this.stmt('markContextGraphAutomaticApprovalRepairPending', `
1476
+ UPDATE context_graph_join_approval_ledger
1477
+ SET repair_pending = 1
1478
+ WHERE context_graph_id = ?
1479
+ AND request_digest = ?
1480
+ AND policy_epoch = ?
1481
+ AND state = 'reserved'
1482
+ `).run(input.contextGraphId, input.requestDigest, input.policyEpoch);
1483
+ return marked.changes === 1;
1484
+ }
1485
+ getContextGraphAutomaticApprovalRepair(contextGraphId, requestDigest) {
1486
+ const repair = this.stmt('getContextGraphAutomaticApprovalRepair', `
1487
+ SELECT policy_epoch, actor, agent_address
1488
+ FROM context_graph_join_approval_ledger
1489
+ WHERE context_graph_id = ?
1490
+ AND request_digest = ?
1491
+ AND state = 'reserved'
1492
+ AND repair_pending = 1
1493
+ ORDER BY reserved_at DESC
1494
+ LIMIT 1
1495
+ `).get(contextGraphId, requestDigest);
1496
+ return repair
1497
+ ? {
1498
+ policyEpoch: repair.policy_epoch,
1499
+ actor: repair.actor,
1500
+ agentAddress: repair.agent_address,
1501
+ }
1502
+ : null;
1503
+ }
1504
+ commitContextGraphAutomaticApproval(input) {
1505
+ const commit = this.db.transaction(() => {
1506
+ const reservation = this.stmt('findExactContextGraphAutomaticApprovalReservation', `
1507
+ SELECT context_graph_id, request_digest, policy_epoch, reserved_at,
1508
+ state, committed_at, actor, agent_address, policy_version
1509
+ FROM context_graph_join_approval_ledger
1510
+ WHERE context_graph_id = ?
1511
+ AND request_digest = ?
1512
+ AND policy_epoch = ?
1513
+ LIMIT 1
1514
+ `).get(input.contextGraphId, input.requestDigest, input.policyEpoch);
1515
+ if (!reservation)
1516
+ return false;
1517
+ if (reservation.state === 'committed')
1518
+ return true;
1519
+ const updated = this.stmt('commitContextGraphAutomaticApprovalReservation', `
1520
+ UPDATE context_graph_join_approval_ledger
1521
+ SET state = 'committed', committed_at = ?, repair_pending = 0
1522
+ WHERE context_graph_id = ?
1523
+ AND request_digest = ?
1524
+ AND policy_epoch = ?
1525
+ AND state = 'reserved'
1526
+ `).run(input.timestamp, reservation.context_graph_id, reservation.request_digest, reservation.policy_epoch);
1527
+ if (updated.changes !== 1)
1528
+ return false;
1529
+ this.appendContextGraphJoinPolicyAudit({
1530
+ timestamp: input.timestamp,
1531
+ contextGraphId: input.contextGraphId,
1532
+ eventType: 'join_admission_committed',
1533
+ actor: reservation.actor || input.actor,
1534
+ agentAddress: reservation.agent_address || input.agentAddress,
1535
+ outcome: 'approved',
1536
+ requestDigest: input.requestDigest,
1537
+ policyVersion: reservation.policy_version,
1538
+ details: {
1539
+ ...input.details,
1540
+ policyEpoch: reservation.policy_epoch,
1541
+ },
1542
+ });
1543
+ return true;
1544
+ });
1545
+ return commit();
1546
+ }
1547
+ getContextGraphAutomaticApprovalUsage(contextGraphId, timestamp) {
1548
+ const since = timestamp - 60 * 60 * 1000;
1549
+ const contextGraphApprovalsLastHour = Number(this.stmt('getContextGraphAutomaticApprovalUsage', `
1550
+ SELECT COUNT(*) AS count
1551
+ FROM context_graph_join_approval_ledger
1552
+ WHERE context_graph_id = ?
1553
+ AND reserved_at >= ?
1554
+ `).get(contextGraphId, since)?.count ?? 0);
1555
+ const nodeApprovalsLastHour = Number(this.stmt('getNodeAutomaticApprovalUsage', `
1556
+ SELECT COUNT(*) AS count
1557
+ FROM context_graph_join_approval_ledger
1558
+ WHERE reserved_at >= ?
1559
+ `).get(since)?.count ?? 0);
1560
+ return { contextGraphApprovalsLastHour, nodeApprovalsLastHour };
1561
+ }
1562
+ listContextGraphJoinPolicyAudit(contextGraphId) {
1563
+ return this.stmt('listContextGraphJoinPolicyAudit', `
1564
+ SELECT id, ts, context_graph_id, event_type, actor, agent_address,
1565
+ outcome, reason, request_digest, policy_version, details
1566
+ FROM context_graph_join_policy_audit
1567
+ WHERE context_graph_id = ?
1568
+ ORDER BY id ASC
1569
+ `).all(contextGraphId);
1570
+ }
1571
+ contextGraphJoinPolicyKey(contextGraphId) {
1572
+ return `contextGraphJoinPolicy:${contextGraphId}`;
1573
+ }
1574
+ upsertVmReconcileNegative(record) {
1575
+ this.stmt('upsertVmReconcileNegative', `
1576
+ INSERT INTO vm_reconcile_negative_cache (
1577
+ cache_key, context_graph_id, failures, next_retry_at, swm_gen,
1578
+ candidate_namespaces, peer_topology_key, updated_at
1579
+ ) VALUES (
1580
+ @cache_key, @context_graph_id, @failures, @next_retry_at, @swm_gen,
1581
+ @candidate_namespaces, @peer_topology_key, @updated_at
1582
+ )
1583
+ ON CONFLICT(cache_key) DO UPDATE SET
1584
+ context_graph_id = excluded.context_graph_id,
1585
+ failures = excluded.failures,
1586
+ next_retry_at = excluded.next_retry_at,
1587
+ swm_gen = excluded.swm_gen,
1588
+ candidate_namespaces = excluded.candidate_namespaces,
1589
+ peer_topology_key = excluded.peer_topology_key,
1590
+ updated_at = excluded.updated_at
1591
+ `).run(record);
1592
+ }
1593
+ getVmReconcileNegative(cacheKey) {
1594
+ return this.stmt('getVmReconcileNegative', 'SELECT * FROM vm_reconcile_negative_cache WHERE cache_key = ?').get(cacheKey);
1595
+ }
1596
+ deleteVmReconcileNegative(cacheKey) {
1597
+ this.stmt('deleteVmReconcileNegative', 'DELETE FROM vm_reconcile_negative_cache WHERE cache_key = ?').run(cacheKey);
1598
+ }
1599
+ deleteVmReconcileNegativesForContextGraph(contextGraphId) {
1600
+ this.stmt('deleteVmReconcileNegativesForContextGraph', 'DELETE FROM vm_reconcile_negative_cache WHERE context_graph_id = ?').run(contextGraphId);
954
1601
  }
955
1602
  // --- Phase F: chain-driven VM reconciliation telemetry ---
956
1603
  /**
@@ -2031,6 +2678,74 @@ export class SqliteSyncCheckpointStore {
2031
2678
  return this.db.prepare(`DELETE FROM sync_checkpoints WHERE expires_at < ?`).run(nowMs).changes;
2032
2679
  }
2033
2680
  }
2681
+ /**
2682
+ * OT-RFC-59 SC5 durable changelog cursor store (duck-compatible with the agent's
2683
+ * ChangelogCursorStore). Keyed by (peer_id, context_graph_id); stores the last
2684
+ * APPLIED (era, seq) from that responder. Like `ka_numbers` / `protocol_outbox`
2685
+ * this state is durable: it is NEVER added to `prune()` — a TTL would defeat the
2686
+ * O(delta) cross-restart catch-up the changelog lane exists for.
2687
+ */
2688
+ export class SqliteChangelogCursorStore {
2689
+ db;
2690
+ clock;
2691
+ constructor(dashboard, options = {}) {
2692
+ this.db = dashboard.db;
2693
+ this.clock = options.clock ?? (() => Date.now());
2694
+ }
2695
+ get(peerId, contextGraphId) {
2696
+ const row = this.db.prepare(`SELECT era, seq, updated_at FROM changelog_cursors WHERE peer_id = ? AND context_graph_id = ?`).get(peerId, contextGraphId);
2697
+ if (!row)
2698
+ return undefined;
2699
+ return { era: row.era, seq: row.seq, updatedAtMs: row.updated_at };
2700
+ }
2701
+ set(peerId, contextGraphId, era, seq, nowMs = this.clock()) {
2702
+ if (!Number.isSafeInteger(seq) || seq < 0) {
2703
+ throw new Error(`Invalid changelog cursor seq for ${peerId}/${contextGraphId}: ${seq}`);
2704
+ }
2705
+ this.db.prepare(`
2706
+ INSERT INTO changelog_cursors (peer_id, context_graph_id, era, seq, updated_at)
2707
+ VALUES (?, ?, ?, ?, ?)
2708
+ ON CONFLICT(peer_id, context_graph_id) DO UPDATE SET
2709
+ era = excluded.era,
2710
+ seq = excluded.seq,
2711
+ updated_at = excluded.updated_at
2712
+ `).run(peerId, contextGraphId, era, seq, nowMs);
2713
+ }
2714
+ }
2715
+ /**
2716
+ * OT-RFC-59 §6 P0 durable era guard (duck-compatible with storage's
2717
+ * ChangelogEraGuard). Persists the single (era, high_seq) high-water in
2718
+ * node-ui.db — which a `store.nq` RDF-store restore does NOT roll back — so the
2719
+ * write-side ChangelogStore can detect a restore/rollback (seq regressed under
2720
+ * the same era) and rotate the era, forcing peers to full-resync instead of
2721
+ * silently skipping. `save` is called after every committed seq, so it must be a
2722
+ * single fast upsert.
2723
+ */
2724
+ export class SqliteChangelogEraGuard {
2725
+ db;
2726
+ clock;
2727
+ constructor(dashboard, options = {}) {
2728
+ this.db = dashboard.db;
2729
+ this.clock = options.clock ?? (() => Date.now());
2730
+ }
2731
+ async load() {
2732
+ const row = this.db.prepare(`SELECT era, high_seq FROM changelog_era WHERE id = 1`).get();
2733
+ return row ? { era: row.era, highSeq: row.high_seq } : null;
2734
+ }
2735
+ async save(era, highSeq) {
2736
+ if (!Number.isSafeInteger(highSeq) || highSeq < 0) {
2737
+ throw new Error(`Invalid changelog era high_seq: ${highSeq}`);
2738
+ }
2739
+ this.db.prepare(`
2740
+ INSERT INTO changelog_era (id, era, high_seq, updated_at)
2741
+ VALUES (1, ?, ?, ?)
2742
+ ON CONFLICT(id) DO UPDATE SET
2743
+ era = excluded.era,
2744
+ high_seq = excluded.high_seq,
2745
+ updated_at = excluded.updated_at
2746
+ `).run(era, highSeq, this.clock());
2747
+ }
2748
+ }
2034
2749
  // --- Universal Messenger substrate stores (rc.9 plan PR-1) ---
2035
2750
  /**
2036
2751
  * SQLite-backed `MessageIdempotencyStore` against the V12
@@ -2176,18 +2891,36 @@ export class SqliteProtocolOutboxStore {
2176
2891
  .get(peer, protocol, messageId);
2177
2892
  return row !== undefined;
2178
2893
  }
2894
+ hasPendingFor(peer) {
2895
+ return this.db.prepare('SELECT 1 FROM protocol_outbox WHERE peer_id = ? LIMIT 1').get(peer) !== undefined;
2896
+ }
2179
2897
  pendingFor(peer) {
2180
2898
  const rows = this.db
2181
- .prepare(`SELECT * FROM protocol_outbox WHERE peer_id = ? ORDER BY first_failure_at ASC`)
2899
+ .prepare(`SELECT * FROM protocol_outbox
2900
+ WHERE peer_id = ?
2901
+ ORDER BY first_failure_at ASC, protocol ASC, message_id ASC`)
2182
2902
  .all(peer);
2183
2903
  return rows.map(SqliteProtocolOutboxStore.rowToEntry);
2184
2904
  }
2185
2905
  due(now) {
2186
2906
  const rows = this.db
2187
- .prepare(`SELECT * FROM protocol_outbox WHERE next_attempt_at <= ?`)
2907
+ .prepare(`SELECT * FROM protocol_outbox
2908
+ WHERE next_attempt_at <= ?
2909
+ ORDER BY next_attempt_at ASC, first_failure_at ASC,
2910
+ peer_id ASC, protocol ASC, message_id ASC`)
2188
2911
  .all(now);
2189
2912
  return rows.map(SqliteProtocolOutboxStore.rowToEntry);
2190
2913
  }
2914
+ duePage(now, limit) {
2915
+ const rows = this.db
2916
+ .prepare(`SELECT * FROM protocol_outbox
2917
+ WHERE next_attempt_at <= ?
2918
+ ORDER BY next_attempt_at ASC, first_failure_at ASC,
2919
+ peer_id ASC, protocol ASC, message_id ASC
2920
+ LIMIT ?`)
2921
+ .all(now, limit);
2922
+ return rows.map(SqliteProtocolOutboxStore.rowToEntry);
2923
+ }
2191
2924
  dropExpired(now) {
2192
2925
  const cutoff = now - this.maxAgeMs;
2193
2926
  const rows = this.db