@cleocode/cleo 2026.9.7 → 2026.9.9

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 (690) hide show
  1. package/dist/cli/index.js +3766 -2232
  2. package/package.json +14 -13
  3. package/scripts/check-cleo-tarball-size.mjs +105 -0
  4. package/scripts/copy-studio-dist.mjs +33 -9
  5. package/studio-dist/client/_app/immutable/chunks/{wI_OOyoh.js → BB7zPpV2.js} +1 -1
  6. package/studio-dist/client/_app/immutable/chunks/{D8NsLz6i.js → BOnrCZaR.js} +1 -1
  7. package/studio-dist/client/_app/immutable/chunks/{TX-23QW3.js → BiaAd37T.js} +1 -1
  8. package/studio-dist/client/_app/immutable/chunks/C6jhAnil.js +1 -0
  9. package/studio-dist/client/_app/immutable/chunks/C87Q3uxm.js +1 -0
  10. package/studio-dist/client/_app/immutable/chunks/{CqF5Lomf.js → CTLGzgdN.js} +1 -1
  11. package/studio-dist/client/_app/immutable/chunks/{o1PZ6Kzq.js → CY3heNub.js} +1 -1
  12. package/studio-dist/client/_app/immutable/chunks/{RptFUp2P.js → Cge6qiwc.js} +1 -1
  13. package/studio-dist/client/_app/immutable/chunks/{BRgWBQlV.js → CnwXLaZJ.js} +1 -1
  14. package/studio-dist/client/_app/immutable/chunks/{Bxz2A0ZC.js → DEH43YwF.js} +1 -1
  15. package/studio-dist/client/_app/immutable/chunks/DGftG5oL.js +3 -0
  16. package/studio-dist/client/_app/immutable/chunks/{DVHIxkgs.js → DV2HHHkd.js} +1 -1
  17. package/studio-dist/client/_app/immutable/chunks/{eK8hf4s7.js → DXxEgUey.js} +1 -1
  18. package/studio-dist/client/_app/immutable/chunks/{CEqOYim7.js → DZrdToAH.js} +1 -1
  19. package/studio-dist/client/_app/immutable/chunks/EZwHPWle.js +2 -0
  20. package/studio-dist/client/_app/immutable/chunks/{B9psmq5-.js → rB_71s8h.js} +1 -1
  21. package/studio-dist/client/_app/immutable/chunks/{JPJd4fEI.js → rUtW7Hx6.js} +1 -1
  22. package/studio-dist/client/_app/immutable/chunks/zWFSpNUo.js +1 -0
  23. package/studio-dist/client/_app/immutable/entry/app.AR3q6Ydw.js +2 -0
  24. package/studio-dist/client/_app/immutable/entry/start.Cf2RUEwO.js +1 -0
  25. package/studio-dist/client/_app/immutable/nodes/{0.CZzEEyVh.js → 0.BEkWfyyt.js} +1 -1
  26. package/studio-dist/client/_app/immutable/nodes/{1.Bj3Opbua.js → 1.DfbrPFgR.js} +1 -1
  27. package/studio-dist/client/_app/immutable/nodes/{10.xe_Mc2Ko.js → 10.3rxHNnSx.js} +1 -1
  28. package/studio-dist/client/_app/immutable/nodes/{11.DuKPA7JD.js → 11.B6jWv3Kz.js} +1 -1
  29. package/studio-dist/client/_app/immutable/nodes/{12.BoZ_QrUS.js → 12.BrDHeTEq.js} +1 -1
  30. package/studio-dist/client/_app/immutable/nodes/{13.DFJiUiv8.js → 13.CE5bd4JV.js} +1 -1
  31. package/studio-dist/client/_app/immutable/nodes/{14.eaAlFU4w.js → 14.DgXD2LRx.js} +1 -1
  32. package/studio-dist/client/_app/immutable/nodes/{15.BfXgiwMC.js → 15.CdRO2NCB.js} +1 -1
  33. package/studio-dist/client/_app/immutable/nodes/{16.mEwy9cqh.js → 16.Cr01BBgh.js} +1 -1
  34. package/studio-dist/client/_app/immutable/nodes/{17.DP2QtQGn.js → 17.PZ1GrCot.js} +1 -1
  35. package/studio-dist/client/_app/immutable/nodes/{18.Bu0054sX.js → 18.Crly2qhe.js} +1 -1
  36. package/studio-dist/client/_app/immutable/nodes/{19.UWt6QbCm.js → 19.su55K8y_.js} +1 -1
  37. package/studio-dist/client/_app/immutable/nodes/{2.y5jKpHiR.js → 2.mqgFVWFQ.js} +1 -1
  38. package/studio-dist/client/_app/immutable/nodes/{20.B1AIHdkP.js → 20.DqQjto4P.js} +1 -1
  39. package/studio-dist/client/_app/immutable/nodes/{21.CpAN71H8.js → 21.BhwhdQCq.js} +1 -1
  40. package/studio-dist/client/_app/immutable/nodes/{22.CrYKrN4c.js → 22.BmJEWwER.js} +1 -1
  41. package/studio-dist/client/_app/immutable/nodes/{23.pGRaHQjq.js → 23.CAVIJEH-.js} +1 -1
  42. package/studio-dist/client/_app/immutable/nodes/{24.BUoUu37z.js → 24.BIypnou8.js} +1 -1
  43. package/studio-dist/client/_app/immutable/nodes/25.CllbQ5CT.js +39 -0
  44. package/studio-dist/client/_app/immutable/nodes/26.DILkE52_.js +1 -0
  45. package/studio-dist/client/_app/immutable/nodes/{28.DcmYM7lb.js → 28.D64RBcfs.js} +1 -1
  46. package/studio-dist/client/_app/immutable/nodes/{29.lbHbMqNO.js → 29.yfuBeH1x.js} +1 -1
  47. package/studio-dist/client/_app/immutable/nodes/{3.Dyir4xXj.js → 3.ChHbetZy.js} +1 -1
  48. package/studio-dist/client/_app/immutable/nodes/{30.DXj0Wm2A.js → 30.1WUv5bh9.js} +1 -1
  49. package/studio-dist/client/_app/immutable/nodes/4.D6vXYBoQ.js +1 -0
  50. package/studio-dist/client/_app/immutable/nodes/{5.VWSdP7oc.js → 5.hjw2O-4e.js} +1 -1
  51. package/studio-dist/client/_app/immutable/nodes/{6.Dl-XLwOj.js → 6.CAoDfDt6.js} +1 -1
  52. package/studio-dist/client/_app/immutable/nodes/{7.CFuC5L-y.js → 7.CLgPSQ8Z.js} +1 -1
  53. package/studio-dist/client/_app/immutable/nodes/{8.BCJ0DhOb.js → 8.zuaO1Hik.js} +1 -1
  54. package/studio-dist/client/_app/immutable/nodes/{9.Bq4SUzL9.js → 9.Cdsd85m2.js} +1 -1
  55. package/studio-dist/client/_app/version.json +1 -1
  56. package/studio-dist/server/chunks/0-BvvArN5K.js +41 -0
  57. package/studio-dist/server/chunks/1-rLPlIpIR.js +9 -0
  58. package/studio-dist/server/chunks/{10-DvOD4ksa.js → 10-CYLc3d5x.js} +3 -3
  59. package/studio-dist/server/chunks/{11-DMnBZ02H.js → 11-DcIYR5vm.js} +3 -3
  60. package/studio-dist/server/chunks/{12-CCyRyRWX.js → 12-WE1u104t.js} +3 -3
  61. package/studio-dist/server/chunks/{13-Dx1c3a_X.js → 13-joBDoxhK.js} +3 -3
  62. package/studio-dist/server/chunks/{14-Bo-dTCaZ.js → 14-Doj5F4uJ.js} +3 -3
  63. package/studio-dist/server/chunks/{15-BvUNl3KL.js → 15-BlL3H5S5.js} +3 -3
  64. package/studio-dist/server/chunks/{16-De6EzPSa.js → 16-C-UvTi7Z.js} +5 -4
  65. package/studio-dist/server/chunks/{17-CZNsiZhi.js → 17-C790IKoE.js} +10 -93
  66. package/studio-dist/server/chunks/{18-CbxxjZox.js → 18-6VS9Vgfx.js} +5 -4
  67. package/studio-dist/server/chunks/{19-DqHyladS.js → 19-BAQHPrMB.js} +3 -3
  68. package/studio-dist/server/chunks/{2-COvmA-ZA.js → 2-DXSVVaej.js} +10 -93
  69. package/studio-dist/server/chunks/20-DUG4_xXL.js +57 -0
  70. package/studio-dist/server/chunks/{21-DXK9ptAC.js → 21-qhGjkW41.js} +4 -31
  71. package/studio-dist/server/chunks/{22-DD5CLWJC.js → 22-Byy16hFA.js} +3 -3
  72. package/studio-dist/server/chunks/23-CZpFrGp4.js +97 -0
  73. package/studio-dist/server/chunks/24-CkIGZX8S.js +67 -0
  74. package/studio-dist/server/chunks/{25-CAg6Xcmn.js → 25-CHBaeXym.js} +15 -96
  75. package/studio-dist/server/chunks/{26-_nfVTvNi.js → 26-BbDd--8j.js} +12 -93
  76. package/studio-dist/server/chunks/{27-C9edghG4.js → 27-Cqzlh9gZ.js} +3 -2
  77. package/studio-dist/server/chunks/{28-VemTJGyc.js → 28-DeYe2rpm.js} +8 -62
  78. package/studio-dist/server/chunks/{29-CXP-m29t.js → 29-6pjLM0Z1.js} +10 -93
  79. package/studio-dist/server/chunks/{3-B8KhfvHp.js → 3-DMSsQV71.js} +9 -25
  80. package/studio-dist/server/chunks/{30-QiM1S4cJ.js → 30-Dw317sSW.js} +10 -93
  81. package/studio-dist/server/chunks/{31-BwdY9NnB.js → 31-Q4kHbp8j.js} +3 -2
  82. package/studio-dist/server/chunks/{4-CSaG4HjO.js → 4-C9BMSh4K.js} +4 -3
  83. package/studio-dist/server/chunks/{5-i5ry4y3C.js → 5-C0CF4WKG.js} +3 -3
  84. package/studio-dist/server/chunks/{6-DR1GWBMT.js → 6-BWyOJust.js} +3 -3
  85. package/studio-dist/server/chunks/{7-DSNwn3aQ.js → 7-CK4MK5gy.js} +3 -3
  86. package/studio-dist/server/chunks/{8-Bj0bx07K.js → 8-CiX7Xk9k.js} +3 -3
  87. package/studio-dist/server/chunks/{9-B3Tz0mZq.js → 9-Be5118w4.js} +3 -3
  88. package/studio-dist/server/chunks/{HeroHeader-v4Q9vaGr.js → HeroHeader-BrGN5WPw.js} +2 -2
  89. package/studio-dist/server/chunks/{Sparkline-BJ-GuhRd.js → Sparkline-DU_NVxz9.js} +2 -2
  90. package/studio-dist/server/chunks/{StatBlock-DHJMmOCx.js → StatBlock-jNsxycS1.js} +2 -2
  91. package/studio-dist/server/chunks/{Tabs-DzG6Bn9a.js → Tabs-DQKDflnT.js} +2 -2
  92. package/studio-dist/server/chunks/_dispatch-DTWViLGL.js +145 -0
  93. package/studio-dist/server/chunks/{_layout.svelte-BEIGQcIv.js → _layout.svelte-BjSWu4Fw.js} +7 -9
  94. package/studio-dist/server/chunks/{_page.svelte-al08ovKm.js → _page.svelte-6iMiGR11.js} +5 -5
  95. package/studio-dist/server/chunks/{_page.svelte-Byz2djto.js → _page.svelte-B-YXGvAW.js} +5 -5
  96. package/studio-dist/server/chunks/{_page.svelte-UYiF7YBK.js → _page.svelte-BCRyjlk2.js} +5 -5
  97. package/studio-dist/server/chunks/{_page.svelte-2cgtvHwW.js → _page.svelte-BdkomWnj.js} +13 -71
  98. package/studio-dist/server/chunks/{_page.svelte-CCoTp89s.js → _page.svelte-BoYafqft.js} +3 -3
  99. package/studio-dist/server/chunks/{_page.svelte-nzffIbBP.js → _page.svelte-C7Wm6_HB.js} +3 -3
  100. package/studio-dist/server/chunks/{_page.svelte-Wfx7ww_h.js → _page.svelte-CIyzbngu.js} +11 -13
  101. package/studio-dist/server/chunks/{_page.svelte-C_VSL8NW.js → _page.svelte-CmbOzJ6A.js} +13 -71
  102. package/studio-dist/server/chunks/{_page.svelte-CophUar3.js → _page.svelte-CqI6hqMm.js} +5 -5
  103. package/studio-dist/server/chunks/{_page.svelte-tYN7_LXI.js → _page.svelte-CtSwzG-R.js} +9 -11
  104. package/studio-dist/server/chunks/{_page.svelte-DYNorA0S.js → _page.svelte-Cx9Ptecp.js} +10 -67
  105. package/studio-dist/server/chunks/{_page.svelte-9OQphlnm.js → _page.svelte-D4Uf5q1X.js} +14 -69
  106. package/studio-dist/server/chunks/{_page.svelte-BIlhmugm.js → _page.svelte-DRjD44sK.js} +11 -13
  107. package/studio-dist/server/chunks/{_page.svelte-BVVTrzTL.js → _page.svelte-DaheDIRs.js} +5 -5
  108. package/studio-dist/server/chunks/{_page.svelte-DOWw416I.js → _page.svelte-DfIiE7gL.js} +11 -13
  109. package/studio-dist/server/chunks/{_page.svelte-M5D2QnMS.js → _page.svelte-Dm8y5a8S.js} +10 -67
  110. package/studio-dist/server/chunks/{_page.svelte-B_pq-Z98.js → _page.svelte-DqXxyNsF.js} +7 -7
  111. package/studio-dist/server/chunks/{_page.svelte-C3aIui4o.js → _page.svelte-DqsbnAhk.js} +4 -4
  112. package/studio-dist/server/chunks/{_page.svelte-ksTJUXce.js → _page.svelte-Dt0_c1T-.js} +3 -3
  113. package/studio-dist/server/chunks/{_page.svelte-bGhsrIjS.js → _page.svelte-Dv6iwhu7.js} +3 -3
  114. package/studio-dist/server/chunks/{_page.svelte-BXyoMME1.js → _page.svelte-GPiHej3P.js} +5 -5
  115. package/studio-dist/server/chunks/{_page.svelte-CYDjW1R0.js → _page.svelte-LuRm8XxE.js} +5 -5
  116. package/studio-dist/server/chunks/{_page.svelte-Clbkk1gm.js → _page.svelte-Z7nL9SC-.js} +8 -9
  117. package/studio-dist/server/chunks/{_page.svelte-DUWbZjC7.js → _page.svelte-_kntHS6v.js} +4 -4
  118. package/studio-dist/server/chunks/{_page.svelte-l4Z_xjl9.js → _page.svelte-k-4NCnbz.js} +9 -11
  119. package/studio-dist/server/chunks/{_page.svelte-CHzXuw2Q.js → _page.svelte-me5SUosG.js} +3 -3
  120. package/studio-dist/server/chunks/{_page.svelte-BgwL2Gyu.js → _page.svelte-sHj6UAzC.js} +11 -13
  121. package/studio-dist/server/chunks/{_server.ts-DSzRdt1U.js → _server.ts--2m_U4SL.js} +11 -94
  122. package/studio-dist/server/chunks/{_server.ts-ujoDR2_g.js → _server.ts--sGLmBsz.js} +10 -92
  123. package/studio-dist/server/chunks/{_server.ts-k2fApwOp.js → _server.ts-0ukyuE0I.js} +5 -60
  124. package/studio-dist/server/chunks/_server.ts-1eW2mTeh.js +40 -0
  125. package/studio-dist/server/chunks/{_server.ts-Bfr4u3EP.js → _server.ts-3LzqLzt0.js} +10 -92
  126. package/studio-dist/server/chunks/_server.ts-5euivmMj.js +38 -0
  127. package/studio-dist/server/chunks/{_server.ts-CShsBkeE.js → _server.ts-B0uoay97.js} +7 -23
  128. package/studio-dist/server/chunks/{_server.ts-hL92cV7Z.js → _server.ts-B5vYKkI6.js} +9 -63
  129. package/studio-dist/server/chunks/{_server.ts-_RJXtmfi.js → _server.ts-B8dTbPHd.js} +8 -23
  130. package/studio-dist/server/chunks/{_server.ts-C3Jo-ju8.js → _server.ts-BA8Xdegq.js} +10 -92
  131. package/studio-dist/server/chunks/_server.ts-BBClE0n9.js +54 -0
  132. package/studio-dist/server/chunks/{_server.ts-CJA1djzJ.js → _server.ts-BCnr9uaO.js} +8 -92
  133. package/studio-dist/server/chunks/{_server.ts-ZS1_yMwR.js → _server.ts-BDwMoyZ_.js} +6 -32
  134. package/studio-dist/server/chunks/{_server.ts-sv4pA808.js → _server.ts-BE-Uhx4N.js} +5 -46
  135. package/studio-dist/server/chunks/{_server.ts-DV6pzF-M.js → _server.ts-BSHADOUJ.js} +4 -3
  136. package/studio-dist/server/chunks/{_server.ts-BdN4AJ6o.js → _server.ts-BSgLJPPQ.js} +10 -91
  137. package/studio-dist/server/chunks/_server.ts-Bl9tMqwZ.js +159 -0
  138. package/studio-dist/server/chunks/_server.ts-BlzDnRI0.js +51 -0
  139. package/studio-dist/server/chunks/{_server.ts-DnE0f-iC.js → _server.ts-BtuemCsf.js} +4 -3
  140. package/studio-dist/server/chunks/{_server.ts-BPcErnS0.js → _server.ts-C2nYqbd9.js} +10 -92
  141. package/studio-dist/server/chunks/{_server.ts-4p6cp9ke.js → _server.ts-C6IDM_3I.js} +10 -92
  142. package/studio-dist/server/chunks/_server.ts-C8Z32vuL.js +45 -0
  143. package/studio-dist/server/chunks/_server.ts-CBXyadsy.js +26 -0
  144. package/studio-dist/server/chunks/{_server.ts-Do9RIPRo.js → _server.ts-CE6h2ATE.js} +5 -32
  145. package/studio-dist/server/chunks/_server.ts-CGROl_5b.js +122 -0
  146. package/studio-dist/server/chunks/_server.ts-CH06JVqZ.js +65 -0
  147. package/studio-dist/server/chunks/_server.ts-CNqcws-M.js +29 -0
  148. package/studio-dist/server/chunks/{_server.ts-uUx7uT2Y.js → _server.ts-CYv5YuHd.js} +6 -43
  149. package/studio-dist/server/chunks/{_server.ts-DSAv66wD.js → _server.ts-CbThsyhY.js} +4 -3
  150. package/studio-dist/server/chunks/{_server.ts-DgbY5EB3.js → _server.ts-CeQolQOK.js} +10 -92
  151. package/studio-dist/server/chunks/_server.ts-ChoGyX5C.js +61 -0
  152. package/studio-dist/server/chunks/_server.ts-CjJYkLJH.js +61 -0
  153. package/studio-dist/server/chunks/{_server.ts-lPlBUIJX.js → _server.ts-CtjArzxf.js} +5 -60
  154. package/studio-dist/server/chunks/{_server.ts-Cq60m8Pw.js → _server.ts-D3QWZwt_.js} +4 -3
  155. package/studio-dist/server/chunks/{_server.ts-CkoJ2HCW.js → _server.ts-D6wP5bCh.js} +10 -92
  156. package/studio-dist/server/chunks/{_server.ts-DwTRjS4y.js → _server.ts-D7JwQTun.js} +10 -93
  157. package/studio-dist/server/chunks/_server.ts-DKFJdXPK.js +97 -0
  158. package/studio-dist/server/chunks/_server.ts-DNAPGKEF.js +55 -0
  159. package/studio-dist/server/chunks/_server.ts-DQiTiY3E.js +78 -0
  160. package/studio-dist/server/chunks/{_server.ts-B2E7fxo9.js → _server.ts-DUPODxVZ.js} +6 -91
  161. package/studio-dist/server/chunks/{_server.ts-BT0Uq2yq.js → _server.ts-DUyeSXbV.js} +10 -92
  162. package/studio-dist/server/chunks/{_server.ts-DRVz9D-J.js → _server.ts-DY7BwZCv.js} +9 -24
  163. package/studio-dist/server/chunks/{_server.ts-Cz0zgaUV.js → _server.ts-D_E276ms.js} +3 -2
  164. package/studio-dist/server/chunks/{_server.ts-B5g3eScF.js → _server.ts-DgtmTAp5.js} +5 -60
  165. package/studio-dist/server/chunks/{_server.ts-CboRJY0Q.js → _server.ts-DhTKaw7h.js} +10 -63
  166. package/studio-dist/server/chunks/_server.ts-DqKBSCPT.js +31 -0
  167. package/studio-dist/server/chunks/{_server.ts-CpmMp5D8.js → _server.ts-DyXBNnRD.js} +4 -3
  168. package/studio-dist/server/chunks/_server.ts-DzjUpduu.js +25 -0
  169. package/studio-dist/server/chunks/{_server.ts-CGiAXy5i.js → _server.ts-Dznqw6Dg.js} +8 -23
  170. package/studio-dist/server/chunks/{_server.ts-tKMjUFUP.js → _server.ts-EVwS7UXb.js} +4 -3
  171. package/studio-dist/server/chunks/_server.ts-J17j25VG.js +62 -0
  172. package/studio-dist/server/chunks/{_server.ts-C4BcDGqS.js → _server.ts-XLV5SvjH.js} +7 -63
  173. package/studio-dist/server/chunks/_server.ts-jZgqFIJz.js +45 -0
  174. package/studio-dist/server/chunks/{_server.ts-Bw7A4zgb.js → _server.ts-lyiGGv7w.js} +5 -3
  175. package/studio-dist/server/chunks/_server.ts-n0fpsfkk.js +67 -0
  176. package/studio-dist/server/chunks/{brain-CXR7ZT7S.js → brain-xt_NeshR.js} +2 -2
  177. package/studio-dist/server/chunks/cleo-home-_XIqkZaL.js +109 -0
  178. package/studio-dist/server/chunks/{cli-action-DQcaqcfn.js → cli-action-VJPEn1pY.js} +2 -2
  179. package/studio-dist/server/chunks/{client-CgX38JBu.js → client-CrfCXSXZ.js} +5 -6
  180. package/studio-dist/server/chunks/cluster-label-layer-CysRMvhi.js +6721 -0
  181. package/studio-dist/server/chunks/connections-CWLPjwBP.js +15 -0
  182. package/studio-dist/server/chunks/{connections2-mi7mZqJM.js → connections2-DHVs5ytW.js} +32 -19
  183. package/studio-dist/server/chunks/{dev-BNpEUZI3.js → dev-Br5zieNA.js} +2 -2
  184. package/studio-dist/server/chunks/dist-BR1f34Gj.js +18726 -0
  185. package/studio-dist/server/chunks/{error.svelte-CplNulnS.js → error.svelte-CmR0NUCQ.js} +6 -8
  186. package/studio-dist/server/chunks/{explorer-loader-BN_mkt3_.js → explorer-loader-B_uCSs9_.js} +2 -2
  187. package/studio-dist/server/chunks/exports-Dg-gdPPq.js +124 -0
  188. package/studio-dist/server/chunks/{hooks.server-DBsyphiX.js → hooks.server-CWBRp8NI.js} +10 -93
  189. package/studio-dist/server/chunks/{http-CRTZe306.js → http-Dso0FRxT.js} +3 -4
  190. package/studio-dist/server/chunks/{index-server-DocJvixA.js → index-server-BgfA6C0S.js} +17 -4
  191. package/studio-dist/server/chunks/{internal-D6eWgsHJ.js → internal-DYUO6aQs.js} +56 -92
  192. package/studio-dist/server/chunks/{memory2-Cgc_4D5q.js → memory-BZHnDZzu.js} +4 -4
  193. package/studio-dist/server/chunks/{nexus-adapter-9EL_VlGI.js → nexus-adapter-D1ryYG1Z.js} +5 -5
  194. package/studio-dist/server/chunks/{no-face-up-DcejJLy-.js → no-face-up-B_3h6Hgs.js} +2 -2
  195. package/studio-dist/server/chunks/project-context-BAXQmgch.js +188 -0
  196. package/studio-dist/server/chunks/{src-BUqGet2c.js → src-D2rW9bIF.js} +422 -5
  197. package/studio-dist/server/chunks/{stores-5DmdjPCH.js → stores-CS6eY2sx.js} +3 -3
  198. package/studio-dist/server/chunks/{tasks2-Bf9VHrv-.js → tasks-sWuEmiGW.js} +2349 -9
  199. package/studio-dist/server/chunks/{ui-DR8zOSpC.js → ui-CkQ-pCs3.js} +2 -2
  200. package/studio-dist/server/chunks/utils-g564cUMV.js +151 -0
  201. package/studio-dist/server/index.js +5 -16
  202. package/studio-dist/server/manifest.js +88 -88
  203. package/dist/cli/index.js.map +0 -6
  204. package/studio-dist/client/_app/immutable/assets/parse-worker.CSEr67Qi.js +0 -1127
  205. package/studio-dist/client/_app/immutable/chunks/2mEksghu2.js +0 -1
  206. package/studio-dist/client/_app/immutable/chunks/3KebaI-z.js +0 -53
  207. package/studio-dist/client/_app/immutable/chunks/3y2yNYEk2.js +0 -3
  208. package/studio-dist/client/_app/immutable/chunks/7UXYlaI1.js +0 -1
  209. package/studio-dist/client/_app/immutable/chunks/83VLmCvk.js +0 -4
  210. package/studio-dist/client/_app/immutable/chunks/84Rhmi6r.js +0 -2
  211. package/studio-dist/client/_app/immutable/chunks/9CV9TXTg2.js +0 -92
  212. package/studio-dist/client/_app/immutable/chunks/9LFtcz24.js +0 -1
  213. package/studio-dist/client/_app/immutable/chunks/9bLMgKwu2.js +0 -1
  214. package/studio-dist/client/_app/immutable/chunks/9kKqohVR2.js +0 -37
  215. package/studio-dist/client/_app/immutable/chunks/B-Ye28EY.js +0 -7
  216. package/studio-dist/client/_app/immutable/chunks/B-jnUl7o.js +0 -1
  217. package/studio-dist/client/_app/immutable/chunks/B0ORuy8x2.js +0 -1
  218. package/studio-dist/client/_app/immutable/chunks/B0xCM1wN2.js +0 -1
  219. package/studio-dist/client/_app/immutable/chunks/B0zCN4sX.js +0 -1
  220. package/studio-dist/client/_app/immutable/chunks/B1wt-iGW.js +0 -7
  221. package/studio-dist/client/_app/immutable/chunks/B2F3KUIU.js +0 -1
  222. package/studio-dist/client/_app/immutable/chunks/B2_6RrlL2.js +0 -1
  223. package/studio-dist/client/_app/immutable/chunks/B2qKEYxA2.js +0 -1
  224. package/studio-dist/client/_app/immutable/chunks/B2u2rNyI2.js +0 -8
  225. package/studio-dist/client/_app/immutable/chunks/B3_X3Lhd2.js +0 -1
  226. package/studio-dist/client/_app/immutable/chunks/B3sNtbqf.js +0 -1
  227. package/studio-dist/client/_app/immutable/chunks/B4NQP1Ax2.js +0 -6
  228. package/studio-dist/client/_app/immutable/chunks/BAtZg5v5.js +0 -2
  229. package/studio-dist/client/_app/immutable/chunks/BB6WKLm82.js +0 -1
  230. package/studio-dist/client/_app/immutable/chunks/BBX8UDtn.js +0 -1
  231. package/studio-dist/client/_app/immutable/chunks/BBs1U1iU.js +0 -1
  232. package/studio-dist/client/_app/immutable/chunks/BEli_4Jp2.js +0 -1
  233. package/studio-dist/client/_app/immutable/chunks/BEt2wIUt.js +0 -1
  234. package/studio-dist/client/_app/immutable/chunks/BEyM8qSW2.js +0 -1
  235. package/studio-dist/client/_app/immutable/chunks/BFZA3Dsk2.js +0 -1
  236. package/studio-dist/client/_app/immutable/chunks/BGdPweG1.js +0 -2
  237. package/studio-dist/client/_app/immutable/chunks/BGiusSqE.js +0 -1
  238. package/studio-dist/client/_app/immutable/chunks/BGmpyfzD.js +0 -1
  239. package/studio-dist/client/_app/immutable/chunks/BH5rhSIR.js +0 -1
  240. package/studio-dist/client/_app/immutable/chunks/BHciFt922.js +0 -2
  241. package/studio-dist/client/_app/immutable/chunks/BHnFwTQY.js +0 -1
  242. package/studio-dist/client/_app/immutable/chunks/BIvye3XP2.js +0 -59
  243. package/studio-dist/client/_app/immutable/chunks/BJ8mcpQA2.js +0 -1
  244. package/studio-dist/client/_app/immutable/chunks/BK17qSHw.js +0 -2
  245. package/studio-dist/client/_app/immutable/chunks/BLkgUtyb.js +0 -1
  246. package/studio-dist/client/_app/immutable/chunks/BMUko3r62.js +0 -5
  247. package/studio-dist/client/_app/immutable/chunks/BMwzdEaa.js +0 -2
  248. package/studio-dist/client/_app/immutable/chunks/BQZx6brg.js +0 -2
  249. package/studio-dist/client/_app/immutable/chunks/BUPJTTTd.js +0 -2
  250. package/studio-dist/client/_app/immutable/chunks/BVNGAeZe2.js +0 -146
  251. package/studio-dist/client/_app/immutable/chunks/BWfLryGm.js +0 -1
  252. package/studio-dist/client/_app/immutable/chunks/BXIfeogw.js +0 -1
  253. package/studio-dist/client/_app/immutable/chunks/BXTfFyUU.js +0 -1
  254. package/studio-dist/client/_app/immutable/chunks/BYJC6_MM.js +0 -2
  255. package/studio-dist/client/_app/immutable/chunks/BZV1ZJzH.js +0 -2
  256. package/studio-dist/client/_app/immutable/chunks/BZu9rehd.js +0 -1
  257. package/studio-dist/client/_app/immutable/chunks/Bbzm48C52.js +0 -1
  258. package/studio-dist/client/_app/immutable/chunks/BfGnlwjH.js +0 -1
  259. package/studio-dist/client/_app/immutable/chunks/Bfjb9LPp.js +0 -2
  260. package/studio-dist/client/_app/immutable/chunks/BieFfu-R.js +0 -1
  261. package/studio-dist/client/_app/immutable/chunks/Bmv63uXe2.js +0 -1
  262. package/studio-dist/client/_app/immutable/chunks/BtBW1A0u.js +0 -1
  263. package/studio-dist/client/_app/immutable/chunks/BxISGQCl2.js +0 -1
  264. package/studio-dist/client/_app/immutable/chunks/ByNR_KvT.js +0 -2
  265. package/studio-dist/client/_app/immutable/chunks/BzjhD6Hr2.js +0 -2
  266. package/studio-dist/client/_app/immutable/chunks/BzpWnq0c2.js +0 -1
  267. package/studio-dist/client/_app/immutable/chunks/C-yJVcyI2.js +0 -1
  268. package/studio-dist/client/_app/immutable/chunks/C0cQ-8kS2.js +0 -1
  269. package/studio-dist/client/_app/immutable/chunks/C1vWmhYe.js +0 -48
  270. package/studio-dist/client/_app/immutable/chunks/C1w6bBje.js +0 -1
  271. package/studio-dist/client/_app/immutable/chunks/C243sn7r.js +0 -4
  272. package/studio-dist/client/_app/immutable/chunks/C2oZaDvt.js +0 -67
  273. package/studio-dist/client/_app/immutable/chunks/C3OziSQx.js +0 -1
  274. package/studio-dist/client/_app/immutable/chunks/C3hFPbMK.js +0 -2
  275. package/studio-dist/client/_app/immutable/chunks/C4P9oFvU2.js +0 -6
  276. package/studio-dist/client/_app/immutable/chunks/C5Nlanei.js +0 -8
  277. package/studio-dist/client/_app/immutable/chunks/C6KBAXPl.js +0 -15
  278. package/studio-dist/client/_app/immutable/chunks/C8MZnWta.js +0 -1
  279. package/studio-dist/client/_app/immutable/chunks/CAoan3K12.js +0 -1
  280. package/studio-dist/client/_app/immutable/chunks/CBpcINYH.js +0 -29
  281. package/studio-dist/client/_app/immutable/chunks/CE7phgWq.js +0 -1
  282. package/studio-dist/client/_app/immutable/chunks/CFb4Co_K.js +0 -2
  283. package/studio-dist/client/_app/immutable/chunks/CGWoRK3t2.js +0 -1
  284. package/studio-dist/client/_app/immutable/chunks/CH3t4xo1.js +0 -1
  285. package/studio-dist/client/_app/immutable/chunks/CHJgk6g-.js +0 -1
  286. package/studio-dist/client/_app/immutable/chunks/CHgPjuDQ.js +0 -17
  287. package/studio-dist/client/_app/immutable/chunks/CHkCZrnr.js +0 -1
  288. package/studio-dist/client/_app/immutable/chunks/CI_91WUN.js +0 -4
  289. package/studio-dist/client/_app/immutable/chunks/CJ7-hP0H.js +0 -1
  290. package/studio-dist/client/_app/immutable/chunks/CKIrNnkY2.js +0 -1
  291. package/studio-dist/client/_app/immutable/chunks/CKoQ7Ko5.js +0 -1
  292. package/studio-dist/client/_app/immutable/chunks/CM8O4zqs.js +0 -56
  293. package/studio-dist/client/_app/immutable/chunks/CMz3AVRO.js +0 -1
  294. package/studio-dist/client/_app/immutable/chunks/CNToUR3P.js +0 -13
  295. package/studio-dist/client/_app/immutable/chunks/CNuQG1yl2.js +0 -1
  296. package/studio-dist/client/_app/immutable/chunks/CQLIZtxj.js +0 -2
  297. package/studio-dist/client/_app/immutable/chunks/CQYVNgM0.js +0 -20
  298. package/studio-dist/client/_app/immutable/chunks/CQeVOmFO2.js +0 -1
  299. package/studio-dist/client/_app/immutable/chunks/CSPHHq09.js +0 -1
  300. package/studio-dist/client/_app/immutable/chunks/CTJEdbIn.js +0 -1
  301. package/studio-dist/client/_app/immutable/chunks/CTTT3wYw.js +0 -35
  302. package/studio-dist/client/_app/immutable/chunks/CV4ck7fz.js +0 -2
  303. package/studio-dist/client/_app/immutable/chunks/CXQrVnHf.js +0 -11
  304. package/studio-dist/client/_app/immutable/chunks/CXTCtOUQ.js +0 -1
  305. package/studio-dist/client/_app/immutable/chunks/C_CsU7Qq.js +0 -1
  306. package/studio-dist/client/_app/immutable/chunks/C_IZx3682.js +0 -2
  307. package/studio-dist/client/_app/immutable/chunks/CbTnu2Po2.js +0 -1
  308. package/studio-dist/client/_app/immutable/chunks/Cba8RZ1a2.js +0 -8
  309. package/studio-dist/client/_app/immutable/chunks/Cc8xxDr5.js +0 -1
  310. package/studio-dist/client/_app/immutable/chunks/CdPxoldu.js +0 -1
  311. package/studio-dist/client/_app/immutable/chunks/CdqDTozW.js +0 -1
  312. package/studio-dist/client/_app/immutable/chunks/CdsmcCfC.js +0 -25
  313. package/studio-dist/client/_app/immutable/chunks/CeYPAoY9.js +0 -1
  314. package/studio-dist/client/_app/immutable/chunks/Cf4kGS9y.js +0 -1
  315. package/studio-dist/client/_app/immutable/chunks/CfWEqXtJ2.js +0 -1
  316. package/studio-dist/client/_app/immutable/chunks/Cg7Wj2b9.js +0 -2
  317. package/studio-dist/client/_app/immutable/chunks/CgTpKZfi2.js +0 -1
  318. package/studio-dist/client/_app/immutable/chunks/Chpy9ca7.js +0 -2
  319. package/studio-dist/client/_app/immutable/chunks/CiLKuLrQ.js +0 -4
  320. package/studio-dist/client/_app/immutable/chunks/CjNDJjwu.js +0 -1
  321. package/studio-dist/client/_app/immutable/chunks/Cjde0ZKr2.js +0 -1
  322. package/studio-dist/client/_app/immutable/chunks/Cjgnade02.js +0 -3
  323. package/studio-dist/client/_app/immutable/chunks/Ckp90om52.js +0 -2
  324. package/studio-dist/client/_app/immutable/chunks/ClAPVhCD.js +0 -1
  325. package/studio-dist/client/_app/immutable/chunks/ClT89UdO.js +0 -11
  326. package/studio-dist/client/_app/immutable/chunks/CnJf5tML.js +0 -1
  327. package/studio-dist/client/_app/immutable/chunks/CpF4Rrcc2.js +0 -1
  328. package/studio-dist/client/_app/immutable/chunks/CqCka5yX2.js +0 -1
  329. package/studio-dist/client/_app/immutable/chunks/CqtqZrOl.js +0 -1
  330. package/studio-dist/client/_app/immutable/chunks/CrJdywQt.js +0 -1
  331. package/studio-dist/client/_app/immutable/chunks/Cs0zC2l2.js +0 -1
  332. package/studio-dist/client/_app/immutable/chunks/CsMn5fBs.js +0 -141
  333. package/studio-dist/client/_app/immutable/chunks/CsvKLHQQ2.js +0 -2
  334. package/studio-dist/client/_app/immutable/chunks/Ctaresvj.js +0 -1
  335. package/studio-dist/client/_app/immutable/chunks/Ctetf8vc2.js +0 -2
  336. package/studio-dist/client/_app/immutable/chunks/Cv7yW6MG.js +0 -1
  337. package/studio-dist/client/_app/immutable/chunks/CxRIz-Zd.js +0 -1
  338. package/studio-dist/client/_app/immutable/chunks/CxbvtjIH.js +0 -3
  339. package/studio-dist/client/_app/immutable/chunks/CxfeoXkZ.js +0 -2
  340. package/studio-dist/client/_app/immutable/chunks/CySLscL2.js +0 -2
  341. package/studio-dist/client/_app/immutable/chunks/D0PKQAbM.js +0 -87
  342. package/studio-dist/client/_app/immutable/chunks/D0WQsLTu.js +0 -1
  343. package/studio-dist/client/_app/immutable/chunks/D1LBVx6r2.js +0 -1
  344. package/studio-dist/client/_app/immutable/chunks/D1NlFXu9.js +0 -1
  345. package/studio-dist/client/_app/immutable/chunks/D1QfC4hu2.js +0 -1
  346. package/studio-dist/client/_app/immutable/chunks/D1yrHAn72.js +0 -1
  347. package/studio-dist/client/_app/immutable/chunks/D2pa4reh.js +0 -7
  348. package/studio-dist/client/_app/immutable/chunks/D3QsWcIw2.js +0 -2
  349. package/studio-dist/client/_app/immutable/chunks/D4OkQuPl2.js +0 -1
  350. package/studio-dist/client/_app/immutable/chunks/D6UDnATk2.js +0 -1
  351. package/studio-dist/client/_app/immutable/chunks/D7w7HllH.js +0 -38
  352. package/studio-dist/client/_app/immutable/chunks/D93KpglN.js +0 -1
  353. package/studio-dist/client/_app/immutable/chunks/D99fQj2G2.js +0 -1
  354. package/studio-dist/client/_app/immutable/chunks/DB5ifLM-.js +0 -3
  355. package/studio-dist/client/_app/immutable/chunks/DBhP8ifO2.js +0 -15
  356. package/studio-dist/client/_app/immutable/chunks/DF_4OWhj.js +0 -2
  357. package/studio-dist/client/_app/immutable/chunks/DFpNK1vs2.js +0 -1
  358. package/studio-dist/client/_app/immutable/chunks/DGRYMk-W.js +0 -142
  359. package/studio-dist/client/_app/immutable/chunks/DH3CcQwq.js +0 -1
  360. package/studio-dist/client/_app/immutable/chunks/DHQfJeLU.js +0 -4
  361. package/studio-dist/client/_app/immutable/chunks/DL89yEDy2.js +0 -1
  362. package/studio-dist/client/_app/immutable/chunks/DLRku_0p2.js +0 -2
  363. package/studio-dist/client/_app/immutable/chunks/DMZkjPYT2.js +0 -1
  364. package/studio-dist/client/_app/immutable/chunks/DNP72foU.js +0 -1
  365. package/studio-dist/client/_app/immutable/chunks/DO2GztVm.js +0 -1
  366. package/studio-dist/client/_app/immutable/chunks/DOVNXmCK2.js +0 -1
  367. package/studio-dist/client/_app/immutable/chunks/DOqzs8kl2.js +0 -1
  368. package/studio-dist/client/_app/immutable/chunks/DQnR63hh2.js +0 -7
  369. package/studio-dist/client/_app/immutable/chunks/DTxtWPkw.js +0 -1
  370. package/studio-dist/client/_app/immutable/chunks/DVDgCC9A2.js +0 -20
  371. package/studio-dist/client/_app/immutable/chunks/DVVCJYjz.js +0 -1
  372. package/studio-dist/client/_app/immutable/chunks/DWRhYUMj2.js +0 -1
  373. package/studio-dist/client/_app/immutable/chunks/DWavBJBI.js +0 -4
  374. package/studio-dist/client/_app/immutable/chunks/DXDSWRw-.js +0 -1
  375. package/studio-dist/client/_app/immutable/chunks/DXoHaH4a.js +0 -2
  376. package/studio-dist/client/_app/immutable/chunks/DXpCX2Hi.js +0 -1
  377. package/studio-dist/client/_app/immutable/chunks/DYP3EJMY.js +0 -1
  378. package/studio-dist/client/_app/immutable/chunks/DYQphR_V.js +0 -1
  379. package/studio-dist/client/_app/immutable/chunks/DYUlIivP.js +0 -1
  380. package/studio-dist/client/_app/immutable/chunks/DYo2ye8X2.js +0 -1
  381. package/studio-dist/client/_app/immutable/chunks/DYxFu8yV.js +0 -1
  382. package/studio-dist/client/_app/immutable/chunks/DZ66sRb6.js +0 -1
  383. package/studio-dist/client/_app/immutable/chunks/D_XTiylm2.js +0 -1
  384. package/studio-dist/client/_app/immutable/chunks/DaRv9jn22.js +0 -1
  385. package/studio-dist/client/_app/immutable/chunks/DaXb9i_E.js +0 -1
  386. package/studio-dist/client/_app/immutable/chunks/Dccf4VP2.js +0 -24
  387. package/studio-dist/client/_app/immutable/chunks/DdODs-Sb.js +0 -1
  388. package/studio-dist/client/_app/immutable/chunks/DdekYWer.js +0 -2
  389. package/studio-dist/client/_app/immutable/chunks/DeHaovsF.js +0 -1
  390. package/studio-dist/client/_app/immutable/chunks/DeRRkRO52.js +0 -1
  391. package/studio-dist/client/_app/immutable/chunks/DeWI5qCn.js +0 -1
  392. package/studio-dist/client/_app/immutable/chunks/DfivR-Md.js +0 -1
  393. package/studio-dist/client/_app/immutable/chunks/DgmxpSo7.js +0 -1
  394. package/studio-dist/client/_app/immutable/chunks/DhPLWrun2.js +0 -1
  395. package/studio-dist/client/_app/immutable/chunks/DhhrVdfi.js +0 -2
  396. package/studio-dist/client/_app/immutable/chunks/Djr2dbcc2.js +0 -37
  397. package/studio-dist/client/_app/immutable/chunks/DjuRo5vv.js +0 -1
  398. package/studio-dist/client/_app/immutable/chunks/Djz4EDzO.js +0 -1
  399. package/studio-dist/client/_app/immutable/chunks/DkRfaw-K2.js +0 -1
  400. package/studio-dist/client/_app/immutable/chunks/DksB5g4W2.js +0 -1
  401. package/studio-dist/client/_app/immutable/chunks/Dmqenb5z2.js +0 -1
  402. package/studio-dist/client/_app/immutable/chunks/DnbToVyf.js +0 -2
  403. package/studio-dist/client/_app/immutable/chunks/DpGZwlmj.js +0 -1
  404. package/studio-dist/client/_app/immutable/chunks/Dq0lE5C32.js +0 -1
  405. package/studio-dist/client/_app/immutable/chunks/Dq54XqHT2.js +0 -1
  406. package/studio-dist/client/_app/immutable/chunks/DqK84mbb.js +0 -1
  407. package/studio-dist/client/_app/immutable/chunks/DqjFtSJD.js +0 -2
  408. package/studio-dist/client/_app/immutable/chunks/DrKWWDpL.js +0 -1
  409. package/studio-dist/client/_app/immutable/chunks/DsQIgu-4.js +0 -1
  410. package/studio-dist/client/_app/immutable/chunks/DsqdgJHS2.js +0 -1
  411. package/studio-dist/client/_app/immutable/chunks/DtNkZUkq.js +0 -17
  412. package/studio-dist/client/_app/immutable/chunks/DutIls4V2.js +0 -1
  413. package/studio-dist/client/_app/immutable/chunks/DvxYFW4C.js +0 -1
  414. package/studio-dist/client/_app/immutable/chunks/Dwp-76GQ.js +0 -2
  415. package/studio-dist/client/_app/immutable/chunks/DxSZd548.js +0 -1
  416. package/studio-dist/client/_app/immutable/chunks/DxiUMhk0.js +0 -5
  417. package/studio-dist/client/_app/immutable/chunks/DxslLswf.js +0 -23
  418. package/studio-dist/client/_app/immutable/chunks/Dy7EjGgb2.js +0 -9
  419. package/studio-dist/client/_app/immutable/chunks/DyGQ_VXC.js +0 -3
  420. package/studio-dist/client/_app/immutable/chunks/DySe3kTV2.js +0 -1
  421. package/studio-dist/client/_app/immutable/chunks/DyzE2Uxw.js +0 -1
  422. package/studio-dist/client/_app/immutable/chunks/DzhuJaxE2.js +0 -2
  423. package/studio-dist/client/_app/immutable/chunks/FmMHyrh6.js +0 -1
  424. package/studio-dist/client/_app/immutable/chunks/Gxs0J274.js +0 -5
  425. package/studio-dist/client/_app/immutable/chunks/HwtzDYxN.js +0 -2
  426. package/studio-dist/client/_app/immutable/chunks/ITRz_Q7H2.js +0 -15
  427. package/studio-dist/client/_app/immutable/chunks/KDNP98XY2.js +0 -1
  428. package/studio-dist/client/_app/immutable/chunks/LmVjFhpk2.js +0 -1
  429. package/studio-dist/client/_app/immutable/chunks/MiAejGV_.js +0 -1
  430. package/studio-dist/client/_app/immutable/chunks/Ml4JR6Jz2.js +0 -2
  431. package/studio-dist/client/_app/immutable/chunks/NU5S3nNs2.js +0 -2
  432. package/studio-dist/client/_app/immutable/chunks/Oh5zPjv3.js +0 -2
  433. package/studio-dist/client/_app/immutable/chunks/QZkvJ9vi.js +0 -1
  434. package/studio-dist/client/_app/immutable/chunks/Rbn8UpBm.js +0 -1
  435. package/studio-dist/client/_app/immutable/chunks/SPX3gYwn2.js +0 -1
  436. package/studio-dist/client/_app/immutable/chunks/UyeQuL132.js +0 -12
  437. package/studio-dist/client/_app/immutable/chunks/VQszoXcV.js +0 -1
  438. package/studio-dist/client/_app/immutable/chunks/VtZsEMxD.js +0 -1
  439. package/studio-dist/client/_app/immutable/chunks/W2IeK61p2.js +0 -1
  440. package/studio-dist/client/_app/immutable/chunks/Wadww7ZG.js +0 -1
  441. package/studio-dist/client/_app/immutable/chunks/WgfA1zWk2.js +0 -1
  442. package/studio-dist/client/_app/immutable/chunks/WxIgAS1f.js +0 -21
  443. package/studio-dist/client/_app/immutable/chunks/XrsBoOj9.js +0 -2
  444. package/studio-dist/client/_app/immutable/chunks/Y3PX4poR.js +0 -2
  445. package/studio-dist/client/_app/immutable/chunks/YxfwqiF32.js +0 -1
  446. package/studio-dist/client/_app/immutable/chunks/Z7uO1YaO.js +0 -1
  447. package/studio-dist/client/_app/immutable/chunks/_4ykQk_8.js +0 -1
  448. package/studio-dist/client/_app/immutable/chunks/_kRcZFVn.js +0 -1
  449. package/studio-dist/client/_app/immutable/chunks/aQNM7eVA2.js +0 -1
  450. package/studio-dist/client/_app/immutable/chunks/bHKz_Fb2.js +0 -45
  451. package/studio-dist/client/_app/immutable/chunks/bJRyRGSZ.js +0 -1
  452. package/studio-dist/client/_app/immutable/chunks/dlzF6lnR.js +0 -1
  453. package/studio-dist/client/_app/immutable/chunks/fjRbpfEr2.js +0 -26
  454. package/studio-dist/client/_app/immutable/chunks/imLk4if5.js +0 -3
  455. package/studio-dist/client/_app/immutable/chunks/lgRG0R-32.js +0 -1
  456. package/studio-dist/client/_app/immutable/chunks/mFhCjxQJ2.js +0 -1
  457. package/studio-dist/client/_app/immutable/chunks/mgnH6qYg.js +0 -1
  458. package/studio-dist/client/_app/immutable/chunks/n6M_5LoW.js +0 -1
  459. package/studio-dist/client/_app/immutable/chunks/nLIOiDZp2.js +0 -1
  460. package/studio-dist/client/_app/immutable/chunks/nab7WB3u2.js +0 -1
  461. package/studio-dist/client/_app/immutable/chunks/orUmAKoW2.js +0 -1
  462. package/studio-dist/client/_app/immutable/chunks/p-fLQDfY2.js +0 -63
  463. package/studio-dist/client/_app/immutable/chunks/s0-WkOGk2.js +0 -1
  464. package/studio-dist/client/_app/immutable/chunks/sPuXfbUF.js +0 -1
  465. package/studio-dist/client/_app/immutable/chunks/t4h0vrqR2.js +0 -2
  466. package/studio-dist/client/_app/immutable/chunks/uE83FKL-.js +0 -1
  467. package/studio-dist/client/_app/immutable/chunks/uzhXlm0g2.js +0 -1
  468. package/studio-dist/client/_app/immutable/chunks/vTrEsGlD2.js +0 -1
  469. package/studio-dist/client/_app/immutable/chunks/xJgCk3dx2.js +0 -28
  470. package/studio-dist/client/_app/immutable/chunks/z0Mt579s2.js +0 -13
  471. package/studio-dist/client/_app/immutable/entry/app.BoQ7cgT2.js +0 -2
  472. package/studio-dist/client/_app/immutable/entry/start.Cg1v8DU4.js +0 -1
  473. package/studio-dist/client/_app/immutable/nodes/25.ddZ1_EFk.js +0 -1
  474. package/studio-dist/client/_app/immutable/nodes/26.6OcmQbLV.js +0 -1
  475. package/studio-dist/client/_app/immutable/nodes/4.CCjOEwVU.js +0 -1
  476. package/studio-dist/server/chunks/0-CvsKDxQ6.js +0 -126
  477. package/studio-dist/server/chunks/1-ck_x2Hf0.js +0 -9
  478. package/studio-dist/server/chunks/20-DVU22np7.js +0 -142
  479. package/studio-dist/server/chunks/23-D_tUnnoc.js +0 -181
  480. package/studio-dist/server/chunks/24-CVlymvLF.js +0 -151
  481. package/studio-dist/server/chunks/AwsRestJsonProtocol-BZuoNBmL.js +0 -416
  482. package/studio-dist/server/chunks/UnionSerde-BflIkhKj.js +0 -2653
  483. package/studio-dist/server/chunks/_dispatch-ncuRztUQ.js +0 -5597
  484. package/studio-dist/server/chunks/_server.ts-4NyKAneX.js +0 -116
  485. package/studio-dist/server/chunks/_server.ts-7hGdxdEI.js +0 -136
  486. package/studio-dist/server/chunks/_server.ts-B6b3b-yA.js +0 -131
  487. package/studio-dist/server/chunks/_server.ts-BOgGqaZp.js +0 -144
  488. package/studio-dist/server/chunks/_server.ts-BV1HiKZ0.js +0 -120
  489. package/studio-dist/server/chunks/_server.ts-BWp3g6Cr.js +0 -110
  490. package/studio-dist/server/chunks/_server.ts-C5NF-UgC.js +0 -122
  491. package/studio-dist/server/chunks/_server.ts-CDMurH5m.js +0 -2301
  492. package/studio-dist/server/chunks/_server.ts-COJHCgbq.js +0 -127
  493. package/studio-dist/server/chunks/_server.ts-CUl-Utkt.js +0 -148
  494. package/studio-dist/server/chunks/_server.ts-D0GAFJRb.js +0 -156
  495. package/studio-dist/server/chunks/_server.ts-DA6p3pmi.js +0 -117
  496. package/studio-dist/server/chunks/_server.ts-DThYFaZj.js +0 -160
  497. package/studio-dist/server/chunks/_server.ts-D_3Pm0gr.js +0 -179
  498. package/studio-dist/server/chunks/_server.ts-DcHxdMsm.js +0 -144
  499. package/studio-dist/server/chunks/_server.ts-Dlfadh2I.js +0 -144
  500. package/studio-dist/server/chunks/_server.ts-JallJnps.js +0 -240
  501. package/studio-dist/server/chunks/_server.ts-NEFTbJpM.js +0 -142
  502. package/studio-dist/server/chunks/_server.ts-d-2NvRwX.js +0 -122
  503. package/studio-dist/server/chunks/_server.ts-rUWMLkIw.js +0 -146
  504. package/studio-dist/server/chunks/abort-events-CDRAJSqb.js +0 -30
  505. package/studio-dist/server/chunks/adapters-BkMgPNUM.js +0 -359
  506. package/studio-dist/server/chunks/adapters2-CQ3PzoaK.js +0 -28
  507. package/studio-dist/server/chunks/api-mode2-3W-I1-XO.js +0 -36
  508. package/studio-dist/server/chunks/api2-CmNQzb0b.js +0 -96
  509. package/studio-dist/server/chunks/attention-consolidate-CidG1jfH.js +0 -485
  510. package/studio-dist/server/chunks/audit3-DMO_fUGp.js +0 -52
  511. package/studio-dist/server/chunks/auto-extract-B84La1Vs.js +0 -34
  512. package/studio-dist/server/chunks/brain-consolidator-JGm6mzFf.js +0 -373
  513. package/studio-dist/server/chunks/brain-doctor-Dc2WqVbs.js +0 -311
  514. package/studio-dist/server/chunks/brain-embedding-Djw9ekXu.js +0 -42
  515. package/studio-dist/server/chunks/brain-lifecycle-BOOa876K.js +0 -969
  516. package/studio-dist/server/chunks/brain-maintenance-U6VvsvmK.js +0 -133
  517. package/studio-dist/server/chunks/brain-maintenance2-CwVSmfDT.js +0 -43
  518. package/studio-dist/server/chunks/brain-retrieval2-exR8mdgh.js +0 -751
  519. package/studio-dist/server/chunks/brain-search-CpSh-SNi.js +0 -48
  520. package/studio-dist/server/chunks/brain-search2-CwI2cXgg.js +0 -716
  521. package/studio-dist/server/chunks/brain-similarity-DI4yzm9e.js +0 -113
  522. package/studio-dist/server/chunks/brain-stdp2-BxSBIlSr.js +0 -960
  523. package/studio-dist/server/chunks/brain-writer-handlers-Dwu0QCAA.js +0 -188
  524. package/studio-dist/server/chunks/brain-writer-thread-6n7wI7Gy.js +0 -360
  525. package/studio-dist/server/chunks/branch-lock-Cq7zzmFK.js +0 -1118
  526. package/studio-dist/server/chunks/branch-lock2-CmDc4CZW.js +0 -45
  527. package/studio-dist/server/chunks/cancelled-child-waiver-audit-DkesrN83.js +0 -54
  528. package/studio-dist/server/chunks/catalog-model-resolver-Bas9qnoP.js +0 -9
  529. package/studio-dist/server/chunks/catalog-model-resolver2-Cexk2TN0.js +0 -237
  530. package/studio-dist/server/chunks/catalog-resolver-CLot3XJO.js +0 -260
  531. package/studio-dist/server/chunks/catalog-resolver2-JmKb91kY.js +0 -33
  532. package/studio-dist/server/chunks/chunk-DDP5xfzh.js +0 -39
  533. package/studio-dist/server/chunks/cleo-home-CCfe6kAQ.js +0 -64
  534. package/studio-dist/server/chunks/concrete-session2-bb_vxJ2N.js +0 -43
  535. package/studio-dist/server/chunks/config-DHj9CcFs.js +0 -386
  536. package/studio-dist/server/chunks/config2-F6PPIn7D.js +0 -35
  537. package/studio-dist/server/chunks/connections-BMGJVYcS.js +0 -98
  538. package/studio-dist/server/chunks/credential-pool-vF6NdtT9.js +0 -4600
  539. package/studio-dist/server/chunks/credentials-C7EJFf5O.js +0 -35
  540. package/studio-dist/server/chunks/credentials-store-CFygIuB2.js +0 -35
  541. package/studio-dist/server/chunks/cross-db-cleanup-1LWTUKPi.js +0 -147
  542. package/studio-dist/server/chunks/data-accessor-CWgVndbR.js +0 -72
  543. package/studio-dist/server/chunks/data-safety-central-DaFxK27G.js +0 -683
  544. package/studio-dist/server/chunks/decisions-CmVXV2-H.js +0 -574
  545. package/studio-dist/server/chunks/decisions2-XQsSYdxa.js +0 -48
  546. package/studio-dist/server/chunks/depends-waiver-audit-CUmlBYHx.js +0 -56
  547. package/studio-dist/server/chunks/dialectic-evaluator2-BOyyXhdI.js +0 -96
  548. package/studio-dist/server/chunks/dist-es2-DQy2Mjn_.js +0 -43
  549. package/studio-dist/server/chunks/dist-es3-CBygU4n5.js +0 -365
  550. package/studio-dist/server/chunks/dist-es4-CavbNu4B.js +0 -162
  551. package/studio-dist/server/chunks/dist-es5-Crpm8Wsl.js +0 -465
  552. package/studio-dist/server/chunks/dist-es6-CgePHzh4.js +0 -99
  553. package/studio-dist/server/chunks/dist-es7-k6c5GEd7.js +0 -311
  554. package/studio-dist/server/chunks/dist-es8-Bd9v0_pE.js +0 -91
  555. package/studio-dist/server/chunks/dist3-23bsJhRw.js +0 -27
  556. package/studio-dist/server/chunks/dist4-DmC1zSXi.js +0 -35618
  557. package/studio-dist/server/chunks/dual-scope-db-DKnWu1KM.js +0 -5672
  558. package/studio-dist/server/chunks/dual-scope-db2-Dht3luRw.js +0 -31
  559. package/studio-dist/server/chunks/duplicate-bypass-audit-BN5w1H2y.js +0 -36
  560. package/studio-dist/server/chunks/duplicate-detector-CaVFMKdE.js +0 -692
  561. package/studio-dist/server/chunks/edge-types-CgBvb0w1.js +0 -28
  562. package/studio-dist/server/chunks/embedding-local-xEnym8Ho.js +0 -92
  563. package/studio-dist/server/chunks/event-streams-Bi2p_fVo.js +0 -232
  564. package/studio-dist/server/chunks/execution-learning-BVCw7Plq.js +0 -278
  565. package/studio-dist/server/chunks/exodus-BBuuN6aA.js +0 -2594
  566. package/studio-dist/server/chunks/externalDataInterceptor-C3C4cObe.js +0 -21
  567. package/studio-dist/server/chunks/extraction-gate-DDNBq7gu.js +0 -48
  568. package/studio-dist/server/chunks/extraction-gate2-D77EZBQ6.js +0 -375
  569. package/studio-dist/server/chunks/getSSOTokenFromFile-yZAJxDci.js +0 -21
  570. package/studio-dist/server/chunks/governor2-CtfV3wfX.js +0 -923
  571. package/studio-dist/server/chunks/graph-auto-populate-Dmsr9ZJM.js +0 -42
  572. package/studio-dist/server/chunks/graph-auto-populate2-Cz0qeebM.js +0 -156
  573. package/studio-dist/server/chunks/graph-memory-bridge-CY8ft_d7.js +0 -43
  574. package/studio-dist/server/chunks/graph-memory-bridge2-Cd0XZwqc.js +0 -429
  575. package/studio-dist/server/chunks/handler-helpers-AANBGA_p.js +0 -49
  576. package/studio-dist/server/chunks/hash-Cm6yMET4.js +0 -14
  577. package/studio-dist/server/chunks/health-monitor-FepUZN1m.js +0 -106
  578. package/studio-dist/server/chunks/identity-DRpurt56.js +0 -30
  579. package/studio-dist/server/chunks/identity2-DeAB5RXE.js +0 -114
  580. package/studio-dist/server/chunks/index-61916rmV.js +0 -632
  581. package/studio-dist/server/chunks/index-DAgVDouG.js +0 -268
  582. package/studio-dist/server/chunks/index-Dtt_h58T.js +0 -2116
  583. package/studio-dist/server/chunks/indexes-DZ52grq6.js +0 -615
  584. package/studio-dist/server/chunks/json-Bfixam3g.js +0 -126
  585. package/studio-dist/server/chunks/learnings-D6Y3YGMU.js +0 -47
  586. package/studio-dist/server/chunks/learnings2-MwBdR9vb.js +0 -171
  587. package/studio-dist/server/chunks/lifecycle-ops2-V2CR5-7X.js +0 -121
  588. package/studio-dist/server/chunks/list-BhObczvn.js +0 -229
  589. package/studio-dist/server/chunks/living-brain2-j0B_Ir8j.js +0 -285
  590. package/studio-dist/server/chunks/llm-backend-resolver-D4ANvUPL.js +0 -295
  591. package/studio-dist/server/chunks/llm-extraction-DVCu4heR.js +0 -408
  592. package/studio-dist/server/chunks/loadSharedConfigFiles-umvW7kuz.js +0 -73
  593. package/studio-dist/server/chunks/loadSso-iidd5iKx.js +0 -599
  594. package/studio-dist/server/chunks/local-model-fit-B0TxifnX.js +0 -53
  595. package/studio-dist/server/chunks/local-model-fit2-BMqo6GJm.js +0 -2664
  596. package/studio-dist/server/chunks/lock-CA7d6lj7.js +0 -57
  597. package/studio-dist/server/chunks/logger-DQIoyZbq.js +0 -34
  598. package/studio-dist/server/chunks/memory-BUbicBbE.js +0 -618
  599. package/studio-dist/server/chunks/memory-accessor-i3P-RHSg.js +0 -42
  600. package/studio-dist/server/chunks/memory-accessor2-B981tHDR.js +0 -467
  601. package/studio-dist/server/chunks/memory-bridge-BOlEWg9w.js +0 -424
  602. package/studio-dist/server/chunks/memory-sqlite2-B7dq8doO.js +0 -42
  603. package/studio-dist/server/chunks/model-runner2-B71xjp4y.js +0 -43
  604. package/studio-dist/server/chunks/mvi-helpers-BNsJ8ZpQ.js +0 -59
  605. package/studio-dist/server/chunks/nexus-plasticity-BTfU8cW_.js +0 -269
  606. package/studio-dist/server/chunks/nexus-risk-audit-p379-TU5.js +0 -54
  607. package/studio-dist/server/chunks/nexus-schema-zeygY8OD.js +0 -11
  608. package/studio-dist/server/chunks/nexus-schema2-DP0Qd7CA.js +0 -444
  609. package/studio-dist/server/chunks/nexus-sqlite-BVNIpwc7.js +0 -36
  610. package/studio-dist/server/chunks/nexus-sqlite2-BBfY2xnY.js +0 -1103
  611. package/studio-dist/server/chunks/node-http-handler-CJI8kn7X.js +0 -386
  612. package/studio-dist/server/chunks/observe-Bo9veHGe.js +0 -47
  613. package/studio-dist/server/chunks/observe2-wzO0MaMY.js +0 -245
  614. package/studio-dist/server/chunks/observer-reflector-BwNOuq1d.js +0 -62
  615. package/studio-dist/server/chunks/observer-reflector2-ggtC1hmk.js +0 -478
  616. package/studio-dist/server/chunks/on-open-Wpcw7DV4.js +0 -740
  617. package/studio-dist/server/chunks/open-cleo-db--8sYNaYY.js +0 -70
  618. package/studio-dist/server/chunks/parseKnownFiles-Dg0pMFcT.js +0 -18
  619. package/studio-dist/server/chunks/parseUrl-qgGfl-Vh.js +0 -120
  620. package/studio-dist/server/chunks/paths-CV7S5-Zm.js +0 -30012
  621. package/studio-dist/server/chunks/patterns-lh5kn13I.js +0 -48
  622. package/studio-dist/server/chunks/patterns2-CzIcT-ai.js +0 -177
  623. package/studio-dist/server/chunks/pipeline-stage-IB_pOft-.js +0 -63
  624. package/studio-dist/server/chunks/prediction-D1lSGJ4c.js +0 -217
  625. package/studio-dist/server/chunks/premature-close-audit-VG09uAco.js +0 -53
  626. package/studio-dist/server/chunks/project-context-BjiqfEBi.js +0 -166
  627. package/studio-dist/server/chunks/provider-registry2-Cu7rAF0R.js +0 -35
  628. package/studio-dist/server/chunks/quality-feedback-CuCGEI6S.js +0 -371
  629. package/studio-dist/server/chunks/quality-scoring-CCPuRQmJ.js +0 -160
  630. package/studio-dist/server/chunks/query-ops-DCrjbZb9.js +0 -78
  631. package/studio-dist/server/chunks/query-promise-BkPmGPJy.js +0 -1257
  632. package/studio-dist/server/chunks/readFile-CxwXhymG.js +0 -31
  633. package/studio-dist/server/chunks/registry-CL2gN1zd.js +0 -27
  634. package/studio-dist/server/chunks/registry2-DoPYJnUW.js +0 -233
  635. package/studio-dist/server/chunks/registry3-BajEb-ll.js +0 -42
  636. package/studio-dist/server/chunks/registry4-DCqffZkT.js +0 -142
  637. package/studio-dist/server/chunks/retry-B3oEKYpp.js +0 -105
  638. package/studio-dist/server/chunks/role-executor-CC8BU54L.js +0 -25379
  639. package/studio-dist/server/chunks/role-resolver-D9QaDT0e.js +0 -6544
  640. package/studio-dist/server/chunks/role-resolver2-nkhzS1oy.js +0 -37
  641. package/studio-dist/server/chunks/rollup-CiefjN_Y.js +0 -194
  642. package/studio-dist/server/chunks/safety-data-accessor-CniDmGJO.js +0 -276
  643. package/studio-dist/server/chunks/service-connections-accessor-yBCdcgU-.js +0 -282
  644. package/studio-dist/server/chunks/session-factory-vbengc42.js +0 -96
  645. package/studio-dist/server/chunks/session-grade-BF4ZZtN_.js +0 -244
  646. package/studio-dist/server/chunks/session-journal-Bvey70Ur.js +0 -235
  647. package/studio-dist/server/chunks/session-narrative-BirZINSU.js +0 -304
  648. package/studio-dist/server/chunks/session-store--N_xHxUH.js +0 -557
  649. package/studio-dist/server/chunks/session-store2-DAM-eVTH.js +0 -43
  650. package/studio-dist/server/chunks/sessions-33DD4hkt.js +0 -2273
  651. package/studio-dist/server/chunks/sessions2-BHswKRDP.js +0 -50
  652. package/studio-dist/server/chunks/sigil-B-RznFXz.js +0 -33
  653. package/studio-dist/server/chunks/sigil2-jRMG5DuR.js +0 -47
  654. package/studio-dist/server/chunks/signin-BAsW3mM8.js +0 -644
  655. package/studio-dist/server/chunks/sleep-consolidation-BnSwXrvq.js +0 -823
  656. package/studio-dist/server/chunks/specialists-B_ZbpQlK.js +0 -441
  657. package/studio-dist/server/chunks/sqlite-Bm5LzbPs.js +0 -5977
  658. package/studio-dist/server/chunks/sqlite-backup-CFG1-b2e.js +0 -42
  659. package/studio-dist/server/chunks/sqlite-data-accessor-DD_RFX6r.js +0 -987
  660. package/studio-dist/server/chunks/sqlite-data-accessor2-CRmgNBnq.js +0 -44
  661. package/studio-dist/server/chunks/sqlite-native-BwlIqtbs.js +0 -42
  662. package/studio-dist/server/chunks/sqlite2-CUPut2fh.js +0 -42
  663. package/studio-dist/server/chunks/src2-DK8DKufK.js +0 -290
  664. package/studio-dist/server/chunks/src3-BJjf7XRG.js +0 -798
  665. package/studio-dist/server/chunks/src4-DNleK_t_.js +0 -28
  666. package/studio-dist/server/chunks/sso-oidc-eUvwseuL.js +0 -861
  667. package/studio-dist/server/chunks/storage-B9UA3BHR.js +0 -198
  668. package/studio-dist/server/chunks/sts-g7LDMLEd.js +0 -4655
  669. package/studio-dist/server/chunks/surprisal-CwTfWQi8.js +0 -224
  670. package/studio-dist/server/chunks/surprisal-tree-7dfHsuw1.js +0 -230
  671. package/studio-dist/server/chunks/system-resolver2-Bzhue_zQ.js +0 -53
  672. package/studio-dist/server/chunks/tasks-SHX7UPGY.js +0 -19694
  673. package/studio-dist/server/chunks/tasks-schema2-CTMoQKQy.js +0 -42
  674. package/studio-dist/server/chunks/temporal-supersession-DP4GPG_B.js +0 -298
  675. package/studio-dist/server/chunks/token-cAhAD9f9.js +0 -139
  676. package/studio-dist/server/chunks/token-util-D6drEz8_.js +0 -353
  677. package/studio-dist/server/chunks/token-util2-8UPSGvbb.js +0 -97
  678. package/studio-dist/server/chunks/transcript-scanner-kJWhWmlg.js +0 -53
  679. package/studio-dist/server/chunks/transformers.node-_xbxmK5p.js +0 -38475
  680. package/studio-dist/server/chunks/typed-query-l1D_QElt.js +0 -18
  681. package/studio-dist/server/chunks/unique-constraint-BZrrwgwc.js +0 -88
  682. package/studio-dist/server/chunks/update-B2YVBZG0.js +0 -1594
  683. package/studio-dist/server/chunks/user-profile-B61ZhVmC.js +0 -89
  684. package/studio-dist/server/chunks/utils-BPSZyajG.js +0 -1176
  685. package/studio-dist/server/chunks/validation-rules-CE27rtoF.js +0 -44
  686. package/studio-dist/server/chunks/zod-DsAeKNLm.js +0 -141
  687. /package/studio-dist/client/_app/immutable/chunks/{CQCxd1p9.js → B8WnZMYa.js} +0 -0
  688. /package/studio-dist/client/_app/immutable/chunks/{D13Quv9e2.js → B_0_YCYG.js} +0 -0
  689. /package/studio-dist/client/_app/immutable/chunks/{djL6V-tr.js → CqgtdAx_.js} +0 -0
  690. /package/studio-dist/client/_app/immutable/chunks/{_FU7h_8p2.js → KrUPZzXZ.js} +0 -0
@@ -1,2594 +0,0 @@
1
- import { y as resolveCleoDir, b as getLogger, g as getCleoHome } from './paths-CV7S5-Zm.js';
2
- import { r as resolveDualScopeDbPath, o as openDualScopeDbAtPath } from './dual-scope-db-DKnWu1KM.js';
3
- import { g as getCleoVersion } from './on-open-Wpcw7DV4.js';
4
- import { o as openCleoDbSnapshot } from './open-cleo-db--8sYNaYY.js';
5
- import { createRequire } from 'node:module';
6
- import { existsSync, unlinkSync, mkdirSync, copyFileSync, statSync, statfsSync, readdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
7
- import { join } from 'node:path';
8
- import 'node:os';
9
- import 'node:events';
10
- import 'node:diagnostics_channel';
11
- import 'fs';
12
- import 'events';
13
- import 'util';
14
- import 'path';
15
- import 'assert';
16
- import 'worker_threads';
17
- import 'module';
18
- import 'node:url';
19
- import 'url';
20
- import 'buffer';
21
- import 'node:child_process';
22
- import 'node:crypto';
23
- import 'node:process';
24
- import 'node:async_hooks';
25
- import './indexes-DZ52grq6.js';
26
- import './utils-BPSZyajG.js';
27
- import './query-promise-BkPmGPJy.js';
28
- import './lock-CA7d6lj7.js';
29
- import './index-Dtt_h58T.js';
30
- import 'constants';
31
- import 'stream';
32
- import 'node:fs/promises';
33
- import './index-61916rmV.js';
34
-
35
- //#region ../core/dist/store/exodus/table-name-map.js
36
- /**
37
- * Deterministic legacy-to-consolidated table-name resolver for exodus migration.
38
- *
39
- * ## Problem (ROOT CAUSE 1 — T11532)
40
- *
41
- * Legacy source DBs use UNPREFIXED table names (`tasks`, `messages`, `skills`, …)
42
- * while the consolidated dual-scope `cleo.db` uses DOMAIN-PREFIXED names
43
- * (`tasks_tasks`, `conduit_messages`, `skills_skills`, …). Without a mapping,
44
- * every `INSERT OR IGNORE INTO main."<name>"` silently copies 0 rows because the
45
- * target table is absent under the legacy name.
46
- *
47
- * ## Design
48
- *
49
- * The mapping is a static lookup table derived from reading every
50
- * `cleo-project/` and `cleo-global/` schema file and matching the physical
51
- * `sqliteTable('<consolidated-name>', …)` names against each legacy DB's schema.
52
- *
53
- * Rules:
54
- * - If a legacy table is already domain-prefixed in the consolidated schema
55
- * (e.g. `brain_observations`, `nexus_audit_log`, `tasks_goal`), map identity.
56
- * - If a legacy table has NO consolidated counterpart (virtual tables, orphan
57
- * telemetry tables, …), return `null` so the caller can log + skip explicitly
58
- * rather than silently discarding rows.
59
- *
60
- * ## Source-DB scope
61
- *
62
- * The resolver takes a `sourceName` (the `LegacyDbDescriptor.name` value, e.g.
63
- * `"tasks"`, `"brain (project)"`, `"conduit"`) to disambiguate tables that
64
- * share the same legacy name across multiple DBs (e.g. `"attachments"` lives in
65
- * both conduit.db and attachments.ts/tasks.db; `"sessions"` lives in both
66
- * tasks.db and signaldock.db).
67
- *
68
- * ## Nexus code-graph residency move (ADR-090 · T11539)
69
- *
70
- * The four nexus code-graph tables — `nexus_nodes`, `nexus_relations`,
71
- * `nexus_contracts`, `nexus_code_index` (ADR-090 "Category A") — were removed
72
- * from the GLOBAL schema (`../schema/cleo-global/nexus.ts`) and now reside in
73
- * PROJECT scope (`../schema/cleo-project/nexus-graph.ts`). They are still
74
- * extracted from the legacy GLOBAL `nexus.db` source, but exodus MUST route them
75
- * into the PROJECT-scope consolidated `cleo.db`, not the global one. The source
76
- * descriptor `nexus` carries `targetScope: 'global'`, so a per-table scope
77
- * override is required for these four tables — see
78
- * {@link NEXUS_GRAPH_PROJECT_TABLES} and {@link resolveTableTargetScope}. The
79
- * six registry/identity tables (`nexus_project_registry`,
80
- * `nexus_project_id_aliases`, `nexus_audit_log`, `nexus_schema_meta`,
81
- * `nexus_user_profile`, `nexus_sigils`) stay GLOBAL.
82
- *
83
- * @task T11532 (ROOT CAUSE 1 — name-mapping gap)
84
- * @task T11533 (ROOT CAUSE 3 — signaldock skills mapping + brain_release_links skip +
85
- * brain_session_narrative mapping)
86
- * @task T11546 (no-home-table fixes — schema_meta→tasks_schema_meta, brain_usage_log mapping,
87
- * brain_schema_meta mapping)
88
- * @task T11539 (nexus code-graph residency — route the 4 graph tables to PROJECT scope)
89
- * @epic T11248
90
- * @saga T11242
91
- */
92
- /**
93
- * tasks.db — tables from tasks-schema.ts (tasks.ts, audit.ts, background-jobs.ts,
94
- * chain-schema.ts, agent-schema.ts, lifecycle.ts, evidence-bindings.ts,
95
- * experiments.ts, attachments.ts, manifest.ts, code-index.ts, playbooks.ts,
96
- * provenance/releases.ts, provenance/commits.ts, provenance/pull-requests.ts,
97
- * goal.ts) all prefixed with `tasks_` in the consolidated schema, except the
98
- * docs tables which become `docs_*`.
99
- */
100
- var TASKS_DB_MAP = new Map([
101
- ["tasks", "tasks_tasks"],
102
- ["task_acceptance_criteria", "tasks_task_acceptance_criteria"],
103
- ["acceptance_projection_state", "tasks_acceptance_projection_state"],
104
- ["acceptance_projection_dirty", "tasks_acceptance_projection_dirty"],
105
- ["task_dependencies", "tasks_task_dependencies"],
106
- ["task_labels", "tasks_task_labels"],
107
- ["task_relations", "tasks_task_relations"],
108
- ["sessions", "tasks_sessions"],
109
- ["session_handoff_entries", "tasks_session_handoff_entries"],
110
- ["task_work_history", "tasks_task_work_history"],
111
- ["task_acceptance_criteria_history", "tasks_task_acceptance_criteria_history"],
112
- ["external_task_links", "tasks_external_task_links"],
113
- ["audit_log", "tasks_audit_log"],
114
- ["token_usage", "tasks_token_usage"],
115
- ["architecture_decisions", "tasks_architecture_decisions"],
116
- ["adr_task_links", "tasks_adr_task_links"],
117
- ["adr_relations", "tasks_adr_relations"],
118
- ["status_registry", "tasks_status_registry"],
119
- ["background_jobs", "tasks_background_jobs"],
120
- ["warp_chains", "tasks_warp_chains"],
121
- ["warp_chain_instances", "tasks_warp_chain_instances"],
122
- ["agent_instances", "tasks_agent_instances"],
123
- ["agent_error_log", "tasks_agent_error_log"],
124
- ["lifecycle_pipelines", "tasks_lifecycle_pipelines"],
125
- ["lifecycle_stages", "tasks_lifecycle_stages"],
126
- ["lifecycle_gate_results", "tasks_lifecycle_gate_results"],
127
- ["lifecycle_evidence", "tasks_lifecycle_evidence"],
128
- ["lifecycle_transitions", "tasks_lifecycle_transitions"],
129
- ["evidence_ac_bindings", "tasks_evidence_ac_bindings"],
130
- ["experiments", "tasks_experiments"],
131
- ["attachments", "docs_attachments"],
132
- ["attachment_refs", "docs_attachment_refs"],
133
- ["manifest_entries", "docs_manifest_entries"],
134
- ["pipeline_manifest", "docs_pipeline_manifest"],
135
- ["commits", "tasks_commits"],
136
- ["task_commits", "tasks_task_commits"],
137
- ["commit_files", "tasks_commit_files"],
138
- ["pull_requests", "tasks_pull_requests"],
139
- ["pr_commits", "tasks_pr_commits"],
140
- ["pr_tasks", "tasks_pr_tasks"],
141
- ["releases", "tasks_releases"],
142
- ["release_commits", "tasks_release_commits"],
143
- ["release_changes", "tasks_release_changes"],
144
- ["release_changesets", "tasks_release_changesets"],
145
- ["release_artifacts", "tasks_release_artifacts"],
146
- ["playbook_runs", "tasks_playbook_runs"],
147
- ["playbook_approvals", "tasks_playbook_approvals"],
148
- ["tasks_goal", "tasks_goal"],
149
- ["schema_meta", "tasks_schema_meta"],
150
- ["agent_credentials", "tasks_agent_credentials"],
151
- ["brain_release_links", "tasks_brain_release_links"]
152
- ]);
153
- /**
154
- * brain.db (project) and global brain.db — tables from memory-schema.ts /
155
- * cleo-shared/brain.ts.
156
- *
157
- * Most tables are already `brain_*` prefixed in the legacy schema and keep the
158
- * same name in the consolidated schema. Three exceptions:
159
- * - `sticky_tags` → `brain_sticky_tags` (lost prefix in legacy)
160
- * - `deriver_queue` → `brain_deriver_queue` (lost prefix in legacy)
161
- * - `session_narrative` → `brain_session_narrative` (lost prefix in legacy)
162
- *
163
- * Tables present in the live DB but NOT in the consolidated target schema
164
- * (virtual tables, orphan telemetry, etc.) map to `null` — they will be
165
- * logged as explicit skips rather than silently discarded.
166
- *
167
- * ## brain_release_links (T11533 → T11549 → T11550 fix)
168
- *
169
- * T11533 incorrectly marked `brain_release_links` as `null` (skip). T11549 added
170
- * the target table `tasks_brain_release_links` to `cleo-project/provenance-orphans.ts`
171
- * and mapped the table here in BRAIN_DB_MAP — but T11550 discovered that the 8 rows
172
- * physically live in **tasks.db**, not brain.db (verified via sqlite_master). The
173
- * correct mapping belongs in TASKS_DB_MAP (added there in T11550). The BRAIN_DB_MAP
174
- * entry is now `null` (skip) to guard against double-migration if brain.db ever
175
- * contains a phantom table by this name.
176
- *
177
- * ## agent_credentials (T11549 → T11550 fix)
178
- *
179
- * Same situation as brain_release_links: the 3 rows physically live in **tasks.db**,
180
- * not brain.db. T11549 added the target table and mapped it here in BRAIN_DB_MAP,
181
- * but the source is tasks.db. T11550 moves the mapping to TASKS_DB_MAP and sets
182
- * this entry to `null` (skip) to prevent double-migration.
183
- */
184
- var BRAIN_DB_MAP = new Map([
185
- ["brain_decisions", "brain_decisions"],
186
- ["brain_patterns", "brain_patterns"],
187
- ["brain_learnings", "brain_learnings"],
188
- ["brain_observations", "brain_observations"],
189
- ["brain_sticky_notes", "brain_sticky_notes"],
190
- ["brain_attention", "brain_attention"],
191
- ["brain_memory_links", "brain_memory_links"],
192
- ["brain_page_nodes", "brain_page_nodes"],
193
- ["brain_page_edges", "brain_page_edges"],
194
- ["brain_retrieval_log", "brain_retrieval_log"],
195
- ["brain_plasticity_events", "brain_plasticity_events"],
196
- ["brain_weight_history", "brain_weight_history"],
197
- ["brain_modulators", "brain_modulators"],
198
- ["brain_consolidation_events", "brain_consolidation_events"],
199
- ["brain_transcript_events", "brain_transcript_events"],
200
- ["brain_promotion_log", "brain_promotion_log"],
201
- ["brain_backfill_runs", "brain_backfill_runs"],
202
- ["brain_memory_trees", "brain_memory_trees"],
203
- ["brain_observations_staging", "brain_observations_staging"],
204
- ["brain_release_links", null],
205
- ["brain_schema_meta", "brain_schema_meta"],
206
- ["sticky_tags", "brain_sticky_tags"],
207
- ["deriver_queue", "brain_deriver_queue"],
208
- ["session_narrative", "brain_session_narrative"],
209
- ["brain_usage_log", "brain_usage_log"],
210
- ["brain_task_observations", null],
211
- ["brain_embeddings", null],
212
- ["brain_embeddings_info", null],
213
- ["agent_credentials", null]
214
- ]);
215
- /**
216
- * conduit.db — tables from conduit-schema.ts, all prefixed `conduit_*`.
217
- *
218
- * Note: conduit.db also has an `attachments` table (conduit attachment tracking),
219
- * which maps to `conduit_attachments` — NOT `docs_attachments` (that is from
220
- * tasks.db/attachments.ts).
221
- */
222
- var CONDUIT_DB_MAP = new Map([
223
- ["messages", "conduit_messages"],
224
- ["delivery_jobs", "conduit_delivery_jobs"],
225
- ["dead_letters", "conduit_dead_letters"],
226
- ["message_pins", "conduit_message_pins"],
227
- ["attachments", "conduit_attachments"],
228
- ["attachment_versions", "conduit_attachment_versions"],
229
- ["attachment_approvals", "conduit_attachment_approvals"],
230
- ["attachment_contributors", "conduit_attachment_contributors"],
231
- ["topics", "conduit_topics"],
232
- ["topic_subscriptions", "conduit_topic_subscriptions"],
233
- ["topic_messages", "conduit_topic_messages"],
234
- ["topic_message_acks", "conduit_topic_message_acks"],
235
- ["conversations", "conduit_conversations"],
236
- ["project_agent_refs", "conduit_project_agent_refs"]
237
- ]);
238
- /**
239
- * nexus.db — tables from nexus-schema.ts.
240
- *
241
- * Some tables are ALREADY prefixed (`nexus_audit_log`, `nexus_nodes`, etc.) and
242
- * map identity. Others lack the prefix and gain `nexus_` in consolidated.
243
- *
244
- * ## Scope split (ADR-090 · T11539)
245
- *
246
- * The legacy `nexus.db` source descriptor carries `targetScope: 'global'`, but
247
- * the four code-graph tables (`code_index`/`nexus_code_index`, `nexus_nodes`,
248
- * `nexus_relations`, `nexus_contracts`) now live in PROJECT scope. Their
249
- * consolidated NAME is unchanged (identity / `nexus_code_index`), but their
250
- * destination DB is the PROJECT `cleo.db` — see {@link NEXUS_GRAPH_PROJECT_TABLES}
251
- * and {@link resolveTableTargetScope}. The remaining six registry/identity
252
- * tables stay GLOBAL.
253
- */
254
- var NEXUS_DB_MAP = new Map([
255
- ["project_registry", "nexus_project_registry"],
256
- ["project_id_aliases", "nexus_project_id_aliases"],
257
- ["user_profile", "nexus_user_profile"],
258
- ["sigils", "nexus_sigils"],
259
- ["code_index", "nexus_code_index"],
260
- ["nexus_audit_log", "nexus_audit_log"],
261
- ["nexus_nodes", "nexus_nodes"],
262
- ["nexus_relations", "nexus_relations"],
263
- ["nexus_relation_weights", "nexus_relation_weights"],
264
- ["nexus_contracts", "nexus_contracts"],
265
- ["nexus_schema_meta", "nexus_schema_meta"]
266
- ]);
267
- /**
268
- * Legacy table names of the four nexus code-graph tables that moved from GLOBAL
269
- * to PROJECT scope (ADR-090 · T11538/T11539).
270
- *
271
- * Keyed by the LEGACY physical name as it appears in `nexus.db`:
272
- * - `nexus_nodes`, `nexus_relations`, `nexus_contracts` — already prefixed.
273
- * - `code_index` — bare in legacy `nexus.db`; → `nexus_code_index` consolidated.
274
- *
275
- * Membership drives {@link resolveTableTargetScope}: exodus copies/verifies
276
- * these against the PROJECT consolidated `cleo.db`, NOT the GLOBAL one, even
277
- * though the `nexus` source descriptor's `targetScope` is `'global'`.
278
- *
279
- * @task T11539
280
- * @epic T11248
281
- * @saga T11242
282
- * @see cleo docs fetch adr-090-nexus-graph-residency-split
283
- */
284
- var NEXUS_GRAPH_PROJECT_TABLES = new Set([
285
- "nexus_nodes",
286
- "nexus_relations",
287
- "nexus_contracts",
288
- "code_index"
289
- ]);
290
- /**
291
- * Resolve the consolidated TARGET SCOPE for a legacy source table.
292
- *
293
- * Most tables share the scope of their source DB descriptor (`sourceScope`).
294
- * The exception is the four nexus code-graph tables — they are extracted from
295
- * the GLOBAL legacy `nexus.db` but land in PROJECT scope per ADR-090 (T11539).
296
- *
297
- * This is the SSoT consumed by BOTH the exodus migrate runner (insert target DB)
298
- * and the exodus verifier (verify target DB) so the two never disagree.
299
- *
300
- * @param sourceName - `LegacyDbDescriptor.name` (e.g. `"nexus"`, `"tasks"`).
301
- * @param legacyTable - Physical table name in the legacy source DB.
302
- * @param sourceScope - The source descriptor's `targetScope`.
303
- * @returns The scope of the consolidated `cleo.db` this table copies into.
304
- *
305
- * @task T11539
306
- * @epic T11248
307
- * @saga T11242
308
- */
309
- function resolveTableTargetScope(sourceName, legacyTable, sourceScope) {
310
- if (inferSourceKind(sourceName) === "nexus" && NEXUS_GRAPH_PROJECT_TABLES.has(legacyTable)) return "project";
311
- return sourceScope;
312
- }
313
- /**
314
- * agent-registry (global) — the legacy on-disk source file is `signaldock.db`
315
- * (the source-descriptor `name` stays `"signaldock"` — see {@link inferSourceKind} —
316
- * because that is the genuine artifact name on disk). Its bare tables map to the
317
- * consolidated PREFIXED `agent_registry_*` tables (renamed from `signaldock_*`
318
- * under T11622 / SG-AGENT-IDENTITY E4).
319
- *
320
- * KEYS are the bare legacy names (the source DB still has the old shape); VALUES
321
- * are the consolidated `agent_registry_*` target names — these MUST equal the
322
- * `sqliteTable` names declared in `cleo-global/agent-registry.ts` (asserted by
323
- * the exodus-map invariant test, the DHQ-046 N→0 deficit catcher).
324
- *
325
- * Note: the legacy `signaldock.db` has a `sessions` table (identity session
326
- * management), which maps to `agent_registry_sessions` — NOT `tasks_sessions`.
327
- *
328
- * ## `skills` mapping (T11533 ROOT CAUSE 3 fix)
329
- *
330
- * The legacy `signaldock.db` contains a `skills` table (36 rows — pre-seeded skill
331
- * slug catalog). In the consolidated global schema this becomes
332
- * `agent_registry_skills`. Without this entry the legacy `skills` table fell
333
- * through to an identity mapping, looked for a `skills` table in the global
334
- * cleo.db (absent), and was silently skipped — resulting in
335
- * agent_registry_skills=0 despite 36 source rows.
336
- *
337
- * @task T11622 (signaldock_* → agent_registry_* target rename; folds T11578 AC2)
338
- */
339
- var AGENT_REGISTRY_DB_MAP = new Map([
340
- ["users", "agent_registry_users"],
341
- ["organization", "agent_registry_organization"],
342
- ["agents", "agent_registry_agents"],
343
- ["claim_codes", "agent_registry_claim_codes"],
344
- ["agent_capabilities", "agent_registry_agent_capabilities"],
345
- ["agent_skills", "agent_registry_agent_skills"],
346
- ["agent_connections", "agent_registry_agent_connections"],
347
- ["accounts", "agent_registry_accounts"],
348
- ["sessions", "agent_registry_sessions"],
349
- ["verifications", "agent_registry_verifications"],
350
- ["org_agent_keys", "agent_registry_org_agent_keys"],
351
- ["capabilities", "agent_registry_capabilities"],
352
- ["skills", "agent_registry_skills"]
353
- ]);
354
- /**
355
- * skills.db (global) — tables from skills-schema.ts, all prefixed `skills_*`.
356
- */
357
- var SKILLS_DB_MAP = new Map([
358
- ["skills", "skills_skills"],
359
- ["skill_usage", "skills_skill_usage"],
360
- ["skill_reviews", "skills_skill_reviews"],
361
- ["skill_patches", "skills_skill_patches"]
362
- ]);
363
- /**
364
- * Infer the source DB kind from `LegacyDbDescriptor.name`.
365
- *
366
- * The descriptor names used in `buildSourceDescriptors()` are:
367
- * `"tasks"`, `"brain (project)"`, `"conduit"`, `"nexus"`, `"signaldock"`, `"skills"`
368
- */
369
- function inferSourceKind(sourceName) {
370
- const n = sourceName.toLowerCase();
371
- if (n.startsWith("tasks")) return "tasks";
372
- if (n.startsWith("brain")) return "brain";
373
- if (n.startsWith("conduit")) return "conduit";
374
- if (n.startsWith("nexus")) return "nexus";
375
- if (n.startsWith("signaldock")) return "signaldock";
376
- if (n.startsWith("skills")) return "skills";
377
- return null;
378
- }
379
- /**
380
- * Internal bookkeeping tables that exist in a legacy source DB but have NO
381
- * consolidated counterpart and carry no user data — they are recreated by the
382
- * runtime, not migrated. Matched by EXACT name.
383
- *
384
- * Each legacy per-domain SQLite file (`conduit.db`, `signaldock.db`,
385
- * `skills.db`, …) carries its OWN private schema-version / migration-ledger
386
- * table that records HOW that file was built — NOT user-payload data:
387
- *
388
- * - `_conduit_meta` / `_conduit_migrations` — conduit-sqlite's own
389
- * schema-version + migration-ledger tables (see `conduit-sqlite.ts`).
390
- * - `_signaldock_meta` / `_signaldock_migrations` — agent-registry-store's own
391
- * ledger tables (same class — T11577 global-scope cutover blocker).
392
- * - `_skills_meta` — skills.db's own schema-version row (same class — T11577).
393
- *
394
- * The consolidated `cleo.db` has its own Drizzle journal (`__drizzle_*`) for the
395
- * same purpose, so these legacy ledgers have no consolidated home. Row-comparing
396
- * them would count internal ledger rows as a user-data deficit and abort the
397
- * cutover. The {@link INTERNAL_LEDGER_PATTERN} below generalises the same skip
398
- * to ANY future `_<domain>_meta` / `_<domain>_migrations` ledger so a new source
399
- * DB never re-introduces this false-positive; these exact entries remain for
400
- * documentation + as a fast path.
401
- *
402
- * @task T11572 (parity gate over-abort — internal/meta exclusion)
403
- * @task T11577 (generalise to signaldock/skills + any per-source ledger)
404
- */
405
- var INTERNAL_BOOKKEEPING_TABLES = new Set([
406
- "_conduit_meta",
407
- "_conduit_migrations",
408
- "_signaldock_meta",
409
- "_signaldock_migrations",
410
- "_skills_meta"
411
- ]);
412
- /**
413
- * Pattern matching a per-source SQLite **internal ledger** table: an underscore-
414
- * prefixed `_<domain>_meta` or `_<domain>_migrations` name where `<domain>` is a
415
- * lowercase identifier (e.g. `_conduit_meta`, `_signaldock_migrations`,
416
- * `_skills_meta`).
417
- *
418
- * Every legacy domain DB (conduit/signaldock/skills/…) materialises one of these
419
- * to track its own schema version / applied-migration history. They are private
420
- * bookkeeping — NOT migratable base data — and have no consolidated home (the
421
- * consolidated `cleo.db` keeps its own `__drizzle_*` journal). Generalising the
422
- * skip to this shape is future-proof: a NEW source DB that follows the same
423
- * `_<domain>_(meta|migrations)` convention is excluded automatically, so this
424
- * exact false-positive class can never abort the cutover again.
425
- *
426
- * The pattern is intentionally conservative — it requires the leading underscore
427
- * and the exact `_meta` / `_migrations` suffix so it can never match a real
428
- * data table (those are never underscore-prefixed in any legacy source schema).
429
- *
430
- * @task T11577 (parity gate over-abort — generalise per-source ledger skip)
431
- */
432
- var INTERNAL_LEDGER_PATTERN = /^_[a-z][a-z0-9]*_(?:meta|migrations)$/;
433
- /**
434
- * FTS5 shadow-table suffixes. A full-text index `<base>_fts` (an `fts5` VIRTUAL
435
- * TABLE — e.g. `brain_decisions_fts`, `messages_fts`) materialises a family of
436
- * backing tables `<base>_fts_data`, `<base>_fts_idx`, `<base>_fts_docsize`,
437
- * `<base>_fts_config`, and (for `content=`-less indexes) `<base>_fts_content`.
438
- *
439
- * These are DERIVED from their content table and are REBUILT post-migration
440
- * (`brain-search.ts` issues `INSERT INTO <base>_fts(<base>_fts) VALUES('rebuild')`).
441
- * Their row counts do NOT correspond 1:1 to user rows, so comparing them against
442
- * an absent consolidated target produces a spurious N→0 deficit. They must be
443
- * excluded from the row-count-parity gate, not migrated.
444
- *
445
- * @task T11572 (parity gate over-abort — FTS5 shadow-table exclusion)
446
- */
447
- var FTS5_SHADOW_SUFFIXES = [
448
- "_fts_data",
449
- "_fts_idx",
450
- "_fts_docsize",
451
- "_fts_config",
452
- "_fts_content"
453
- ];
454
- /**
455
- * Return `true` if `tableName` is a DERIVED or INTERNAL table that must be
456
- * EXCLUDED from the exodus row-count-parity gate (and from the copy path).
457
- *
458
- * This is the single, named, documented classification consumed by BOTH the
459
- * migrate copy loop and the `verifyMigration` parity check, so the two never
460
- * disagree about which tables carry migratable user data. It recognises:
461
- *
462
- * 1. **FTS5 virtual tables** — a bare `*_fts` name (e.g. `brain_decisions_fts`,
463
- * `messages_fts`). The virtual table itself cannot be `INSERT … SELECT`-ed
464
- * and is rebuilt from its content table after migration.
465
- * 2. **FTS5 shadow/backing tables** — `*_fts_data`, `*_fts_idx`,
466
- * `*_fts_docsize`, `*_fts_config`, `*_fts_content` (see
467
- * {@link FTS5_SHADOW_SUFFIXES}). Derived; rebuilt post-migration.
468
- * 3. **Internal bookkeeping** — any per-source schema-version / migration
469
- * ledger: `_conduit_meta`, `_conduit_migrations`, `_signaldock_meta`,
470
- * `_signaldock_migrations`, `_skills_meta`, and — generalised via
471
- * {@link INTERNAL_LEDGER_PATTERN} — ANY `_<domain>_meta` /
472
- * `_<domain>_migrations` ledger from a future source DB (see
473
- * {@link INTERNAL_BOOKKEEPING_TABLES}). Schema-version ledgers with no
474
- * consolidated home.
475
- *
476
- * A migration is "safe" iff every BASE-DATA row survives; these derived/internal
477
- * tables are NOT base data, so a 0-row consolidated counterpart for them is
478
- * expected and correct — not data loss.
479
- *
480
- * @param tableName - Physical table name from a legacy source DB.
481
- * @returns `true` when the table is derived/internal and must be skipped.
482
- *
483
- * @task T11572 (exodus parity gate: exclude FTS5 + internal/meta shadow tables)
484
- * @task T11577 (generalise internal-ledger skip to signaldock/skills + any per-source ledger)
485
- * @epic T11249 (E6)
486
- * @saga T11242
487
- */
488
- function isDerivedOrInternalTable(tableName) {
489
- if (INTERNAL_BOOKKEEPING_TABLES.has(tableName)) return true;
490
- if (INTERNAL_LEDGER_PATTERN.test(tableName)) return true;
491
- if (tableName.endsWith("_fts")) return true;
492
- return FTS5_SHADOW_SUFFIXES.some((suffix) => tableName.endsWith(suffix));
493
- }
494
- /**
495
- * Resolve the consolidated target table name for a legacy source table.
496
- *
497
- * @param sourceName - `LegacyDbDescriptor.name` (e.g. `"tasks"`, `"brain (project)"`).
498
- * @param legacyTable - Physical table name from the legacy source DB.
499
- * @returns A `TableNameResolution` describing how to copy (or skip) the table.
500
- */
501
- function resolveConsolidatedTableName(sourceName, legacyTable) {
502
- if (isDerivedOrInternalTable(legacyTable)) return {
503
- kind: "skip",
504
- reason: legacyTable.includes("_fts") ? `derived FTS5 table — rebuilt post-migration from its content table, not row-compared` : `internal bookkeeping table — no consolidated home (recreated by runtime)`
505
- };
506
- const kind = inferSourceKind(sourceName);
507
- if (kind === null) return {
508
- kind: "unknown",
509
- targetName: legacyTable
510
- };
511
- let map;
512
- switch (kind) {
513
- case "tasks":
514
- map = TASKS_DB_MAP;
515
- break;
516
- case "brain":
517
- map = BRAIN_DB_MAP;
518
- break;
519
- case "conduit":
520
- map = CONDUIT_DB_MAP;
521
- break;
522
- case "nexus":
523
- map = NEXUS_DB_MAP;
524
- break;
525
- case "signaldock":
526
- map = AGENT_REGISTRY_DB_MAP;
527
- break;
528
- case "skills":
529
- map = SKILLS_DB_MAP;
530
- break;
531
- }
532
- if (!map.has(legacyTable)) return {
533
- kind: "mapped",
534
- targetName: legacyTable
535
- };
536
- const consolidated = map.get(legacyTable);
537
- if (consolidated === null || consolidated === void 0) return {
538
- kind: "skip",
539
- reason: getSkipReason(kind, legacyTable)
540
- };
541
- return {
542
- kind: "mapped",
543
- targetName: consolidated
544
- };
545
- }
546
- /**
547
- * Return a human-readable explanation for why a table is intentionally excluded
548
- * from the consolidated schema.
549
- */
550
- function getSkipReason(sourceKind, legacyTable) {
551
- return {
552
- brain_task_observations: "runtime-only observation cache (not Drizzle-managed); recreated lazily after exodus cutover",
553
- brain_embeddings: "vec0 VIRTUAL TABLE — cannot be migrated via INSERT/SELECT; requires sqlite-vec extension; recreated lazily after exodus cutover",
554
- brain_embeddings_info: "metadata companion to brain_embeddings vec0 virtual table; excluded from consolidated schema (derived/recreatable)"
555
- }[legacyTable] ?? `table '${legacyTable}' from ${sourceKind} has no consolidated target`;
556
- }
557
- getLogger("exodus-count-parity");
558
- //#endregion
559
- //#region ../core/dist/store/exodus/plan.js
560
- /**
561
- * Exodus pre-flight plan builder.
562
- *
563
- * `buildExodusPlan()` computes the full migration plan — source DB paths,
564
- * combined size, free-disk availability, staging directory — BEFORE any
565
- * writes occur. A `--dry-run` caller can print the plan and exit early.
566
- *
567
- * ## Disk pre-flight (AC8 · right-sized T11838)
568
- *
569
- * The original gate required `availableBytes >= 3 * totalSourceBytes` — an
570
- * over-estimate that blocked large-fleet migrations even with ample headroom:
571
- * exodus never holds 3× the SUM of every source on disk at once. It copies one
572
- * source into staging at a time (lock released before the next) and writes a
573
- * single consolidated cleo.db whose size approximates the sum of source ROW data.
574
- *
575
- * The right-sized requirement is therefore
576
- * `STAGING_HEADROOM_FACTOR * largestSourceBytes + consolidatedEstimate`, where
577
- * `consolidatedEstimate ≈ totalSourceBytes` ({@link computeRequiredBytes}). The
578
- * check uses `statvfs` via `node:fs.statfsSync()` (Node 18+).
579
- *
580
- * @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
581
- * @task T11838 (right-sized preflight + optional staging copy for large sources)
582
- * @saga T11242
583
- */
584
- /**
585
- * Headroom multiplier applied to the LARGEST single source when sizing the
586
- * staging-copy footprint. The staging dir only ever holds ONE source backup at a
587
- * time (its advisory lock is released before the next source is touched), so the
588
- * peak staging footprint is the largest source plus a small slack for the
589
- * write-then-rename journal + SQLite sidecars — hence `1.2×`, not `3×` of the SUM.
590
- *
591
- * @task T11838
592
- */
593
- var STAGING_HEADROOM_FACTOR = 1.2;
594
- /**
595
- * Per-source byte threshold above which the full staging `copyFileSync` backup
596
- * is skipped (the source is archived, not deleted, on success). Sized at 256 MiB
597
- * — comfortably above the small project DBs (tasks/conduit/skills/signaldock are
598
- * sub-MB to low-MB) but below the large global stores (a multi-GB `brain.db` or
599
- * `nexus.db`) whose redundant full-file copy is the costly case T11838 removes.
600
- *
601
- * @task T11838
602
- */
603
- var STAGING_COPY_SKIP_THRESHOLD_BYTES = 256 * 1024 * 1024;
604
- /**
605
- * Compute the right-sized free-disk requirement for an exodus run (T11838).
606
- *
607
- * `STAGING_HEADROOM_FACTOR * largestSourceBytes` covers the peak staging-copy
608
- * footprint (one source at a time, plus slack), and `totalSourceBytes` is the
609
- * consolidated-cleo.db estimate (every source's row data lands there). The two
610
- * are additive because staging and the consolidated DB coexist on disk until the
611
- * sources are archived.
612
- *
613
- * @param totalSourceBytes - Combined size of all source DB files in bytes.
614
- * @param largestSourceBytes - Size of the single largest source DB in bytes.
615
- * @returns The minimum free bytes the target filesystem must have.
616
- */
617
- function computeRequiredBytes(totalSourceBytes, largestSourceBytes) {
618
- return Math.ceil(STAGING_HEADROOM_FACTOR * largestSourceBytes) + totalSourceBytes;
619
- }
620
- /**
621
- * Build the ordered list of legacy source DB descriptors for a given project.
622
- *
623
- * Project-scoped sources live under `<project>/.cleo/`.
624
- * Global-scoped sources live under `getCleoHome()`.
625
- */
626
- function buildSourceDescriptors(cwd) {
627
- const cleoDir = resolveCleoDir(cwd);
628
- const cleoHome = getCleoHome();
629
- return [
630
- {
631
- name: "tasks",
632
- path: join(cleoDir, "tasks.db"),
633
- targetScope: "project"
634
- },
635
- {
636
- name: "brain (project)",
637
- path: join(cleoDir, "brain.db"),
638
- targetScope: "project"
639
- },
640
- {
641
- name: "conduit",
642
- path: join(cleoDir, "conduit.db"),
643
- targetScope: "project"
644
- },
645
- {
646
- name: "nexus",
647
- path: join(cleoHome, "nexus.db"),
648
- targetScope: "global"
649
- },
650
- {
651
- name: "signaldock",
652
- path: join(cleoHome, "signaldock.db"),
653
- targetScope: "global"
654
- },
655
- {
656
- name: "skills",
657
- path: join(cleoHome, "skills.db"),
658
- targetScope: "global"
659
- }
660
- ];
661
- }
662
- /**
663
- * Safely stat a file and return its size in bytes, or 0 if it does not exist.
664
- */
665
- function safeFileBytes(filePath) {
666
- try {
667
- return statSync(filePath).size;
668
- } catch {
669
- return 0;
670
- }
671
- }
672
- /**
673
- * Return available bytes on the filesystem that contains `dir`.
674
- *
675
- * Uses `statfsSync` (Node 18+). Falls back to 0 if unavailable.
676
- */
677
- function getAvailableBytes(dir) {
678
- try {
679
- const result = statfsSync(dir);
680
- return (result.bavail ?? result.bfree ?? 0) * (result.bsize ?? 4096);
681
- } catch {
682
- return 0;
683
- }
684
- }
685
- /**
686
- * Derive the staging directory name from the current ISO-8601 timestamp.
687
- *
688
- * Pattern: `.cleo/exodus-staging-<YYYYMMDDTHHMMSSZ>`.
689
- * Colons are replaced with empty string (NTFS + shell-safe).
690
- */
691
- function deriveStagingDirName() {
692
- return `exodus-staging-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:]/g, "").replace(/\..+Z$/, "Z")}`;
693
- }
694
- /**
695
- * Scan for an existing staging directory from a previous (possibly crashed)
696
- * run inside the given `.cleo/` directory.
697
- *
698
- * Returns the absolute path to the most-recent staging dir, or `null` if none
699
- * exists.
700
- */
701
- function findExistingStaging(cleoDir) {
702
- try {
703
- const stagingDirs = readdirSync(cleoDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("exodus-staging-")).map((e) => e.name).sort().reverse();
704
- if (stagingDirs.length > 0) return join(cleoDir, stagingDirs[0]);
705
- } catch {}
706
- return null;
707
- }
708
- /**
709
- * Build the complete exodus plan.
710
- *
711
- * This is a pure read operation — no files are created or modified.
712
- * Pass the result to `runExodusMigrate()` to execute the migration.
713
- *
714
- * @param cwd - Working directory used to resolve the project root. Defaults to
715
- * `process.cwd()`.
716
- * @returns {@link ExodusPlan} describing sources, disk availability, and paths.
717
- *
718
- * @task T11248 (AC8 — disk pre-flight)
719
- * @task T11838 (right-sized: largest-source factor + consolidated estimate)
720
- */
721
- function buildExodusPlan(cwd) {
722
- const cleoDir = resolveCleoDir(cwd);
723
- const sources = buildSourceDescriptors(cwd);
724
- const sourceBytes = sources.map((s) => safeFileBytes(s.path));
725
- const totalSourceBytes = sourceBytes.reduce((sum, b) => sum + b, 0);
726
- const largestSourceBytes = sourceBytes.reduce((max, b) => Math.max(max, b), 0);
727
- const requiredBytes = computeRequiredBytes(totalSourceBytes, largestSourceBytes);
728
- const availableBytes = getAvailableBytes(cleoDir);
729
- const diskPreflight = totalSourceBytes === 0 || availableBytes >= requiredBytes;
730
- const existingStaging = findExistingStaging(cleoDir);
731
- return {
732
- sources,
733
- totalSourceBytes,
734
- largestSourceBytes,
735
- requiredBytes,
736
- availableBytes,
737
- diskPreflight,
738
- stagingCopyThresholdBytes: STAGING_COPY_SKIP_THRESHOLD_BYTES,
739
- stagingDir: existingStaging ?? join(cleoDir, deriveStagingDirName()),
740
- resumeFromStaging: existingStaging !== null,
741
- projectDbPath: resolveDualScopeDbPath("project", cwd),
742
- globalDbPath: resolveDualScopeDbPath("global")
743
- };
744
- }
745
- //#endregion
746
- //#region ../core/dist/store/exodus/column-transforms.js
747
- /**
748
- * Shared column-value transform layer for the exodus migration.
749
- *
750
- * The exodus copy (`migrate.ts`) does not move source values byte-for-byte into
751
- * the consolidated `cleo.db`: a handful of columns are TRANSFORMED on the way in
752
- * so they satisfy the consolidated schema's CHECK constraints (which the legacy
753
- * runtime DBs did not carry). Three transform classes exist:
754
- *
755
- * 1. **Epoch INTEGER → ISO-8601 TEXT** ({@link buildEpochToIsoExpr}) — for any
756
- * target column carrying an ISO-GLOB CHECK ({@link detectIsoGlobColumns}).
757
- * A per-row magnitude heuristic ({@link EPOCH_SECONDS_THRESHOLD}) classifies
758
- * seconds vs milliseconds.
759
- * 2. **Legacy enum → canonical member** ({@link ENUM_NORMALIZATIONS} +
760
- * {@link enumNormExpr}) — maps pre-tightening enum aliases (e.g.
761
- * `tasks_commits.conventional_type` `'style'`/`'merge'` → `'chore'`).
762
- * 3. **Non-finite REAL → finite** ({@link NUMERIC_CLAMPS} +
763
- * {@link numericClampExpr}) — `Inf`/`-Inf`/`NaN` → a finite in-range value
764
- * (`brain_weight_history.delta_weight`).
765
- *
766
- * ## Why this module exists (T11809 · AC2)
767
- *
768
- * Before T11809 these transforms lived ONLY in `migrate.ts`. The parity verifier
769
- * (`verify-migration.ts`) digested RAW source values against the TRANSFORMED
770
- * target values, so every coerced column (epoch INTEGER vs ISO TEXT, legacy enum
771
- * vs canonical, Inf vs clamped) produced a `hashMatch === false` even on a
772
- * perfectly lossless migration — a false-negative that aborted the cutover and
773
- * lost the batch writes accumulated during the migrating open.
774
- *
775
- * Extracting the transform logic into ONE shared module lets the verifier digest
776
- * the SOURCE side **through the same transforms migrate applied**
777
- * ({@link buildDigestExpr}), so equal logical data digests equal. `migrate.ts`
778
- * re-imports these primitives so its copy behaviour stays byte-identical; the
779
- * verifier imports {@link buildDigestExpr} for the digest-oriented variant.
780
- *
781
- * @task T11809 (exodus verify applies source-side coercion — hashMatch on equal data)
782
- * @task T11546 (epoch→ISO coercion — original)
783
- * @task T11547 (enum normalization — original)
784
- * @task T11782 (non-finite numeric clamp — original)
785
- * @epic T11249 (E6)
786
- * @saga T11242
787
- */
788
- /**
789
- * Determine a safe SQL literal default for a NOT NULL column with no schema
790
- * default, given its SQLite type affinity.
791
- *
792
- * Used by `migrate.ts` to coalesce NULL source values for target-only NOT NULL
793
- * columns so that rows are not silently dropped by `INSERT OR IGNORE` when a
794
- * source value is NULL (T11533 ROOT CAUSE 2 fix).
795
- *
796
- * @param colType - Raw `type` string from `PRAGMA table_info` (e.g. `"INTEGER"`,
797
- * `"TEXT"`, `"REAL"`, `"BLOB"`, or compound forms like `"text NOT NULL"`).
798
- * @returns A SQL literal string suitable for embedding in a `COALESCE()` call.
799
- */
800
- function typeDefaultLiteral(colType) {
801
- const upper = colType.toUpperCase();
802
- if (upper.includes("INT")) return "0";
803
- if (upper.includes("REAL") || upper.includes("FLOAT") || upper.includes("DOUBLE")) return "0.0";
804
- if (upper.includes("BLOB")) return "x''";
805
- return "''";
806
- }
807
- /**
808
- * Per-(targetTable, column) normalization rules that map legacy enum values to
809
- * the canonical enum accepted by the consolidated schema CHECK constraints.
810
- *
811
- * Each entry is a function that, given the `srcRef` SQL expression for the
812
- * column, returns a SQL CASE expression that produces the canonical value.
813
- * Rows with already-canonical values pass through unchanged (the ELSE branch).
814
- *
815
- * ## Brain enum normalizations REMOVED (T11647)
816
- *
817
- * The brain memory family no longer participates in enum normalization. Its
818
- * consolidated exodus target now matches the LEGACY RUNTIME shape, which carries
819
- * NO SQL CHECK constraints (the `text({ enum })` unions are enforced at the
820
- * application layer only). With no CHECK to satisfy, coercing brain enum values
821
- * would be data corruption, not a fix — so every brain enum value is copied
822
- * VERBATIM. The historical brain rules (`brain_observations.{source_type,type}`,
823
- * `brain_decisions.{confirmation_state,decision_category,confidence,outcome,
824
- * decided_by}`) were deleted. The TASKS-domain rules below remain because those
825
- * consolidated tables keep their CHECK constraints.
826
- *
827
- * ## nexus/signaldock enum-drift audit (T11809 · AC1)
828
- *
829
- * A real-data audit of `nexus.db` (106 MB) and `signaldock.db` (280 KB) against
830
- * the consolidated CHECK enums found ZERO out-of-enum values for every
831
- * CHECK-constrained column that has source data: `nexus_nodes.kind` (24,482
832
- * rows), `nexus_relations.type` (39,163 rows), `nexus_contracts.type` (0 rows),
833
- * `nexus_sigils.role` (8 rows), `agent_registry_agents.status` (19 rows),
834
- * `agent_registry_users.role` (0 rows). No new normalization entry was required.
835
- * The reported "nexus/signaldock drop rows" symptom was in fact the AC2 verify
836
- * false-negative (epoch→ISO coercion makes the source digest differ from the
837
- * target digest), which fixing {@link buildDigestExpr} resolves — NOT a CHECK
838
- * drop. See the T11809 return notes for the full per-column row counts.
839
- *
840
- * Lookup key: `${targetTable}.${column}` (lowercase, dotted).
841
- *
842
- * @since T11547 (P0 data-loss fix)
843
- * @since T11548 (P0 final enum coverage)
844
- * @since T11647 (brain target = runtime shape — brain enum coercions removed)
845
- */
846
- var ENUM_NORMALIZATIONS = new Map([
847
- ["tasks_task_commits.link_source", (src) => `CASE ${src} WHEN 'commit-message' THEN 'commit-subject' ELSE ${src} END`],
848
- ["tasks_architecture_decisions.status", (src) => `CASE WHEN lower(${src}) = 'accepted' OR lower(${src}) LIKE 'accepted %' OR lower(${src}) = 'approved' THEN 'accepted' WHEN lower(${src}) = 'proposed' THEN 'proposed' WHEN lower(${src}) = 'superseded' THEN 'superseded' WHEN lower(${src}) = 'deprecated' THEN 'deprecated' ELSE ${src} END`],
849
- ["tasks_commits.conventional_type", (src) => `CASE WHEN ${src} IS NULL THEN NULL WHEN ${src} IN ('feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'build', 'ci', 'perf', 'revert', 'breaking') THEN ${src} ELSE 'chore' END`],
850
- ["tasks_task_relations.relation_type", (src) => `CASE ${src} WHEN 'grouped-by' THEN 'groups' ELSE ${src} END`],
851
- ["tasks_lifecycle_stages.stage_name", (src) => `CASE ${src} WHEN 'implemented' THEN 'implementation' WHEN 'qaPassed' THEN 'validation' WHEN 'testsPassed' THEN 'testing' ELSE ${src} END`],
852
- ["tasks_architecture_decisions.gate_status", (src) => `CASE WHEN ${src} LIKE 'passed%' THEN 'passed' WHEN ${src} = 'approved' THEN 'passed' ELSE ${src} END`],
853
- ["tasks_evidence_ac_bindings.binding_type", (src) => `CASE WHEN ${src} LIKE 'validator:%' THEN 'direct' ELSE ${src} END`]
854
- ]);
855
- /**
856
- * Return a SQL CASE expression that normalises legacy enum values for `col` in
857
- * `targetTableName` to the canonical values accepted by the consolidated CHECK,
858
- * or return `null` when no normalization rule exists for this (table, column).
859
- *
860
- * @param targetTableName - Physical consolidated target table name.
861
- * @param col - Column name.
862
- * @param srcRef - SQL expression referencing the source column.
863
- * @returns A SQL CASE expression string, or `null` if no rule applies.
864
- */
865
- function enumNormExpr(targetTableName, col, srcRef) {
866
- const key = `${targetTableName}.${col}`;
867
- const fn = ENUM_NORMALIZATIONS.get(key);
868
- return fn ? fn(srcRef) : null;
869
- }
870
- /**
871
- * Per-(targetTable, column) numeric-clamp rules that coerce non-finite legacy
872
- * REAL values (`Inf` / `-Inf` / `NaN`) to a finite in-range value so the row is
873
- * NOT silently dropped by `INSERT OR IGNORE`.
874
- *
875
- * ## Why this exists (T11782)
876
- *
877
- * 188,926 of 697,780 legacy `brain_weight_history` rows carry
878
- * `delta_weight = Inf`/`-Inf` (the R-STDP plasticity writer saturated the delta
879
- * before the value was clamped at write time). SQLite stores ±Inf as the IEEE-754
880
- * float, but the consolidated `brain_weight_history.delta_weight` column is a
881
- * plain `real NOT NULL` with NO CHECK — so a verbatim copy would land the Inf
882
- * value. The historical deficit, however, was that those rows tripped a constraint
883
- * elsewhere in the copy chain and `INSERT OR IGNORE` dropped them, yielding a
884
- * deficit that fired the parity-gate abort. Clamping the non-finite value to a
885
- * finite member of the column's domain guarantees every row lands.
886
- *
887
- * The clamp mirrors the {@link ENUM_NORMALIZATIONS} shape: each entry is a
888
- * function `(srcRef) => CASE …`. Finite values pass through unchanged via the
889
- * ELSE branch. The `col != col` self-comparison is the canonical SQL NaN guard
890
- * (NaN is the only value not equal to itself); `9e999` is the SQLite literal that
891
- * evaluates to `+Infinity` (and `-9e999` to `-Infinity`).
892
- *
893
- * Lookup key: `${targetTable}.${column}` (lowercase, dotted).
894
- *
895
- * @since T11782 (P0 — brain_weight_history Inf recovery)
896
- */
897
- var NUMERIC_CLAMPS = new Map([["brain_weight_history.delta_weight", (src) => `CASE WHEN ${src} = 9e999 THEN 1.0 WHEN ${src} = -9e999 THEN -1.0 WHEN ${src} != ${src} THEN 0.0 ELSE ${src} END`]]);
898
- /**
899
- * Return a SQL CASE expression that clamps non-finite legacy REAL values for
900
- * `col` in `targetTableName` to a finite in-range value, or `null` when no
901
- * clamp rule exists for this (table, column).
902
- *
903
- * @param targetTableName - Physical consolidated target table name.
904
- * @param col - Column name.
905
- * @param srcRef - SQL expression referencing the source column.
906
- * @returns A SQL CASE expression string, or `null` if no rule applies.
907
- */
908
- function numericClampExpr(targetTableName, col, srcRef) {
909
- const key = `${targetTableName}.${col}`;
910
- const fn = NUMERIC_CLAMPS.get(key);
911
- return fn ? fn(srcRef) : null;
912
- }
913
- /**
914
- * Regex to detect ISO GLOB CHECK constraints in DDL SQL.
915
- * Matches: `CHECK ("colname" IS NULL OR "colname" GLOB '[0-9]...')`
916
- * Uses `\[0-9` to match the literal `[0-9` at the start of the GLOB pattern.
917
- */
918
- var ISO_CHECK_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+IS\s+NULL\s+OR\s+"[^"]+"\s+GLOB\s+'\[0-9/gi;
919
- /**
920
- * Magnitude threshold distinguishing epoch SECONDS from epoch MILLISECONDS.
921
- *
922
- * A Unix epoch value for years 2020–2100 is roughly 1.6e9 – 4.1e9 seconds,
923
- * or 1.6e12 – 4.1e12 milliseconds. The safe boundary is 1e11 (100 billion):
924
- * any value BELOW 1e11 is in seconds (even year 2100 seconds ≈ 4.1e9 < 1e11);
925
- * any value AT OR ABOVE 1e11 is in milliseconds (year 2020 ms ≈ 1.6e12 > 1e11).
926
- *
927
- * This constant is embedded directly in the generated SQL CASE expression so
928
- * it is evaluated per-row — each row's epoch is classified independently.
929
- */
930
- var EPOCH_SECONDS_THRESHOLD = 1e11;
931
- /**
932
- * Build a SQL expression that converts an INTEGER epoch column to ISO-8601 TEXT,
933
- * automatically detecting whether the stored value is in seconds or milliseconds
934
- * using a magnitude heuristic (T11549 correctness fix).
935
- *
936
- * ## Heuristic
937
- *
938
- * A per-row CASE checks whether the column value is below {@link EPOCH_SECONDS_THRESHOLD}
939
- * (100 billion). If so, the value is treated as seconds and passed directly to
940
- * `strftime(..., 'unixepoch')`. If at or above the threshold, it is divided by
941
- * 1000.0 first (milliseconds → seconds).
942
- *
943
- * This replaces the previous per-source heuristic which failed when individual
944
- * columns within a source DB used a different epoch unit than the majority of that
945
- * source's columns. The specific bug: `nexus.user_profile.{first_observed_at,
946
- * last_reinforced_at}` stores SECONDS (value ≈ 1.78e9) but the nexus source was
947
- * labeled `milliseconds`, causing these values to be divided by 1000 and converted
948
- * to a 1970 date.
949
- *
950
- * ## NULL handling
951
- *
952
- * A NULL source value is preserved as NULL so it passes the `IS NULL` branch of
953
- * the ISO GLOB CHECK constraint on the target column.
954
- *
955
- * @param srcRef - SQL expression referencing the source column value.
956
- * @returns A SQL CASE expression producing an ISO-8601 TEXT timestamp.
957
- */
958
- function buildEpochToIsoExpr(srcRef) {
959
- return `CASE WHEN ${srcRef} IS NULL THEN NULL WHEN ${srcRef} < ${EPOCH_SECONDS_THRESHOLD} THEN strftime('%Y-%m-%dT%H:%M:%fZ', ${srcRef}, 'unixepoch') ELSE strftime('%Y-%m-%dT%H:%M:%fZ', ${srcRef}/1000.0, 'unixepoch') END`;
960
- }
961
- /**
962
- * Parse the DDL for a given table from `sqlite_master` and return the set of
963
- * column names that have an ISO GLOB CHECK constraint.
964
- *
965
- * Reads the raw DDL text and uses a regex to extract column names appearing in
966
- * `CHECK ("colname" IS NULL OR "colname" GLOB '[0-9]...')` patterns. This is
967
- * robust to Drizzle's generated CHECK format (all CHECK constraints generated
968
- * by T11363 follow this exact pattern).
969
- *
970
- * @param db - Target DB with the consolidated schema.
971
- * @param tableName - Physical table name (consolidated, e.g. `conduit_messages`).
972
- * @param targetSchema - Schema name the target table lives in (`'main'`, or an
973
- * ATTACH alias for cross-scope routing — ADR-090 nexus graph residency, T11539).
974
- * @returns Set of column names that require ISO GLOB validation.
975
- */
976
- function detectIsoGlobColumns(db, tableName, targetSchema = "main") {
977
- const escapedTable = tableName.replace(/'/g, "''");
978
- const row = db.prepare(`SELECT sql FROM "${targetSchema}".sqlite_master WHERE type='table' AND name='${escapedTable}'`).get();
979
- if (!row?.sql) return /* @__PURE__ */ new Set();
980
- const isoColumns = /* @__PURE__ */ new Set();
981
- ISO_CHECK_REGEX.lastIndex = 0;
982
- for (const match of row.sql.matchAll(ISO_CHECK_REGEX)) isoColumns.add(match[1]);
983
- return isoColumns;
984
- }
985
- /**
986
- * Return `true` when the source column's type affinity is INTEGER-like, so an
987
- * epoch→ISO coercion applies when the matching target column carries an ISO GLOB
988
- * CHECK. Empty affinity and `NUMERIC` are treated as integer-like (matching the
989
- * historical `buildSelectExpr` behaviour — legacy epoch columns sometimes carry
990
- * no declared type or a `NUMERIC` affinity).
991
- *
992
- * @param srcType - Raw `type` string from the source `PRAGMA table_info`.
993
- * @returns `true` if the source column should be considered an INTEGER epoch.
994
- */
995
- function isIntegerSourceType(srcType) {
996
- const upper = srcType.toUpperCase();
997
- return upper.includes("INT") || upper === "" || upper === "NUMERIC";
998
- }
999
- /**
1000
- * Wrap a digest value expression in the SAME `COALESCE(expr, type_default)`
1001
- * substitution migrate's `buildSelectExpr` applies, when (and only when) the
1002
- * matching TARGET column is NOT NULL without a schema default (T11836).
1003
- *
1004
- * On a faithful FRESH migration a NULL source value in such a column is stored
1005
- * as the type default (`''`/`0`/`0.0`/`x''`) by migrate; digesting the source
1006
- * raw would read NULL and diverge from the stored default — a false
1007
- * `hashMatch === false` at equal row counts. Mirroring the COALESCE here makes
1008
- * the source digest reflect what migrate actually stored, so the substitution is
1009
- * a NON-FATAL diagnostic class rather than a content-corruption failure.
1010
- *
1011
- * @param expr - The value expression produced by an upstream transform branch.
1012
- * @param tgtCol - Target column metadata, or `undefined` when unavailable (no
1013
- * COALESCE applied — best-effort, biased to surfacing a real difference).
1014
- * @returns The expression, COALESCE-wrapped when the target is NOT-NULL-without-default.
1015
- */
1016
- function maybeCoalesceNotNull(expr, tgtCol) {
1017
- if (tgtCol === void 0) return expr;
1018
- if (!(tgtCol.notnull === 1 && tgtCol.dflt_value === null)) return expr;
1019
- return `COALESCE(${expr}, ${typeDefaultLiteral(tgtCol.type)})`;
1020
- }
1021
- /**
1022
- * Build the SQL expression a column's SOURCE value must pass through so it
1023
- * matches the canonical value the migration STORES in the target — for use by
1024
- * the parity verifier's content digest (T11809 · AC2).
1025
- *
1026
- * This is the digest-oriented sibling of migrate's `buildSelectExpr`. It applies
1027
- * the SAME value transforms the migration actually performs — and ONLY those:
1028
- *
1029
- * 1. **Epoch→ISO-8601** ({@link buildEpochToIsoExpr}) — when the target has an
1030
- * ISO GLOB CHECK and the source column is INTEGER-typed.
1031
- * 2. **Non-finite numeric clamp** ({@link numericClampExpr}).
1032
- * 3. **Enum-value normalization** ({@link enumNormExpr}).
1033
- * 4. **Plain column reference** otherwise.
1034
- *
1035
- * ## NOT-NULL default substitution mirroring (T11836)
1036
- *
1037
- * After the value transform, the result is wrapped in the SAME
1038
- * `COALESCE(expr, type_default)` substitution migrate's `buildSelectExpr` uses
1039
- * for a TARGET column that is NOT NULL without a schema default (see
1040
- * {@link maybeCoalesceNotNull}). migrate stores the type default (`''`/`0`/
1041
- * `0.0`/`x''`) for a NULL source value in such a column; without mirroring it
1042
- * here the source digest would read NULL and diverge from the stored default — a
1043
- * false `hashMatch === false` on a faithful FRESH migration (the exact T11836
1044
- * symptom). Mirroring it makes a NULL→NOT-NULL-default substitution a NON-FATAL
1045
- * diagnostic class rather than a hash failure. The target metadata is supplied
1046
- * via the `tgtColByCol` map; when it is absent (best-effort) no COALESCE is
1047
- * applied and a genuine NULL→'' divergence remains visible as before.
1048
- *
1049
- * The returned expression is a bare SQL value expression (no `AS "col"` alias)
1050
- * suitable for embedding directly in a `SELECT <expr> ... ORDER BY ...` digest
1051
- * query. When no transform applies, a plain quoted column reference is returned.
1052
- *
1053
- * @param targetTableName - Physical consolidated target table name (transform
1054
- * lookup key).
1055
- * @param col - Column name (present in BOTH source and target).
1056
- * @param srcType - Raw `type` string from the source `PRAGMA table_info`.
1057
- * @param isoGlobCols - Set of target columns carrying an ISO GLOB CHECK.
1058
- * @param tgtCol - Target column metadata (NOT-NULL flag + default +
1059
- * affinity) for `col`, or `undefined` when unavailable.
1060
- * @returns A SQL value expression that maps the raw source value to the canonical
1061
- * value the target stores.
1062
- */
1063
- function buildDigestExpr(targetTableName, col, srcType, isoGlobCols, tgtCol) {
1064
- const srcRef = `"${col}"`;
1065
- if (isoGlobCols.has(col) && isIntegerSourceType(srcType)) return maybeCoalesceNotNull(buildEpochToIsoExpr(srcRef), tgtCol);
1066
- const clampExpr = numericClampExpr(targetTableName, col, srcRef);
1067
- if (clampExpr !== null) return maybeCoalesceNotNull(clampExpr, tgtCol);
1068
- const normExpr = enumNormExpr(targetTableName, col, srcRef);
1069
- if (normExpr !== null) return maybeCoalesceNotNull(normExpr, tgtCol);
1070
- return maybeCoalesceNotNull(srcRef, tgtCol);
1071
- }
1072
- //#endregion
1073
- //#region ../core/dist/store/exodus/types.js
1074
- /**
1075
- * Shared types for the `cleo exodus` migration subsystem.
1076
- *
1077
- * The exodus subsystem migrates data from the legacy multi-DB fleet
1078
- * (tasks.db · brain.db · conduit.db · nexus.db · signaldock.db · skills.db)
1079
- * into the consolidated dual-scope `cleo.db` (project + global).
1080
- *
1081
- * @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
1082
- * @saga T11242
1083
- * @adr ADR-068, ADR-069
1084
- */
1085
- /**
1086
- * Canonical schema version tag embedded in the exodus journal.
1087
- *
1088
- * This constant represents the Drizzle v1.0.0-rc.3 target schema "epoch"
1089
- * for the dual-scope consolidation (T11245 · E2 · SG-DB-SUBSTRATE-V2).
1090
- * The exact value is a stable marker — not a live hash — so that import
1091
- * refuses cross-version migration unless `--force-cross-version` is passed.
1092
- */
1093
- var EXODUS_TARGET_SCHEMA_VERSION = "drizzle-v1.0.0-rc.3/dual-scope/2026-05";
1094
- //#endregion
1095
- //#region ../core/dist/store/exodus/migrate.js
1096
- /**
1097
- * Exodus migration engine.
1098
- *
1099
- * `runExodusMigrate()` performs the actual data migration from legacy DBs
1100
- * to the consolidated dual-scope `cleo.db`. Key invariants:
1101
- *
1102
- * - Source DBs are opened **read-only** via `openCleoDbSnapshot` (AC4).
1103
- * - Source files are backed up to the staging dir before any writes (AC5).
1104
- * - Import is wrapped in `BEGIN … COMMIT` per source; partial failure leaves
1105
- * the target DB untouched (AC6).
1106
- * - Idempotency keys are propagated where the source row has them; generated
1107
- * where it does not (AC7).
1108
- * - The staging journal is written atomically before each table copy so a
1109
- * crash can be resumed (AC5).
1110
- *
1111
- * ## Type coercion — epoch INTEGER → ISO-8601 TEXT (ROOT CAUSE 1 fix — T11546)
1112
- *
1113
- * Many legacy tables store timestamps as INTEGER epoch values (seconds or
1114
- * milliseconds). The consolidated schema declares these columns as `text` with
1115
- * a CHECK constraint: `CHECK ("col" IS NULL OR "col" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]*')`.
1116
- *
1117
- * When a source INTEGER value is inserted into a target TEXT+GLOB column, SQLite
1118
- * coerces the integer to its decimal string representation (e.g. `"1717200000"`),
1119
- * which fails the GLOB check. `INSERT OR IGNORE` then SILENTLY DROPS the entire
1120
- * row. Result: all conduit_messages, brain_observations, etc. → 0 rows copied
1121
- * while migrate reports `success: true, rowsCopied: 0`.
1122
- *
1123
- * Fix (two parts):
1124
- * (a) Per-column value transform: `detectEpochToIsoColumns()` reads the target
1125
- * table DDL from `sqlite_master` to identify columns with an ISO GLOB
1126
- * CHECK constraint. For those columns, if the source type affinity is
1127
- * INTEGER, the SELECT expression applies `strftime('%Y-%m-%dT%H:%M:%fZ',
1128
- * col, 'unixepoch')` (seconds) or the `/1000.0` ms variant depending on
1129
- * the per-source/table heuristic.
1130
- * (b) No-swallow assertion: after the bulk INSERT OR IGNORE, the actual
1131
- * `changes` count is compared against the source row count. Any shortfall
1132
- * is surfaced as a hard table-level error (not a silent success).
1133
- * PK/UNIQUE conflicts on resume are tolerated (they are expected and safe
1134
- * to ignore); unexpected shortfalls are flagged with a detailed error.
1135
- *
1136
- * ## ATTACH-once-per-source design (P0 fix — T11531)
1137
- *
1138
- * Each legacy source DB is ATTACHed to the target handle ONCE using a unique
1139
- * per-source alias, all tables from that source are copied under the single
1140
- * attachment, and then the alias is DETACHed. The ATTACH and DETACH are
1141
- * performed **outside** the BEGIN/COMMIT transaction block because SQLite
1142
- * forbids DETACH inside an active multi-statement transaction. The INSERT
1143
- * statements themselves are issued inside the transaction for atomicity (AC6).
1144
- *
1145
- * Prior implementation called ATTACH/DETACH per-table inside an open
1146
- * transaction — DETACH silently failed (SQLite restriction), the alias stayed
1147
- * attached, and every subsequent table for the same source threw
1148
- * "database _exodus_src_ is already in use", causing ~80 % data loss.
1149
- *
1150
- * ## FK-defer bulk copy (ROOT CAUSE 1 fix — T11533)
1151
- *
1152
- * Legacy tasks.db has self-referential FKs (`tasks.parent_id → tasks.id`),
1153
- * cross-table FKs, and ~18 tables with FK relationships. When FK enforcement
1154
- * is ON and tables are copied in arbitrary order (children before parents),
1155
- * each child INSERT fails with FOREIGN KEY constraint error (SQLite errcode 787)
1156
- * and the table is silently skipped — causing ~114K rows of data loss.
1157
- *
1158
- * Fix: set `PRAGMA foreign_keys = OFF` on the target connection before any
1159
- * bulk INSERT, then after all tables are committed run `PRAGMA foreign_key_check`
1160
- * to validate referential integrity. Genuine orphan rows surface as verify
1161
- * failures; child-before-parent ordering artifacts are NOT dropped.
1162
- * `PRAGMA foreign_keys = ON` is restored after the check.
1163
- *
1164
- * ## Name-mapping (ROOT CAUSE 1 fix — T11532)
1165
- *
1166
- * Legacy source DBs use UNPREFIXED table names (`tasks`, `messages`, `skills`,
1167
- * …) while the consolidated `cleo.db` uses DOMAIN-PREFIXED names
1168
- * (`tasks_tasks`, `conduit_messages`, `skills_skills`, …). The
1169
- * `resolveConsolidatedTableName()` function from `table-name-map.ts` performs
1170
- * the deterministic legacy→consolidated mapping before every copy. Tables with
1171
- * no consolidated home emit an explicit WARN journal entry rather than being
1172
- * silently discarded.
1173
- *
1174
- * ## Column-drift tolerance (ROOT CAUSE 2 fix — T11532, hardened T11533)
1175
- *
1176
- * When the source and target schemas differ (consolidated schema added/changed
1177
- * columns vs legacy), the copy uses the INTERSECTION of source and target
1178
- * column names. New target-only columns take their schema defaults; old
1179
- * source-only columns are dropped. This is implemented by introspecting both
1180
- * schemas via `PRAGMA table_info` and building an explicit column list.
1181
- *
1182
- * **NOT NULL / no-default hazard (T11533)**: target-only NOT NULL columns
1183
- * WITHOUT a schema default caused `INSERT OR IGNORE` to silently drop rows
1184
- * whose source value was NULL for that column (constraint violation → IGNORE).
1185
- * The fix: for each intersection column that is NOT NULL in the target AND
1186
- * has no `dflt_value`, the SELECT clause wraps the source reference in
1187
- * `COALESCE(src_col, type_default)` so no row is silently dropped.
1188
- * A type_default of `''` is used for TEXT affinity and `0` for numeric.
1189
- *
1190
- * ## Advisory file lock (AC4)
1191
- *
1192
- * The source DB files are opened read-only via `openCleoDbSnapshot` which
1193
- * calls `new DatabaseSync(path, { readOnly: true })`. Node's SQLite binding
1194
- * opens with `SQLITE_OPEN_READONLY`, which prevents any writes from this
1195
- * process. We additionally write a `.lock` sentinel file next to each source
1196
- * DB for the duration of the migration so that other CLEO processes can detect
1197
- * an in-progress exodus and refuse to write.
1198
- *
1199
- * @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
1200
- * @task T11531 (P0 attach-leak fix)
1201
- * @task T11532 (P0 name-mapping + column-drift + verify-rowid fix)
1202
- * @task T11533 (P0 FK-defer + NOT NULL coalesce + signaldock-global map + nexus hash fix)
1203
- * @task T11547 (P0 enum normalization — 7,421 rows recovered)
1204
- * @task T11548 (P0 final enum coverage — 285 remaining rows zero-loss)
1205
- * @task T11549 (P0 zero-loss final mile — brain_decisions enums + seconds-epoch coercion +
1206
- * agent_credentials/brain_release_links tables)
1207
- * @saga T11242
1208
- */
1209
- var log$2 = getLogger("exodus-migrate");
1210
- var LOCK_SENTINEL_SUFFIX = ".exodus-lock";
1211
- var JOURNAL_FILENAME = "exodus-journal.json";
1212
- /**
1213
- * Get the SQLite version string from an open DatabaseSync handle.
1214
- */
1215
- function getSqliteVersion(db) {
1216
- try {
1217
- return db.prepare("SELECT sqlite_version() AS v").get()?.v ?? "unknown";
1218
- } catch {
1219
- return "unknown";
1220
- }
1221
- }
1222
- /**
1223
- * Read the tables list from a legacy SQLite DB (excluding SQLite internals).
1224
- */
1225
- function listTables$1(db) {
1226
- return db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name").all().map((r) => r.name);
1227
- }
1228
- /**
1229
- * Write the journal file atomically (write-then-rename pattern).
1230
- */
1231
- function writeJournal(stagingDir, journal) {
1232
- const journalPath = join(stagingDir, JOURNAL_FILENAME);
1233
- const tmpPath = `${journalPath}.tmp`;
1234
- writeFileSync(tmpPath, JSON.stringify(journal, null, 2) + "\n", "utf8");
1235
- renameSync(tmpPath, journalPath);
1236
- }
1237
- /**
1238
- * Read an existing journal from the staging dir, or return `null`.
1239
- */
1240
- function readJournal(stagingDir) {
1241
- const journalPath = join(stagingDir, JOURNAL_FILENAME);
1242
- if (!existsSync(journalPath)) return null;
1243
- try {
1244
- return JSON.parse(readFileSync(journalPath, "utf8"));
1245
- } catch {
1246
- return null;
1247
- }
1248
- }
1249
- /**
1250
- * Invalidate the migrate journal after an aborted/rolled-back migration so a
1251
- * post-abort retry RE-COPIES every table from scratch (T11572).
1252
- *
1253
- * ## Why this is required for retry correctness
1254
- *
1255
- * `runExodusMigrate` is resumable (AC5): after each table copy it writes a
1256
- * journal entry with `status: 'done'`, and a subsequent run SKIPS any table
1257
- * already marked `done`. That resume optimisation is correct only while the
1258
- * consolidated rows it copied still EXIST. When the parity gate aborts, the
1259
- * exodus-on-open hook truncates the consolidated tables back to EMPTY
1260
- * (`rollbackConsolidatedToEmpty`) — but the on-disk journal still says every
1261
- * table is `done`. The next open therefore re-triggers (consolidated empty),
1262
- * runs `runExodusMigrate` again, finds every table `done` in the journal, copies
1263
- * NOTHING, re-verifies the still-empty target, and re-aborts — a permanent loop
1264
- * that pays the full migrate+verify cost on every open.
1265
- *
1266
- * Deleting the journal file forces `runExodusMigrate` to `initJournal()` afresh,
1267
- * so the retry actually re-copies. Idempotent and best-effort: a missing journal
1268
- * (or a missing staging dir) is a no-op. The staging backups are intentionally
1269
- * LEFT in place (they are the legacy source-of-truth snapshots — harmless to
1270
- * keep, valuable for forensics); only the progress journal is cleared.
1271
- *
1272
- * @param stagingDir - The staging directory whose `exodus-journal.json` to clear.
1273
- * @returns `true` if a journal file was removed, `false` if there was nothing to
1274
- * remove.
1275
- *
1276
- * @task T11572 (abort/rollback must invalidate journal so retry re-copies)
1277
- * @epic T11249 (E6)
1278
- * @saga T11242
1279
- */
1280
- function clearExodusJournal(stagingDir) {
1281
- const journalPath = join(stagingDir, JOURNAL_FILENAME);
1282
- try {
1283
- if (!existsSync(journalPath)) return false;
1284
- unlinkSync(journalPath);
1285
- log$2.info({ stagingDir }, "exodus: cleared migrate journal after abort/rollback — a retry will RE-COPY all tables");
1286
- return true;
1287
- } catch (err) {
1288
- log$2.warn({
1289
- err,
1290
- stagingDir
1291
- }, "exodus: failed to clear migrate journal after rollback (a retry may skip already-\"done\" tables)");
1292
- return false;
1293
- }
1294
- }
1295
- /**
1296
- * Initialise a fresh journal object.
1297
- */
1298
- function initJournal(sqliteVersion) {
1299
- const now = (/* @__PURE__ */ new Date()).toISOString();
1300
- return {
1301
- version: 1,
1302
- cleoVersion: getCleoVersion(),
1303
- targetSchemaVersion: EXODUS_TARGET_SCHEMA_VERSION,
1304
- nodeVersion: process.version,
1305
- sqliteVersion,
1306
- startedAt: now,
1307
- updatedAt: now,
1308
- tables: []
1309
- };
1310
- }
1311
- function lockPath(dbPath) {
1312
- return `${dbPath}${LOCK_SENTINEL_SUFFIX}`;
1313
- }
1314
- function acquireAdvisoryLock(dbPath) {
1315
- writeFileSync(lockPath(dbPath), JSON.stringify({
1316
- pid: process.pid,
1317
- ts: (/* @__PURE__ */ new Date()).toISOString()
1318
- }), "utf8");
1319
- }
1320
- function releaseAdvisoryLock(dbPath) {
1321
- try {
1322
- unlinkSync(lockPath(dbPath));
1323
- } catch {}
1324
- }
1325
- /**
1326
- * Return a file's size in bytes, or `0` when it cannot be stat'd (T11838).
1327
- *
1328
- * Used to decide whether a source exceeds the staging-copy skip threshold. A
1329
- * non-existent / unreadable source is treated as 0 bytes (it is filtered out of
1330
- * the copy loop separately), so it never trips the large-source skip branch.
1331
- *
1332
- * @param filePath - Absolute path to the file to stat.
1333
- * @returns The file size in bytes, or `0` on any stat failure.
1334
- */
1335
- function safeStatBytes(filePath) {
1336
- try {
1337
- return statSync(filePath).size;
1338
- } catch {
1339
- return 0;
1340
- }
1341
- }
1342
- /**
1343
- * Convert a source DB name to a safe, unique SQLite ATTACH alias.
1344
- *
1345
- * SQLite identifiers may contain only word characters; we prefix with
1346
- * `_src_` so they never collide with target table names.
1347
- *
1348
- * @param name - Logical name from `LegacyDbDescriptor.name` (e.g. `"brain (project)"`).
1349
- * @param index - Positional index used when names collide after sanitisation.
1350
- * @returns A unique, identifier-safe alias string.
1351
- */
1352
- function makeAttachAlias(name, index) {
1353
- return `_src_${name.replace(/[^a-z0-9]/gi, "_").replace(/_+/g, "_").slice(0, 20)}_${index}`;
1354
- }
1355
- /**
1356
- * Per-source-DB epoch unit lookup — used as a primary hint for the SQL magnitude
1357
- * heuristic (T11549 coercion fix). When the magnitude heuristic is ambiguous
1358
- * (value in the overlap zone), the source-level hint wins.
1359
- *
1360
- * Verified from schema source comments (§8.1 resolution):
1361
- * - conduit.db: `Math.floor(Date.now() / 1000)` → SECONDS
1362
- * - brain.db: `Date.now()` / `unixepoch * 1000` → MILLISECONDS
1363
- * - signaldock.db: `strftime('%s','now')` → SECONDS
1364
- * - tasks.db: `Date.now()` → MILLISECONDS (most writers)
1365
- * - nexus.db: mixed — most columns are MILLISECONDS but `user_profile`
1366
- * `first_observed_at`/`last_reinforced_at` are SECONDS
1367
- * (written by PSYCHE dialectic writer via `Math.floor(Date.now() / 1000)`).
1368
- * The magnitude heuristic handles this automatically.
1369
- * - skills.db: `Date.now()` → MILLISECONDS
1370
- */
1371
- var SOURCE_EPOCH_UNITS = new Map([
1372
- ["conduit", "seconds"],
1373
- ["brain", "milliseconds"],
1374
- ["brain (project)", "milliseconds"],
1375
- ["brain (global)", "milliseconds"],
1376
- ["signaldock", "seconds"],
1377
- ["tasks", "milliseconds"],
1378
- ["nexus", "milliseconds"],
1379
- ["skills", "milliseconds"]
1380
- ]);
1381
- /**
1382
- * Return the epoch unit used by a given source DB's INTEGER timestamp columns.
1383
- * Defaults to `'seconds'` for unknown sources (safe default — ISO-8601 output
1384
- * will be off by 1000x only for ms sources, which are already enumerated above).
1385
- *
1386
- * NOTE: This is the per-source hint. The actual SQL expression generated by
1387
- * `buildEpochToIsoExpr` uses a magnitude-based heuristic at the row level so
1388
- * individual columns that diverge from the source default are handled correctly
1389
- * (T11549 bug fix — nexus `user_profile` stores seconds despite nexus being
1390
- * labeled milliseconds at the source level).
1391
- */
1392
- function epochUnitForSource(sourceName) {
1393
- const key = sourceName.toLowerCase();
1394
- for (const [pattern, unit] of SOURCE_EPOCH_UNITS) if (key === pattern || key.startsWith(pattern)) return unit;
1395
- return "seconds";
1396
- }
1397
- /**
1398
- * Build a SQL SELECT expression for a shared column, applying (in priority order):
1399
- *
1400
- * 1. **Epoch→ISO-8601 coercion** (T11546): when the target has an ISO GLOB CHECK
1401
- * and the source column is INTEGER-typed.
1402
- * 2. **Non-finite numeric clamp** (T11782): when `NUMERIC_CLAMPS` has an entry
1403
- * for `(targetTableName, col)`, mapping `Inf`/`-Inf`/`NaN` to a finite in-range
1404
- * value so the row is not silently dropped.
1405
- * 3. **Enum-value normalization** (T11547): when `ENUM_NORMALIZATIONS` has an
1406
- * entry for `(targetTableName, col)`, producing a SQL CASE expression that
1407
- * maps legacy values to canonical enum members without losing semantics.
1408
- * 4. **NOT NULL coalesce** (T11533): for non-epoch, non-normalized columns whose
1409
- * target is NOT NULL with no schema default.
1410
- * 5. **Plain column reference** otherwise.
1411
- *
1412
- * The epoch→ISO conversion uses `buildEpochToIsoExpr` (from `column-transforms.ts`)
1413
- * which detects the epoch scale (seconds vs milliseconds) per-row using a magnitude
1414
- * heuristic: values below `EPOCH_SECONDS_THRESHOLD` (1e11) are treated as seconds;
1415
- * values at or above are treated as milliseconds and divided by 1000.0 before conversion
1416
- * (T11549 correctness fix — the previous per-source unit was incorrect for
1417
- * individual columns that diverged from the source default, e.g.
1418
- * `nexus.user_profile.first_observed_at` stores SECONDS even though most nexus
1419
- * writers use milliseconds).
1420
- *
1421
- * A NULL source value is preserved as NULL (passes the `IS NULL` branch of the
1422
- * GLOB CHECK, and is OK for nullable columns).
1423
- *
1424
- * @param attachAlias - ATTACH alias for the source DB.
1425
- * @param legacyTable - Legacy table name in the source.
1426
- * @param targetTableName - Physical consolidated target table name (for enum lookup).
1427
- * @param col - Column name.
1428
- * @param srcType - Raw type string from source `PRAGMA table_info`.
1429
- * @param tgtInfo - Target column metadata from `PRAGMA table_info`.
1430
- * @param isoGlobCols - Set of columns requiring ISO GLOB in the target.
1431
- * @returns SQL expression string suitable for use in a SELECT clause.
1432
- */
1433
- function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType, tgtInfo, isoGlobCols) {
1434
- const srcRef = `"${attachAlias}"."${legacyTable}"."${col}"`;
1435
- const srcUpper = srcType.toUpperCase();
1436
- const isIntegerSource = srcUpper.includes("INT") || srcUpper === "" || srcUpper === "NUMERIC";
1437
- const isNotNullWithoutDefault = tgtInfo.notnull === 1 && tgtInfo.dflt_value === null;
1438
- if (isoGlobCols.has(col) && isIntegerSource) {
1439
- const isoExpr = buildEpochToIsoExpr(srcRef);
1440
- if (isNotNullWithoutDefault) return `COALESCE(${isoExpr}, '') AS "${col}"`;
1441
- return `${isoExpr} AS "${col}"`;
1442
- }
1443
- const clampExpr = numericClampExpr(targetTableName, col, srcRef);
1444
- if (clampExpr !== null) {
1445
- if (isNotNullWithoutDefault) return `COALESCE(${clampExpr}, ${typeDefaultLiteral(tgtInfo.type)}) AS "${col}"`;
1446
- return `${clampExpr} AS "${col}"`;
1447
- }
1448
- const normExpr = enumNormExpr(targetTableName, col, srcRef);
1449
- if (normExpr !== null) {
1450
- if (isNotNullWithoutDefault) return `COALESCE(${normExpr}, ${typeDefaultLiteral(tgtInfo.type)}) AS "${col}"`;
1451
- return `${normExpr} AS "${col}"`;
1452
- }
1453
- if (isNotNullWithoutDefault) return `COALESCE(${srcRef}, ${typeDefaultLiteral(tgtInfo.type)}) AS "${col}"`;
1454
- return srcRef;
1455
- }
1456
- /**
1457
- * Copy all rows from a legacy source table (in the already-attached alias) into
1458
- * the corresponding consolidated target table.
1459
- *
1460
- * ## What changed in T11532 vs the T11531 version:
1461
- *
1462
- * 1. **Name mapping (ROOT CAUSE 1 — T11532)**: `legacyTableName` is resolved to
1463
- * its consolidated name via `resolveConsolidatedTableName()`. Without this,
1464
- * `tasks` (legacy) was looked up as `main."tasks"` which doesn't exist in
1465
- * the consolidated schema (the real target is `tasks_tasks`).
1466
- *
1467
- * 2. **Column-drift tolerance (ROOT CAUSE 2 — T11532 + T11533)**: the INSERT
1468
- * uses the INTERSECTION of source and target column lists rather than source
1469
- * columns verbatim. When the consolidated schema added new columns, old code
1470
- * failed. Target-only NOT NULL columns without defaults now get COALESCE()
1471
- * wrapping in the SELECT so NULL source values don't cause silent row drops.
1472
- *
1473
- * 3. **Explicit skip (ROOT CAUSE 5)**: tables intentionally excluded from the
1474
- * consolidated schema (virtual tables, orphan telemetry, etc.) now return
1475
- * a logged skip result rather than being silently treated as "target not
1476
- * found".
1477
- *
1478
- * 4. **Epoch→ISO coercion (ROOT CAUSE 1 — T11546)**: columns with an ISO GLOB
1479
- * CHECK in the target that are INTEGER-typed in the source are converted via
1480
- * `strftime('%Y-%m-%dT%H:%M:%fZ', col[/1000.0], 'unixepoch')`. Without this,
1481
- * `INSERT OR IGNORE` silently drops ALL rows for those tables (CHECK fails
1482
- * for every row because an integer like `1717200000` doesn't match the GLOB).
1483
- *
1484
- * 5. **No-swallow assertion (ROOT CAUSE 1b — T11546)**: after the bulk INSERT,
1485
- * `changes` is compared against the source row count. A shortfall is a hard
1486
- * per-table error. PK/UNIQUE conflicts on idempotent resume are expected and
1487
- * tolerated (checked via count of existing rows); CHECK constraint drops are
1488
- * not tolerated.
1489
- *
1490
- * **Pre-condition**: the caller has already executed
1491
- * `ATTACH DATABASE '<path>' AS "<attachAlias>"` on `targetNativeDb`, and
1492
- * `PRAGMA foreign_keys = OFF` has been set so FK ordering doesn't matter.
1493
- *
1494
- * ## Cross-scope routing (ADR-090 · T11539)
1495
- *
1496
- * `targetSchema` defaults to `'main'` (the connection's own consolidated DB). For
1497
- * the four nexus code-graph tables, which are extracted from the GLOBAL `nexus.db`
1498
- * source but must land in the PROJECT consolidated `cleo.db`, the caller attaches
1499
- * the project DB under an alias on the SAME connection and passes that alias as
1500
- * `targetSchema`. All target-side introspection (`sqlite_master`, `PRAGMA
1501
- * table_info`) and the `INSERT OR IGNORE` are then schema-qualified to that
1502
- * attached DB instead of `main`.
1503
- *
1504
- * @param targetNativeDb - Writable target handle (mid-transaction, FK OFF).
1505
- * @param srcNativeDb - Read-only source snapshot (for metadata queries).
1506
- * @param attachAlias - Alias under which the source is attached.
1507
- * @param legacyTableName - Physical table name in the legacy source DB.
1508
- * @param sourceName - `LegacyDbDescriptor.name` (for name resolution).
1509
- * @param targetSchema - Schema the consolidated target table lives in
1510
- * (`'main'` by default, or an ATTACH alias for cross-scope routing).
1511
- */
1512
- function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyTableName, sourceName, targetSchema = "main") {
1513
- const resolution = resolveConsolidatedTableName(sourceName, legacyTableName);
1514
- if (resolution.kind === "skip") {
1515
- log$2.warn({
1516
- legacyTableName,
1517
- sourceName,
1518
- reason: resolution.reason
1519
- }, `Exodus: explicitly skipping table — ${resolution.reason}`);
1520
- return {
1521
- rowsCopied: 0,
1522
- skipped: true,
1523
- reason: resolution.reason
1524
- };
1525
- }
1526
- const targetTableName = resolution.targetName;
1527
- const srcPragma = srcNativeDb.prepare(`PRAGMA table_info("${legacyTableName}")`).all();
1528
- const srcColumns = new Set(srcPragma.map((r) => r.name));
1529
- if (srcColumns.size === 0) return {
1530
- rowsCopied: 0,
1531
- skipped: false
1532
- };
1533
- const sourceCount = srcNativeDb.prepare(`SELECT COUNT(*) AS c FROM "${legacyTableName}"`).get()?.c ?? 0;
1534
- if (sourceCount === 0) return {
1535
- rowsCopied: 0,
1536
- skipped: false
1537
- };
1538
- const escapedTarget = targetTableName.replace(/'/g, "''");
1539
- if (!targetNativeDb.prepare(`SELECT name FROM "${targetSchema}".sqlite_master WHERE type='table' AND name='${escapedTarget}'`).get()) {
1540
- const reason = `consolidated target '${targetTableName}' not found (mapped from legacy '${legacyTableName}')`;
1541
- log$2.warn({
1542
- legacyTableName,
1543
- targetTableName,
1544
- sourceName,
1545
- attachAlias,
1546
- targetSchema
1547
- }, `Exodus: ${reason}`);
1548
- return {
1549
- rowsCopied: 0,
1550
- skipped: true,
1551
- reason
1552
- };
1553
- }
1554
- const tgtPragma = targetNativeDb.prepare(`PRAGMA "${targetSchema}".table_info("${targetTableName}")`).all();
1555
- const tgtColMap = new Map(tgtPragma.map((r) => [r.name, r]));
1556
- const sharedColumns = srcPragma.map((r) => r.name).filter((col) => tgtColMap.has(col));
1557
- if (sharedColumns.length === 0) {
1558
- const reason = `no overlapping columns between source '${legacyTableName}' and target '${targetTableName}'`;
1559
- log$2.warn({
1560
- legacyTableName,
1561
- targetTableName,
1562
- sourceName
1563
- }, `Exodus: ${reason}`);
1564
- return {
1565
- rowsCopied: 0,
1566
- skipped: true,
1567
- reason
1568
- };
1569
- }
1570
- const srcOnlyColumns = srcPragma.map((r) => r.name).filter((c) => !tgtColMap.has(c));
1571
- const tgtOnlyColumns = tgtPragma.map((r) => r.name).filter((c) => !srcColumns.has(c));
1572
- if (srcOnlyColumns.length > 0 || tgtOnlyColumns.length > 0) log$2.info({
1573
- legacyTableName,
1574
- targetTableName,
1575
- sourceName,
1576
- srcOnlyColumns,
1577
- tgtOnlyColumns
1578
- }, "Exodus: column drift detected — copying intersection, dropping src-only cols, using defaults for tgt-only cols");
1579
- const isoGlobCols = detectIsoGlobColumns(targetNativeDb, targetTableName, targetSchema);
1580
- const epochUnitHint = epochUnitForSource(sourceName);
1581
- const srcTypeMap = new Map(srcPragma.map((r) => [r.name, r.type]));
1582
- if (isoGlobCols.size > 0) {
1583
- const coercedCols = sharedColumns.filter((col) => {
1584
- const upper = (srcTypeMap.get(col) ?? "").toUpperCase();
1585
- return isoGlobCols.has(col) && (upper.includes("INT") || upper === "" || upper === "NUMERIC");
1586
- });
1587
- if (coercedCols.length > 0) log$2.info({
1588
- legacyTableName,
1589
- targetTableName,
1590
- sourceName,
1591
- coercedCols,
1592
- epochUnitHint
1593
- }, `Exodus: applying epoch→ISO coercion (magnitude heuristic) for ${coercedCols.length} column(s) (T11546+T11549)`);
1594
- }
1595
- const normalizedCols = sharedColumns.filter((col) => ENUM_NORMALIZATIONS.has(`${targetTableName}.${col}`));
1596
- if (normalizedCols.length > 0) log$2.info({
1597
- legacyTableName,
1598
- targetTableName,
1599
- sourceName,
1600
- normalizedCols
1601
- }, `Exodus: applying enum-value normalization for ${normalizedCols.length} column(s) (T11547)`);
1602
- const clampedCols = sharedColumns.filter((col) => NUMERIC_CLAMPS.has(`${targetTableName}.${col}`));
1603
- if (clampedCols.length > 0) log$2.info({
1604
- legacyTableName,
1605
- targetTableName,
1606
- sourceName,
1607
- clampedCols
1608
- }, `Exodus: applying non-finite numeric clamp for ${clampedCols.length} column(s) (T11782)`);
1609
- const selectExprs = sharedColumns.map((col) => {
1610
- return buildSelectExpr(attachAlias, legacyTableName, targetTableName, col, srcTypeMap.get(col) ?? "", tgtColMap.get(col), isoGlobCols);
1611
- });
1612
- const tgtOnlyNotNullCols = tgtOnlyColumns.filter((col) => {
1613
- const info = tgtColMap.get(col);
1614
- return info !== void 0 && info.notnull === 1 && info.dflt_value === null;
1615
- });
1616
- const allInsertCols = [...sharedColumns, ...tgtOnlyNotNullCols];
1617
- const allSelectExprs = [...selectExprs, ...tgtOnlyNotNullCols.map((col) => {
1618
- return `${typeDefaultLiteral(tgtColMap.get(col).type)} AS "${col}"`;
1619
- })];
1620
- const colList = allInsertCols.map((c) => `"${c}"`).join(", ");
1621
- const selectList = allSelectExprs.join(", ");
1622
- const existingBeforeRow = targetNativeDb.prepare(`SELECT COUNT(*) AS c FROM "${targetSchema}"."${targetTableName}"`).get();
1623
- const existingBefore = Number(existingBeforeRow?.c ?? 0);
1624
- const rowsCopied = targetNativeDb.prepare(`INSERT OR IGNORE INTO "${targetSchema}"."${targetTableName}" (${colList}) SELECT ${selectList} FROM "${attachAlias}"."${legacyTableName}"`).run().changes ?? 0;
1625
- if (rowsCopied < sourceCount) {
1626
- const presentAccountedFor = existingBefore + rowsCopied;
1627
- if (presentAccountedFor >= sourceCount) {
1628
- log$2.info({
1629
- legacyTableName,
1630
- targetTableName,
1631
- sourceName,
1632
- sourceCount,
1633
- rowsCopied,
1634
- existingBefore
1635
- }, `Exodus: '${legacyTableName}'→'${targetTableName}' — ${sourceCount - rowsCopied} of ${sourceCount} row(s) already present in target (idempotent PK dedup), ${rowsCopied} newly copied; no loss`);
1636
- return {
1637
- rowsCopied,
1638
- skipped: false
1639
- };
1640
- }
1641
- const missing = sourceCount - presentAccountedFor;
1642
- const reason = `INSERT OR IGNORE lost ${missing} of ${sourceCount} rows from '${legacyTableName}'→'${targetTableName}' (rowsCopied=${rowsCopied}, existingBefore=${existingBefore}) — a CHECK/NOT NULL/type/enum constraint rejected them (NOT PK dedup); inspect epoch coercion + enum normalization. verify will confirm.`;
1643
- log$2.error({
1644
- legacyTableName,
1645
- targetTableName,
1646
- sourceName,
1647
- sourceCount,
1648
- rowsCopied,
1649
- existingBefore,
1650
- missing
1651
- }, `Exodus: ${reason}`);
1652
- return {
1653
- rowsCopied,
1654
- skipped: false,
1655
- reason
1656
- };
1657
- }
1658
- return {
1659
- rowsCopied,
1660
- skipped: false
1661
- };
1662
- }
1663
- /**
1664
- * Validate that the journal's schema version matches the current target version.
1665
- *
1666
- * When `forceCrossVersion === true`, mismatches are logged but not fatal.
1667
- */
1668
- function checkSchemaVersion(journal, forceCrossVersion) {
1669
- if (journal.targetSchemaVersion !== "drizzle-v1.0.0-rc.3/dual-scope/2026-05") {
1670
- const msg = `Schema version mismatch: journal=${journal.targetSchemaVersion}, expected=${EXODUS_TARGET_SCHEMA_VERSION}`;
1671
- if (forceCrossVersion) {
1672
- log$2.warn(msg + " (--force-cross-version: continuing anyway)");
1673
- return true;
1674
- }
1675
- log$2.error(msg + " — pass --force-cross-version to override");
1676
- return false;
1677
- }
1678
- return true;
1679
- }
1680
- /**
1681
- * Run the exodus migration.
1682
- *
1683
- * @param plan - Pre-flight plan from `buildExodusPlan()`.
1684
- * @param forceCrossVersion - Skip the schema-version guard (AC9).
1685
- * @param onProgress - Optional progress callback called after each table.
1686
- *
1687
- * @returns {@link ExodusMigrateResult}
1688
- *
1689
- * @task T11248 (AC4, AC5, AC6, AC7, AC9)
1690
- * @task T11531 (P0 attach-leak fix)
1691
- */
1692
- async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
1693
- const { sources, stagingDir, diskPreflight, projectDbPath, globalDbPath } = plan;
1694
- if (!diskPreflight) {
1695
- const shortfall = Math.max(0, plan.requiredBytes - plan.availableBytes);
1696
- return {
1697
- ok: false,
1698
- tables: [],
1699
- stagingDir,
1700
- backupPaths: [],
1701
- error: `Insufficient disk space for exodus: need ≥${plan.requiredBytes} bytes (≈${STAGING_HEADROOM_FACTOR}× largest source ${plan.largestSourceBytes} + consolidated estimate ${plan.totalSourceBytes}), but only ${plan.availableBytes} bytes are free on the target filesystem — ${shortfall} bytes short. Free up at least ${shortfall} bytes (e.g. \`cleo backup prune\`, clear caches), or move the .cleo/ directory to a larger volume, then retry.`
1702
- };
1703
- }
1704
- mkdirSync(stagingDir, { recursive: true });
1705
- let sqliteVersion = "unknown";
1706
- for (const src of sources) if (existsSync(src.path)) {
1707
- const snap = openCleoDbSnapshot(src.path, { readOnly: true });
1708
- sqliteVersion = getSqliteVersion(snap.db);
1709
- snap.close();
1710
- break;
1711
- }
1712
- let journal = readJournal(stagingDir);
1713
- if (journal === null) journal = initJournal(sqliteVersion);
1714
- else {
1715
- if (!checkSchemaVersion(journal, forceCrossVersion)) return {
1716
- ok: false,
1717
- tables: [],
1718
- stagingDir,
1719
- backupPaths: [],
1720
- error: "Schema version mismatch. Pass --force-cross-version to override."
1721
- };
1722
- onProgress?.("Resuming from existing staging journal…");
1723
- }
1724
- const backupPaths = [];
1725
- const allTableResults = [];
1726
- const lockedPaths = [];
1727
- let projectHandle = null;
1728
- let globalHandle = null;
1729
- try {
1730
- for (const src of sources) {
1731
- if (!existsSync(src.path)) continue;
1732
- const backupDest = join(stagingDir, `${src.name.replace(/[^a-z0-9-]/g, "_")}-backup.db`);
1733
- const srcBytes = safeStatBytes(src.path);
1734
- if (srcBytes > plan.stagingCopyThresholdBytes) onProgress?.(`Skipping full staging copy of ${src.name} (${srcBytes} bytes > ${plan.stagingCopyThresholdBytes} threshold) — source is archived, not deleted, on success.`);
1735
- else if (!existsSync(backupDest)) {
1736
- onProgress?.(`Backing up ${src.name} → staging dir…`);
1737
- copyFileSync(src.path, backupDest);
1738
- backupPaths.push(backupDest);
1739
- }
1740
- acquireAdvisoryLock(src.path);
1741
- lockedPaths.push(src.path);
1742
- }
1743
- onProgress?.("Opening DEDICATED project-scope cleo.db connection (running migrations)…");
1744
- projectHandle = await openDualScopeDbAtPath("project", projectDbPath, void 0, { dedicated: true });
1745
- onProgress?.("Opening DEDICATED global-scope cleo.db connection (running migrations)…");
1746
- globalHandle = await openDualScopeDbAtPath("global", globalDbPath, void 0, { dedicated: true });
1747
- function extractNativeDb(handle) {
1748
- const drizzleHandle = handle.db;
1749
- const client = drizzleHandle["$client"];
1750
- if (client && typeof client["prepare"] === "function") return client;
1751
- if (typeof drizzleHandle["prepare"] === "function") return drizzleHandle;
1752
- throw new Error("Could not extract native DatabaseSync from dual-scope DB handle");
1753
- }
1754
- const projectNative = extractNativeDb(projectHandle);
1755
- const globalNative = extractNativeDb(globalHandle);
1756
- const projectSources = sources.filter((s) => s.targetScope === "project" && existsSync(s.path));
1757
- const globalSources = sources.filter((s) => s.targetScope === "global" && existsSync(s.path));
1758
- await migrateScope("project", projectSources, projectNative, journal, stagingDir, allTableResults, onProgress);
1759
- await migrateScope("global", globalSources, globalNative, journal, stagingDir, allTableResults, onProgress, projectDbPath);
1760
- journal.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1761
- writeJournal(stagingDir, journal);
1762
- return {
1763
- ok: true,
1764
- tables: allTableResults,
1765
- stagingDir,
1766
- backupPaths
1767
- };
1768
- } catch (err) {
1769
- const error = err instanceof Error ? err.message : String(err);
1770
- log$2.error({ err }, "Exodus migration failed");
1771
- return {
1772
- ok: false,
1773
- tables: allTableResults,
1774
- stagingDir,
1775
- backupPaths,
1776
- error
1777
- };
1778
- } finally {
1779
- try {
1780
- projectHandle?.close();
1781
- } catch {}
1782
- try {
1783
- globalHandle?.close();
1784
- } catch {}
1785
- for (const p of lockedPaths) releaseAdvisoryLock(p);
1786
- }
1787
- }
1788
- /**
1789
- * Migrate all tables from the given sources into the target native DB.
1790
- *
1791
- * ## ATTACH-once-per-source protocol (T11531)
1792
- *
1793
- * SQLite forbids `DETACH` inside an active multi-statement transaction.
1794
- * To avoid the "database alias is already in use" error that caused ~80 %
1795
- * data loss, we use the following sequence **per source DB**:
1796
- *
1797
- * 1. ATTACH the source path under a unique alias (outside any transaction).
1798
- * 2. Open a read-only snapshot of the source for metadata queries.
1799
- * 3. BEGIN the write transaction on the target.
1800
- * 4. INSERT OR IGNORE … SELECT for each table using the attached alias.
1801
- * 5. COMMIT (or ROLLBACK on error).
1802
- * 6. DETACH the source alias in `finally` (outside the committed transaction).
1803
- * 7. Close the read-only snapshot.
1804
- *
1805
- * Each source gets its own unique alias (`_src_<name>_<index>`) so multiple
1806
- * sources can be processed sequentially without alias conflicts.
1807
- *
1808
- * ## FK-defer protocol (T11533 ROOT CAUSE 1)
1809
- *
1810
- * Before the first INSERT in any scope, foreign-key enforcement is switched OFF
1811
- * on the target connection (`PRAGMA foreign_keys = OFF`) so copy order does not
1812
- * matter (avoids "FOREIGN KEY constraint failed" for child-before-parent copies).
1813
- * After all sources in the scope are committed, `PRAGMA foreign_key_check` is
1814
- * executed to surface genuinely orphaned rows, and then FK enforcement is
1815
- * restored (`PRAGMA foreign_keys = ON`).
1816
- *
1817
- * Sequence across all sources in a scope:
1818
- * PRAGMA foreign_keys = OFF
1819
- * for each source:
1820
- * ATTACH … AS alias (outside tx)
1821
- * BEGIN
1822
- * INSERT … SELECT for each table
1823
- * COMMIT
1824
- * DETACH alias (outside tx)
1825
- * PRAGMA foreign_key_check → log orphans as warnings
1826
- * PRAGMA foreign_keys = ON
1827
- */
1828
- async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir, allTableResults, onProgress, crossScopeTargetPath) {
1829
- if (sources.length === 0) return;
1830
- onProgress?.(`Migrating ${scope}-scope sources…`);
1831
- targetNativeDb.exec("PRAGMA foreign_keys = OFF");
1832
- log$2.info({ scope }, "Exodus: foreign_keys=OFF for bulk copy (T11533 FK-defer)");
1833
- try {
1834
- for (let i = 0; i < sources.length; i++) {
1835
- const src = sources[i];
1836
- const attachAlias = makeAttachAlias(src.name, i);
1837
- const escapedPath = src.path.replace(/'/g, "''");
1838
- targetNativeDb.exec(`ATTACH DATABASE '${escapedPath}' AS "${attachAlias}"`);
1839
- onProgress?.(` [${src.name}] Attached as "${attachAlias}"`);
1840
- let crossAlias = null;
1841
- if (crossScopeTargetPath) {
1842
- crossAlias = `_xscope_${attachAlias}`;
1843
- const escapedCrossPath = crossScopeTargetPath.replace(/'/g, "''");
1844
- targetNativeDb.exec(`ATTACH DATABASE '${escapedCrossPath}' AS "${crossAlias}"`);
1845
- onProgress?.(` [${src.name}] Cross-scope target attached as "${crossAlias}"`);
1846
- }
1847
- const snap = openCleoDbSnapshot(src.path, { readOnly: true });
1848
- try {
1849
- const tables = listTables$1(snap.db);
1850
- targetNativeDb.exec("BEGIN");
1851
- let txOpen = true;
1852
- try {
1853
- for (const tableName of tables) {
1854
- const existing = journal.tables.find((e) => e.sourceDb === src.name && e.tableName === tableName);
1855
- if (existing?.status === "done") {
1856
- onProgress?.(` ↳ ${src.name}.${tableName} — already done (resuming)`);
1857
- allTableResults.push({
1858
- sourceDb: src.name,
1859
- tableName,
1860
- rowsCopied: existing.rowsCopied,
1861
- skipped: false
1862
- });
1863
- continue;
1864
- }
1865
- onProgress?.(` ↳ Copying ${src.name}.${tableName}…`);
1866
- let rowsCopied = 0;
1867
- let status = "done";
1868
- let errorMsg;
1869
- let skipped = false;
1870
- try {
1871
- const effectiveScope = resolveTableTargetScope(src.name, tableName, scope);
1872
- const targetSchema = effectiveScope !== scope && crossAlias ? crossAlias : "main";
1873
- if (effectiveScope !== scope && !crossAlias) throw new Error(`Table '${tableName}' from '${src.name}' routes to ${effectiveScope} scope but no cross-scope target was attached for the ${scope} pass (ADR-090 T11539)`);
1874
- const copyResult = copyTableFromAttached(targetNativeDb, snap.db, attachAlias, tableName, src.name, targetSchema);
1875
- rowsCopied = copyResult.rowsCopied;
1876
- if (copyResult.skipped) {
1877
- status = "skipped";
1878
- errorMsg = copyResult.reason;
1879
- skipped = true;
1880
- } else if (copyResult.reason) {
1881
- status = "partial";
1882
- errorMsg = copyResult.reason;
1883
- }
1884
- } catch (err) {
1885
- const msg = err instanceof Error ? err.message : String(err);
1886
- log$2.warn({
1887
- tableName,
1888
- sourceDb: src.name,
1889
- err
1890
- }, "Table copy failed — skipping");
1891
- status = "skipped";
1892
- errorMsg = msg;
1893
- skipped = true;
1894
- }
1895
- const entry = {
1896
- sourceDb: src.name,
1897
- tableName,
1898
- status,
1899
- rowsCopied,
1900
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1901
- ...errorMsg ? { error: errorMsg } : {}
1902
- };
1903
- const idx = journal.tables.findIndex((e) => e.sourceDb === src.name && e.tableName === tableName);
1904
- if (idx >= 0) journal.tables[idx] = entry;
1905
- else journal.tables.push(entry);
1906
- journal.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1907
- writeJournal(stagingDir, journal);
1908
- allTableResults.push({
1909
- sourceDb: src.name,
1910
- tableName,
1911
- rowsCopied,
1912
- skipped,
1913
- reason: errorMsg
1914
- });
1915
- }
1916
- targetNativeDb.exec("COMMIT");
1917
- txOpen = false;
1918
- } catch (err) {
1919
- if (txOpen) try {
1920
- targetNativeDb.exec("ROLLBACK");
1921
- } catch {}
1922
- throw err;
1923
- }
1924
- } finally {
1925
- snap.close();
1926
- try {
1927
- targetNativeDb.exec(`DETACH DATABASE "${attachAlias}"`);
1928
- onProgress?.(` [${src.name}] Detached "${attachAlias}"`);
1929
- } catch (detachErr) {
1930
- log$2.warn({
1931
- attachAlias,
1932
- sourceDb: src.name,
1933
- err: detachErr
1934
- }, "DETACH failed — alias will be released on DB close");
1935
- }
1936
- if (crossAlias) try {
1937
- targetNativeDb.exec(`DETACH DATABASE "${crossAlias}"`);
1938
- onProgress?.(` [${src.name}] Cross-scope target detached "${crossAlias}"`);
1939
- } catch (detachErr) {
1940
- log$2.warn({
1941
- crossAlias,
1942
- sourceDb: src.name,
1943
- err: detachErr
1944
- }, "Cross-scope DETACH failed — alias will be released on DB close");
1945
- }
1946
- }
1947
- }
1948
- } finally {
1949
- try {
1950
- const orphans = targetNativeDb.prepare("PRAGMA foreign_key_check").all();
1951
- if (orphans.length > 0) log$2.warn({
1952
- scope,
1953
- orphanCount: orphans.length,
1954
- sample: orphans.slice(0, 5)
1955
- }, `Exodus: PRAGMA foreign_key_check found ${orphans.length} orphan row(s) after bulk copy — these are genuine data orphans (not ordering artifacts)`);
1956
- else log$2.info({ scope }, "Exodus: PRAGMA foreign_key_check PASSED — no orphan rows");
1957
- } catch (checkErr) {
1958
- log$2.warn({
1959
- scope,
1960
- err: checkErr
1961
- }, "Exodus: PRAGMA foreign_key_check failed (non-fatal) — target schema may not have FK constraints enabled");
1962
- }
1963
- try {
1964
- targetNativeDb.exec("PRAGMA foreign_keys = ON");
1965
- log$2.info({ scope }, "Exodus: foreign_keys=ON restored after bulk copy");
1966
- } catch (fkErr) {
1967
- log$2.warn({
1968
- scope,
1969
- err: fkErr
1970
- }, "Exodus: could not restore foreign_keys=ON (non-fatal)");
1971
- }
1972
- }
1973
- }
1974
- getLogger("exodus-seal");
1975
- //#endregion
1976
- //#region ../core/dist/store/exodus/verify-migration.js
1977
- /**
1978
- * Reusable CORE migration-parity primitive — `verifyMigration()`.
1979
- *
1980
- * This is the durable guard (T11551 · DHQ-045) that prevents the exodus
1981
- * dual-DB consolidation from silently losing rows. It is intentionally generic:
1982
- * given a set of legacy source DBs and the consolidated target DB paths, it
1983
- * returns a typed {@link VerifyMigrationResult} covering FOUR failure classes:
1984
- *
1985
- * 1. **Per-table row-count parity** — every data-bearing source table's
1986
- * consolidated counterpart MUST NOT have FEWER rows than the source (a
1987
- * DEFICIT = data loss → failure). A SURPLUS (target > source — e.g.
1988
- * `nexus_audit_log` gaining the migration's own audit writes during the
1989
- * migrating open) is NOT loss and is tolerated with a WARN (T11577). The
1990
- * per-table `countMatch` field stays a strict `source === target`
1991
- * diagnostic; only a deficit contributes a failure.
1992
- * 2. **`PRAGMA foreign_key_check`** — genuine referential orphans on the
1993
- * consolidated target surface as failures (not copy-order artifacts).
1994
- * 3. **Content checksum** — an ordered canonical-JSON SHA-256 digest over the
1995
- * sorted intersection of source/target columns catches content drift even
1996
- * when counts match.
1997
- * 4. **Enum/type-drift report** — source values outside the target column's
1998
- * CHECK enum. This is the EXACT class that `INSERT OR IGNORE` used to drop
1999
- * silently (the root cause of the ~805K-row exodus loss).
2000
- *
2001
- * ## Relationship to `runExodusVerify`
2002
- *
2003
- * `runExodusVerify` ({@link ./verify.ts}) is the exodus-specific entry point.
2004
- * It now DELEGATES the row-count + checksum parity to this primitive (DRY —
2005
- * T11551 AC2) and additionally surfaces the FK + enum-drift checks this module
2006
- * adds. The digest, name-mapping, and rowid-safe ordering logic that the exodus
2007
- * campaign hardened (T11531/32/33) live here as the single implementation.
2008
- *
2009
- * ## False-pass guard (T11531, preserved)
2010
- *
2011
- * When `ok === false`, `error` is ALWAYS populated with a human-readable
2012
- * failure summary so a caller that only checks `result.error` cannot mistake a
2013
- * silent loss for success.
2014
- *
2015
- * @task T11551 (DHQ-045 — exodus zero-loss durable guard)
2016
- * @epic T10878
2017
- * @saga T11242
2018
- */
2019
- var log = getLogger("verify-migration");
2020
- var _require = createRequire(import.meta.url);
2021
- /**
2022
- * Determine a deterministic ORDER BY clause for a table.
2023
- *
2024
- * Uses the table's declared primary-key columns (from `PRAGMA table_info`
2025
- * where `pk > 0`) so ordering is stable for both WITH ROWID and WITHOUT ROWID
2026
- * tables. Falls back to `rowid` only for ordinary tables that declare no
2027
- * explicit primary key. Avoids the `no such column: rowid` crash on virtual /
2028
- * WITHOUT ROWID tables (T11532 ROOT CAUSE 3).
2029
- *
2030
- * @param db - Database handle to introspect.
2031
- * @param tableName - Physical table name.
2032
- * @returns A SQL ORDER BY column list.
2033
- */
2034
- function orderByClause(db, tableName) {
2035
- try {
2036
- const pkCols = db.prepare(`PRAGMA table_info("${tableName}")`).all().filter((r) => r.pk > 0).sort((a, b) => a.pk - b.pk).map((r) => `"${r.name}"`);
2037
- if (pkCols.length > 0) return pkCols.join(", ");
2038
- } catch {}
2039
- return "rowid";
2040
- }
2041
- /**
2042
- * Compute an ordered canonical-JSON SHA-256 digest (32 hex chars) for all rows
2043
- * in a table, restricted to the given column list.
2044
- *
2045
- * When the caller passes the SORTED INTERSECTION of source and target columns,
2046
- * both sides produce identically-structured JSON rows, eliminating spurious
2047
- * hash mismatches from schema-definition column reordering (T11533 ROOT CAUSE
2048
- * 4). Returns `{ count: 0, hash: '' }` for virtual tables that cannot be
2049
- * selected from, rather than throwing.
2050
- *
2051
- * ## Source-side coercion (T11809 · AC2)
2052
- *
2053
- * When `transform` is provided (SOURCE side only), each column is SELECTed
2054
- * through {@link buildDigestExpr} — the SAME epoch→ISO / enum-normalize /
2055
- * non-finite-clamp transforms the migration applied — and aliased back to its
2056
- * original name so the canonical-key serialisation matches the (already
2057
- * canonical) target row. The TARGET side passes `transform === undefined` and
2058
- * digests its stored values raw.
2059
- *
2060
- * @param db - Database handle to query.
2061
- * @param tableName - Physical table name.
2062
- * @param columns - Explicit column list in canonical order, or `null` for
2063
- * `SELECT *` (used when there is no counterpart table to intersect with).
2064
- * @param transform - Source-side transform spec, or `undefined` for the raw
2065
- * (target-side) digest.
2066
- * @returns `{ count, hash }` for the table.
2067
- */
2068
- function computeTableDigest(db, tableName, columns, transform) {
2069
- let count = 0;
2070
- try {
2071
- const row = db.prepare(`SELECT COUNT(*) AS c FROM "${tableName}"`).get();
2072
- count = Number(row?.c ?? 0);
2073
- } catch (err) {
2074
- const msg = err instanceof Error ? err.message : String(err);
2075
- log.warn({
2076
- tableName,
2077
- err: msg
2078
- }, "computeTableDigest: COUNT(*) failed (possibly a virtual/FTS table) — treating as 0 rows");
2079
- return {
2080
- count: 0,
2081
- hash: ""
2082
- };
2083
- }
2084
- const { createHash } = _require("node:crypto");
2085
- const hasher = createHash("sha256");
2086
- const orderBy = orderByClause(db, tableName);
2087
- let selectClause;
2088
- if (columns !== null && columns.length > 0) selectClause = columns.map((c) => {
2089
- if (transform === void 0) return `"${c}"`;
2090
- const srcType = transform.srcTypeByCol.get(c) ?? "";
2091
- const tgtCol = transform.tgtColByCol.get(c);
2092
- return `${buildDigestExpr(transform.targetTableName, c, srcType, transform.isoGlobCols, tgtCol)} AS "${c}"`;
2093
- }).join(", ");
2094
- else selectClause = "*";
2095
- try {
2096
- const stmt = db.prepare(`SELECT ${selectClause} FROM "${tableName}" ORDER BY ${orderBy}`);
2097
- for (const row of stmt.iterate()) {
2098
- const rowObj = row;
2099
- hasher.update(JSON.stringify(rowObj, Object.keys(rowObj).sort()));
2100
- }
2101
- } catch (err) {
2102
- const msg = err instanceof Error ? err.message : String(err);
2103
- log.warn({
2104
- tableName,
2105
- err: msg
2106
- }, "computeTableDigest: streamed SELECT failed (possibly a virtual/FTS table) — digest skipped (COUNT(*) parity still enforced)");
2107
- return {
2108
- count,
2109
- hash: ""
2110
- };
2111
- }
2112
- return {
2113
- count,
2114
- hash: hasher.digest("hex").slice(0, 32)
2115
- };
2116
- }
2117
- /**
2118
- * Return the sorted intersection of column names present in both the source and
2119
- * target tables, for use as the canonical column ordering in
2120
- * {@link computeTableDigest}. Returns `null` when either side has no columns
2121
- * (virtual/FTS-table fallback).
2122
- *
2123
- * @param srcDb - Source database handle.
2124
- * @param srcTable - Physical table name in the source DB.
2125
- * @param tgtDb - Target database handle.
2126
- * @param tgtTable - Physical table name in the target DB.
2127
- */
2128
- function sharedColumnsSorted(srcDb, srcTable, tgtDb, tgtTable) {
2129
- try {
2130
- const srcCols = srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => r.name);
2131
- const tgtColSet = new Set(tgtDb.prepare(`PRAGMA table_info("${tgtTable}")`).all().map((r) => r.name));
2132
- if (srcCols.length === 0 || tgtColSet.size === 0) return null;
2133
- return srcCols.filter((c) => tgtColSet.has(c)).sort();
2134
- } catch {
2135
- return null;
2136
- }
2137
- }
2138
- /**
2139
- * Build the source-side {@link DigestTransformSpec} for a source→target table
2140
- * pair: the source column type affinities, the target's ISO-GLOB columns, and
2141
- * the target column metadata (NOT-NULL flag + default + affinity), keyed by the
2142
- * consolidated target name (the transform lookup key). The target metadata
2143
- * drives the NULL→NOT-NULL-default `COALESCE` mirroring (T11836).
2144
- *
2145
- * Returns `undefined` (raw digest, no transform) when the source `PRAGMA
2146
- * table_info` cannot be read — best-effort, biased to surfacing a real
2147
- * difference rather than masking one.
2148
- *
2149
- * @param srcDb - Source database handle.
2150
- * @param srcTable - Physical legacy source table name.
2151
- * @param tgtDb - Target database handle (consolidated cleo.db).
2152
- * @param targetTableName - Physical consolidated target table name.
2153
- * @returns A transform spec, or `undefined` when source metadata is unavailable.
2154
- */
2155
- function buildSourceDigestTransform(srcDb, srcTable, tgtDb, targetTableName) {
2156
- try {
2157
- return {
2158
- targetTableName,
2159
- srcTypeByCol: new Map(srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => [r.name, r.type])),
2160
- isoGlobCols: detectIsoGlobColumns(tgtDb, targetTableName),
2161
- tgtColByCol: new Map(tgtDb.prepare(`PRAGMA table_info("${targetTableName}")`).all().map((r) => [r.name, {
2162
- notnull: r.notnull,
2163
- dflt_value: r.dflt_value,
2164
- type: r.type
2165
- }]))
2166
- };
2167
- } catch {
2168
- return;
2169
- }
2170
- }
2171
- /**
2172
- * List user tables in a DB (excluding SQLite internals + Drizzle journal).
2173
- */
2174
- function listTables(db) {
2175
- return db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name").all().map((r) => r.name);
2176
- }
2177
- /**
2178
- * Return `true` if `tableName` exists in `db`. Used to route an FK-orphan row to
2179
- * the scope DB that actually declares its child table (T11572).
2180
- */
2181
- function tableExists(db, tableName) {
2182
- try {
2183
- const escaped = tableName.replace(/'/g, "''");
2184
- return db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='${escaped}'`).get() !== void 0;
2185
- } catch {
2186
- return false;
2187
- }
2188
- }
2189
- /**
2190
- * Regex that extracts a single-column `IN (...)` CHECK enum from a table's DDL.
2191
- *
2192
- * Matches the canonical Drizzle-generated form:
2193
- * `CHECK ("col" IN ('a', 'b', 'c'))`
2194
- * and the NULL-tolerant variant:
2195
- * `CHECK ("col" IS NULL OR "col" IN ('a', 'b'))`
2196
- *
2197
- * Capture group 1 is the column name; group 2 is the raw `'a', 'b', …` list.
2198
- * The regex is intentionally conservative — it only recognises the
2199
- * string-literal `IN` enum shape (the one that drops rows on drift), not GLOB
2200
- * or arithmetic CHECKs.
2201
- */
2202
- var CHECK_ENUM_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+(?:IS\s+NULL\s+OR\s+"[^"]+"\s+)?IN\s*\(([^)]*)\)\s*\)/gi;
2203
- /**
2204
- * Parse the `'a', 'b', 'c'` body of an `IN (...)` clause into the set of
2205
- * canonical enum members (single-quoted SQL string literals, `''` un-escaped).
2206
- */
2207
- function parseEnumMembers(body) {
2208
- const members = [];
2209
- for (const m of body.matchAll(/'((?:[^']|'')*)'/g)) members.push(m[1].replace(/''/g, "'"));
2210
- return members;
2211
- }
2212
- /**
2213
- * Read a target table's DDL and return a map of `column → allowed enum members`
2214
- * for every single-column string-literal CHECK enum on that table.
2215
- *
2216
- * @param db - Target DB with the consolidated schema.
2217
- * @param tableName - Physical consolidated table name.
2218
- * @returns Map from column name to its allowed enum members. Empty when the
2219
- * table declares no recognised CHECK enums.
2220
- */
2221
- function detectCheckEnums(db, tableName) {
2222
- const escaped = tableName.replace(/'/g, "''");
2223
- const row = db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='${escaped}'`).get();
2224
- const out = /* @__PURE__ */ new Map();
2225
- if (!row?.sql) return out;
2226
- CHECK_ENUM_REGEX.lastIndex = 0;
2227
- for (const match of row.sql.matchAll(CHECK_ENUM_REGEX)) {
2228
- const col = match[1];
2229
- const members = parseEnumMembers(match[2]);
2230
- if (members.length > 0) out.set(col, members);
2231
- }
2232
- return out;
2233
- }
2234
- /**
2235
- * Detect enum/type drift for one source→target table pair: source values in an
2236
- * enum-constrained column that are NOT members of the target CHECK enum.
2237
- *
2238
- * Reads the DISTINCT non-null values of each enum column from the source table
2239
- * and compares them against the target's allowed members. Only columns present
2240
- * in BOTH source and target are inspected. The check is purely diagnostic — it
2241
- * reports raw source drift; the migration layer is responsible for normalising
2242
- * known aliases before insert.
2243
- *
2244
- * @param srcDb - Source DB handle.
2245
- * @param srcTable - Physical source table name.
2246
- * @param tgtDb - Target DB handle.
2247
- * @param tgtTable - Physical consolidated target table name.
2248
- * @returns Drift findings for this table (empty when fully canonical).
2249
- */
2250
- function detectTableEnumDrift(srcDb, srcTable, tgtDb, tgtTable) {
2251
- const enums = detectCheckEnums(tgtDb, tgtTable);
2252
- if (enums.size === 0) return [];
2253
- let srcCols;
2254
- try {
2255
- srcCols = new Set(srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => r.name));
2256
- } catch {
2257
- return [];
2258
- }
2259
- const findings = [];
2260
- for (const [col, allowed] of enums) {
2261
- if (!srcCols.has(col)) continue;
2262
- const allowedSet = new Set(allowed);
2263
- let rows;
2264
- try {
2265
- rows = srcDb.prepare(`SELECT "${col}" AS v, COUNT(*) AS c FROM "${srcTable}" WHERE "${col}" IS NOT NULL GROUP BY "${col}"`).all();
2266
- } catch {
2267
- continue;
2268
- }
2269
- const offending = [];
2270
- let driftCount = 0;
2271
- for (const r of rows) {
2272
- const value = String(r.v);
2273
- if (!allowedSet.has(value)) {
2274
- driftCount += r.c;
2275
- if (offending.length < 20) offending.push(value);
2276
- }
2277
- }
2278
- if (driftCount > 0) findings.push({
2279
- targetTable: tgtTable,
2280
- column: col,
2281
- offendingValues: offending,
2282
- allowedValues: allowed,
2283
- driftCount
2284
- });
2285
- }
2286
- return findings;
2287
- }
2288
- /**
2289
- * Run `PRAGMA foreign_key_check` on a target DB and return any orphan rows.
2290
- *
2291
- * @param db - Target DB handle (consolidated cleo.db).
2292
- * @param scope - Scope label, attached to log context only.
2293
- * @returns The list of FK violations (empty when referential integrity holds).
2294
- */
2295
- function foreignKeyCheck(db, scope) {
2296
- try {
2297
- const rows = db.prepare("PRAGMA foreign_key_check").all();
2298
- if (rows.length > 0) log.warn({
2299
- scope,
2300
- count: rows.length,
2301
- sample: rows.slice(0, 5)
2302
- }, `verifyMigration: PRAGMA foreign_key_check found ${rows.length} orphan row(s)`);
2303
- return rows.map((r) => ({
2304
- table: r.table,
2305
- rowid: r.rowid ?? null,
2306
- parent: r.parent,
2307
- fkid: r.fkid
2308
- }));
2309
- } catch (err) {
2310
- log.warn({
2311
- scope,
2312
- err
2313
- }, "verifyMigration: PRAGMA foreign_key_check failed (non-fatal)");
2314
- return [];
2315
- }
2316
- }
2317
- /**
2318
- * Compute a **migration-stable, name-mapping-stable signature** for one FK
2319
- * orphan so the same dangling row can be matched between the legacy SOURCE and
2320
- * the consolidated TARGET — despite (a) a different `rowid` after the copy AND
2321
- * (b) the legacy→consolidated table RENAME (`task_relations`→`tasks_task_relations`,
2322
- * parent `tasks`→`tasks_tasks`).
2323
- *
2324
- * `PRAGMA foreign_key_check` returns `{ table, rowid, parent, fkid }`. We key the
2325
- * orphan on:
2326
- * - the CONSOLIDATED child-table name (source names are mapped via
2327
- * {@link resolveConsolidatedTableName} so both sides agree),
2328
- * - the CONSOLIDATED parent-table name (same mapping),
2329
- * - the actual VALUES of the foreign-key child columns for that row (read via
2330
- * `PRAGMA foreign_key_list` + the row at `rowid`) — these travel with the row
2331
- * unchanged, so the dangling reference is identical on both sides.
2332
- *
2333
- * `fkid` is intentionally EXCLUDED — the consolidated table may declare its FKs
2334
- * in a different order than the legacy one, so the per-table fkid is not stable.
2335
- * The (consolidated child, consolidated parent, dangling FK column values) triple
2336
- * uniquely identifies the orphan reference.
2337
- *
2338
- * Falls back to a column-name-only shape if the child columns can't be read
2339
- * (best-effort — a fallback only ever makes two orphans compare as *different*,
2340
- * which biases toward treating an orphan as "introduced", the SAFE/strict
2341
- * direction).
2342
- *
2343
- * @param db - DB handle the orphan was found in.
2344
- * @param v - One `PRAGMA foreign_key_check` row.
2345
- * @param sourceName - When set, the orphan is on the SOURCE side and its
2346
- * `table`/`parent` names are mapped to their consolidated equivalents so the
2347
- * signature matches the target side. Omit for the already-consolidated target.
2348
- * @returns A stable string signature.
2349
- */
2350
- function orphanSignature(db, v, sourceName) {
2351
- const mapName = (name) => {
2352
- if (sourceName === void 0) return name;
2353
- const res = resolveConsolidatedTableName(sourceName, name);
2354
- return res.kind === "skip" ? name : res.targetName;
2355
- };
2356
- const childTable = mapName(v.table);
2357
- const parentTable = mapName(v.parent);
2358
- let childCols = [];
2359
- try {
2360
- childCols = db.prepare(`PRAGMA foreign_key_list("${v.table}")`).all().filter((r) => r.id === v.fkid).map((r) => r.from);
2361
- } catch {}
2362
- if (v.rowid !== null && childCols.length > 0) try {
2363
- const sel = childCols.map((c) => `"${c}"`).join(", ");
2364
- const row = db.prepare(`SELECT ${sel} FROM "${v.table}" WHERE rowid = ?`).get(v.rowid);
2365
- if (row) return `${childTable}|${parentTable}|${childCols.slice().sort().map((c) => `${c}=${JSON.stringify(row[c])}`).join("&")}`;
2366
- } catch {}
2367
- return `${childTable}|${parentTable}|rowid:${v.rowid ?? "?"}`;
2368
- }
2369
- /**
2370
- * Collect the set of {@link orphanSignature}s for every FK orphan in a SOURCE DB.
2371
- *
2372
- * Used to compute SOURCE-side orphan signatures so the parity gate can subtract
2373
- * pre-existing orphans from the target's orphan set and fail only on the orphans
2374
- * the migration INTRODUCED (T11572). The signatures are mapped to consolidated
2375
- * table names so they compare equal to the target-side signatures.
2376
- *
2377
- * @param db - SOURCE DB handle to scan.
2378
- * @param sourceName - `LegacyDbDescriptor.name` (for table-name mapping).
2379
- * @param scope - Scope label for diagnostics.
2380
- * @returns A set of name-mapping-stable orphan signatures.
2381
- */
2382
- function sourceOrphanSignatures(db, sourceName, scope) {
2383
- const sigs = /* @__PURE__ */ new Set();
2384
- try {
2385
- const rows = db.prepare("PRAGMA foreign_key_check").all();
2386
- for (const r of rows) sigs.add(orphanSignature(db, r, sourceName));
2387
- if (rows.length > 0) log.warn({
2388
- scope,
2389
- count: rows.length,
2390
- sample: rows.slice(0, 5)
2391
- }, `verifyMigration: source already has ${rows.length} pre-existing FK orphan(s) — these are tolerated (carried forward losslessly, flagged for data-hygiene)`);
2392
- } catch (err) {
2393
- log.warn({
2394
- scope,
2395
- err
2396
- }, "verifyMigration: source PRAGMA foreign_key_check failed (non-fatal)");
2397
- }
2398
- return sigs;
2399
- }
2400
- /**
2401
- * Verify that a source→target SQLite migration preserved every row, referential
2402
- * integrity, content, and enum validity.
2403
- *
2404
- * Opens all source DBs and the consolidated target DBs **read-only**, then for
2405
- * each legacy source table:
2406
- *
2407
- * 1. Resolves the consolidated target name via {@link resolveConsolidatedTableName}.
2408
- * 2. Compares row counts (parity gate).
2409
- * 3. Computes a column-intersection content digest on both sides.
2410
- * 4. Records any enum/type drift (source values outside the target CHECK enum).
2411
- *
2412
- * After all tables, it runs `PRAGMA foreign_key_check` on each distinct target
2413
- * DB and folds the orphan rows into the result.
2414
- *
2415
- * @param sources - Legacy source descriptors (from `buildExodusPlan()`).
2416
- * @param projectDbPath - Absolute path to the consolidated project `cleo.db`.
2417
- * @param globalDbPath - Absolute path to the consolidated global `cleo.db`.
2418
- * @param onProgress - Optional progress callback.
2419
- *
2420
- * @returns A {@link VerifyMigrationResult}. `ok === false` (with `error`
2421
- * populated) on any count mismatch, content mismatch, FK orphan, or enum
2422
- * drift.
2423
- *
2424
- * @task T11551 (DHQ-045 — exodus zero-loss durable guard · AC1)
2425
- */
2426
- function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
2427
- const tables = [];
2428
- const enumDrift = [];
2429
- const foreignKeyViolations = [];
2430
- const introducedForeignKeyViolations = [];
2431
- const preExistingForeignKeyViolations = [];
2432
- const failureLines = [];
2433
- /**
2434
- * Signatures of FK orphans ALREADY present in the legacy SOURCE DBs (T11572).
2435
- * Target orphans whose signature is in this set are pre-existing — carried
2436
- * forward losslessly — and do NOT fail the parity gate.
2437
- */
2438
- const sourceOrphanSigs = /* @__PURE__ */ new Set();
2439
- if (!existsSync(projectDbPath)) return {
2440
- ok: false,
2441
- tables: [],
2442
- foreignKeyViolations: [],
2443
- introducedForeignKeyViolations: [],
2444
- preExistingForeignKeyViolations: [],
2445
- enumDrift: [],
2446
- error: `Consolidated project cleo.db not found at ${projectDbPath}. Run 'cleo exodus migrate' first.`
2447
- };
2448
- if (!existsSync(globalDbPath)) return {
2449
- ok: false,
2450
- tables: [],
2451
- foreignKeyViolations: [],
2452
- introducedForeignKeyViolations: [],
2453
- preExistingForeignKeyViolations: [],
2454
- enumDrift: [],
2455
- error: `Consolidated global cleo.db not found at ${globalDbPath}. Run 'cleo exodus migrate' first.`
2456
- };
2457
- const projectSnap = openCleoDbSnapshot(projectDbPath, { readOnly: true });
2458
- const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
2459
- try {
2460
- for (const src of sources) {
2461
- if (!existsSync(src.path)) {
2462
- onProgress?.(`Skipping ${src.name} (not present)`);
2463
- continue;
2464
- }
2465
- const srcSnap = openCleoDbSnapshot(src.path, { readOnly: true });
2466
- try {
2467
- for (const sig of sourceOrphanSignatures(srcSnap.db, src.name, `source:${src.name}`)) sourceOrphanSigs.add(sig);
2468
- const sourceTables = listTables(srcSnap.db);
2469
- const projectTables = new Set(listTables(projectSnap.db));
2470
- const globalTables = new Set(listTables(globalSnap.db));
2471
- for (const legacyTableName of sourceTables) {
2472
- onProgress?.(`Verifying ${src.name}.${legacyTableName}…`);
2473
- const resolution = resolveConsolidatedTableName(src.name, legacyTableName);
2474
- if (resolution.kind === "skip") {
2475
- onProgress?.(` [skip] ${src.name}.${legacyTableName} — ${resolution.reason}`);
2476
- continue;
2477
- }
2478
- const targetTableName = resolution.targetName;
2479
- const scope = resolveTableTargetScope(src.name, legacyTableName, src.targetScope);
2480
- const targetSnap = scope === "project" ? projectSnap : globalSnap;
2481
- const targetTables = scope === "project" ? projectTables : globalTables;
2482
- if (targetTables.has(targetTableName)) {
2483
- const drift = detectTableEnumDrift(srcSnap.db, legacyTableName, targetSnap.db, targetTableName);
2484
- if (drift.length > 0) {
2485
- enumDrift.push(...drift);
2486
- for (const d of drift) failureLines.push(`[${scope}] ${targetTableName}.${d.column}: ${d.driftCount} row(s) with value(s) outside enum {${d.allowedValues.join(", ")}} — e.g. ${d.offendingValues.map((v) => `'${v}'`).join(", ")}`);
2487
- }
2488
- }
2489
- if (!targetTables.has(targetTableName)) {
2490
- const srcResult = computeTableDigest(srcSnap.db, legacyTableName, null);
2491
- const countMatch = srcResult.count === 0;
2492
- tables.push({
2493
- sourceTable: legacyTableName,
2494
- targetTable: targetTableName,
2495
- scope,
2496
- sourceCount: srcResult.count,
2497
- targetCount: 0,
2498
- sourceHash: srcResult.hash,
2499
- targetHash: "",
2500
- hashMatch: countMatch,
2501
- countMatch
2502
- });
2503
- if (!countMatch) failureLines.push(`[${scope}] ${src.name}.${legacyTableName} → ${targetTableName}: missing from target (source has ${srcResult.count} rows)`);
2504
- continue;
2505
- }
2506
- const cols = sharedColumnsSorted(srcSnap.db, legacyTableName, targetSnap.db, targetTableName);
2507
- const srcTransform = buildSourceDigestTransform(srcSnap.db, legacyTableName, targetSnap.db, targetTableName);
2508
- const srcDigest = computeTableDigest(srcSnap.db, legacyTableName, cols, srcTransform);
2509
- const tgtDigest = computeTableDigest(targetSnap.db, targetTableName, cols);
2510
- const countMatch = srcDigest.count === tgtDigest.count;
2511
- const hashMatch = srcDigest.hash === tgtDigest.hash;
2512
- if (tgtDigest.count < srcDigest.count) failureLines.push(`[${scope}] ${src.name}.${legacyTableName} → ${targetTableName}: DEFICIT — source=${srcDigest.count} rows, target=${tgtDigest.count} rows (${srcDigest.count - tgtDigest.count} missing), hashMatch=${hashMatch}`);
2513
- else if (tgtDigest.count > srcDigest.count) log.warn({
2514
- scope,
2515
- source: src.name,
2516
- table: targetTableName,
2517
- sourceCount: srcDigest.count,
2518
- targetCount: tgtDigest.count,
2519
- delta: tgtDigest.count - srcDigest.count
2520
- }, `verifyMigration: ${targetTableName} has ${tgtDigest.count - srcDigest.count} MORE row(s) in target than source (surplus — NOT data loss, tolerated; e.g. migration-time audit writes). Verify it is not an unexpected double-copy.`);
2521
- else if (!hashMatch) failureLines.push(`[${scope}] ${src.name}.${legacyTableName} → ${targetTableName}: source=${srcDigest.count} rows, target=${tgtDigest.count} rows, hashMatch=${hashMatch}`);
2522
- tables.push({
2523
- sourceTable: legacyTableName,
2524
- targetTable: targetTableName,
2525
- scope,
2526
- sourceCount: srcDigest.count,
2527
- targetCount: tgtDigest.count,
2528
- sourceHash: srcDigest.hash,
2529
- targetHash: tgtDigest.hash,
2530
- hashMatch,
2531
- countMatch
2532
- });
2533
- }
2534
- } finally {
2535
- srcSnap.close();
2536
- }
2537
- }
2538
- const targetOrphans = [];
2539
- targetOrphans.push(...foreignKeyCheck(projectSnap.db, "project"));
2540
- targetOrphans.push(...foreignKeyCheck(globalSnap.db, "global"));
2541
- foreignKeyViolations.push(...targetOrphans);
2542
- for (const fk of targetOrphans) {
2543
- const sig = orphanSignature(tableExists(projectSnap.db, fk.table) ? projectSnap.db : globalSnap.db, fk);
2544
- if (sourceOrphanSigs.has(sig)) preExistingForeignKeyViolations.push(fk);
2545
- else {
2546
- introducedForeignKeyViolations.push(fk);
2547
- failureLines.push(`[fk] ${fk.table}.rowid=${fk.rowid ?? "?"} references missing ${fk.parent} (fkid=${fk.fkid}) — INTRODUCED by migration`);
2548
- }
2549
- }
2550
- if (preExistingForeignKeyViolations.length > 0) log.warn({
2551
- count: preExistingForeignKeyViolations.length,
2552
- sample: preExistingForeignKeyViolations.slice(0, 5)
2553
- }, `verifyMigration: ${preExistingForeignKeyViolations.length} pre-existing source FK orphan(s) carried forward losslessly — tolerated (flag for data-hygiene follow-up, NOT a migration failure)`);
2554
- } catch (err) {
2555
- const error = err instanceof Error ? err.message : String(err);
2556
- log.error({ err }, "verifyMigration failed");
2557
- return {
2558
- ok: false,
2559
- tables,
2560
- foreignKeyViolations,
2561
- introducedForeignKeyViolations,
2562
- preExistingForeignKeyViolations,
2563
- enumDrift,
2564
- error
2565
- };
2566
- } finally {
2567
- projectSnap.close();
2568
- globalSnap.close();
2569
- }
2570
- if (failureLines.length > 0) {
2571
- const error = `verifyMigration FAILED: ${failureLines.length} issue(s):\n${failureLines.map((l) => ` • ${l}`).join("\n")}`;
2572
- log.error({ failureCount: failureLines.length }, error);
2573
- return {
2574
- ok: false,
2575
- tables,
2576
- foreignKeyViolations,
2577
- introducedForeignKeyViolations,
2578
- preExistingForeignKeyViolations,
2579
- enumDrift,
2580
- error
2581
- };
2582
- }
2583
- return {
2584
- ok: true,
2585
- tables,
2586
- foreignKeyViolations,
2587
- introducedForeignKeyViolations,
2588
- preExistingForeignKeyViolations,
2589
- enumDrift
2590
- };
2591
- }
2592
-
2593
- export { buildExodusPlan, clearExodusJournal, runExodusMigrate, verifyMigration };
2594
- //# sourceMappingURL=exodus-BBuuN6aA.js.map