chainlesschain 0.166.17 → 0.166.18

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 (210) hide show
  1. package/README.md +14 -7
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-CVW2n2rh.js → AIOps-CQSBl3hY.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DtuM163O.js → ActionButton-IQt-vRLt.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-BXvl_qHu.js → Analytics-DkHC8O7k.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-VDBTFKOp.js → AppLayout-CSti2P2T.js} +3 -3
  7. package/src/assets/web-panel/assets/{Artifacts-BNz2xCiG.js → Artifacts-C57hxn3v.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-DTu9Hn9P.js → Audit-DByLntcf.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-D2OCPzLe.js → BackgroundAgents-D1Bd2EOa.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-C9M-vG7G.js → Backup-VstHzHng.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-BUkCNiUQ.js → BaseInput-CLhdexIf.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-OJ2ajF7l.js → Chat-BefC_JZS.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BdZt76Ab.js → ChatBubbleRenderer-DbXMvgmn.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-C1gXKDsc.js → Checkbox-CEY982vx.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BXbznLg7.js → Codegen-Dcm_DTvJ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-CbCq5N0i.js → Col-C8SSu4KA.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-V8Ks6Yun.js → Community-BVV9vwRR.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-DkeHQTpQ.js → Compact-B7c5Vo18.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-JeLKwn4Q.js → Compliance-Tq9jSZ5a.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-azfetcBz.js → Cowork-BJUjT09Q.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-Q691i_Gp.js → Cron-CjV9PEaS.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-BLjDRbr_.js → Crosschain-Dl7ReL8F.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-D8ptXplK.js → DID-CG8PjWmM.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-Ctj2VVZL.js → Dashboard-CDBM6wmF.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-D8VgkMJ8.js → Dropdown-8_55bXTD.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-CQPe5S6q.js → EmailListRenderer-D3qOU1Cm.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-ySA-7SiN.js → FamilyGuardDashboard-Bv5oWMZF.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-1PMD-fBf.js → Federation-hpZ1xkKd.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-C_jr6U4Q.js → FormItemContext-BN7GT191.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-Bu3bKf2B.js → GenericCardRenderer-PCRhfA7M.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Dv4Fa_T5.js → Git-CIlP2vy7.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-DOEy1n1k.js → Governance-81lgm7YL.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-DkvqzWcJ.js → Inference-Cs24xMOV.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-DdUJK949.js → KnowledgeGraph-CrJLVCZo.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-CRelcdUZ.js → Logs-DLyO8RKT.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-0AHbbTA4.js → MarkdownRenderer-DBOqijrP.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-BuuBKsev.js → Marketplace-Dz7AbQZc.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DlgpEmA1.js → McpTools-BM0aFiWv.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-DTfdqUGy.js → Memory-C9SJzENX.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CXLE5ZFO.js → MobileBridge-BO2HnNRk.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-Dz25OB0n.js → MobileProjects-CK_jMz0l.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-kEL0fhgD.js → Mtc-CfhrJsdO.js} +2 -2
  43. package/src/assets/web-panel/assets/{MtcAudit-BE4UvFJU.js → MtcAudit-BE2ELxgI.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-CSApSn8c.js → Multisig-CgR_E3Jd.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-D11FpKWx.js → NLProgramming-Cw9445_K.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-CDL-D2CS.js → Notes-DZimkTrb.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-C0Q6lO-c.js → NotificationSettings-DxHVh4uT.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-Iy-pnX56.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-Dc-maVAq.js → Organization-pPXJtWo4.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-B5mUpSvi.js → Overflow-YamdTJwA.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-TjXV53wq.js → P2P-BvqP53x7.js} +2 -2
  52. package/src/assets/web-panel/assets/PdhVaultBrowser-Blz1Xn2n.js +7 -0
  53. package/src/assets/web-panel/assets/{Permissions-DKRzQ2O6.js → Permissions-CgFRQqFJ.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Dx2eqft8.js → PersonalDataHub-CFeo3-6O.js} +4 -4
  55. package/src/assets/web-panel/assets/{Pipeline-CnYZMB5p.js → Pipeline-BkaRojEK.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BonKpPnp.js → Privacy-BNZEx2Qp.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-DWO0Du3J.js → ProjectInit-HUBOgEEK.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-C9jqyYRv.js → ProjectSettings-CFMmhROS.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-CYpKwXbz.js → Projects-Dc517BXp.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-CW-fkwJq.js → Providers-DTLf8bgj.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-Cw0J3Zud.js → QrScannerModal-BBNjPszk.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-BH4A4IpR.js → QuickAsk-DCsHvk3G.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-CDwMwJus.js → Recommend-Dpr5YSop.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DKIPwzTf.js → RemoteSession-DmKnvqGg.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-CklDj7qS.js → Reputation-CSRfuurX.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DJWZKsH0.js → Row-CDj3pQ_w.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-BfSLCewo.js → RssFeed-DI0a_kNq.js} +2 -2
  68. package/src/assets/web-panel/assets/{Search-B0IST--x.js → Search-jAy7B2nU.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-h1sYNWtt.js → Security-CkHTqYhc.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-Gh0-oxI_.js → Services-CP-0Nzga.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-CNDV_jOs.js → Skeleton-BkF5-p3n.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-ByCLTh1n.js → Skills-BDu-utDE.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-BfyrJTJz.js → Sla-CQvq_2Qb.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-BQzo9kNX.js → SpeechSettings-T7Ap512g.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-B6fS6z_1.js → SyncSettings-Bt-PLvot.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-C-uKCP-l.js → Tasks-yLEcCogg.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-D6oDy7wL.js → Templates-ByN3fmbr.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-D3LT2wNQ.js → Tenant-DBI7hITH.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-BteLcv_O.js → Terminal-B77_DlGX.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-DHHuxfqH.js → TimelineRenderer-CsLTB_QO.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BRQeykXw.js → Tokens-BV2N6cR8.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-p2XaFWEu.js → Trigger-CjXWGH3p.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-BxadBMft.js → Trust-uqXIq3WC.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-DRBP7LDk.js → UkeySign-D-6qu4vY.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-CZ3NZkh7.js → VideoEditing-CxGmx_2e.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-C3NTBN7G.js → Wallet-DSq7WlAg.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-C7LVgMlb.js → WebAuthn-CDBiD57O.js} +2 -2
  88. package/src/assets/web-panel/assets/{WorkflowEditor-0_Iveog7.js → WorkflowEditor-Z8f63TSb.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DzzluYo3.js → chat-B-UZFBjy.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DSMYXpaj.js → colors-CFk7K-7Q.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-W76u1VSV.js → compact-item-X0IVMvgi.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-otsESjb5.js → createContext-DWg4j2n6.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-B2IW1J_J.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-3sNLrvug.js → hasIn-CkVCwNNF.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BdtmEKat.js → index-5OcZHdzc.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DS9WYDMf.js → index-B273IY-E.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-NaO9AE-F.js → index-B3-B3FWD.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BcGJtmHM.js → index-BB4KHHcv.js} +1 -1
  99. package/src/assets/web-panel/assets/index-BCp2ir-1.js +1 -0
  100. package/src/assets/web-panel/assets/{index-pVm_iUre.js → index-BG-GaZri.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BpcQYzP7.js → index-BPBia4b-.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BXLx9HfA.js → index-BipwEbTA.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-uSCkbpZH.js → index-BoskgPt9.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DMmdIpLA.js → index-Btqk6zWb.js} +1 -1
  105. package/src/assets/web-panel/assets/index-Bw4iuzRD.js +1 -0
  106. package/src/assets/web-panel/assets/{index-DLDh1OW3.js → index-BzXjYUmd.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-B9X_jqXe.js → index-C1ZAOJRW.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-vBKPcmAP.js → index-CCLwoATF.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-vUmYkqmu.js → index-CDfSD07S.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D7A5U2IH.js → index-CLlP1E4X.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-ClOFeZ-t.js → index-CMCEXBRh.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Cg86EzNp.js → index-CN8HZlLH.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Ddd4mird.js → index-CNKFsBlz.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CE7tSow-.js → index-CUBkvFPH.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-GlIOE4ja.js → index-Ca0mij1n.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DdU7IkKX.js → index-CbIAWbma.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-UUHCwfNq.js → index-CbihMThv.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BRsD1PT2.js → index-CvOa-IFV.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-C2aht9-M.js → index-Cvwu_rA_.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BJsQio5O.js → index-D-EJNxgR.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-q7ftLqNt.js → index-D8uEL8Uw.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B1I1AYJs.js → index-D92g4l3n.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DZKAzEge.js → index-DCqpl_E_.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CKoFyHYQ.js → index-DLSBks8h.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BDlDO2px.js → index-Db_5xhrU.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CENWwjef.js → index-DcDggpWR.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DH4cl52p.js → index-DeWKPQGs.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DTA8Iavv.js → index-DhVHoqGL.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-DmpaC2yk.js → index-DyWlOuak.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BA8mZxd-.js → index-LU54bChK.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CtFOt5x1.js → index-OjnAwUJo.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-B5LOYdYm.js → index-P83LDnCz.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-MhQQ3h8q.js → index-_-9k3Tna.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-DF6llUXV.js → initDefaultProps-CFNgY8lJ.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-vtDV8_5K.js → motion-D-5RrDzn.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-B6c0bdy2.js → move-DN-L0Bd-.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-DlLYWuBK.js → mtc-parser-6mgLSeCK.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-BH0zOxLN.js → omit-ClWW8eTP.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-D9di8_In.js → pickAttrs-Vystq3R8.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BDSngufM.js → placementArrow-D0H9hK4W.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-C_EawMvP.js → responsiveObserve-B3iNRU-a.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-B_4wShxA.js → slide-CiMXe6am.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-CbQu6qJ-.js → statusUtils-CGDZJ1uT.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-CvXJWYXb.js → styleChecker-D2zA6NkY.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-jjY1XknO.js → useFlexGapSupport-C1sYU6QE.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CMXBdsK_.js → useFs-BzUHSjg2.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-YbAhhPXx.js → usePersonalDataHub-CxB5yZCq.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BFCTKc0H.js → vnode-BfUfMwtc.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CY9aBl5O.js → zoom-CFYmj1TO.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +1 -1
  152. package/src/commands/agent.js +80 -2
  153. package/src/commands/evolution.js +33 -27
  154. package/src/commands/serve.js +11 -2
  155. package/src/data/changelog.json +5 -5
  156. package/src/gateways/ws/session-protocol.js +1 -0
  157. package/src/gateways/ws/ws-agent-handler.js +70 -2
  158. package/src/gateways/ws/ws-server.js +8 -0
  159. package/src/lib/app-server/cli-agent-kernel-adapter.js +4 -0
  160. package/src/lib/app-server/graph-runtime.js +115 -8
  161. package/src/lib/app-server/server.js +21 -2
  162. package/src/lib/app-server/websocket-transport.js +2 -0
  163. package/src/lib/evolution/agent-evolution-ingress.js +300 -0
  164. package/src/lib/evolution/agent-evolution-runtime-composition-brand.js +22 -0
  165. package/src/lib/evolution/agent-evolution-runtime-composition.js +369 -0
  166. package/src/lib/evolution/evidence-backed-wiki-maintainer.js +752 -0
  167. package/src/lib/evolution/evolution-artifact-ports.js +48 -0
  168. package/src/lib/evolution/evolution-eval-gate.js +11 -4
  169. package/src/lib/evolution/evolution-evidence-artifact-adapter.js +499 -0
  170. package/src/lib/evolution/evolution-evidence-projector.js +61 -1
  171. package/src/lib/evolution/evolution-file-witness.js +601 -0
  172. package/src/lib/evolution/evolution-ledger-file-backend.js +176 -0
  173. package/src/lib/evolution/evolution-ledger-ports.js +353 -16
  174. package/src/lib/evolution/evolution-ledger.js +1355 -54
  175. package/src/lib/evolution/evolution-run-ledger-adapter.js +409 -0
  176. package/src/lib/evolution/evolution-run-wiki-maintenance-source.js +300 -0
  177. package/src/lib/evolution/scheduled-batch-wiki-maintenance-source.js +368 -0
  178. package/src/lib/evolution/skill-candidate-registry.js +649 -0
  179. package/src/lib/evolution/skill-promotion-controller.js +109 -38
  180. package/src/lib/evolution/skill-promotion-review-ledger-adapter.js +451 -0
  181. package/src/lib/evolution/skill-promotion-review.js +793 -0
  182. package/src/lib/evolution/skill-registry-transition-capability.js +55 -0
  183. package/src/lib/evolution/skill-registry-transition-ledger-adapter.js +1280 -0
  184. package/src/lib/evolution/skill-registry-transition-source.js +372 -0
  185. package/src/lib/evolution/skill-release-registry.js +2191 -11
  186. package/src/lib/evolution/skill-target-matrix-eval.js +133 -10
  187. package/src/lib/evolution/structured-memory-agent-control-plane.js +222 -0
  188. package/src/lib/evolution/structured-memory-authority-ledger-adapter.js +351 -0
  189. package/src/lib/evolution/structured-memory-ledger-adapter.js +220 -0
  190. package/src/lib/evolution/structured-memory-policy-receipt-writer.js +164 -0
  191. package/src/lib/evolution/structured-memory-post-compact-hook.js +87 -0
  192. package/src/lib/evolution/structured-memory-promotion-receipt-writer.js +221 -0
  193. package/src/lib/evolution/structured-memory-semantic-review-pipeline.js +313 -0
  194. package/src/lib/evolution/wiki-maintainer-ledger-adapter.js +200 -0
  195. package/src/lib/evolution/wiki-maintenance-trigger-ledger-adapter.js +687 -0
  196. package/src/lib/evolution-system.js +65 -30
  197. package/src/lib/mcp-lifecycle-authority.js +21 -2
  198. package/src/lib/remote-approval-bridge.js +24 -0
  199. package/src/lib/scheduler-kernel/store.js +62 -14
  200. package/src/repl/agent-repl.js +45 -0
  201. package/src/runtime/agent-runtime.js +114 -1
  202. package/src/runtime/headless-runner.js +19 -0
  203. package/src/runtime/headless-stream.js +43 -0
  204. package/src/runtime/runtime-factory.js +19 -1
  205. package/src/assets/web-panel/assets/OrderTableRenderer-ZRE0OKbj.js +0 -1
  206. package/src/assets/web-panel/assets/PdhVaultBrowser-DvV2btIr.js +0 -7
  207. package/src/assets/web-panel/assets/devWarning-BztaXF84.js +0 -1
  208. package/src/assets/web-panel/assets/index-4jP_hG_4.js +0 -1
  209. package/src/assets/web-panel/assets/index-zxwuAXL5.js +0 -1
  210. package/src/lib/learning/learning-hooks.js +0 -123
@@ -5,7 +5,11 @@ import path from "node:path";
5
5
  import { types as utilTypes } from "node:util";
6
6
  import { getHomeDir } from "../paths.js";
7
7
  import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
8
- import { verifySkillCandidateDraft } from "./skill-candidate-registry.js";
8
+ import {
9
+ SKILL_CANDIDATE_MIGRATION_RECEIPT_SCHEMA,
10
+ SKILL_CANDIDATE_MIGRATION_RECORD_SCHEMA,
11
+ verifySkillCandidateDraft,
12
+ } from "./skill-candidate-registry.js";
9
13
  import {
10
14
  SKILL_MUTATION_OPERATIONS,
11
15
  SKILL_MUTATION_RECEIPT_KINDS,
@@ -17,7 +21,7 @@ import {
17
21
  verifySkillMutationConsumptionReceipt,
18
22
  verifySkillMutationRequest,
19
23
  } from "./skill-mutation-authority.js";
20
- import { consumeRegistryTransitionCapability } from "./skill-promotion-controller.js";
24
+ import { consumeRegistryTransitionCapability } from "./skill-registry-transition-capability.js";
21
25
 
22
26
  export const SKILL_RELEASE_SCHEMA = "chainlesschain.skill-release/v4";
23
27
  export const SKILL_RELEASE_STATE_SCHEMA =
@@ -30,6 +34,30 @@ export const SKILL_RELEASE_TENANT_MARKER_SCHEMA =
30
34
  "chainlesschain.skill-release-tenant-marker/v1";
31
35
  export const SKILL_RELEASE_MIGRATION_REQUIRED_CODE =
32
36
  "SKILL_RELEASE_MIGRATION_REQUIRED";
37
+ export const SKILL_RELEASE_MIGRATION_RECORD_SCHEMA =
38
+ "chainlesschain.skill-release-migration/v1";
39
+ export const SKILL_RELEASE_MIGRATION_AUTHORITY_SCHEMA =
40
+ "chainlesschain.skill-release-migration-authority/v1";
41
+ export const SKILL_RELEASE_MIGRATION_RECEIPT_SCHEMA =
42
+ "chainlesschain.skill-release-migration-receipt/v1";
43
+ export const SKILL_RELEASE_STATE_MIGRATION_PLAN_SCHEMA =
44
+ "chainlesschain.skill-release-state-migration-plan/v1";
45
+ export const SKILL_RELEASE_STATE_MIGRATION_AUTHORITY_SCHEMA =
46
+ "chainlesschain.skill-release-state-migration-authority/v1";
47
+ export const SKILL_RELEASE_STATE_MIGRATION_RECEIPT_SCHEMA =
48
+ "chainlesschain.skill-release-state-migration-receipt/v1";
49
+ export const SKILL_RELEASE_STATE_LEDGER_MIGRATION_SCHEMA =
50
+ "chainlesschain.skill-release-state-ledger-migration/v1";
51
+ export const SKILL_RELEASE_STATE_MIGRATION_JOURNAL_SCHEMA =
52
+ "chainlesschain.skill-release-state-migration-journal/v1";
53
+ export const SKILL_RELEASE_JOURNAL_RESOLUTION_RECEIPT_SCHEMA =
54
+ "chainlesschain.skill-release-journal-resolution-receipt/v1";
55
+ export const SKILL_RELEASE_JOURNAL_RESOLUTION_AUTHORITY_SCHEMA =
56
+ "chainlesschain.skill-release-journal-resolution-authority/v1";
57
+ export const SKILL_RELEASE_JOURNAL_DISPOSITION_PLAN_SCHEMA =
58
+ "chainlesschain.skill-release-journal-disposition-plan/v1";
59
+ export const SKILL_RELEASE_JOURNAL_ARCHIVE_SCHEMA =
60
+ "chainlesschain.skill-release-journal-archive/v1";
33
61
 
34
62
  const JOURNAL_SCHEMA = "chainlesschain.skill-release-journal/v4";
35
63
  const INTENT_SCHEMA = "chainlesschain.skill-release-transition-intent/v2";
@@ -60,10 +88,19 @@ const TENANT_MARKER_DIGEST_DOMAIN =
60
88
  const TENANT_MARKER_COMPONENT = "skill-release-registry";
61
89
  const TENANT_MARKER_FILE = "_tenant.json";
62
90
  const LEGACY_RELEASE_SCHEMA = "chainlesschain.skill-release/v3";
91
+ const LEGACY_RELEASE_DOMAIN = `${LEGACY_RELEASE_SCHEMA}\0`;
63
92
  const LEGACY_STATE_SCHEMA = "chainlesschain.skill-release-state/v2";
93
+ const LEGACY_STATE_DOMAIN = `${LEGACY_STATE_SCHEMA}\0`;
64
94
  const LEGACY_JOURNAL_SCHEMA = "chainlesschain.skill-release-journal/v3";
95
+ const LEGACY_JOURNAL_DOMAIN = `${LEGACY_JOURNAL_SCHEMA}\0`;
65
96
  const LEGACY_LOCK_OWNER_SCHEMA = "chainlesschain.skill-release-lock-owner/v1";
66
97
  const LOCK_OWNER_SCHEMA = "chainlesschain.skill-release-lock-owner/v2";
98
+ const RELEASE_MIGRATION_DOMAIN = `${SKILL_RELEASE_MIGRATION_RECORD_SCHEMA}\0`;
99
+ const STATE_MIGRATION_PLAN_DOMAIN = `${SKILL_RELEASE_STATE_MIGRATION_PLAN_SCHEMA}\0`;
100
+ const JOURNAL_RESOLUTION_RECEIPT_DOMAIN = `${SKILL_RELEASE_JOURNAL_RESOLUTION_RECEIPT_SCHEMA}\0`;
101
+ const JOURNAL_DISPOSITION_PLAN_DOMAIN = `${SKILL_RELEASE_JOURNAL_DISPOSITION_PLAN_SCHEMA}\0`;
102
+ const STATE_MIGRATION_JOURNAL_DOMAIN = `${SKILL_RELEASE_STATE_MIGRATION_JOURNAL_SCHEMA}\0`;
103
+ const JOURNAL_ARCHIVE_DOMAIN = `${SKILL_RELEASE_JOURNAL_ARCHIVE_SCHEMA}\0`;
67
104
 
68
105
  export const EMPTY_SKILL_ACTIVE_DIGEST = sha256(
69
106
  Buffer.from(EMPTY_ACTIVE_DOMAIN, "utf8"),
@@ -426,6 +463,253 @@ const RELEASE_KEYS = new Set([
426
463
  "tenantId",
427
464
  "transitionSubjectDigest",
428
465
  ]);
466
+ const LEGACY_RELEASE_KEYS = new Set([
467
+ "authorityReceiptDigest",
468
+ "candidateId",
469
+ "content",
470
+ "contentDigest",
471
+ "dependencyLock",
472
+ "dependencyLockDigest",
473
+ "mutationRequestDigest",
474
+ "parentDigest",
475
+ "receiptDigests",
476
+ "releaseDigest",
477
+ "requestedCapabilities",
478
+ "schema",
479
+ "skillName",
480
+ "targetRuntimes",
481
+ "transitionSubjectDigest",
482
+ ]);
483
+ const RELEASE_MIGRATION_INPUT_KEYS = new Set([
484
+ "candidateMigration",
485
+ "legacyRelease",
486
+ ]);
487
+ const CANDIDATE_MIGRATION_RESULT_KEYS = new Set([
488
+ "candidate",
489
+ "created",
490
+ "migration",
491
+ "receipt",
492
+ ]);
493
+ const CANDIDATE_MIGRATION_RECORD_KEYS = new Set([
494
+ "authorityId",
495
+ "candidateId",
496
+ "contentDigest",
497
+ "dependencyLockDigest",
498
+ "handlerArtifactDigest",
499
+ "legacyArtifactDigest",
500
+ "legacyCandidateId",
501
+ "migrationDigest",
502
+ "runtimeManifestDigest",
503
+ "schema",
504
+ "targetMatrixRoot",
505
+ "tenantId",
506
+ "trust",
507
+ ]);
508
+ const CANDIDATE_MIGRATION_RECEIPT_KEYS = new Set([
509
+ "authenticated",
510
+ "authorityId",
511
+ "durable",
512
+ "handlerArtifactDigest",
513
+ "migrationDigest",
514
+ "receiptDigest",
515
+ "schema",
516
+ "trust",
517
+ ]);
518
+ const RELEASE_MIGRATION_AUTHORITY_KEYS = new Set([
519
+ "audit",
520
+ "authorityId",
521
+ "handlerArtifactDigest",
522
+ "schema",
523
+ "trust",
524
+ ]);
525
+ const RELEASE_MIGRATION_RECEIPT_KEYS = new Set([
526
+ "authenticated",
527
+ "authorityId",
528
+ "durable",
529
+ "handlerArtifactDigest",
530
+ "migrationDigest",
531
+ "receiptDigest",
532
+ "schema",
533
+ "trust",
534
+ ]);
535
+ const RELEASE_MIGRATION_RECORD_KEYS = new Set([
536
+ "authorityId",
537
+ "candidateMigrationDigest",
538
+ "candidateMigrationReceiptDigest",
539
+ "handlerArtifactDigest",
540
+ "legacyCandidateId",
541
+ "legacyReleaseDigest",
542
+ "migrationDigest",
543
+ "releaseDigest",
544
+ "schema",
545
+ "skillName",
546
+ "tenantId",
547
+ "trust",
548
+ ]);
549
+ const RELEASE_MIGRATION_RESULT_KEYS = new Set([
550
+ "created",
551
+ "migration",
552
+ "receipt",
553
+ "release",
554
+ ]);
555
+ const LEGACY_STATE_KEYS = new Set([
556
+ "activeReleaseDigest",
557
+ "authorityReceiptDigest",
558
+ "dependencyLockDigest",
559
+ "fence",
560
+ "lastKnownGoodReleaseDigest",
561
+ "revision",
562
+ "schema",
563
+ "skillName",
564
+ "stateDigest",
565
+ "transactionId",
566
+ ]);
567
+ const LEGACY_JOURNAL_KEYS = new Set([
568
+ "intent",
569
+ "journalDigest",
570
+ "leaseToken",
571
+ "nextState",
572
+ "phase",
573
+ "prepareReceipt",
574
+ "previousState",
575
+ "schema",
576
+ "skillName",
577
+ "stagedFile",
578
+ "transactionId",
579
+ ]);
580
+ const LEGACY_INTENT_KEYS = new Set([
581
+ "authorityReceiptDigest",
582
+ "authorityReceipt",
583
+ "candidateId",
584
+ "dependencyLockDigest",
585
+ "expectedParentDigest",
586
+ "expectedRevision",
587
+ "mutationRequest",
588
+ "intentDigest",
589
+ "nextStateDigest",
590
+ "operation",
591
+ "operationId",
592
+ "pointerDigest",
593
+ "previousStateDigest",
594
+ "receiptDigests",
595
+ "requestDigest",
596
+ "schema",
597
+ "skillName",
598
+ "targetReleaseDigest",
599
+ "transactionId",
600
+ "transitionSubjectDigest",
601
+ ]);
602
+ const JOURNAL_RESOLUTION_RECEIPT_KEYS = new Set([
603
+ "authenticated",
604
+ "authorityId",
605
+ "durable",
606
+ "handlerArtifactDigest",
607
+ "journalDigest",
608
+ "ledgerProjection",
609
+ "receiptDigest",
610
+ "schema",
611
+ "tenantId",
612
+ "transactionId",
613
+ "trust",
614
+ ]);
615
+ const JOURNAL_DISPOSITION_INPUT_KEYS = new Set([
616
+ "legacyJournal",
617
+ "resolutionAuthority",
618
+ "stateMigrationPlan",
619
+ "tenantId",
620
+ ]);
621
+ const JOURNAL_RESOLUTION_AUTHORITY_OPTION_KEYS = new Set([
622
+ "authorityId",
623
+ "handlerArtifactDigest",
624
+ "resolve",
625
+ "trust",
626
+ ]);
627
+ const JOURNAL_DISPOSITION_PLAN_KEYS = new Set([
628
+ "action",
629
+ "journalDigest",
630
+ "ledgerCurrent",
631
+ "ledgerStatus",
632
+ "legacyNextStateDigest",
633
+ "planDigest",
634
+ "resolutionReceipt",
635
+ "resolutionReceiptDigest",
636
+ "schema",
637
+ "skillName",
638
+ "stateMigrationDigest",
639
+ "tenantId",
640
+ "transactionId",
641
+ ]);
642
+ const JOURNAL_ARCHIVE_INPUT_KEYS = new Set([
643
+ "archiveDir",
644
+ "crashHook",
645
+ "dispositionPlan",
646
+ "fsImpl",
647
+ "legacyJournal",
648
+ "secure",
649
+ "sourcePath",
650
+ "stateMigrationResult",
651
+ ]);
652
+ const JOURNAL_ARCHIVE_KEYS = new Set([
653
+ "action",
654
+ "archiveDigest",
655
+ "dispositionPlan",
656
+ "legacyJournal",
657
+ "schema",
658
+ "stateMigrationStateDigest",
659
+ ]);
660
+ const STATE_MIGRATION_PLAN_INPUT_KEYS = new Set([
661
+ "activeReleaseMigration",
662
+ "lastKnownGoodReleaseMigration",
663
+ "legacyState",
664
+ ]);
665
+ const STATE_MIGRATION_PLAN_KEYS = new Set([
666
+ "activeReleaseDigest",
667
+ "activeReleaseMigrationDigest",
668
+ "activeReleaseMigrationReceiptDigest",
669
+ "dependencyLockDigest",
670
+ "lastKnownGoodReleaseDigest",
671
+ "lastKnownGoodReleaseMigrationDigest",
672
+ "lastKnownGoodReleaseMigrationReceiptDigest",
673
+ "legacyActiveReleaseDigest",
674
+ "legacyFence",
675
+ "legacyLastKnownGoodReleaseDigest",
676
+ "legacyRevision",
677
+ "legacyStateDigest",
678
+ "legacyTransactionId",
679
+ "requiresAuthenticatedLedgerMigration",
680
+ "schema",
681
+ "skillName",
682
+ "stateMigrationDigest",
683
+ "tenantId",
684
+ ]);
685
+ const STATE_MIGRATION_AUTHORITY_KEYS = new Set([
686
+ "audit",
687
+ "authorityId",
688
+ "handlerArtifactDigest",
689
+ "schema",
690
+ "trust",
691
+ ]);
692
+ const STATE_MIGRATION_RECEIPT_KEYS = new Set([
693
+ "authenticated",
694
+ "authorityId",
695
+ "durable",
696
+ "handlerArtifactDigest",
697
+ "receiptDigest",
698
+ "schema",
699
+ "stateMigrationDigest",
700
+ "trust",
701
+ ]);
702
+ const STATE_MIGRATION_JOURNAL_KEYS = new Set([
703
+ "journalDigest",
704
+ "leaseToken",
705
+ "plan",
706
+ "receipt",
707
+ "schema",
708
+ "skillName",
709
+ "state",
710
+ "tenantId",
711
+ "transactionId",
712
+ ]);
429
713
  const BUILD_RELEASE_INPUT_KEYS = new Set([
430
714
  "candidate",
431
715
  "consumptionReceipt",
@@ -502,10 +786,1327 @@ export function buildSkillRelease(input) {
502
786
  tenantId: verifiedCandidate.tenantId,
503
787
  transitionSubjectDigest,
504
788
  };
505
- return deepFreeze({
789
+ return deepFreeze({
790
+ ...core,
791
+ releaseDigest: domainDigest(RELEASE_DOMAIN, core),
792
+ });
793
+ }
794
+
795
+ /** Verify the exact pre-tenant v3 release before explicit migration. */
796
+ export function verifyLegacySkillRelease(value) {
797
+ assertExactKeys(
798
+ value,
799
+ LEGACY_RELEASE_KEYS,
800
+ "legacy release",
801
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
802
+ );
803
+ if (
804
+ value.schema !== LEGACY_RELEASE_SCHEMA ||
805
+ typeof value.content !== "string"
806
+ ) {
807
+ throw migrationRequired("legacy release schema or content is invalid");
808
+ }
809
+ const core = {
810
+ authorityReceiptDigest: digest(
811
+ value.authorityReceiptDigest,
812
+ "authorityReceiptDigest",
813
+ ),
814
+ candidateId: digest(value.candidateId, "candidateId"),
815
+ content: value.content,
816
+ contentDigest: digest(value.contentDigest, "contentDigest"),
817
+ dependencyLock: normalizeJson(value.dependencyLock, "dependencyLock"),
818
+ dependencyLockDigest: digest(
819
+ value.dependencyLockDigest,
820
+ "dependencyLockDigest",
821
+ ),
822
+ mutationRequestDigest: digest(
823
+ value.mutationRequestDigest,
824
+ "mutationRequestDigest",
825
+ ),
826
+ parentDigest: digest(value.parentDigest, "parentDigest", {
827
+ nullable: true,
828
+ }),
829
+ receiptDigests: normalizeReceiptDigests(value.receiptDigests),
830
+ requestedCapabilities: normalizeJson(
831
+ value.requestedCapabilities,
832
+ "requestedCapabilities",
833
+ ),
834
+ schema: LEGACY_RELEASE_SCHEMA,
835
+ skillName: skillName(value.skillName),
836
+ targetRuntimes: normalizeJson(value.targetRuntimes, "targetRuntimes"),
837
+ transitionSubjectDigest: digest(
838
+ value.transitionSubjectDigest,
839
+ "transitionSubjectDigest",
840
+ ),
841
+ };
842
+ if (
843
+ !Array.isArray(core.requestedCapabilities) ||
844
+ !Array.isArray(core.targetRuntimes) ||
845
+ sha256(Buffer.from(core.content, "utf8")) !== core.contentDigest ||
846
+ digestDependencyLock(core.dependencyLock) !== core.dependencyLockDigest ||
847
+ digest(value.releaseDigest, "releaseDigest") !==
848
+ domainDigest(LEGACY_RELEASE_DOMAIN, core) ||
849
+ canonicalJson(value) !==
850
+ canonicalJson({ ...core, releaseDigest: value.releaseDigest })
851
+ ) {
852
+ throw migrationRequired("legacy release bytes or digests are invalid", {
853
+ legacyReleaseDigest: value.releaseDigest,
854
+ });
855
+ }
856
+ return deepFreeze({ ...core, releaseDigest: value.releaseDigest });
857
+ }
858
+
859
+ export function buildMigratedSkillRelease(input) {
860
+ assertExactKeys(
861
+ input,
862
+ RELEASE_MIGRATION_INPUT_KEYS,
863
+ "release migration input",
864
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
865
+ );
866
+ const legacy = verifyLegacySkillRelease(input.legacyRelease);
867
+ assertExactKeys(
868
+ input.candidateMigration,
869
+ CANDIDATE_MIGRATION_RESULT_KEYS,
870
+ "candidate migration result",
871
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
872
+ );
873
+ if (typeof input.candidateMigration.created !== "boolean") {
874
+ throw migrationRequired(
875
+ "candidate migration result created flag is invalid",
876
+ );
877
+ }
878
+ const candidate = verifySkillCandidateDraft(
879
+ input.candidateMigration.candidate,
880
+ );
881
+ const migration = input.candidateMigration.migration;
882
+ const migrationReceipt = input.candidateMigration.receipt;
883
+ assertExactKeys(
884
+ migration,
885
+ CANDIDATE_MIGRATION_RECORD_KEYS,
886
+ "candidate migration record",
887
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
888
+ );
889
+ assertExactKeys(
890
+ migrationReceipt,
891
+ CANDIDATE_MIGRATION_RECEIPT_KEYS,
892
+ "candidate migration receipt",
893
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
894
+ );
895
+ const migrationCore = { ...migration };
896
+ delete migrationCore.migrationDigest;
897
+ if (
898
+ migration.schema !== SKILL_CANDIDATE_MIGRATION_RECORD_SCHEMA ||
899
+ migration.trust !== "trusted" ||
900
+ migration.migrationDigest !==
901
+ domainDigest(
902
+ `${SKILL_CANDIDATE_MIGRATION_RECORD_SCHEMA}\0`,
903
+ migrationCore,
904
+ ) ||
905
+ migrationReceipt.schema !== SKILL_CANDIDATE_MIGRATION_RECEIPT_SCHEMA ||
906
+ migrationReceipt.authenticated !== true ||
907
+ migrationReceipt.durable !== true ||
908
+ migrationReceipt.trust !== "trusted" ||
909
+ migrationReceipt.migrationDigest !== migration.migrationDigest ||
910
+ migrationReceipt.authorityId !== migration.authorityId ||
911
+ migrationReceipt.handlerArtifactDigest !==
912
+ migration.handlerArtifactDigest ||
913
+ migration.legacyCandidateId !== legacy.candidateId ||
914
+ migration.candidateId !== candidate.candidateId ||
915
+ migration.tenantId !== candidate.tenantId ||
916
+ migration.contentDigest !== candidate.contentDigest ||
917
+ migration.dependencyLockDigest !== candidate.dependencyLockDigest ||
918
+ migration.runtimeManifestDigest !== candidate.runtimeManifestDigest ||
919
+ migration.targetMatrixRoot !== candidate.targetMatrixRoot ||
920
+ legacy.skillName !== candidate.skillName ||
921
+ legacy.parentDigest !== candidate.parentDigest ||
922
+ legacy.contentDigest !== candidate.contentDigest ||
923
+ legacy.content !== candidate.content ||
924
+ legacy.dependencyLockDigest !== candidate.dependencyLock.lockDigest ||
925
+ canonicalJson(legacy.dependencyLock) !==
926
+ canonicalJson(candidate.dependencyLock.lock) ||
927
+ canonicalJson(legacy.requestedCapabilities) !==
928
+ canonicalJson(candidate.requestedCapabilities) ||
929
+ canonicalJson(legacy.targetRuntimes) !==
930
+ canonicalJson(candidate.targetRuntimes)
931
+ ) {
932
+ throw migrationRequired(
933
+ "legacy release is not exactly bound to its audited candidate migration",
934
+ { legacyReleaseDigest: legacy.releaseDigest },
935
+ );
936
+ }
937
+ const core = {
938
+ authorityReceiptDigest: legacy.authorityReceiptDigest,
939
+ candidate,
940
+ candidateId: candidate.candidateId,
941
+ contentDigest: candidate.contentDigest,
942
+ dependencyLock: candidate.dependencyLock,
943
+ dependencyLockDigest: candidate.dependencyLockDigest,
944
+ mutationRequestDigest: legacy.mutationRequestDigest,
945
+ parentDigest: candidate.parentDigest,
946
+ receiptDigests: legacy.receiptDigests,
947
+ runtimeManifest: candidate.runtimeManifest,
948
+ runtimeManifestDigest: candidate.runtimeManifestDigest,
949
+ schema: SKILL_RELEASE_SCHEMA,
950
+ skillName: candidate.skillName,
951
+ targetMatrix: candidate.targetMatrix,
952
+ targetMatrixRoot: candidate.targetMatrixRoot,
953
+ targetRuntimes: candidate.targetRuntimes,
954
+ tenantId: candidate.tenantId,
955
+ transitionSubjectDigest: legacy.transitionSubjectDigest,
956
+ };
957
+ return verifySkillRelease({
958
+ ...core,
959
+ releaseDigest: domainDigest(RELEASE_DOMAIN, core),
960
+ });
961
+ }
962
+
963
+ export function verifySkillReleaseMigrationResult(value) {
964
+ assertExactKeys(
965
+ value,
966
+ RELEASE_MIGRATION_RESULT_KEYS,
967
+ "release migration result",
968
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
969
+ );
970
+ if (typeof value.created !== "boolean") {
971
+ throw migrationRequired("release migration created flag is invalid");
972
+ }
973
+ const release = verifySkillRelease(value.release);
974
+ assertExactKeys(
975
+ value.migration,
976
+ RELEASE_MIGRATION_RECORD_KEYS,
977
+ "release migration record",
978
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
979
+ );
980
+ const migrationCore = {
981
+ authorityId: boundedString(
982
+ value.migration.authorityId,
983
+ "releaseMigration.authorityId",
984
+ 256,
985
+ ),
986
+ candidateMigrationDigest: digest(
987
+ value.migration.candidateMigrationDigest,
988
+ "releaseMigration.candidateMigrationDigest",
989
+ ),
990
+ candidateMigrationReceiptDigest: digest(
991
+ value.migration.candidateMigrationReceiptDigest,
992
+ "releaseMigration.candidateMigrationReceiptDigest",
993
+ ),
994
+ handlerArtifactDigest: digest(
995
+ value.migration.handlerArtifactDigest,
996
+ "releaseMigration.handlerArtifactDigest",
997
+ ),
998
+ legacyCandidateId: digest(
999
+ value.migration.legacyCandidateId,
1000
+ "releaseMigration.legacyCandidateId",
1001
+ ),
1002
+ legacyReleaseDigest: digest(
1003
+ value.migration.legacyReleaseDigest,
1004
+ "releaseMigration.legacyReleaseDigest",
1005
+ ),
1006
+ releaseDigest: digest(
1007
+ value.migration.releaseDigest,
1008
+ "releaseMigration.releaseDigest",
1009
+ ),
1010
+ schema: value.migration.schema,
1011
+ skillName: skillName(value.migration.skillName),
1012
+ tenantId: tenantId(value.migration.tenantId),
1013
+ trust: value.migration.trust,
1014
+ };
1015
+ const migration = deepFreeze({
1016
+ ...migrationCore,
1017
+ migrationDigest: digest(
1018
+ value.migration.migrationDigest,
1019
+ "releaseMigration.migrationDigest",
1020
+ ),
1021
+ });
1022
+ assertExactKeys(
1023
+ value.receipt,
1024
+ RELEASE_MIGRATION_RECEIPT_KEYS,
1025
+ "release migration receipt",
1026
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1027
+ );
1028
+ const receipt = deepFreeze({
1029
+ authenticated: value.receipt.authenticated,
1030
+ authorityId: boundedString(
1031
+ value.receipt.authorityId,
1032
+ "releaseMigrationReceipt.authorityId",
1033
+ 256,
1034
+ ),
1035
+ durable: value.receipt.durable,
1036
+ handlerArtifactDigest: digest(
1037
+ value.receipt.handlerArtifactDigest,
1038
+ "releaseMigrationReceipt.handlerArtifactDigest",
1039
+ ),
1040
+ migrationDigest: digest(
1041
+ value.receipt.migrationDigest,
1042
+ "releaseMigrationReceipt.migrationDigest",
1043
+ ),
1044
+ receiptDigest: digest(
1045
+ value.receipt.receiptDigest,
1046
+ "releaseMigrationReceipt.receiptDigest",
1047
+ ),
1048
+ schema: value.receipt.schema,
1049
+ trust: value.receipt.trust,
1050
+ });
1051
+ if (
1052
+ migration.schema !== SKILL_RELEASE_MIGRATION_RECORD_SCHEMA ||
1053
+ migration.trust !== "trusted" ||
1054
+ migration.migrationDigest !==
1055
+ domainDigest(RELEASE_MIGRATION_DOMAIN, migrationCore) ||
1056
+ migration.releaseDigest !== release.releaseDigest ||
1057
+ migration.skillName !== release.skillName ||
1058
+ migration.tenantId !== release.tenantId ||
1059
+ receipt.schema !== SKILL_RELEASE_MIGRATION_RECEIPT_SCHEMA ||
1060
+ receipt.authenticated !== true ||
1061
+ receipt.durable !== true ||
1062
+ receipt.trust !== "trusted" ||
1063
+ receipt.authorityId !== migration.authorityId ||
1064
+ receipt.handlerArtifactDigest !== migration.handlerArtifactDigest ||
1065
+ receipt.migrationDigest !== migration.migrationDigest
1066
+ ) {
1067
+ throw migrationRequired(
1068
+ "release migration result is not exactly bound and durable",
1069
+ { legacyReleaseDigest: migration.legacyReleaseDigest },
1070
+ );
1071
+ }
1072
+ return deepFreeze({
1073
+ created: value.created,
1074
+ migration,
1075
+ receipt,
1076
+ release,
1077
+ });
1078
+ }
1079
+
1080
+ /** Verify the exact pre-tenant v2 active-state pointer. */
1081
+ export function verifyLegacySkillReleaseState(value) {
1082
+ assertExactKeys(
1083
+ value,
1084
+ LEGACY_STATE_KEYS,
1085
+ "legacy release state",
1086
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1087
+ );
1088
+ if (value.schema !== LEGACY_STATE_SCHEMA) {
1089
+ throw migrationRequired("legacy release state schema is invalid");
1090
+ }
1091
+ const core = {
1092
+ activeReleaseDigest: digest(
1093
+ value.activeReleaseDigest,
1094
+ "legacyState.activeReleaseDigest",
1095
+ ),
1096
+ authorityReceiptDigest: digest(
1097
+ value.authorityReceiptDigest,
1098
+ "legacyState.authorityReceiptDigest",
1099
+ ),
1100
+ dependencyLockDigest: digest(
1101
+ value.dependencyLockDigest,
1102
+ "legacyState.dependencyLockDigest",
1103
+ ),
1104
+ fence: safeInteger(value.fence, "legacyState.fence", { minimum: 1 }),
1105
+ lastKnownGoodReleaseDigest: digest(
1106
+ value.lastKnownGoodReleaseDigest,
1107
+ "legacyState.lastKnownGoodReleaseDigest",
1108
+ ),
1109
+ revision: safeInteger(value.revision, "legacyState.revision", {
1110
+ minimum: 1,
1111
+ }),
1112
+ schema: LEGACY_STATE_SCHEMA,
1113
+ skillName: skillName(value.skillName),
1114
+ transactionId: digest(value.transactionId, "legacyState.transactionId"),
1115
+ };
1116
+ const state = deepFreeze({
1117
+ ...core,
1118
+ stateDigest: digest(value.stateDigest, "legacyState.stateDigest"),
1119
+ });
1120
+ if (
1121
+ state.stateDigest !== domainDigest(LEGACY_STATE_DOMAIN, core) ||
1122
+ canonicalJson(value) !== canonicalJson(state)
1123
+ ) {
1124
+ throw migrationRequired(
1125
+ "legacy release state bytes or digest are invalid",
1126
+ {
1127
+ legacyStateDigest: value.stateDigest,
1128
+ },
1129
+ );
1130
+ }
1131
+ return state;
1132
+ }
1133
+
1134
+ function verifyLegacySkillReleaseIntent(value) {
1135
+ assertExactKeys(
1136
+ value,
1137
+ LEGACY_INTENT_KEYS,
1138
+ "legacy release transition intent",
1139
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1140
+ );
1141
+ const core = { ...value };
1142
+ delete core.intentDigest;
1143
+ if (
1144
+ core.schema !== INTENT_SCHEMA ||
1145
+ !["promote", "rollback"].includes(core.operation) ||
1146
+ domainDigest(INTENT_DOMAIN, core) !== value.intentDigest
1147
+ ) {
1148
+ throw migrationRequired("legacy journal intent digest is invalid");
1149
+ }
1150
+ for (const field of [
1151
+ "authorityReceiptDigest",
1152
+ "dependencyLockDigest",
1153
+ "expectedParentDigest",
1154
+ "nextStateDigest",
1155
+ "pointerDigest",
1156
+ "requestDigest",
1157
+ "targetReleaseDigest",
1158
+ "transactionId",
1159
+ "transitionSubjectDigest",
1160
+ "intentDigest",
1161
+ ]) {
1162
+ digest(value[field], `legacyIntent.${field}`);
1163
+ }
1164
+ digest(value.previousStateDigest, "legacyIntent.previousStateDigest", {
1165
+ nullable: true,
1166
+ });
1167
+ digest(value.candidateId, "legacyIntent.candidateId", { nullable: true });
1168
+ safeInteger(value.expectedRevision, "legacyIntent.expectedRevision");
1169
+ skillName(value.skillName);
1170
+ boundedString(value.operationId, "legacyIntent.operationId");
1171
+ normalizeReceiptDigests(value.receiptDigests);
1172
+ const authorityReceipt = verifySkillMutationConsumptionReceipt(
1173
+ value.authorityReceipt,
1174
+ );
1175
+ const mutationRequest = verifySkillMutationRequest(value.mutationRequest);
1176
+ if (
1177
+ authorityReceipt.receiptDigest !== value.authorityReceiptDigest ||
1178
+ authorityReceipt.requestDigest !== value.requestDigest ||
1179
+ authorityReceipt.operationId !== value.operationId ||
1180
+ authorityReceipt.operation !== value.operation ||
1181
+ authorityReceipt.transitionSubjectDigest !==
1182
+ value.transitionSubjectDigest ||
1183
+ authorityReceipt.skillName !== value.skillName ||
1184
+ authorityReceipt.expectedTargetDigest !== value.expectedParentDigest ||
1185
+ authorityReceipt.expectedTargetRevision !== value.expectedRevision
1186
+ ) {
1187
+ throw migrationRequired(
1188
+ "legacy journal intent authority bindings are invalid",
1189
+ );
1190
+ }
1191
+ verifyRequestReceiptBinding(mutationRequest, authorityReceipt);
1192
+ const expectedTransitionSubjectDigest = digestSkillMutationTransitionSubject({
1193
+ tenantId: mutationRequest.tenantId,
1194
+ skillName: value.skillName,
1195
+ operation: value.operation,
1196
+ candidateId: value.candidateId,
1197
+ rollbackTargetReleaseDigest:
1198
+ value.operation === SKILL_MUTATION_OPERATIONS.ROLLBACK
1199
+ ? value.targetReleaseDigest
1200
+ : null,
1201
+ dependencyLockDigest: value.dependencyLockDigest,
1202
+ expectedActiveContentDigest: value.expectedParentDigest,
1203
+ expectedActiveRevision: value.expectedRevision,
1204
+ });
1205
+ if (
1206
+ mutationRequest.requestDigest !== value.requestDigest ||
1207
+ mutationRequest.operation !== value.operation ||
1208
+ mutationRequest.transitionSubjectDigest !== value.transitionSubjectDigest ||
1209
+ canonicalJson(receiptDigestsFromRequest(mutationRequest)) !==
1210
+ canonicalJson(value.receiptDigests) ||
1211
+ value.transitionSubjectDigest !== expectedTransitionSubjectDigest ||
1212
+ canonicalJson(value) !==
1213
+ canonicalJson({ ...core, intentDigest: value.intentDigest })
1214
+ ) {
1215
+ throw migrationRequired("legacy journal intent is not exactly bound");
1216
+ }
1217
+ return deepFreeze({ ...value });
1218
+ }
1219
+
1220
+ /** Verify the exact pre-tenant v3 crash-recovery journal without recovering it. */
1221
+ export function verifyLegacySkillReleaseJournal(value) {
1222
+ assertExactKeys(
1223
+ value,
1224
+ LEGACY_JOURNAL_KEYS,
1225
+ "legacy release journal",
1226
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1227
+ );
1228
+ const core = { ...value };
1229
+ delete core.journalDigest;
1230
+ if (
1231
+ core.schema !== LEGACY_JOURNAL_SCHEMA ||
1232
+ !["journaled", "prepared", "pointer-written"].includes(core.phase) ||
1233
+ typeof core.leaseToken !== "string" ||
1234
+ !TOKEN_PATTERN.test(core.leaseToken) ||
1235
+ typeof core.stagedFile !== "string" ||
1236
+ !TEMP_PATTERN.test(core.stagedFile) ||
1237
+ domainDigest(LEGACY_JOURNAL_DOMAIN, core) !== value.journalDigest
1238
+ ) {
1239
+ throw migrationRequired("legacy release journal digest is invalid");
1240
+ }
1241
+ const intent = verifyLegacySkillReleaseIntent(core.intent);
1242
+ const nextState = verifyLegacySkillReleaseState(core.nextState);
1243
+ const previousState =
1244
+ core.previousState === null
1245
+ ? null
1246
+ : verifyLegacySkillReleaseState(core.previousState);
1247
+ if (
1248
+ intent.transactionId !== core.transactionId ||
1249
+ nextState.transactionId !== core.transactionId ||
1250
+ nextState.skillName !== core.skillName ||
1251
+ intent.skillName !== core.skillName ||
1252
+ nextState.stateDigest !== intent.nextStateDigest ||
1253
+ nextState.stateDigest !== intent.pointerDigest ||
1254
+ nextState.activeReleaseDigest !== intent.targetReleaseDigest ||
1255
+ nextState.authorityReceiptDigest !== intent.authorityReceiptDigest ||
1256
+ nextState.dependencyLockDigest !== intent.dependencyLockDigest ||
1257
+ nextState.revision !== intent.expectedRevision + 1 ||
1258
+ (previousState === null
1259
+ ? intent.previousStateDigest !== null || intent.expectedRevision !== 0
1260
+ : previousState.skillName !== core.skillName ||
1261
+ previousState.stateDigest !== intent.previousStateDigest ||
1262
+ previousState.revision !== intent.expectedRevision)
1263
+ ) {
1264
+ throw migrationRequired(
1265
+ "legacy release journal transition bindings are invalid",
1266
+ );
1267
+ }
1268
+ if (core.prepareReceipt !== null) {
1269
+ const prepared = verifyLedgerProjection(core.prepareReceipt, {
1270
+ authorityReceiptDigest: intent.authorityReceiptDigest,
1271
+ intentDigest: intent.intentDigest,
1272
+ transactionId: intent.transactionId,
1273
+ });
1274
+ if (prepared.status !== "prepared") {
1275
+ throw migrationRequired(
1276
+ "legacy journal prepare receipt has the wrong status",
1277
+ );
1278
+ }
1279
+ } else if (core.phase !== "journaled") {
1280
+ throw migrationRequired(
1281
+ "legacy prepared journal is missing its projection",
1282
+ );
1283
+ }
1284
+ const journal = deepFreeze({
1285
+ ...core,
1286
+ intent,
1287
+ journalDigest: value.journalDigest,
1288
+ nextState,
1289
+ previousState,
1290
+ });
1291
+ if (canonicalJson(journal) !== canonicalJson(value)) {
1292
+ throw migrationRequired("legacy release journal is not canonical");
1293
+ }
1294
+ return journal;
1295
+ }
1296
+
1297
+ const journalResolutionAuthorities = new WeakMap();
1298
+ const completedStateMigrations = new WeakSet();
1299
+
1300
+ function completedStateMigration(value) {
1301
+ const result = deepFreeze(value);
1302
+ completedStateMigrations.add(result);
1303
+ return result;
1304
+ }
1305
+
1306
+ export function createSkillReleaseJournalResolutionAuthority(options) {
1307
+ assertExactKeys(
1308
+ options,
1309
+ JOURNAL_RESOLUTION_AUTHORITY_OPTION_KEYS,
1310
+ "legacy journal resolution authority options",
1311
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1312
+ );
1313
+ if (options.trust !== "trusted" || typeof options.resolve !== "function") {
1314
+ throw migrationRequired("legacy journal resolution authority is invalid");
1315
+ }
1316
+ const descriptor = Object.freeze({
1317
+ authorityId: boundedString(
1318
+ options.authorityId,
1319
+ "journalResolutionAuthority.authorityId",
1320
+ 256,
1321
+ ),
1322
+ handlerArtifactDigest: digest(
1323
+ options.handlerArtifactDigest,
1324
+ "journalResolutionAuthority.handlerArtifactDigest",
1325
+ ),
1326
+ schema: SKILL_RELEASE_JOURNAL_RESOLUTION_AUTHORITY_SCHEMA,
1327
+ trust: "trusted",
1328
+ });
1329
+ journalResolutionAuthorities.set(descriptor, options.resolve);
1330
+ return descriptor;
1331
+ }
1332
+
1333
+ function verifyLegacyJournalResolutionReceipt(
1334
+ value,
1335
+ journal,
1336
+ ownerTenantId,
1337
+ authority,
1338
+ ) {
1339
+ assertExactKeys(
1340
+ value,
1341
+ JOURNAL_RESOLUTION_RECEIPT_KEYS,
1342
+ "legacy journal resolution receipt",
1343
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1344
+ );
1345
+ const projection = verifyLedgerProjection(value.ledgerProjection, {
1346
+ authorityReceiptDigest: journal.intent.authorityReceiptDigest,
1347
+ intentDigest: journal.intent.intentDigest,
1348
+ pointerDigest: journal.nextState.stateDigest,
1349
+ prepareReceiptDigest: journal.prepareReceipt?.receiptDigest,
1350
+ revision: journal.nextState.revision,
1351
+ skillName: journal.skillName,
1352
+ stateDigest: journal.nextState.stateDigest,
1353
+ transactionId: journal.transactionId,
1354
+ });
1355
+ if (
1356
+ projection.status === "prepared" &&
1357
+ journal.prepareReceipt !== null &&
1358
+ canonicalJson(projection) !== canonicalJson(journal.prepareReceipt)
1359
+ ) {
1360
+ throw migrationRequired(
1361
+ "legacy journal resolution differs from its stored prepare projection",
1362
+ );
1363
+ }
1364
+ const core = {
1365
+ authenticated: value.authenticated,
1366
+ authorityId: boundedString(
1367
+ value.authorityId,
1368
+ "journalResolution.authorityId",
1369
+ 256,
1370
+ ),
1371
+ durable: value.durable,
1372
+ handlerArtifactDigest: digest(
1373
+ value.handlerArtifactDigest,
1374
+ "journalResolution.handlerArtifactDigest",
1375
+ ),
1376
+ journalDigest: digest(
1377
+ value.journalDigest,
1378
+ "journalResolution.journalDigest",
1379
+ ),
1380
+ ledgerProjection: projection,
1381
+ schema: value.schema,
1382
+ tenantId: tenantId(value.tenantId, "journalResolution.tenantId"),
1383
+ transactionId: digest(
1384
+ value.transactionId,
1385
+ "journalResolution.transactionId",
1386
+ ),
1387
+ trust: value.trust,
1388
+ };
1389
+ const receipt = deepFreeze({
1390
+ ...core,
1391
+ receiptDigest: digest(
1392
+ value.receiptDigest,
1393
+ "journalResolution.receiptDigest",
1394
+ ),
1395
+ });
1396
+ if (
1397
+ receipt.schema !== SKILL_RELEASE_JOURNAL_RESOLUTION_RECEIPT_SCHEMA ||
1398
+ receipt.authenticated !== true ||
1399
+ receipt.durable !== true ||
1400
+ receipt.trust !== "trusted" ||
1401
+ receipt.authorityId !== authority.authorityId ||
1402
+ receipt.handlerArtifactDigest !== authority.handlerArtifactDigest ||
1403
+ receipt.tenantId !== ownerTenantId ||
1404
+ receipt.journalDigest !== journal.journalDigest ||
1405
+ receipt.transactionId !== journal.transactionId ||
1406
+ receipt.receiptDigest !==
1407
+ domainDigest(JOURNAL_RESOLUTION_RECEIPT_DOMAIN, core) ||
1408
+ canonicalJson(receipt) !== canonicalJson(value)
1409
+ ) {
1410
+ throw migrationRequired(
1411
+ "legacy journal resolution receipt is not exactly bound and durable",
1412
+ );
1413
+ }
1414
+ return receipt;
1415
+ }
1416
+
1417
+ export function buildLegacySkillReleaseJournalDisposition(input) {
1418
+ assertExactKeys(
1419
+ input,
1420
+ JOURNAL_DISPOSITION_INPUT_KEYS,
1421
+ "legacy journal disposition input",
1422
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1423
+ );
1424
+ const journal = verifyLegacySkillReleaseJournal(input.legacyJournal);
1425
+ const ownerTenantId = tenantId(input.tenantId, "journalDisposition.tenantId");
1426
+ if (journal.intent.mutationRequest.tenantId !== ownerTenantId) {
1427
+ throw migrationRequired(
1428
+ "legacy journal mutation authority belongs to another tenant",
1429
+ );
1430
+ }
1431
+ const resolver = journalResolutionAuthorities.get(input.resolutionAuthority);
1432
+ if (!resolver) {
1433
+ throw migrationRequired(
1434
+ "legacy journal disposition requires a branded resolution authority",
1435
+ );
1436
+ }
1437
+ let rawReceipt;
1438
+ try {
1439
+ rawReceipt = resolver(
1440
+ deepFreeze({
1441
+ journalDigest: journal.journalDigest,
1442
+ skillName: journal.skillName,
1443
+ tenantId: ownerTenantId,
1444
+ transactionId: journal.transactionId,
1445
+ }),
1446
+ );
1447
+ } catch (cause) {
1448
+ throw failure(
1449
+ "SKILL_RELEASE_JOURNAL_RESOLUTION_FAILED",
1450
+ "legacy journal resolution authority failed closed",
1451
+ { cause, journalDigest: journal.journalDigest },
1452
+ );
1453
+ }
1454
+ if (utilTypes.isPromise(rawReceipt)) {
1455
+ throw failure(
1456
+ "SKILL_RELEASE_JOURNAL_RESOLUTION_FAILED",
1457
+ "legacy journal resolution authority must be synchronous",
1458
+ );
1459
+ }
1460
+ const receipt = verifyLegacyJournalResolutionReceipt(
1461
+ rawReceipt,
1462
+ journal,
1463
+ ownerTenantId,
1464
+ input.resolutionAuthority,
1465
+ );
1466
+ const projection = receipt.ledgerProjection;
1467
+ const action =
1468
+ projection.status === "absent"
1469
+ ? "abort"
1470
+ : projection.status === "committed" && projection.current === true
1471
+ ? "migrate-committed-state"
1472
+ : "quarantine";
1473
+ let stateMigrationPlan = null;
1474
+ if (action === "migrate-committed-state") {
1475
+ stateMigrationPlan = verifySkillReleaseStateMigrationPlan(
1476
+ input.stateMigrationPlan,
1477
+ );
1478
+ if (
1479
+ stateMigrationPlan.tenantId !== ownerTenantId ||
1480
+ stateMigrationPlan.skillName !== journal.skillName ||
1481
+ stateMigrationPlan.legacyStateDigest !== journal.nextState.stateDigest ||
1482
+ stateMigrationPlan.legacyTransactionId !== journal.transactionId ||
1483
+ stateMigrationPlan.legacyRevision !== journal.nextState.revision ||
1484
+ stateMigrationPlan.legacyFence !== journal.nextState.fence ||
1485
+ stateMigrationPlan.legacyActiveReleaseDigest !==
1486
+ journal.nextState.activeReleaseDigest ||
1487
+ stateMigrationPlan.legacyLastKnownGoodReleaseDigest !==
1488
+ journal.nextState.lastKnownGoodReleaseDigest
1489
+ ) {
1490
+ throw migrationRequired(
1491
+ "committed legacy journal is not bound to its state migration plan",
1492
+ );
1493
+ }
1494
+ } else if (input.stateMigrationPlan !== null) {
1495
+ throw migrationRequired(
1496
+ "non-committed legacy journal disposition must not carry a state migration plan",
1497
+ );
1498
+ }
1499
+ const core = {
1500
+ action,
1501
+ journalDigest: journal.journalDigest,
1502
+ ledgerCurrent:
1503
+ projection.status === "committed" ? projection.current : null,
1504
+ ledgerStatus: projection.status,
1505
+ legacyNextStateDigest: journal.nextState.stateDigest,
1506
+ resolutionReceipt: receipt,
1507
+ resolutionReceiptDigest: receipt.receiptDigest,
1508
+ schema: SKILL_RELEASE_JOURNAL_DISPOSITION_PLAN_SCHEMA,
1509
+ skillName: journal.skillName,
1510
+ stateMigrationDigest: stateMigrationPlan?.stateMigrationDigest ?? null,
1511
+ tenantId: ownerTenantId,
1512
+ transactionId: journal.transactionId,
1513
+ };
1514
+ return deepFreeze({
1515
+ ...core,
1516
+ planDigest: domainDigest(JOURNAL_DISPOSITION_PLAN_DOMAIN, core),
1517
+ });
1518
+ }
1519
+
1520
+ export function verifyLegacySkillReleaseJournalDisposition(value) {
1521
+ assertExactKeys(
1522
+ value,
1523
+ JOURNAL_DISPOSITION_PLAN_KEYS,
1524
+ "legacy journal disposition plan",
1525
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1526
+ );
1527
+ assertExactKeys(
1528
+ value.resolutionReceipt,
1529
+ JOURNAL_RESOLUTION_RECEIPT_KEYS,
1530
+ "journal disposition resolution receipt",
1531
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1532
+ );
1533
+ const resolutionCore = { ...value.resolutionReceipt };
1534
+ delete resolutionCore.receiptDigest;
1535
+ const resolutionReceipt = deepFreeze({
1536
+ ...resolutionCore,
1537
+ receiptDigest: digest(
1538
+ value.resolutionReceipt.receiptDigest,
1539
+ "journalDisposition.resolutionReceipt.receiptDigest",
1540
+ ),
1541
+ });
1542
+ boundedString(
1543
+ resolutionReceipt.authorityId,
1544
+ "journalDisposition.resolutionReceipt.authorityId",
1545
+ 256,
1546
+ );
1547
+ digest(
1548
+ resolutionReceipt.handlerArtifactDigest,
1549
+ "journalDisposition.resolutionReceipt.handlerArtifactDigest",
1550
+ );
1551
+ digest(
1552
+ resolutionReceipt.journalDigest,
1553
+ "journalDisposition.resolutionReceipt.journalDigest",
1554
+ );
1555
+ tenantId(
1556
+ resolutionReceipt.tenantId,
1557
+ "journalDisposition.resolutionReceipt.tenantId",
1558
+ );
1559
+ digest(
1560
+ resolutionReceipt.transactionId,
1561
+ "journalDisposition.resolutionReceipt.transactionId",
1562
+ );
1563
+ if (
1564
+ resolutionReceipt.schema !==
1565
+ SKILL_RELEASE_JOURNAL_RESOLUTION_RECEIPT_SCHEMA ||
1566
+ resolutionReceipt.authenticated !== true ||
1567
+ resolutionReceipt.durable !== true ||
1568
+ resolutionReceipt.trust !== "trusted" ||
1569
+ !isPlainObject(resolutionReceipt.ledgerProjection) ||
1570
+ resolutionReceipt.receiptDigest !==
1571
+ domainDigest(JOURNAL_RESOLUTION_RECEIPT_DOMAIN, resolutionCore)
1572
+ ) {
1573
+ throw migrationRequired(
1574
+ "journal disposition resolution receipt is invalid",
1575
+ );
1576
+ }
1577
+ const core = {
1578
+ action: value.action,
1579
+ journalDigest: digest(
1580
+ value.journalDigest,
1581
+ "journalDisposition.journalDigest",
1582
+ ),
1583
+ ledgerCurrent: value.ledgerCurrent,
1584
+ ledgerStatus: value.ledgerStatus,
1585
+ legacyNextStateDigest: digest(
1586
+ value.legacyNextStateDigest,
1587
+ "journalDisposition.legacyNextStateDigest",
1588
+ ),
1589
+ resolutionReceipt,
1590
+ resolutionReceiptDigest: digest(
1591
+ value.resolutionReceiptDigest,
1592
+ "journalDisposition.resolutionReceiptDigest",
1593
+ ),
1594
+ schema: value.schema,
1595
+ skillName: skillName(value.skillName),
1596
+ stateMigrationDigest: digest(
1597
+ value.stateMigrationDigest,
1598
+ "journalDisposition.stateMigrationDigest",
1599
+ { nullable: true },
1600
+ ),
1601
+ tenantId: tenantId(value.tenantId, "journalDisposition.tenantId"),
1602
+ transactionId: digest(
1603
+ value.transactionId,
1604
+ "journalDisposition.transactionId",
1605
+ ),
1606
+ };
1607
+ const plan = deepFreeze({
1608
+ ...core,
1609
+ planDigest: digest(value.planDigest, "journalDisposition.planDigest"),
1610
+ });
1611
+ const shapeValid =
1612
+ (plan.action === "abort" &&
1613
+ plan.ledgerStatus === "absent" &&
1614
+ plan.ledgerCurrent === null &&
1615
+ plan.stateMigrationDigest === null) ||
1616
+ (plan.action === "migrate-committed-state" &&
1617
+ plan.ledgerStatus === "committed" &&
1618
+ plan.ledgerCurrent === true &&
1619
+ plan.stateMigrationDigest !== null) ||
1620
+ (plan.action === "quarantine" &&
1621
+ ((plan.ledgerStatus === "prepared" && plan.ledgerCurrent === null) ||
1622
+ (plan.ledgerStatus === "committed" && plan.ledgerCurrent === false)) &&
1623
+ plan.stateMigrationDigest === null);
1624
+ if (
1625
+ plan.schema !== SKILL_RELEASE_JOURNAL_DISPOSITION_PLAN_SCHEMA ||
1626
+ !shapeValid ||
1627
+ resolutionReceipt.receiptDigest !== plan.resolutionReceiptDigest ||
1628
+ resolutionReceipt.journalDigest !== plan.journalDigest ||
1629
+ resolutionReceipt.transactionId !== plan.transactionId ||
1630
+ resolutionReceipt.tenantId !== plan.tenantId ||
1631
+ resolutionReceipt.ledgerProjection.status !== plan.ledgerStatus ||
1632
+ (plan.ledgerStatus === "committed"
1633
+ ? resolutionReceipt.ledgerProjection.current !== plan.ledgerCurrent
1634
+ : plan.ledgerCurrent !== null) ||
1635
+ plan.planDigest !== domainDigest(JOURNAL_DISPOSITION_PLAN_DOMAIN, core) ||
1636
+ canonicalJson(plan) !== canonicalJson(value)
1637
+ ) {
1638
+ throw migrationRequired("legacy journal disposition plan is invalid");
1639
+ }
1640
+ return plan;
1641
+ }
1642
+
1643
+ function verifyCompletedStateMigration(result, disposition) {
1644
+ if (!completedStateMigrations.has(result)) {
1645
+ throw migrationRequired(
1646
+ "committed journal retirement requires a branded completed state migration",
1647
+ );
1648
+ }
1649
+ const plan = verifySkillReleaseStateMigrationPlan(result.plan);
1650
+ const state = verifySkillReleaseState(result.state);
1651
+ if (
1652
+ typeof result.created !== "boolean" ||
1653
+ result.receipt?.schema !== SKILL_RELEASE_STATE_MIGRATION_RECEIPT_SCHEMA ||
1654
+ result.receipt.authenticated !== true ||
1655
+ result.receipt.durable !== true ||
1656
+ result.receipt.trust !== "trusted" ||
1657
+ result.receipt.stateMigrationDigest !== plan.stateMigrationDigest ||
1658
+ disposition.stateMigrationDigest !== plan.stateMigrationDigest ||
1659
+ state.transactionId !== plan.stateMigrationDigest ||
1660
+ state.tenantId !== disposition.tenantId ||
1661
+ state.skillName !== disposition.skillName ||
1662
+ state.revision !== plan.legacyRevision ||
1663
+ state.fence !== plan.legacyFence ||
1664
+ state.activeReleaseDigest !== plan.activeReleaseDigest ||
1665
+ state.lastKnownGoodReleaseDigest !== plan.lastKnownGoodReleaseDigest ||
1666
+ state.dependencyLockDigest !== plan.dependencyLockDigest
1667
+ ) {
1668
+ throw migrationRequired(
1669
+ "completed state migration differs from the journal disposition",
1670
+ );
1671
+ }
1672
+ return result;
1673
+ }
1674
+
1675
+ export function verifySkillReleaseJournalArchive(value) {
1676
+ assertExactKeys(
1677
+ value,
1678
+ JOURNAL_ARCHIVE_KEYS,
1679
+ "legacy journal archive",
1680
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1681
+ );
1682
+ const journal = verifyLegacySkillReleaseJournal(value.legacyJournal);
1683
+ const disposition = verifyLegacySkillReleaseJournalDisposition(
1684
+ value.dispositionPlan,
1685
+ );
1686
+ verifyLegacyJournalResolutionReceipt(
1687
+ disposition.resolutionReceipt,
1688
+ journal,
1689
+ disposition.tenantId,
1690
+ {
1691
+ authorityId: disposition.resolutionReceipt.authorityId,
1692
+ handlerArtifactDigest:
1693
+ disposition.resolutionReceipt.handlerArtifactDigest,
1694
+ },
1695
+ );
1696
+ const core = {
1697
+ action: value.action,
1698
+ dispositionPlan: disposition,
1699
+ legacyJournal: journal,
1700
+ schema: value.schema,
1701
+ stateMigrationStateDigest: digest(
1702
+ value.stateMigrationStateDigest,
1703
+ "journalArchive.stateMigrationStateDigest",
1704
+ { nullable: true },
1705
+ ),
1706
+ };
1707
+ const archive = deepFreeze({
1708
+ ...core,
1709
+ archiveDigest: digest(value.archiveDigest, "journalArchive.archiveDigest"),
1710
+ });
1711
+ if (
1712
+ archive.schema !== SKILL_RELEASE_JOURNAL_ARCHIVE_SCHEMA ||
1713
+ archive.action !== disposition.action ||
1714
+ disposition.journalDigest !== journal.journalDigest ||
1715
+ disposition.transactionId !== journal.transactionId ||
1716
+ disposition.skillName !== journal.skillName ||
1717
+ (archive.action === "migrate-committed-state") !==
1718
+ (archive.stateMigrationStateDigest !== null) ||
1719
+ archive.archiveDigest !== domainDigest(JOURNAL_ARCHIVE_DOMAIN, core) ||
1720
+ canonicalJson(archive) !== canonicalJson(value)
1721
+ ) {
1722
+ throw migrationRequired("legacy journal archive is invalid");
1723
+ }
1724
+ return archive;
1725
+ }
1726
+
1727
+ export function archiveLegacySkillReleaseJournal(input) {
1728
+ assertExactKeys(
1729
+ input,
1730
+ JOURNAL_ARCHIVE_INPUT_KEYS,
1731
+ "legacy journal archive input",
1732
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1733
+ );
1734
+ const journal = verifyLegacySkillReleaseJournal(input.legacyJournal);
1735
+ const fsImpl = input.fsImpl;
1736
+ const crashHook = input.crashHook;
1737
+ if (
1738
+ !fsImpl ||
1739
+ typeof fsImpl !== "object" ||
1740
+ utilTypes.isProxy(fsImpl) ||
1741
+ (crashHook !== null && typeof crashHook !== "function")
1742
+ ) {
1743
+ throw migrationRequired("legacy journal archive runtime is invalid");
1744
+ }
1745
+ const checkpoint = (phase, details) => {
1746
+ if (crashHook === null) return;
1747
+ const result = crashHook(phase, deepFreeze({ ...details }));
1748
+ if (utilTypes.isPromise(result)) {
1749
+ throw migrationRequired(
1750
+ "legacy journal archive crash hook must be synchronous",
1751
+ );
1752
+ }
1753
+ };
1754
+ if (input.secure !== true && input.secure !== false) {
1755
+ throw migrationRequired("legacy journal archive secure mode is invalid");
1756
+ }
1757
+ const disposition = verifyLegacySkillReleaseJournalDisposition(
1758
+ input.dispositionPlan,
1759
+ );
1760
+ if (
1761
+ disposition.journalDigest !== journal.journalDigest ||
1762
+ disposition.transactionId !== journal.transactionId ||
1763
+ disposition.skillName !== journal.skillName
1764
+ ) {
1765
+ throw migrationRequired(
1766
+ "legacy journal differs from its authenticated disposition",
1767
+ );
1768
+ }
1769
+ let stateMigrationStateDigest = null;
1770
+ if (disposition.action === "migrate-committed-state") {
1771
+ const migration = verifyCompletedStateMigration(
1772
+ input.stateMigrationResult,
1773
+ disposition,
1774
+ );
1775
+ stateMigrationStateDigest = migration.state.stateDigest;
1776
+ } else if (input.stateMigrationResult !== null) {
1777
+ throw migrationRequired(
1778
+ "abort and quarantine retirement must not carry state migration proof",
1779
+ );
1780
+ }
1781
+ const archiveCore = {
1782
+ action: disposition.action,
1783
+ dispositionPlan: disposition,
1784
+ legacyJournal: journal,
1785
+ schema: SKILL_RELEASE_JOURNAL_ARCHIVE_SCHEMA,
1786
+ stateMigrationStateDigest,
1787
+ };
1788
+ const archive = deepFreeze({
1789
+ ...archiveCore,
1790
+ archiveDigest: domainDigest(JOURNAL_ARCHIVE_DOMAIN, archiveCore),
1791
+ });
1792
+ const requestedSourcePath = path.resolve(
1793
+ boundedString(input.sourcePath, "journalArchive.sourcePath", 4096),
1794
+ );
1795
+ const requestedSourceDirectory = path.dirname(requestedSourcePath);
1796
+ const requestedArchiveDirectory = path.resolve(
1797
+ boundedString(input.archiveDir, "journalArchive.archiveDir", 4096),
1798
+ );
1799
+ if (
1800
+ path.basename(requestedSourcePath) !== `${journal.skillName}.json` ||
1801
+ path.basename(requestedSourceDirectory) !== "journals"
1802
+ ) {
1803
+ throw migrationRequired("legacy journal archive paths are invalid");
1804
+ }
1805
+ const canonicalDirectories = [
1806
+ requestedSourceDirectory,
1807
+ requestedArchiveDirectory,
1808
+ ].map((directory) => {
1809
+ const requestedStat = fsImpl.lstatSync(directory);
1810
+ const canonical = realpath(fsImpl, directory);
1811
+ const canonicalStat = fsImpl.lstatSync(canonical);
1812
+ const canonicalParent = realpath(fsImpl, path.dirname(directory));
1813
+ const expectedCanonical = path.join(
1814
+ canonicalParent,
1815
+ path.basename(directory),
1816
+ );
1817
+ if (
1818
+ !requestedStat.isDirectory() ||
1819
+ requestedStat.isSymbolicLink() ||
1820
+ !canonicalStat.isDirectory() ||
1821
+ canonicalStat.isSymbolicLink() ||
1822
+ identity(canonicalStat) !== identity(requestedStat) ||
1823
+ !samePath(canonical, expectedCanonical) ||
1824
+ !samePath(realpath(fsImpl, canonical), canonical)
1825
+ ) {
1826
+ throw migrationRequired("legacy journal archive directory is unsafe");
1827
+ }
1828
+ return canonical;
1829
+ });
1830
+ const [sourceDirectory, archiveDirectory] = canonicalDirectories;
1831
+ if (
1832
+ samePath(sourceDirectory, archiveDirectory) ||
1833
+ isContained(sourceDirectory, archiveDirectory) ||
1834
+ isContained(archiveDirectory, sourceDirectory)
1835
+ ) {
1836
+ throw migrationRequired("legacy journal archive paths are invalid");
1837
+ }
1838
+ const sourcePath = path.join(
1839
+ sourceDirectory,
1840
+ path.basename(requestedSourcePath),
1841
+ );
1842
+ if (input.secure) {
1843
+ ensurePrivateDirectory(archiveDirectory, {
1844
+ applyWindowsAcl: true,
1845
+ failIfUnavailable: true,
1846
+ });
1847
+ }
1848
+ const archivePath = path.join(
1849
+ archiveDirectory,
1850
+ `${disposition.planDigest.slice("sha256:".length)}.json`,
1851
+ );
1852
+ const expectedBytes = serialize(archive);
1853
+ let created = false;
1854
+ if (lstatOrNull(fsImpl, archivePath) === null) {
1855
+ const source = readBoundedSingleLinkFile(
1856
+ fsImpl,
1857
+ sourcePath,
1858
+ MAX_FILE_BYTES,
1859
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1860
+ "legacy release journal source",
1861
+ );
1862
+ if (!source.bytes.equals(serialize(journal))) {
1863
+ throw migrationRequired(
1864
+ "legacy release journal source bytes differ from the verified input",
1865
+ );
1866
+ }
1867
+ const temporaryPath = path.join(
1868
+ archiveDirectory,
1869
+ `.journal-${process.pid}-${crypto.randomBytes(16).toString("hex")}.tmp`,
1870
+ );
1871
+ let descriptor = null;
1872
+ try {
1873
+ descriptor = fsImpl.openSync(temporaryPath, "wx", 0o600);
1874
+ fsImpl.writeFileSync(descriptor, expectedBytes);
1875
+ fsImpl.fsyncSync(descriptor);
1876
+ fsImpl.closeSync(descriptor);
1877
+ descriptor = null;
1878
+ if (input.secure) {
1879
+ ensurePrivateFile(temporaryPath, {
1880
+ applyWindowsAcl: true,
1881
+ failIfUnavailable: true,
1882
+ });
1883
+ }
1884
+ const staged = readBoundedSingleLinkFile(
1885
+ fsImpl,
1886
+ temporaryPath,
1887
+ MAX_FILE_BYTES,
1888
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1889
+ "legacy journal archive temporary",
1890
+ );
1891
+ if (!staged.bytes.equals(expectedBytes)) {
1892
+ throw migrationRequired("legacy journal archive write was incomplete");
1893
+ }
1894
+ checkpoint("after-archive-fsync", {
1895
+ archiveDigest: archive.archiveDigest,
1896
+ dispositionPlanDigest: disposition.planDigest,
1897
+ });
1898
+ try {
1899
+ fsImpl.linkSync(temporaryPath, archivePath);
1900
+ created = true;
1901
+ } catch (error) {
1902
+ if (error?.code !== "EEXIST") throw error;
1903
+ }
1904
+ fsImpl.unlinkSync(temporaryPath);
1905
+ fsyncDirectory(fsImpl, archiveDirectory);
1906
+ checkpoint("after-archive-publish", {
1907
+ archiveDigest: archive.archiveDigest,
1908
+ dispositionPlanDigest: disposition.planDigest,
1909
+ });
1910
+ } finally {
1911
+ if (descriptor !== null) fsImpl.closeSync(descriptor);
1912
+ if (lstatOrNull(fsImpl, temporaryPath) !== null)
1913
+ fsImpl.unlinkSync(temporaryPath);
1914
+ }
1915
+ }
1916
+ const persisted = readBoundedSingleLinkFile(
1917
+ fsImpl,
1918
+ archivePath,
1919
+ MAX_FILE_BYTES,
1920
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1921
+ "legacy journal archive",
1922
+ );
1923
+ if (
1924
+ !persisted.bytes.equals(expectedBytes) ||
1925
+ canonicalJson(
1926
+ verifySkillReleaseJournalArchive(
1927
+ JSON.parse(
1928
+ new TextDecoder("utf-8", { fatal: true }).decode(persisted.bytes),
1929
+ ),
1930
+ ),
1931
+ ) !== canonicalJson(archive)
1932
+ ) {
1933
+ throw migrationRequired("legacy journal archive readback differs");
1934
+ }
1935
+ checkpoint("after-archive-readback", {
1936
+ archiveDigest: archive.archiveDigest,
1937
+ dispositionPlanDigest: disposition.planDigest,
1938
+ });
1939
+ if (lstatOrNull(fsImpl, sourcePath) !== null) {
1940
+ const source = readBoundedSingleLinkFile(
1941
+ fsImpl,
1942
+ sourcePath,
1943
+ MAX_FILE_BYTES,
1944
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1945
+ "legacy release journal source",
1946
+ );
1947
+ if (!source.bytes.equals(serialize(journal))) {
1948
+ throw migrationRequired(
1949
+ "legacy release journal changed before retirement",
1950
+ );
1951
+ }
1952
+ fsImpl.unlinkSync(sourcePath);
1953
+ fsyncDirectory(fsImpl, sourceDirectory);
1954
+ checkpoint("after-source-retire", {
1955
+ archiveDigest: archive.archiveDigest,
1956
+ dispositionPlanDigest: disposition.planDigest,
1957
+ });
1958
+ }
1959
+ return deepFreeze({ archive, archivePath, created, sourceRetired: true });
1960
+ }
1961
+
1962
+ /**
1963
+ * Build the immutable input for a future authenticated ledger migration.
1964
+ * This never writes an active pointer: only a ledger adapter that can retain
1965
+ * this exact plan and return an authenticated current projection may do so.
1966
+ */
1967
+ export function buildSkillReleaseStateMigrationPlan(input) {
1968
+ assertExactKeys(
1969
+ input,
1970
+ STATE_MIGRATION_PLAN_INPUT_KEYS,
1971
+ "release state migration plan input",
1972
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
1973
+ );
1974
+ const legacyState = verifyLegacySkillReleaseState(input.legacyState);
1975
+ const active = verifySkillReleaseMigrationResult(
1976
+ input.activeReleaseMigration,
1977
+ );
1978
+ const lastKnownGood = verifySkillReleaseMigrationResult(
1979
+ input.lastKnownGoodReleaseMigration,
1980
+ );
1981
+ if (
1982
+ active.migration.legacyReleaseDigest !== legacyState.activeReleaseDigest ||
1983
+ lastKnownGood.migration.legacyReleaseDigest !==
1984
+ legacyState.lastKnownGoodReleaseDigest ||
1985
+ active.release.skillName !== legacyState.skillName ||
1986
+ lastKnownGood.release.skillName !== legacyState.skillName ||
1987
+ active.release.tenantId !== lastKnownGood.release.tenantId ||
1988
+ active.release.authorityReceiptDigest !==
1989
+ legacyState.authorityReceiptDigest ||
1990
+ active.release.dependencyLock.lockDigest !==
1991
+ legacyState.dependencyLockDigest
1992
+ ) {
1993
+ throw migrationRequired(
1994
+ "legacy state is not exactly bound to its migrated releases",
1995
+ { legacyStateDigest: legacyState.stateDigest },
1996
+ );
1997
+ }
1998
+ const core = {
1999
+ activeReleaseDigest: active.release.releaseDigest,
2000
+ activeReleaseMigrationDigest: active.migration.migrationDigest,
2001
+ activeReleaseMigrationReceiptDigest: active.receipt.receiptDigest,
2002
+ dependencyLockDigest: active.release.dependencyLockDigest,
2003
+ lastKnownGoodReleaseDigest: lastKnownGood.release.releaseDigest,
2004
+ lastKnownGoodReleaseMigrationDigest:
2005
+ lastKnownGood.migration.migrationDigest,
2006
+ lastKnownGoodReleaseMigrationReceiptDigest:
2007
+ lastKnownGood.receipt.receiptDigest,
2008
+ legacyActiveReleaseDigest: legacyState.activeReleaseDigest,
2009
+ legacyFence: legacyState.fence,
2010
+ legacyLastKnownGoodReleaseDigest: legacyState.lastKnownGoodReleaseDigest,
2011
+ legacyRevision: legacyState.revision,
2012
+ legacyStateDigest: legacyState.stateDigest,
2013
+ legacyTransactionId: legacyState.transactionId,
2014
+ requiresAuthenticatedLedgerMigration: true,
2015
+ schema: SKILL_RELEASE_STATE_MIGRATION_PLAN_SCHEMA,
2016
+ skillName: legacyState.skillName,
2017
+ tenantId: active.release.tenantId,
2018
+ };
2019
+ return deepFreeze({
2020
+ ...core,
2021
+ stateMigrationDigest: domainDigest(STATE_MIGRATION_PLAN_DOMAIN, core),
2022
+ });
2023
+ }
2024
+
2025
+ export function verifySkillReleaseStateMigrationPlan(value) {
2026
+ assertExactKeys(
2027
+ value,
2028
+ STATE_MIGRATION_PLAN_KEYS,
2029
+ "release state migration plan",
2030
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
2031
+ );
2032
+ const core = {
2033
+ activeReleaseDigest: digest(
2034
+ value.activeReleaseDigest,
2035
+ "stateMigration.activeReleaseDigest",
2036
+ ),
2037
+ activeReleaseMigrationDigest: digest(
2038
+ value.activeReleaseMigrationDigest,
2039
+ "stateMigration.activeReleaseMigrationDigest",
2040
+ ),
2041
+ activeReleaseMigrationReceiptDigest: digest(
2042
+ value.activeReleaseMigrationReceiptDigest,
2043
+ "stateMigration.activeReleaseMigrationReceiptDigest",
2044
+ ),
2045
+ dependencyLockDigest: digest(
2046
+ value.dependencyLockDigest,
2047
+ "stateMigration.dependencyLockDigest",
2048
+ ),
2049
+ lastKnownGoodReleaseDigest: digest(
2050
+ value.lastKnownGoodReleaseDigest,
2051
+ "stateMigration.lastKnownGoodReleaseDigest",
2052
+ ),
2053
+ lastKnownGoodReleaseMigrationDigest: digest(
2054
+ value.lastKnownGoodReleaseMigrationDigest,
2055
+ "stateMigration.lastKnownGoodReleaseMigrationDigest",
2056
+ ),
2057
+ lastKnownGoodReleaseMigrationReceiptDigest: digest(
2058
+ value.lastKnownGoodReleaseMigrationReceiptDigest,
2059
+ "stateMigration.lastKnownGoodReleaseMigrationReceiptDigest",
2060
+ ),
2061
+ legacyActiveReleaseDigest: digest(
2062
+ value.legacyActiveReleaseDigest,
2063
+ "stateMigration.legacyActiveReleaseDigest",
2064
+ ),
2065
+ legacyFence: safeInteger(value.legacyFence, "stateMigration.legacyFence", {
2066
+ minimum: 1,
2067
+ }),
2068
+ legacyLastKnownGoodReleaseDigest: digest(
2069
+ value.legacyLastKnownGoodReleaseDigest,
2070
+ "stateMigration.legacyLastKnownGoodReleaseDigest",
2071
+ ),
2072
+ legacyRevision: safeInteger(
2073
+ value.legacyRevision,
2074
+ "stateMigration.legacyRevision",
2075
+ { minimum: 1 },
2076
+ ),
2077
+ legacyStateDigest: digest(
2078
+ value.legacyStateDigest,
2079
+ "stateMigration.legacyStateDigest",
2080
+ ),
2081
+ legacyTransactionId: digest(
2082
+ value.legacyTransactionId,
2083
+ "stateMigration.legacyTransactionId",
2084
+ ),
2085
+ requiresAuthenticatedLedgerMigration:
2086
+ value.requiresAuthenticatedLedgerMigration,
2087
+ schema: value.schema,
2088
+ skillName: skillName(value.skillName),
2089
+ tenantId: tenantId(value.tenantId),
2090
+ };
2091
+ const plan = deepFreeze({
506
2092
  ...core,
507
- releaseDigest: domainDigest(RELEASE_DOMAIN, core),
2093
+ stateMigrationDigest: digest(
2094
+ value.stateMigrationDigest,
2095
+ "stateMigration.stateMigrationDigest",
2096
+ ),
508
2097
  });
2098
+ if (
2099
+ plan.schema !== SKILL_RELEASE_STATE_MIGRATION_PLAN_SCHEMA ||
2100
+ plan.requiresAuthenticatedLedgerMigration !== true ||
2101
+ plan.stateMigrationDigest !==
2102
+ domainDigest(STATE_MIGRATION_PLAN_DOMAIN, core) ||
2103
+ canonicalJson(plan) !== canonicalJson(value)
2104
+ ) {
2105
+ throw migrationRequired("release state migration plan is invalid", {
2106
+ stateMigrationDigest: value.stateMigrationDigest,
2107
+ });
2108
+ }
2109
+ return plan;
509
2110
  }
510
2111
 
511
2112
  export function verifySkillRelease(value) {
@@ -635,7 +2236,7 @@ function initialState(name, ownerTenantId) {
635
2236
  });
636
2237
  }
637
2238
 
638
- function verifyState(value) {
2239
+ export function verifySkillReleaseState(value) {
639
2240
  if (
640
2241
  value &&
641
2242
  typeof value === "object" &&
@@ -1055,6 +2656,8 @@ export class SkillReleaseRegistry {
1055
2656
 
1056
2657
  #ledgerFinalize;
1057
2658
 
2659
+ #ledgerMigrate;
2660
+
1058
2661
  #ledgerQuery;
1059
2662
 
1060
2663
  #pins = new WeakMap();
@@ -1106,6 +2709,10 @@ export class SkillReleaseRegistry {
1106
2709
  this.#crashHook = crashHook;
1107
2710
  this.#ledgerPrepare = transactionLedger.prepare.bind(transactionLedger);
1108
2711
  this.#ledgerFinalize = transactionLedger.finalize.bind(transactionLedger);
2712
+ this.#ledgerMigrate =
2713
+ typeof transactionLedger.migrate === "function"
2714
+ ? transactionLedger.migrate.bind(transactionLedger)
2715
+ : null;
1109
2716
  this.#ledgerQuery = transactionLedger.query.bind(transactionLedger);
1110
2717
  Object.freeze(transactionLedger);
1111
2718
 
@@ -1137,6 +2744,7 @@ export class SkillReleaseRegistry {
1137
2744
  "active",
1138
2745
  "journals",
1139
2746
  "locks",
2747
+ "state-migrations",
1140
2748
  "staging",
1141
2749
  ]) {
1142
2750
  const directory = path.join(this.rootDir, name);
@@ -1530,6 +3138,13 @@ export class SkillReleaseRegistry {
1530
3138
  return this.#path("journals", `${skillName(name)}.json`);
1531
3139
  }
1532
3140
 
3141
+ #stateMigrationJournalPath(transactionIdValue) {
3142
+ return this.#path(
3143
+ "state-migrations",
3144
+ `${digest(transactionIdValue, "stateMigration.transactionId").slice("sha256:".length)}.json`,
3145
+ );
3146
+ }
3147
+
1533
3148
  #leasePath(name) {
1534
3149
  return this.#path("locks", `${skillName(name)}.lock`);
1535
3150
  }
@@ -1758,6 +3373,355 @@ export class SkillReleaseRegistry {
1758
3373
  return deepFreeze({ release, created });
1759
3374
  }
1760
3375
 
3376
+ migrateLegacyRelease(input, migrationAuthority) {
3377
+ if (arguments.length !== 2) {
3378
+ throw migrationRequired(
3379
+ "legacy release migration requires input and a durable audit authority",
3380
+ );
3381
+ }
3382
+ assertExactKeys(
3383
+ migrationAuthority,
3384
+ RELEASE_MIGRATION_AUTHORITY_KEYS,
3385
+ "release migration authority",
3386
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
3387
+ );
3388
+ if (
3389
+ migrationAuthority.schema !== SKILL_RELEASE_MIGRATION_AUTHORITY_SCHEMA ||
3390
+ migrationAuthority.trust !== "trusted" ||
3391
+ typeof migrationAuthority.audit !== "function"
3392
+ ) {
3393
+ throw migrationRequired(
3394
+ "release migration authority is not a trusted synchronous auditor",
3395
+ );
3396
+ }
3397
+ const authorityId = boundedString(
3398
+ migrationAuthority.authorityId,
3399
+ "migrationAuthority.authorityId",
3400
+ 256,
3401
+ );
3402
+ const handlerArtifactDigest = digest(
3403
+ migrationAuthority.handlerArtifactDigest,
3404
+ "migrationAuthority.handlerArtifactDigest",
3405
+ );
3406
+ const audit = migrationAuthority.audit;
3407
+ const release = buildMigratedSkillRelease(input);
3408
+ if (release.tenantId !== this.tenantId) {
3409
+ throw migrationRequired("migrated release belongs to another tenant", {
3410
+ candidateTenantId: release.tenantId,
3411
+ tenantId: this.tenantId,
3412
+ });
3413
+ }
3414
+ const legacy = verifyLegacySkillRelease(input.legacyRelease);
3415
+ const created = this.#publishRelease(release);
3416
+ const migrationCore = deepFreeze({
3417
+ authorityId,
3418
+ candidateMigrationDigest:
3419
+ input.candidateMigration.migration.migrationDigest,
3420
+ candidateMigrationReceiptDigest:
3421
+ input.candidateMigration.receipt.receiptDigest,
3422
+ handlerArtifactDigest,
3423
+ legacyCandidateId: legacy.candidateId,
3424
+ legacyReleaseDigest: legacy.releaseDigest,
3425
+ releaseDigest: release.releaseDigest,
3426
+ schema: SKILL_RELEASE_MIGRATION_RECORD_SCHEMA,
3427
+ skillName: release.skillName,
3428
+ tenantId: this.tenantId,
3429
+ trust: "trusted",
3430
+ });
3431
+ const migration = deepFreeze({
3432
+ ...migrationCore,
3433
+ migrationDigest: domainDigest(RELEASE_MIGRATION_DOMAIN, migrationCore),
3434
+ });
3435
+ let receipt;
3436
+ try {
3437
+ receipt = audit(migration);
3438
+ } catch (cause) {
3439
+ throw failure(
3440
+ "SKILL_RELEASE_MIGRATION_AUDIT_FAILED",
3441
+ "release migration audit failed after inactive release publication",
3442
+ {
3443
+ cause,
3444
+ commitState: "inactive-release-only",
3445
+ migrationDigest: migration.migrationDigest,
3446
+ releaseDigest: release.releaseDigest,
3447
+ },
3448
+ );
3449
+ }
3450
+ if (utilTypes.isPromise(receipt)) {
3451
+ throw failure(
3452
+ "SKILL_RELEASE_MIGRATION_AUDIT_FAILED",
3453
+ "release migration audit authority must be synchronous",
3454
+ {
3455
+ commitState: "inactive-release-only",
3456
+ migrationDigest: migration.migrationDigest,
3457
+ releaseDigest: release.releaseDigest,
3458
+ },
3459
+ );
3460
+ }
3461
+ assertExactKeys(
3462
+ receipt,
3463
+ RELEASE_MIGRATION_RECEIPT_KEYS,
3464
+ "release migration receipt",
3465
+ "SKILL_RELEASE_MIGRATION_AUDIT_FAILED",
3466
+ );
3467
+ const normalizedReceipt = deepFreeze({
3468
+ authenticated: receipt.authenticated,
3469
+ authorityId: boundedString(
3470
+ receipt.authorityId,
3471
+ "migrationReceipt.authorityId",
3472
+ 256,
3473
+ ),
3474
+ durable: receipt.durable,
3475
+ handlerArtifactDigest: digest(
3476
+ receipt.handlerArtifactDigest,
3477
+ "migrationReceipt.handlerArtifactDigest",
3478
+ ),
3479
+ migrationDigest: digest(
3480
+ receipt.migrationDigest,
3481
+ "migrationReceipt.migrationDigest",
3482
+ ),
3483
+ receiptDigest: digest(
3484
+ receipt.receiptDigest,
3485
+ "migrationReceipt.receiptDigest",
3486
+ ),
3487
+ schema: receipt.schema,
3488
+ trust: receipt.trust,
3489
+ });
3490
+ if (
3491
+ normalizedReceipt.schema !== SKILL_RELEASE_MIGRATION_RECEIPT_SCHEMA ||
3492
+ normalizedReceipt.authenticated !== true ||
3493
+ normalizedReceipt.durable !== true ||
3494
+ normalizedReceipt.trust !== "trusted" ||
3495
+ normalizedReceipt.authorityId !== authorityId ||
3496
+ normalizedReceipt.handlerArtifactDigest !== handlerArtifactDigest ||
3497
+ normalizedReceipt.migrationDigest !== migration.migrationDigest
3498
+ ) {
3499
+ throw failure(
3500
+ "SKILL_RELEASE_MIGRATION_AUDIT_FAILED",
3501
+ "release migration receipt is not exactly bound and durable",
3502
+ {
3503
+ commitState: "inactive-release-only",
3504
+ migrationDigest: migration.migrationDigest,
3505
+ releaseDigest: release.releaseDigest,
3506
+ },
3507
+ );
3508
+ }
3509
+ return deepFreeze({
3510
+ created,
3511
+ migration,
3512
+ receipt: normalizedReceipt,
3513
+ release,
3514
+ });
3515
+ }
3516
+
3517
+ migrateLegacyState(planInput, migrationAuthority) {
3518
+ if (arguments.length !== 2 || this.#ledgerMigrate === null) {
3519
+ throw migrationRequired(
3520
+ "legacy state migration requires a plan, durable authority, and authenticated ledger migration port",
3521
+ );
3522
+ }
3523
+ const plan = verifySkillReleaseStateMigrationPlan(planInput);
3524
+ if (plan.tenantId !== this.tenantId) {
3525
+ throw migrationRequired("state migration plan belongs to another tenant");
3526
+ }
3527
+ assertExactKeys(
3528
+ migrationAuthority,
3529
+ STATE_MIGRATION_AUTHORITY_KEYS,
3530
+ "state migration authority",
3531
+ SKILL_RELEASE_MIGRATION_REQUIRED_CODE,
3532
+ );
3533
+ if (
3534
+ migrationAuthority.schema !==
3535
+ SKILL_RELEASE_STATE_MIGRATION_AUTHORITY_SCHEMA ||
3536
+ migrationAuthority.trust !== "trusted" ||
3537
+ typeof migrationAuthority.audit !== "function"
3538
+ ) {
3539
+ throw migrationRequired("state migration authority is not trusted");
3540
+ }
3541
+ const authorityId = boundedString(
3542
+ migrationAuthority.authorityId,
3543
+ "stateMigrationAuthority.authorityId",
3544
+ 256,
3545
+ );
3546
+ const handlerArtifactDigest = digest(
3547
+ migrationAuthority.handlerArtifactDigest,
3548
+ "stateMigrationAuthority.handlerArtifactDigest",
3549
+ );
3550
+ const active = this.readRelease(plan.activeReleaseDigest);
3551
+ const lastKnownGood = this.readRelease(plan.lastKnownGoodReleaseDigest);
3552
+ if (
3553
+ active.skillName !== plan.skillName ||
3554
+ lastKnownGood.skillName !== plan.skillName ||
3555
+ active.tenantId !== this.tenantId ||
3556
+ lastKnownGood.tenantId !== this.tenantId ||
3557
+ active.dependencyLockDigest !== plan.dependencyLockDigest
3558
+ ) {
3559
+ throw migrationRequired(
3560
+ "state migration plan release bindings are unavailable or changed",
3561
+ );
3562
+ }
3563
+ let rawReceipt;
3564
+ try {
3565
+ rawReceipt = migrationAuthority.audit(plan);
3566
+ } catch (cause) {
3567
+ throw failure(
3568
+ "SKILL_RELEASE_STATE_MIGRATION_AUDIT_FAILED",
3569
+ "state migration audit authority failed before ledger commit",
3570
+ { cause, stateMigrationDigest: plan.stateMigrationDigest },
3571
+ );
3572
+ }
3573
+ if (utilTypes.isPromise(rawReceipt)) {
3574
+ throw failure(
3575
+ "SKILL_RELEASE_STATE_MIGRATION_AUDIT_FAILED",
3576
+ "state migration audit authority must be synchronous",
3577
+ { stateMigrationDigest: plan.stateMigrationDigest },
3578
+ );
3579
+ }
3580
+ assertExactKeys(
3581
+ rawReceipt,
3582
+ STATE_MIGRATION_RECEIPT_KEYS,
3583
+ "state migration receipt",
3584
+ "SKILL_RELEASE_STATE_MIGRATION_AUDIT_FAILED",
3585
+ );
3586
+ const receipt = deepFreeze({
3587
+ authenticated: rawReceipt.authenticated,
3588
+ authorityId: boundedString(
3589
+ rawReceipt.authorityId,
3590
+ "stateMigrationReceipt.authorityId",
3591
+ 256,
3592
+ ),
3593
+ durable: rawReceipt.durable,
3594
+ handlerArtifactDigest: digest(
3595
+ rawReceipt.handlerArtifactDigest,
3596
+ "stateMigrationReceipt.handlerArtifactDigest",
3597
+ ),
3598
+ receiptDigest: digest(
3599
+ rawReceipt.receiptDigest,
3600
+ "stateMigrationReceipt.receiptDigest",
3601
+ ),
3602
+ schema: rawReceipt.schema,
3603
+ stateMigrationDigest: digest(
3604
+ rawReceipt.stateMigrationDigest,
3605
+ "stateMigrationReceipt.stateMigrationDigest",
3606
+ ),
3607
+ trust: rawReceipt.trust,
3608
+ });
3609
+ if (
3610
+ receipt.schema !== SKILL_RELEASE_STATE_MIGRATION_RECEIPT_SCHEMA ||
3611
+ receipt.authenticated !== true ||
3612
+ receipt.durable !== true ||
3613
+ receipt.trust !== "trusted" ||
3614
+ receipt.authorityId !== authorityId ||
3615
+ receipt.handlerArtifactDigest !== handlerArtifactDigest ||
3616
+ receipt.stateMigrationDigest !== plan.stateMigrationDigest
3617
+ ) {
3618
+ throw failure(
3619
+ "SKILL_RELEASE_STATE_MIGRATION_AUDIT_FAILED",
3620
+ "state migration receipt is not exactly bound and durable",
3621
+ { stateMigrationDigest: plan.stateMigrationDigest },
3622
+ );
3623
+ }
3624
+ const existing = this.readState(plan.skillName);
3625
+ if (existing.revision > 0) {
3626
+ if (
3627
+ existing.transactionId !== plan.stateMigrationDigest ||
3628
+ existing.activeReleaseDigest !== plan.activeReleaseDigest ||
3629
+ existing.lastKnownGoodReleaseDigest !==
3630
+ plan.lastKnownGoodReleaseDigest ||
3631
+ existing.dependencyLockDigest !== plan.dependencyLockDigest ||
3632
+ existing.revision !== plan.legacyRevision ||
3633
+ existing.fence !== plan.legacyFence
3634
+ ) {
3635
+ throw failure(
3636
+ "SKILL_RELEASE_STATE_MIGRATION_CONFLICT",
3637
+ "active state is not the exact result of this migration plan",
3638
+ );
3639
+ }
3640
+ return completedStateMigration({
3641
+ created: false,
3642
+ plan,
3643
+ receipt,
3644
+ state: existing,
3645
+ });
3646
+ }
3647
+ const acquired = this.#acquireLease(
3648
+ plan.skillName,
3649
+ plan.stateMigrationDigest,
3650
+ 0,
3651
+ EMPTY_SKILL_ACTIVE_DIGEST,
3652
+ );
3653
+ const { heartbeat, lease } = acquired;
3654
+ try {
3655
+ const state = buildState({
3656
+ activeReleaseDigest: plan.activeReleaseDigest,
3657
+ authorityReceiptDigest: active.authorityReceiptDigest,
3658
+ dependencyLockDigest: plan.dependencyLockDigest,
3659
+ fence: plan.legacyFence,
3660
+ lastKnownGoodReleaseDigest: plan.lastKnownGoodReleaseDigest,
3661
+ revision: plan.legacyRevision,
3662
+ skillName: plan.skillName,
3663
+ tenantId: this.tenantId,
3664
+ transactionId: plan.stateMigrationDigest,
3665
+ });
3666
+ const request = deepFreeze({
3667
+ plan,
3668
+ receipt,
3669
+ schema: SKILL_RELEASE_STATE_LEDGER_MIGRATION_SCHEMA,
3670
+ state,
3671
+ });
3672
+ const journal = this.#persistStateMigrationJournal({
3673
+ leaseToken: lease.token,
3674
+ plan,
3675
+ receipt,
3676
+ state,
3677
+ });
3678
+ let projection;
3679
+ try {
3680
+ projection = this.#ledgerMigrate(request);
3681
+ } catch (cause) {
3682
+ throw failure(
3683
+ "SKILL_RELEASE_STATE_MIGRATION_LEDGER_FAILED",
3684
+ "state migration ledger commit failed closed",
3685
+ { cause, stateMigrationDigest: plan.stateMigrationDigest },
3686
+ );
3687
+ }
3688
+ if (utilTypes.isPromise(projection)) {
3689
+ throw failure(
3690
+ "SKILL_RELEASE_LEDGER_INVALID",
3691
+ "state migration ledger port must be synchronous",
3692
+ );
3693
+ }
3694
+ const committed = verifyLedgerProjection(projection, {
3695
+ authorityReceiptDigest: state.authorityReceiptDigest,
3696
+ intentDigest: plan.stateMigrationDigest,
3697
+ pointerDigest: state.stateDigest,
3698
+ prepareReceiptDigest: receipt.receiptDigest,
3699
+ revision: state.revision,
3700
+ skillName: state.skillName,
3701
+ stateDigest: state.stateDigest,
3702
+ transactionId: state.transactionId,
3703
+ });
3704
+ if (committed.status !== "committed" || committed.current !== true) {
3705
+ throw failure(
3706
+ "SKILL_RELEASE_LEDGER_INVALID",
3707
+ "state migration did not produce a current committed projection",
3708
+ );
3709
+ }
3710
+ if (
3711
+ this.#recoverStateMigration(journal, { force: true }) !== "committed"
3712
+ ) {
3713
+ throw failure(
3714
+ "SKILL_RELEASE_STATE_MIGRATION_RECOVERY_FAILED",
3715
+ "state migration did not converge after ledger commit",
3716
+ );
3717
+ }
3718
+ return completedStateMigration({ created: true, plan, receipt, state });
3719
+ } finally {
3720
+ clearInterval(heartbeat);
3721
+ this.#releaseLease(lease);
3722
+ }
3723
+ }
3724
+
1761
3725
  readRelease(releaseDigest) {
1762
3726
  const expected = digest(releaseDigest, "releaseDigest");
1763
3727
  let value;
@@ -1796,7 +3760,7 @@ export class SkillReleaseRegistry {
1796
3760
  #readStateRaw(name) {
1797
3761
  const normalizedName = skillName(name);
1798
3762
  try {
1799
- const state = verifyState(
3763
+ const state = verifySkillReleaseState(
1800
3764
  this.#readJson(
1801
3765
  this.#statePath(normalizedName),
1802
3766
  "SKILL_RELEASE_STATE_CORRUPT",
@@ -2392,6 +4356,133 @@ export class SkillReleaseRegistry {
2392
4356
  return deepFreeze({ ...value });
2393
4357
  }
2394
4358
 
4359
+ #stateMigrationJournal(value) {
4360
+ const core = { ...value };
4361
+ delete core.journalDigest;
4362
+ return deepFreeze({
4363
+ ...core,
4364
+ journalDigest: domainDigest(STATE_MIGRATION_JOURNAL_DOMAIN, core),
4365
+ });
4366
+ }
4367
+
4368
+ #verifyStateMigrationJournal(value) {
4369
+ assertExactKeys(
4370
+ value,
4371
+ STATE_MIGRATION_JOURNAL_KEYS,
4372
+ "state migration journal",
4373
+ "SKILL_RELEASE_STATE_MIGRATION_JOURNAL_CORRUPT",
4374
+ );
4375
+ const core = { ...value };
4376
+ delete core.journalDigest;
4377
+ if (
4378
+ core.schema !== SKILL_RELEASE_STATE_MIGRATION_JOURNAL_SCHEMA ||
4379
+ tenantId(core.tenantId, "stateMigrationJournal.tenantId") !==
4380
+ this.tenantId ||
4381
+ !TOKEN_PATTERN.test(core.leaseToken) ||
4382
+ domainDigest(STATE_MIGRATION_JOURNAL_DOMAIN, core) !== value.journalDigest
4383
+ ) {
4384
+ throw failure(
4385
+ "SKILL_RELEASE_STATE_MIGRATION_JOURNAL_CORRUPT",
4386
+ "state migration journal digest is invalid",
4387
+ );
4388
+ }
4389
+ const plan = verifySkillReleaseStateMigrationPlan(core.plan);
4390
+ const state = verifySkillReleaseState(core.state);
4391
+ assertExactKeys(
4392
+ core.receipt,
4393
+ STATE_MIGRATION_RECEIPT_KEYS,
4394
+ "state migration journal receipt",
4395
+ "SKILL_RELEASE_STATE_MIGRATION_JOURNAL_CORRUPT",
4396
+ );
4397
+ const receipt = deepFreeze({
4398
+ authenticated: core.receipt.authenticated,
4399
+ authorityId: boundedString(
4400
+ core.receipt.authorityId,
4401
+ "stateMigrationJournal.receipt.authorityId",
4402
+ 256,
4403
+ ),
4404
+ durable: core.receipt.durable,
4405
+ handlerArtifactDigest: digest(
4406
+ core.receipt.handlerArtifactDigest,
4407
+ "stateMigrationJournal.receipt.handlerArtifactDigest",
4408
+ ),
4409
+ receiptDigest: digest(
4410
+ core.receipt.receiptDigest,
4411
+ "stateMigrationJournal.receipt.receiptDigest",
4412
+ ),
4413
+ schema: core.receipt.schema,
4414
+ stateMigrationDigest: digest(
4415
+ core.receipt.stateMigrationDigest,
4416
+ "stateMigrationJournal.receipt.stateMigrationDigest",
4417
+ ),
4418
+ trust: core.receipt.trust,
4419
+ });
4420
+ if (
4421
+ receipt.schema !== SKILL_RELEASE_STATE_MIGRATION_RECEIPT_SCHEMA ||
4422
+ receipt.authenticated !== true ||
4423
+ receipt.durable !== true ||
4424
+ receipt.trust !== "trusted" ||
4425
+ receipt.stateMigrationDigest !== plan.stateMigrationDigest ||
4426
+ core.transactionId !== plan.stateMigrationDigest ||
4427
+ core.skillName !== plan.skillName ||
4428
+ state.transactionId !== plan.stateMigrationDigest ||
4429
+ state.tenantId !== this.tenantId ||
4430
+ state.skillName !== plan.skillName ||
4431
+ state.activeReleaseDigest !== plan.activeReleaseDigest ||
4432
+ state.lastKnownGoodReleaseDigest !== plan.lastKnownGoodReleaseDigest ||
4433
+ state.dependencyLockDigest !== plan.dependencyLockDigest ||
4434
+ state.revision !== plan.legacyRevision ||
4435
+ state.fence !== plan.legacyFence
4436
+ ) {
4437
+ throw failure(
4438
+ "SKILL_RELEASE_STATE_MIGRATION_JOURNAL_CORRUPT",
4439
+ "state migration journal bindings are invalid",
4440
+ );
4441
+ }
4442
+ const journal = deepFreeze({
4443
+ ...core,
4444
+ journalDigest: value.journalDigest,
4445
+ plan,
4446
+ receipt,
4447
+ state,
4448
+ });
4449
+ if (canonicalJson(journal) !== canonicalJson(value)) {
4450
+ throw failure(
4451
+ "SKILL_RELEASE_STATE_MIGRATION_JOURNAL_CORRUPT",
4452
+ "state migration journal is not canonical",
4453
+ );
4454
+ }
4455
+ return journal;
4456
+ }
4457
+
4458
+ #persistStateMigrationJournal({ leaseToken, plan, receipt, state }) {
4459
+ const journal = this.#stateMigrationJournal({
4460
+ leaseToken,
4461
+ plan,
4462
+ receipt,
4463
+ schema: SKILL_RELEASE_STATE_MIGRATION_JOURNAL_SCHEMA,
4464
+ skillName: plan.skillName,
4465
+ state,
4466
+ tenantId: this.tenantId,
4467
+ transactionId: plan.stateMigrationDigest,
4468
+ });
4469
+ this.#atomicWrite(
4470
+ "state-migrations",
4471
+ this.#stateMigrationJournalPath(plan.stateMigrationDigest),
4472
+ journal,
4473
+ );
4474
+ return journal;
4475
+ }
4476
+
4477
+ #readStateMigrationJournal(transactionIdValue) {
4478
+ return this.#verifyStateMigrationJournal(
4479
+ this.#readJson(
4480
+ this.#stateMigrationJournalPath(transactionIdValue),
4481
+ "SKILL_RELEASE_STATE_MIGRATION_JOURNAL_CORRUPT",
4482
+ ),
4483
+ );
4484
+ }
4485
+
2395
4486
  #journal(value) {
2396
4487
  const core = { ...value };
2397
4488
  delete core.journalDigest;
@@ -2459,9 +4550,11 @@ export class SkillReleaseRegistry {
2459
4550
  );
2460
4551
  }
2461
4552
  const intent = this.#verifyIntent(core.intent);
2462
- const nextState = verifyState(core.nextState);
4553
+ const nextState = verifySkillReleaseState(core.nextState);
2463
4554
  const previousState =
2464
- core.previousState === null ? null : verifyState(core.previousState);
4555
+ core.previousState === null
4556
+ ? null
4557
+ : verifySkillReleaseState(core.previousState);
2465
4558
  if (
2466
4559
  intent.transactionId !== core.transactionId ||
2467
4560
  nextState.transactionId !== core.transactionId ||
@@ -2600,7 +4693,7 @@ export class SkillReleaseRegistry {
2600
4693
  );
2601
4694
  let verifiedStaged;
2602
4695
  try {
2603
- verifiedStaged = verifyState(
4696
+ verifiedStaged = verifySkillReleaseState(
2604
4697
  this.#parseCanonical(stagedBytes, "SKILL_RELEASE_STATE_CORRUPT"),
2605
4698
  );
2606
4699
  } catch (cause) {
@@ -2654,7 +4747,7 @@ export class SkillReleaseRegistry {
2654
4747
  );
2655
4748
  let verifiedPublished;
2656
4749
  try {
2657
- verifiedPublished = verifyState(
4750
+ verifiedPublished = verifySkillReleaseState(
2658
4751
  this.#parseCanonical(publishedBytes, "SKILL_RELEASE_STATE_CORRUPT"),
2659
4752
  );
2660
4753
  } catch (cause) {
@@ -2686,7 +4779,7 @@ export class SkillReleaseRegistry {
2686
4779
  this.#statePath(state.skillName),
2687
4780
  "SKILL_RELEASE_STATE_CORRUPT",
2688
4781
  );
2689
- const verifiedState = verifyState(
4782
+ const verifiedState = verifySkillReleaseState(
2690
4783
  this.#parseCanonical(stateBytes, "SKILL_RELEASE_STATE_CORRUPT"),
2691
4784
  );
2692
4785
  if (
@@ -2869,8 +4962,95 @@ export class SkillReleaseRegistry {
2869
4962
  this.#recoverJournal(journal);
2870
4963
  }
2871
4964
 
4965
+ #cleanupStateMigration(journal) {
4966
+ const lockPath = this.#leasePath(journal.skillName);
4967
+ try {
4968
+ this.#removeLock(lockPath, journal.leaseToken);
4969
+ } catch (error) {
4970
+ if (error?.code !== "ENOENT") throw error;
4971
+ }
4972
+ this.#unlink(
4973
+ this.#stateMigrationJournalPath(journal.transactionId),
4974
+ this.#directories["state-migrations"].path,
4975
+ );
4976
+ }
4977
+
4978
+ #recoverStateMigration(journal, { force = false } = {}) {
4979
+ const lockPath = this.#leasePath(journal.skillName);
4980
+ if (this.#exists(lockPath)) {
4981
+ const owner = this.#readOwner(lockPath);
4982
+ if (
4983
+ owner.token !== journal.leaseToken ||
4984
+ owner.transactionId !== journal.transactionId
4985
+ ) {
4986
+ throw failure(
4987
+ "SKILL_RELEASE_RECOVERY_CONFLICT",
4988
+ "state migration journal and lease owner differ",
4989
+ );
4990
+ }
4991
+ if (!force && !this.#ownerStale(owner)) return "pending";
4992
+ }
4993
+ const expected = {
4994
+ authorityReceiptDigest: journal.state.authorityReceiptDigest,
4995
+ intentDigest: journal.plan.stateMigrationDigest,
4996
+ pointerDigest: journal.state.stateDigest,
4997
+ prepareReceiptDigest: journal.receipt.receiptDigest,
4998
+ revision: journal.state.revision,
4999
+ skillName: journal.state.skillName,
5000
+ stateDigest: journal.state.stateDigest,
5001
+ };
5002
+ const projection = this.#query(journal.transactionId, expected);
5003
+ if (projection.status === "absent") {
5004
+ this.#cleanupStateMigration(journal);
5005
+ return "aborted";
5006
+ }
5007
+ if (projection.status !== "committed" || projection.current !== true) {
5008
+ throw failure(
5009
+ "SKILL_RELEASE_RECOVERY_CONFLICT",
5010
+ "state migration ledger projection is not current and committed",
5011
+ );
5012
+ }
5013
+ const current = this.#readStateRaw(journal.skillName);
5014
+ if (current.stateDigest !== journal.state.stateDigest) {
5015
+ if (current.revision !== 0) {
5016
+ throw failure(
5017
+ "SKILL_RELEASE_RECOVERY_CONFLICT",
5018
+ "state migration recovery found an unrelated active pointer",
5019
+ );
5020
+ }
5021
+ this.#atomicWrite(
5022
+ "active",
5023
+ this.#statePath(journal.skillName),
5024
+ journal.state,
5025
+ );
5026
+ }
5027
+ const release = this.readRelease(journal.state.activeReleaseDigest);
5028
+ this.#assertFinalizationArtifacts(journal.state, release);
5029
+ this.#cleanupStateMigration(journal);
5030
+ return "committed";
5031
+ }
5032
+
2872
5033
  #recoverAll() {
2873
5034
  this.#withRecoveryLock(() => {
5035
+ for (const name of this.#fs.readdirSync(
5036
+ this.#directories["state-migrations"].path,
5037
+ )) {
5038
+ if (!/^[a-f0-9]{64}\.json$/u.test(name)) {
5039
+ throw failure(
5040
+ "SKILL_RELEASE_STATE_MIGRATION_JOURNAL_CORRUPT",
5041
+ "unexpected state migration journal entry",
5042
+ );
5043
+ }
5044
+ const transactionId = `sha256:${name.slice(0, -5)}`;
5045
+ const journal = this.#readStateMigrationJournal(transactionId);
5046
+ if (journal.transactionId !== transactionId) {
5047
+ throw failure(
5048
+ "SKILL_RELEASE_STATE_MIGRATION_JOURNAL_CORRUPT",
5049
+ "state migration journal path differs from its transaction",
5050
+ );
5051
+ }
5052
+ this.#recoverStateMigration(journal);
5053
+ }
2874
5054
  const names = this.#fs.readdirSync(this.#directories.journals.path);
2875
5055
  for (const name of names) {
2876
5056
  if (!name.endsWith(".json")) {