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.
- package/README.md +14 -7
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-CVW2n2rh.js → AIOps-CQSBl3hY.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DtuM163O.js → ActionButton-IQt-vRLt.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BXvl_qHu.js → Analytics-DkHC8O7k.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-VDBTFKOp.js → AppLayout-CSti2P2T.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BNz2xCiG.js → Artifacts-C57hxn3v.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DTu9Hn9P.js → Audit-DByLntcf.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D2OCPzLe.js → BackgroundAgents-D1Bd2EOa.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C9M-vG7G.js → Backup-VstHzHng.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BUkCNiUQ.js → BaseInput-CLhdexIf.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-OJ2ajF7l.js → Chat-BefC_JZS.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BdZt76Ab.js → ChatBubbleRenderer-DbXMvgmn.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-C1gXKDsc.js → Checkbox-CEY982vx.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BXbznLg7.js → Codegen-Dcm_DTvJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CbCq5N0i.js → Col-C8SSu4KA.js} +1 -1
- package/src/assets/web-panel/assets/{Community-V8Ks6Yun.js → Community-BVV9vwRR.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DkeHQTpQ.js → Compact-B7c5Vo18.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-JeLKwn4Q.js → Compliance-Tq9jSZ5a.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-azfetcBz.js → Cowork-BJUjT09Q.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Q691i_Gp.js → Cron-CjV9PEaS.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BLjDRbr_.js → Crosschain-Dl7ReL8F.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D8ptXplK.js → DID-CG8PjWmM.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Ctj2VVZL.js → Dashboard-CDBM6wmF.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D8VgkMJ8.js → Dropdown-8_55bXTD.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CQPe5S6q.js → EmailListRenderer-D3qOU1Cm.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-ySA-7SiN.js → FamilyGuardDashboard-Bv5oWMZF.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-1PMD-fBf.js → Federation-hpZ1xkKd.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C_jr6U4Q.js → FormItemContext-BN7GT191.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Bu3bKf2B.js → GenericCardRenderer-PCRhfA7M.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Dv4Fa_T5.js → Git-CIlP2vy7.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DOEy1n1k.js → Governance-81lgm7YL.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DkvqzWcJ.js → Inference-Cs24xMOV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DdUJK949.js → KnowledgeGraph-CrJLVCZo.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CRelcdUZ.js → Logs-DLyO8RKT.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-0AHbbTA4.js → MarkdownRenderer-DBOqijrP.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BuuBKsev.js → Marketplace-Dz7AbQZc.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DlgpEmA1.js → McpTools-BM0aFiWv.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-DTfdqUGy.js → Memory-C9SJzENX.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CXLE5ZFO.js → MobileBridge-BO2HnNRk.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dz25OB0n.js → MobileProjects-CK_jMz0l.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-kEL0fhgD.js → Mtc-CfhrJsdO.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BE4UvFJU.js → MtcAudit-BE2ELxgI.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-CSApSn8c.js → Multisig-CgR_E3Jd.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D11FpKWx.js → NLProgramming-Cw9445_K.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CDL-D2CS.js → Notes-DZimkTrb.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C0Q6lO-c.js → NotificationSettings-DxHVh4uT.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Iy-pnX56.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Dc-maVAq.js → Organization-pPXJtWo4.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B5mUpSvi.js → Overflow-YamdTJwA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-TjXV53wq.js → P2P-BvqP53x7.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-Blz1Xn2n.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DKRzQ2O6.js → Permissions-CgFRQqFJ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dx2eqft8.js → PersonalDataHub-CFeo3-6O.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CnYZMB5p.js → Pipeline-BkaRojEK.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BonKpPnp.js → Privacy-BNZEx2Qp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DWO0Du3J.js → ProjectInit-HUBOgEEK.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C9jqyYRv.js → ProjectSettings-CFMmhROS.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CYpKwXbz.js → Projects-Dc517BXp.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CW-fkwJq.js → Providers-DTLf8bgj.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Cw0J3Zud.js → QrScannerModal-BBNjPszk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BH4A4IpR.js → QuickAsk-DCsHvk3G.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CDwMwJus.js → Recommend-Dpr5YSop.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DKIPwzTf.js → RemoteSession-DmKnvqGg.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CklDj7qS.js → Reputation-CSRfuurX.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DJWZKsH0.js → Row-CDj3pQ_w.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BfSLCewo.js → RssFeed-DI0a_kNq.js} +2 -2
- package/src/assets/web-panel/assets/{Search-B0IST--x.js → Search-jAy7B2nU.js} +1 -1
- package/src/assets/web-panel/assets/{Security-h1sYNWtt.js → Security-CkHTqYhc.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Gh0-oxI_.js → Services-CP-0Nzga.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CNDV_jOs.js → Skeleton-BkF5-p3n.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-ByCLTh1n.js → Skills-BDu-utDE.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BfyrJTJz.js → Sla-CQvq_2Qb.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BQzo9kNX.js → SpeechSettings-T7Ap512g.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B6fS6z_1.js → SyncSettings-Bt-PLvot.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C-uKCP-l.js → Tasks-yLEcCogg.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D6oDy7wL.js → Templates-ByN3fmbr.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-D3LT2wNQ.js → Tenant-DBI7hITH.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BteLcv_O.js → Terminal-B77_DlGX.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DHHuxfqH.js → TimelineRenderer-CsLTB_QO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BRQeykXw.js → Tokens-BV2N6cR8.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-p2XaFWEu.js → Trigger-CjXWGH3p.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BxadBMft.js → Trust-uqXIq3WC.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DRBP7LDk.js → UkeySign-D-6qu4vY.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CZ3NZkh7.js → VideoEditing-CxGmx_2e.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C3NTBN7G.js → Wallet-DSq7WlAg.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C7LVgMlb.js → WebAuthn-CDBiD57O.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-0_Iveog7.js → WorkflowEditor-Z8f63TSb.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DzzluYo3.js → chat-B-UZFBjy.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DSMYXpaj.js → colors-CFk7K-7Q.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-W76u1VSV.js → compact-item-X0IVMvgi.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-otsESjb5.js → createContext-DWg4j2n6.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-B2IW1J_J.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-3sNLrvug.js → hasIn-CkVCwNNF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdtmEKat.js → index-5OcZHdzc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DS9WYDMf.js → index-B273IY-E.js} +1 -1
- package/src/assets/web-panel/assets/{index-NaO9AE-F.js → index-B3-B3FWD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGJtmHM.js → index-BB4KHHcv.js} +1 -1
- package/src/assets/web-panel/assets/index-BCp2ir-1.js +1 -0
- package/src/assets/web-panel/assets/{index-pVm_iUre.js → index-BG-GaZri.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpcQYzP7.js → index-BPBia4b-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXLx9HfA.js → index-BipwEbTA.js} +1 -1
- package/src/assets/web-panel/assets/{index-uSCkbpZH.js → index-BoskgPt9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMmdIpLA.js → index-Btqk6zWb.js} +1 -1
- package/src/assets/web-panel/assets/index-Bw4iuzRD.js +1 -0
- package/src/assets/web-panel/assets/{index-DLDh1OW3.js → index-BzXjYUmd.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9X_jqXe.js → index-C1ZAOJRW.js} +1 -1
- package/src/assets/web-panel/assets/{index-vBKPcmAP.js → index-CCLwoATF.js} +1 -1
- package/src/assets/web-panel/assets/{index-vUmYkqmu.js → index-CDfSD07S.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7A5U2IH.js → index-CLlP1E4X.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClOFeZ-t.js → index-CMCEXBRh.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cg86EzNp.js → index-CN8HZlLH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ddd4mird.js → index-CNKFsBlz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE7tSow-.js → index-CUBkvFPH.js} +1 -1
- package/src/assets/web-panel/assets/{index-GlIOE4ja.js → index-Ca0mij1n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdU7IkKX.js → index-CbIAWbma.js} +1 -1
- package/src/assets/web-panel/assets/{index-UUHCwfNq.js → index-CbihMThv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRsD1PT2.js → index-CvOa-IFV.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2aht9-M.js → index-Cvwu_rA_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJsQio5O.js → index-D-EJNxgR.js} +1 -1
- package/src/assets/web-panel/assets/{index-q7ftLqNt.js → index-D8uEL8Uw.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1I1AYJs.js → index-D92g4l3n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZKAzEge.js → index-DCqpl_E_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKoFyHYQ.js → index-DLSBks8h.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDlDO2px.js → index-Db_5xhrU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CENWwjef.js → index-DcDggpWR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DH4cl52p.js → index-DeWKPQGs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTA8Iavv.js → index-DhVHoqGL.js} +3 -3
- package/src/assets/web-panel/assets/{index-DmpaC2yk.js → index-DyWlOuak.js} +1 -1
- package/src/assets/web-panel/assets/{index-BA8mZxd-.js → index-LU54bChK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtFOt5x1.js → index-OjnAwUJo.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5LOYdYm.js → index-P83LDnCz.js} +1 -1
- package/src/assets/web-panel/assets/{index-MhQQ3h8q.js → index-_-9k3Tna.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DF6llUXV.js → initDefaultProps-CFNgY8lJ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-vtDV8_5K.js → motion-D-5RrDzn.js} +1 -1
- package/src/assets/web-panel/assets/{move-B6c0bdy2.js → move-DN-L0Bd-.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DlLYWuBK.js → mtc-parser-6mgLSeCK.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BH0zOxLN.js → omit-ClWW8eTP.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D9di8_In.js → pickAttrs-Vystq3R8.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BDSngufM.js → placementArrow-D0H9hK4W.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C_EawMvP.js → responsiveObserve-B3iNRU-a.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B_4wShxA.js → slide-CiMXe6am.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CbQu6qJ-.js → statusUtils-CGDZJ1uT.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CvXJWYXb.js → styleChecker-D2zA6NkY.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-jjY1XknO.js → useFlexGapSupport-C1sYU6QE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CMXBdsK_.js → useFs-BzUHSjg2.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-YbAhhPXx.js → usePersonalDataHub-CxB5yZCq.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BFCTKc0H.js → vnode-BfUfMwtc.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CY9aBl5O.js → zoom-CFYmj1TO.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agent.js +80 -2
- package/src/commands/evolution.js +33 -27
- package/src/commands/serve.js +11 -2
- package/src/data/changelog.json +5 -5
- package/src/gateways/ws/session-protocol.js +1 -0
- package/src/gateways/ws/ws-agent-handler.js +70 -2
- package/src/gateways/ws/ws-server.js +8 -0
- package/src/lib/app-server/cli-agent-kernel-adapter.js +4 -0
- package/src/lib/app-server/graph-runtime.js +115 -8
- package/src/lib/app-server/server.js +21 -2
- package/src/lib/app-server/websocket-transport.js +2 -0
- package/src/lib/evolution/agent-evolution-ingress.js +300 -0
- package/src/lib/evolution/agent-evolution-runtime-composition-brand.js +22 -0
- package/src/lib/evolution/agent-evolution-runtime-composition.js +369 -0
- package/src/lib/evolution/evidence-backed-wiki-maintainer.js +752 -0
- package/src/lib/evolution/evolution-artifact-ports.js +48 -0
- package/src/lib/evolution/evolution-eval-gate.js +11 -4
- package/src/lib/evolution/evolution-evidence-artifact-adapter.js +499 -0
- package/src/lib/evolution/evolution-evidence-projector.js +61 -1
- package/src/lib/evolution/evolution-file-witness.js +601 -0
- package/src/lib/evolution/evolution-ledger-file-backend.js +176 -0
- package/src/lib/evolution/evolution-ledger-ports.js +353 -16
- package/src/lib/evolution/evolution-ledger.js +1355 -54
- package/src/lib/evolution/evolution-run-ledger-adapter.js +409 -0
- package/src/lib/evolution/evolution-run-wiki-maintenance-source.js +300 -0
- package/src/lib/evolution/scheduled-batch-wiki-maintenance-source.js +368 -0
- package/src/lib/evolution/skill-candidate-registry.js +649 -0
- package/src/lib/evolution/skill-promotion-controller.js +109 -38
- package/src/lib/evolution/skill-promotion-review-ledger-adapter.js +451 -0
- package/src/lib/evolution/skill-promotion-review.js +793 -0
- package/src/lib/evolution/skill-registry-transition-capability.js +55 -0
- package/src/lib/evolution/skill-registry-transition-ledger-adapter.js +1280 -0
- package/src/lib/evolution/skill-registry-transition-source.js +372 -0
- package/src/lib/evolution/skill-release-registry.js +2191 -11
- package/src/lib/evolution/skill-target-matrix-eval.js +133 -10
- package/src/lib/evolution/structured-memory-agent-control-plane.js +222 -0
- package/src/lib/evolution/structured-memory-authority-ledger-adapter.js +351 -0
- package/src/lib/evolution/structured-memory-ledger-adapter.js +220 -0
- package/src/lib/evolution/structured-memory-policy-receipt-writer.js +164 -0
- package/src/lib/evolution/structured-memory-post-compact-hook.js +87 -0
- package/src/lib/evolution/structured-memory-promotion-receipt-writer.js +221 -0
- package/src/lib/evolution/structured-memory-semantic-review-pipeline.js +313 -0
- package/src/lib/evolution/wiki-maintainer-ledger-adapter.js +200 -0
- package/src/lib/evolution/wiki-maintenance-trigger-ledger-adapter.js +687 -0
- package/src/lib/evolution-system.js +65 -30
- package/src/lib/mcp-lifecycle-authority.js +21 -2
- package/src/lib/remote-approval-bridge.js +24 -0
- package/src/lib/scheduler-kernel/store.js +62 -14
- package/src/repl/agent-repl.js +45 -0
- package/src/runtime/agent-runtime.js +114 -1
- package/src/runtime/headless-runner.js +19 -0
- package/src/runtime/headless-stream.js +43 -0
- package/src/runtime/runtime-factory.js +19 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-ZRE0OKbj.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-DvV2btIr.js +0 -7
- package/src/assets/web-panel/assets/devWarning-BztaXF84.js +0 -1
- package/src/assets/web-panel/assets/index-4jP_hG_4.js +0 -1
- package/src/assets/web-panel/assets/index-zxwuAXL5.js +0 -1
- 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 {
|
|
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-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
4553
|
+
const nextState = verifySkillReleaseState(core.nextState);
|
|
2463
4554
|
const previousState =
|
|
2464
|
-
core.previousState === null
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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")) {
|