chainlesschain 0.163.6 → 0.163.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-axqQfwv8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-DV-Ro0By.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-Bt72WEVu.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-BtepG55E.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-B7KaJ7S_.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-FNFaUg7A.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-CC7YBhyj.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-RkgTNVtj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-hHEmGZy-.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-z5wXOQP1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-DVaDJnxy.js} +1 -1
- package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-CJmaRDR5.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-DF29_2bI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-DtkQkBzn.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-xoHDxp7I.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-BUm-R7ht.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-Cgl14ZkB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-BNcbmoqh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-Bwja0Zo9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-Bk5XjEdJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-Ci36Oitq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer--U04ZN4q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-C4KghYn7.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-QL7x_Q_0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-2HassK5k.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-DoBIp7oe.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference--W01QhZ-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-BBJ4yAel.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-aMtgN-R6.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-D5lNngSI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CkEVOMKH.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-B1Ow1xVE.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-SgYbUfb6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-BWdOvQAj.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-DcTXSSma.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-Dt3WG4oj.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-B9B38UZx.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-B0S4GGvG.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-DZ5U58Ng.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-PjL5ZW34.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-Cv1GYs-c.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-Bbkp_PL3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-BD8QUgoZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DKTf5xIP.js → PdhVaultBrowser-BO-2PeaB.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-D_OPVa02.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-mh-pEmaB.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-6JC2EDdb.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-CUshzUAG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-Cs2Aalri.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-1ak49tcj.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BV4oApsm.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-CXXxvafH.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-PTcDsF58.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-CDwucrJw.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-CJlLVRXR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-DlN-ETsO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-DI23LR6m.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-Cl7UQMrM.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-CexHNfh8.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-BneBWvRN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-DX7Phgrp.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-BWXO8Mte.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DCyOjKPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-CXqERsTB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-bhbrbz8E.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-3IPU0HBZ.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-DBNTQLbM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-quP0bkN-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-BouWmgho.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-qv0D9s7z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-BxvbE8BB.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-CFMKKyVG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-L8as3UuJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-MxmA4VZG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-C0VvJYRi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-Bf5QZj8N.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-BIJ9riRz.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-B8p9Mhkw.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-BVsuaPen.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-BfkjoKpV.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DeAlsWK8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-Cs97bz5j.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BqbG60LH.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-i_RY0b1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-0-agYlWL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-79V_zv1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-B-3tEpTc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-B-JVTrpr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B1CbOH1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-B5UkyA3c.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-BESLFt4q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BOVkf1K3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-BYUVuwKt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-BcUXAB2s.js} +1 -1
- package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-BkBi5TmC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-Bm7lLR4c.js} +1 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
- package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-BuLQobzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-CE2VtTlp.js} +1 -1
- package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-CED2PL_y.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-CG3PRyAL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocEhinm.js → index-CIG8fQ2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-COdGA-8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CeWoNHau.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-Cf3WCLmy.js} +1 -1
- package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-CgsqdnRV.js} +1 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
- package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-D2B4VPyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-D9evtvg2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DUUrv141.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-DiSkx4UB.js} +3 -3
- package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-Dk2qouAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-Dk7-v4hP.js} +1 -1
- package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-DmbfhJUP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-DqBgBpgp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-PT_5sBMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-U4f5izHz.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-YXgLng0K.js} +1 -1
- package/src/assets/web-panel/assets/{index-COev0W2g.js → index-ZxO_-E8a.js} +1 -1
- package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-dIHr4B_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-gyFpvL7l.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-s1opAL-M.js} +1 -1
- package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-yObj1O4n.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-C9h9yKL3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BvmLsPdu.js} +1 -1
- package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-CoL4ekU1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-C4pMy6xV.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-DPuJfNxx.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-D0KEeaK2.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BpkyINxt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DQI_wOq8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DYfdDiRg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-B5dtvb0D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-CF310mSj.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-C7_iyvJK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-D3dsi0lZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-WdbOlvdQ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +90 -1
- package/src/commands/chat.js +11 -2
- package/src/commands/compact.js +106 -23
- package/src/commands/loop.js +28 -10
- package/src/commands/routine.js +1 -1
- package/src/commands/scheduler-daemon.js +359 -2
- package/src/commands/session-observability.js +305 -0
- package/src/commands/session.js +38 -0
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/ws-agent-handler.js +439 -5
- package/src/harness/jsonl-session-store.js +463 -125
- package/src/harness/prompt-compressor.js +86 -13
- package/src/harness/provider-backed-compaction.js +100 -27
- package/src/lib/advisor-runtime.js +3 -1
- package/src/lib/agent-schedule-store.js +414 -4
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/automation-engine.js +494 -0
- package/src/lib/background-agent-supervisor.js +793 -215
- package/src/lib/background-command-argv.js +24 -0
- package/src/lib/background-interaction-journal.js +361 -52
- package/src/lib/background-interaction-resolver.js +32 -0
- package/src/lib/background-launch-profile.js +2 -0
- package/src/lib/causal-observability.js +2661 -0
- package/src/lib/chat-session-admission.js +76 -0
- package/src/lib/cost-budget.js +5 -7
- package/src/lib/cowork/ab-comparator-cli.js +11 -0
- package/src/lib/cowork/debate-review-cli.js +12 -0
- package/src/lib/cowork-adapter.js +159 -38
- package/src/lib/cowork-cron.js +357 -1
- package/src/lib/cowork-task-runner.js +112 -17
- package/src/lib/delivery-coordinator.js +142 -0
- package/src/lib/direct-model-usage.js +121 -0
- package/src/lib/file-lines.js +183 -47
- package/src/lib/llm-pricing.js +12 -17
- package/src/lib/loop.js +26 -2
- package/src/lib/routine-store.js +380 -21
- package/src/lib/runtime-usage-ledger.js +182 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +308 -3
- package/src/lib/scheduler-kernel/automation-adapter.js +410 -8
- package/src/lib/scheduler-kernel/automation-event-adapter.js +36 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +285 -3
- package/src/lib/scheduler-kernel/loop-adapter.js +541 -6
- package/src/lib/scheduler-kernel/migration-admin.js +346 -0
- package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +394 -20
- package/src/lib/scheduler-kernel/runtime.js +63 -1
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +2586 -85
- package/src/lib/session-host-lease.js +171 -2
- package/src/lib/session-host-snapshot.js +14 -3
- package/src/lib/session-persistence-failure.js +1 -0
- package/src/lib/session-tail.js +179 -34
- package/src/lib/session-transcript-structure.js +96 -0
- package/src/lib/sub-agent-context.js +328 -22
- package/src/lib/ws-agent-handler.js +4 -1
- package/src/repl/agent-repl.js +732 -91
- package/src/repl/chat-repl.js +5 -1
- package/src/runtime/agent-core.js +1272 -207
- package/src/runtime/fallback-model.js +5 -0
- package/src/runtime/headless-runner.js +324 -82
- package/src/runtime/headless-stream.js +232 -26
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/workers/background-agent-worker.js +403 -96
- package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
- package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
- package/src/assets/web-panel/assets/index-fikqml4k.js +0 -1
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { canonicalJson } from "./contract.js";
|
|
6
|
+
import {
|
|
7
|
+
schedulerMigrationScopeDigest,
|
|
8
|
+
schedulerMigrationSourceDigest,
|
|
9
|
+
} from "./store.js";
|
|
10
|
+
import { showSchedulerMigration } from "./migration-admin.js";
|
|
11
|
+
import { canonicalSchedulerSourcePath } from "./source-locator-path.js";
|
|
12
|
+
|
|
13
|
+
const requireCjs = createRequire(import.meta.url);
|
|
14
|
+
const AUTOMATION_NATIVE_DRIVERS = Object.freeze([
|
|
15
|
+
"better-sqlite3-multiple-ciphers",
|
|
16
|
+
"better-sqlite3",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
function rollbackAdminError(code, message, details = undefined) {
|
|
20
|
+
const error = new Error(message);
|
|
21
|
+
error.code = code;
|
|
22
|
+
if (details !== undefined) error.details = details;
|
|
23
|
+
return error;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function automationSourceOpenError(code) {
|
|
27
|
+
if (code === "SCHEDULER_MIGRATION_ROLLBACK_DURABILITY_UNAVAILABLE") {
|
|
28
|
+
return rollbackAdminError(
|
|
29
|
+
code,
|
|
30
|
+
"Automation rollback requires an available native SQLite driver",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return rollbackAdminError(
|
|
34
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_OPEN_FAILED",
|
|
35
|
+
"Automation rollback could not open the recorded source database",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sanitizeAutomationSourceError(error) {
|
|
40
|
+
if (
|
|
41
|
+
typeof error?.code === "string" &&
|
|
42
|
+
(error.code.startsWith("SCHEDULER_MIGRATION_") ||
|
|
43
|
+
error.code.startsWith("AUTOMATION_SCHEDULER_MIGRATION_"))
|
|
44
|
+
) {
|
|
45
|
+
return error;
|
|
46
|
+
}
|
|
47
|
+
return rollbackAdminError(
|
|
48
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_INSPECTION_FAILED",
|
|
49
|
+
"Automation rollback could not inspect the recorded source database",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sanitizeSchedulerSourceError(error, domain, operation) {
|
|
54
|
+
if (
|
|
55
|
+
typeof error?.code === "string" &&
|
|
56
|
+
error.code.startsWith("SCHEDULER_MIGRATION_ROLLBACK_")
|
|
57
|
+
) {
|
|
58
|
+
return error;
|
|
59
|
+
}
|
|
60
|
+
const stableMigrationCode =
|
|
61
|
+
typeof error?.code === "string" &&
|
|
62
|
+
(error.code.startsWith("SCHEDULER_MIGRATION_") ||
|
|
63
|
+
error.code.includes("_SCHEDULER_MIGRATION_"))
|
|
64
|
+
? error.code
|
|
65
|
+
: null;
|
|
66
|
+
return rollbackAdminError(
|
|
67
|
+
stableMigrationCode ||
|
|
68
|
+
`SCHEDULER_MIGRATION_ROLLBACK_SOURCE_${operation.toUpperCase()}_FAILED`,
|
|
69
|
+
`Scheduler rollback could not ${operation} the recorded ${domain} source`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function openAutomationSourceDatabase(database, dependencies) {
|
|
74
|
+
if (typeof dependencies.openAutomationDatabase === "function") {
|
|
75
|
+
try {
|
|
76
|
+
const opened = await dependencies.openAutomationDatabase(database, {
|
|
77
|
+
fileMustExist: true,
|
|
78
|
+
});
|
|
79
|
+
const db = opened?.db || opened;
|
|
80
|
+
if (!db || typeof db.prepare !== "function") {
|
|
81
|
+
throw new Error("invalid Automation database handle");
|
|
82
|
+
}
|
|
83
|
+
const close =
|
|
84
|
+
typeof opened?.close === "function"
|
|
85
|
+
? () => opened.close()
|
|
86
|
+
: typeof db.close === "function"
|
|
87
|
+
? () => db.close()
|
|
88
|
+
: async () => {};
|
|
89
|
+
return { db, close };
|
|
90
|
+
} catch {
|
|
91
|
+
throw automationSourceOpenError();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check before driver loading for a stable, path-free missing-source error.
|
|
96
|
+
// The native constructor repeats this check atomically with open, preventing
|
|
97
|
+
// a delete race from turning a stale locator into a fresh empty database.
|
|
98
|
+
if (!existsSync(database)) throw automationSourceOpenError();
|
|
99
|
+
let nativeDriverAvailable = false;
|
|
100
|
+
for (const packageName of AUTOMATION_NATIVE_DRIVERS) {
|
|
101
|
+
try {
|
|
102
|
+
const Database = requireCjs(packageName);
|
|
103
|
+
nativeDriverAvailable = true;
|
|
104
|
+
const db = new Database(database, { fileMustExist: true });
|
|
105
|
+
return { db, close: () => db.close() };
|
|
106
|
+
} catch {
|
|
107
|
+
// Do not expose module locations, database paths, or SQLite diagnostics.
|
|
108
|
+
// A second native candidate may still be able to open the source.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
throw automationSourceOpenError(
|
|
112
|
+
nativeDriverAvailable
|
|
113
|
+
? "SCHEDULER_MIGRATION_ROLLBACK_SOURCE_OPEN_FAILED"
|
|
114
|
+
: "SCHEDULER_MIGRATION_ROLLBACK_DURABILITY_UNAVAILABLE",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizedPath(value, field) {
|
|
119
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
120
|
+
throw rollbackAdminError(
|
|
121
|
+
"SCHEDULER_MIGRATION_ROLLBACK_CONTEXT_INVALID",
|
|
122
|
+
`${field} is required`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (value !== value.trim()) {
|
|
126
|
+
throw rollbackAdminError(
|
|
127
|
+
"SCHEDULER_MIGRATION_ROLLBACK_CONTEXT_INVALID",
|
|
128
|
+
`${field} must not contain leading or trailing whitespace`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return canonicalSchedulerSourcePath(value);
|
|
133
|
+
} catch {
|
|
134
|
+
throw rollbackAdminError(
|
|
135
|
+
"SCHEDULER_MIGRATION_ROLLBACK_CONTEXT_INVALID",
|
|
136
|
+
`${field} must be a canonical source path`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function exactContext(explicit, located, field, { path = false } = {}) {
|
|
142
|
+
const expected = path ? normalizedPath(located, field) : located;
|
|
143
|
+
if (explicit === undefined || explicit === null || explicit === "") {
|
|
144
|
+
return located;
|
|
145
|
+
}
|
|
146
|
+
const actual = path ? normalizedPath(explicit, field) : String(explicit);
|
|
147
|
+
if (actual !== expected) {
|
|
148
|
+
throw rollbackAdminError(
|
|
149
|
+
"SCHEDULER_MIGRATION_ROLLBACK_CONTEXT_MISMATCH",
|
|
150
|
+
`${field} does not match the migration source locator`,
|
|
151
|
+
{ field },
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
// The journal locator remains the authority. An explicit equivalent path is
|
|
155
|
+
// only a binding assertion; it must not rewrite the scope used for digesting.
|
|
156
|
+
return located;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function rollbackEvidencePayload(status) {
|
|
160
|
+
return {
|
|
161
|
+
schemaVersion: 1,
|
|
162
|
+
migrationId: status.id,
|
|
163
|
+
migrationState: status.state,
|
|
164
|
+
manifestDigest: status.manifestDigest,
|
|
165
|
+
entries: status.journal.entries.map((entry) => ({
|
|
166
|
+
entryId: entry.entryId,
|
|
167
|
+
domain: entry.domain,
|
|
168
|
+
sourceId: entry.sourceId,
|
|
169
|
+
sourceScopeDigest: entry.sourceScopeDigest,
|
|
170
|
+
sourceLocatorAvailable: entry.sourceLocatorAvailable,
|
|
171
|
+
sourceLocatorDigest: entry.sourceLocatorDigest,
|
|
172
|
+
sourceDigest: entry.sourceDigest,
|
|
173
|
+
targetJobId: entry.targetJobId,
|
|
174
|
+
targetJobDigest: entry.targetJobDigest,
|
|
175
|
+
rollbackStrategy: entry.rollbackStrategy,
|
|
176
|
+
state: entry.state,
|
|
177
|
+
targetAction: entry.targetAction,
|
|
178
|
+
targetAppliedRevision: entry.targetAppliedRevision,
|
|
179
|
+
targetOccurrenceCountBefore: entry.targetOccurrenceCountBefore,
|
|
180
|
+
targetExecutionEventCountBefore: entry.targetExecutionEventCountBefore,
|
|
181
|
+
targetRollbackRevision: entry.targetRollbackRevision,
|
|
182
|
+
sourceRetirementDigest: entry.sourceRetirementDigest,
|
|
183
|
+
sourceRestoredDigest: entry.sourceRestoredDigest,
|
|
184
|
+
})),
|
|
185
|
+
targets: status.targets.map((target) => ({
|
|
186
|
+
jobId: target.jobId,
|
|
187
|
+
exists: target.exists,
|
|
188
|
+
revision: target.revision,
|
|
189
|
+
enabled: target.enabled,
|
|
190
|
+
definitionDigest: target.definitionDigest ?? null,
|
|
191
|
+
occurrenceCount: target.occurrenceCount,
|
|
192
|
+
eventCount: target.eventCount,
|
|
193
|
+
executionEventCount: target.executionEventCount,
|
|
194
|
+
})),
|
|
195
|
+
blockers: status.rollback.blockers,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function schedulerMigrationRollbackEvidenceDigest(status) {
|
|
200
|
+
return `sha256:${createHash("sha256")
|
|
201
|
+
.update("chainlesschain.scheduler-migration.rollback-evidence.v1\0", "utf8")
|
|
202
|
+
.update(canonicalJson(rollbackEvidencePayload(status)), "utf8")
|
|
203
|
+
.digest("hex")}`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function showSchedulerMigrationRollback(repository, migrationId) {
|
|
207
|
+
const status = showSchedulerMigration(repository, migrationId);
|
|
208
|
+
return {
|
|
209
|
+
...status,
|
|
210
|
+
rollback: {
|
|
211
|
+
...status.rollback,
|
|
212
|
+
evidenceDigest: schedulerMigrationRollbackEvidenceDigest(status),
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function buildSchedulerMigrationRollbackChallenge({
|
|
218
|
+
migrationId,
|
|
219
|
+
evidenceDigest,
|
|
220
|
+
sourceScopeDigest,
|
|
221
|
+
} = {}) {
|
|
222
|
+
if (
|
|
223
|
+
typeof migrationId !== "string" ||
|
|
224
|
+
migrationId.length === 0 ||
|
|
225
|
+
!/^sha256:[a-f0-9]{64}$/.test(evidenceDigest || "") ||
|
|
226
|
+
!/^sha256:[a-f0-9]{64}$/.test(sourceScopeDigest || "")
|
|
227
|
+
) {
|
|
228
|
+
throw rollbackAdminError(
|
|
229
|
+
"SCHEDULER_MIGRATION_ROLLBACK_INVALID_ARGUMENT",
|
|
230
|
+
"Scheduler migration rollback challenge input is invalid",
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return (
|
|
234
|
+
`ROLL BACK SCHEDULER MIGRATION ${migrationId} ${evidenceDigest} ` +
|
|
235
|
+
`SOURCE ${sourceScopeDigest}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function requireRollbackTTY(request, dependencies) {
|
|
240
|
+
const stdin = dependencies.stdin || process.stdin;
|
|
241
|
+
const stdout = dependencies.stdout || process.stdout;
|
|
242
|
+
if (stdin?.isTTY !== true || stdout?.isTTY !== true) {
|
|
243
|
+
throw rollbackAdminError(
|
|
244
|
+
"SCHEDULER_MIGRATION_ROLLBACK_NON_INTERACTIVE",
|
|
245
|
+
"Scheduler migration rollback requires an interactive TTY",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const challenge = buildSchedulerMigrationRollbackChallenge(request);
|
|
249
|
+
const readChallenge =
|
|
250
|
+
dependencies.readChallenge ||
|
|
251
|
+
(async (expected) => {
|
|
252
|
+
const { input } = await import("@inquirer/prompts");
|
|
253
|
+
return input({
|
|
254
|
+
message:
|
|
255
|
+
"Stop legacy and scheduler workers for this source. " +
|
|
256
|
+
`Type this authorization exactly:\n${expected}`,
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
if ((await readChallenge(challenge)) !== challenge) {
|
|
260
|
+
throw rollbackAdminError(
|
|
261
|
+
"SCHEDULER_MIGRATION_ROLLBACK_CHALLENGE_FAILED",
|
|
262
|
+
"Scheduler migration rollback challenge did not match; no change was made",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
return challenge;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function requireLocator(entry, type, key) {
|
|
269
|
+
const locator = entry.sourceLocator;
|
|
270
|
+
if (
|
|
271
|
+
locator?.schemaVersion !== 1 ||
|
|
272
|
+
locator.type !== type ||
|
|
273
|
+
typeof locator[key] !== "string" ||
|
|
274
|
+
locator[key].length === 0
|
|
275
|
+
) {
|
|
276
|
+
throw rollbackAdminError(
|
|
277
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_UNBOUND",
|
|
278
|
+
`Migration source locator is unavailable for ${entry.domain}:${entry.sourceId}`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return locator;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function allowedSourceMarkerStates(entry) {
|
|
285
|
+
const loop = entry.domain === "loop-iteration";
|
|
286
|
+
switch (entry.state) {
|
|
287
|
+
case "prepared":
|
|
288
|
+
case "applied":
|
|
289
|
+
case "verified":
|
|
290
|
+
return loop ? [] : ["prepared"];
|
|
291
|
+
case "retiring":
|
|
292
|
+
return loop ? ["retired"] : ["prepared", "retired"];
|
|
293
|
+
case "retired":
|
|
294
|
+
return ["retired"];
|
|
295
|
+
case "rollback_target_disabled":
|
|
296
|
+
// A rollback can begin after the retirement token was allocated but
|
|
297
|
+
// before the legacy source was fenced and confirmed. Both states are
|
|
298
|
+
// therefore valid when resuming this durable intermediate state.
|
|
299
|
+
return loop ? ["retired"] : ["prepared", "retired"];
|
|
300
|
+
case "source_restored":
|
|
301
|
+
case "rolled_back":
|
|
302
|
+
default:
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function sourceMarkerRequired(entry) {
|
|
308
|
+
if (entry.domain === "loop-iteration") {
|
|
309
|
+
return entry.sourceRetirementDigest != null;
|
|
310
|
+
}
|
|
311
|
+
return [
|
|
312
|
+
"applied",
|
|
313
|
+
"verified",
|
|
314
|
+
"retiring",
|
|
315
|
+
"retired",
|
|
316
|
+
"rollback_target_disabled",
|
|
317
|
+
].includes(entry.state);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function sourceAlreadyRestored(migration, entry, evidence) {
|
|
321
|
+
if (
|
|
322
|
+
migration.state !== "rolling_back" ||
|
|
323
|
+
!["prepared", "rollback_target_disabled", "source_restored"].includes(
|
|
324
|
+
entry.state,
|
|
325
|
+
)
|
|
326
|
+
) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
if (evidence.hasRetirementFence === true) return false;
|
|
330
|
+
if (entry.domain === "loop-iteration") {
|
|
331
|
+
const marker = evidence.marker;
|
|
332
|
+
return (
|
|
333
|
+
evidence.hasRetirementFence === false &&
|
|
334
|
+
(!marker ||
|
|
335
|
+
(marker.schemaVersion === 1 &&
|
|
336
|
+
["retired", "rolled_back"].includes(marker.state) &&
|
|
337
|
+
marker.migrationId === migration.id &&
|
|
338
|
+
marker.targetJobId === entry.targetJobId &&
|
|
339
|
+
marker.retirementToken === entry.retirementToken))
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
return evidence.marker == null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function assertSourceEvidence(migration, entry, evidence) {
|
|
346
|
+
if (!evidence || typeof evidence.rollback !== "function") {
|
|
347
|
+
throw rollbackAdminError(
|
|
348
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_UNBOUND",
|
|
349
|
+
`Migration source cannot be opened for ${entry.domain}:${entry.sourceId}`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const actualScopeDigest = schedulerMigrationScopeDigest(evidence.scope);
|
|
353
|
+
const legacyAutomationCurrentScope =
|
|
354
|
+
entry.domain === "automation" &&
|
|
355
|
+
schedulerMigrationScopeDigest({
|
|
356
|
+
store: "automation-engine",
|
|
357
|
+
database: "current",
|
|
358
|
+
}) === entry.sourceScopeDigest;
|
|
359
|
+
if (
|
|
360
|
+
actualScopeDigest !== entry.sourceScopeDigest &&
|
|
361
|
+
!legacyAutomationCurrentScope
|
|
362
|
+
) {
|
|
363
|
+
throw rollbackAdminError(
|
|
364
|
+
"SCHEDULER_MIGRATION_ROLLBACK_CONTEXT_MISMATCH",
|
|
365
|
+
`Opened source scope does not match ${entry.domain}:${entry.sourceId}`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
if (
|
|
369
|
+
schedulerMigrationSourceDigest(evidence.snapshot) !== entry.sourceDigest
|
|
370
|
+
) {
|
|
371
|
+
throw rollbackAdminError(
|
|
372
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_CHANGED",
|
|
373
|
+
`Migration source digest changed for ${entry.domain}:${entry.sourceId}`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
const marker = evidence.marker;
|
|
377
|
+
const alreadyRestored = sourceAlreadyRestored(migration, entry, evidence);
|
|
378
|
+
if (!marker && sourceMarkerRequired(entry) && !alreadyRestored) {
|
|
379
|
+
throw rollbackAdminError(
|
|
380
|
+
"SCHEDULER_MIGRATION_ROLLBACK_MARKER_MISSING",
|
|
381
|
+
`Migration source marker is missing for ${entry.domain}:${entry.sourceId}`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
if (marker && !alreadyRestored) {
|
|
385
|
+
if (
|
|
386
|
+
marker.schemaVersion !== 1 ||
|
|
387
|
+
!["prepared", "retired"].includes(marker.state) ||
|
|
388
|
+
marker.migrationId !== migration.id ||
|
|
389
|
+
marker.targetJobId !== entry.targetJobId ||
|
|
390
|
+
(marker.sourceDigest !== undefined &&
|
|
391
|
+
marker.sourceDigest !== evidence.nativeSourceDigest) ||
|
|
392
|
+
(entry.retirementToken &&
|
|
393
|
+
marker.state === "retired" &&
|
|
394
|
+
marker.retirementToken !== entry.retirementToken)
|
|
395
|
+
) {
|
|
396
|
+
throw rollbackAdminError(
|
|
397
|
+
"SCHEDULER_MIGRATION_ROLLBACK_MARKER_MISMATCH",
|
|
398
|
+
`Migration source marker does not match ${entry.domain}:${entry.sourceId}`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (!allowedSourceMarkerStates(entry).includes(marker.state)) {
|
|
402
|
+
throw rollbackAdminError(
|
|
403
|
+
"SCHEDULER_MIGRATION_ROLLBACK_MARKER_MISMATCH",
|
|
404
|
+
`Migration source marker state does not match ${entry.domain}:${entry.sourceId}`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (
|
|
409
|
+
entry.sourceRetirementDigest != null &&
|
|
410
|
+
!["source_restored", "rolled_back"].includes(entry.state) &&
|
|
411
|
+
!alreadyRestored
|
|
412
|
+
) {
|
|
413
|
+
const actualRetirementDigest =
|
|
414
|
+
evidence.retirementSource == null
|
|
415
|
+
? null
|
|
416
|
+
: schedulerMigrationSourceDigest(evidence.retirementSource);
|
|
417
|
+
if (
|
|
418
|
+
marker?.state !== "retired" ||
|
|
419
|
+
actualRetirementDigest !== entry.sourceRetirementDigest
|
|
420
|
+
) {
|
|
421
|
+
throw rollbackAdminError(
|
|
422
|
+
"SCHEDULER_MIGRATION_ROLLBACK_RETIREMENT_EVIDENCE_CHANGED",
|
|
423
|
+
`Retired source evidence changed for ${entry.domain}:${entry.sourceId}`,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (!alreadyRestored && typeof evidence.assertRollbackSafe === "function") {
|
|
428
|
+
evidence.assertRollbackSafe({ migration, entry });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function resolveAgendaSource(migration, entry, options) {
|
|
433
|
+
const locator = requireLocator(entry, "agenda-store", "directory");
|
|
434
|
+
const directory = exactContext(
|
|
435
|
+
options.sourceDirectory,
|
|
436
|
+
locator.directory,
|
|
437
|
+
"sourceDirectory",
|
|
438
|
+
{ path: true },
|
|
439
|
+
);
|
|
440
|
+
const {
|
|
441
|
+
AgentScheduleStore,
|
|
442
|
+
agendaMigrationSourceDigest,
|
|
443
|
+
agendaMigrationSourceSnapshot,
|
|
444
|
+
} = await import("../agent-schedule-store.js");
|
|
445
|
+
const { rollbackAgendaSchedulerMigration } =
|
|
446
|
+
await import("./agenda-adapter.js");
|
|
447
|
+
const agendaStore = new AgentScheduleStore({ dir: directory });
|
|
448
|
+
const source = agendaStore.get(entry.sourceId);
|
|
449
|
+
if (!source) {
|
|
450
|
+
throw rollbackAdminError(
|
|
451
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_NOT_FOUND",
|
|
452
|
+
`Agenda source does not exist: ${entry.sourceId}`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
scope: { store: "agent-schedule", directory: locator.directory },
|
|
457
|
+
snapshot: agendaMigrationSourceSnapshot(source),
|
|
458
|
+
nativeSourceDigest: agendaMigrationSourceDigest(source),
|
|
459
|
+
marker: source.schedulerMigration || null,
|
|
460
|
+
hasRetirementFence: String(source.executionLease?.owner || "").startsWith(
|
|
461
|
+
"scheduler-migration:",
|
|
462
|
+
),
|
|
463
|
+
retirementSource: source,
|
|
464
|
+
assertRollbackSafe: ({ entry: expected }) => {
|
|
465
|
+
const marker = source.schedulerMigration;
|
|
466
|
+
if (marker?.state === "retired") {
|
|
467
|
+
if (
|
|
468
|
+
source.executionLease?.owner !==
|
|
469
|
+
`scheduler-migration:${expected.retirementToken}` ||
|
|
470
|
+
Number(source.executionLease?.expiresAt) <= Date.now()
|
|
471
|
+
) {
|
|
472
|
+
throw rollbackAdminError(
|
|
473
|
+
"SCHEDULER_MIGRATION_ROLLBACK_FENCE_MISMATCH",
|
|
474
|
+
`Agenda retirement fence changed for ${expected.sourceId}`,
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
} else if (
|
|
478
|
+
String(source.executionLease?.owner || "").startsWith(
|
|
479
|
+
"scheduler-migration:",
|
|
480
|
+
)
|
|
481
|
+
) {
|
|
482
|
+
throw rollbackAdminError(
|
|
483
|
+
"SCHEDULER_MIGRATION_ROLLBACK_FENCE_MISMATCH",
|
|
484
|
+
`Agenda rollback observed an orphan scheduler fence for ${expected.sourceId}`,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
rollback: (schedulerStore) =>
|
|
489
|
+
rollbackAgendaSchedulerMigration({
|
|
490
|
+
agendaStore,
|
|
491
|
+
schedulerStore,
|
|
492
|
+
migrationId: migration.id,
|
|
493
|
+
}),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function resolveRoutineSource(migration, entry, options) {
|
|
498
|
+
const locator = requireLocator(entry, "routine-store", "directory");
|
|
499
|
+
const directory = exactContext(
|
|
500
|
+
options.sourceDirectory,
|
|
501
|
+
locator.directory,
|
|
502
|
+
"sourceDirectory",
|
|
503
|
+
{ path: true },
|
|
504
|
+
);
|
|
505
|
+
const {
|
|
506
|
+
RoutineStore,
|
|
507
|
+
routineMigrationSourceDigest,
|
|
508
|
+
routineMigrationSourceSnapshot,
|
|
509
|
+
} = await import("../routine-store.js");
|
|
510
|
+
const { rollbackRoutineMigration } = await import("./routine-adapter.js");
|
|
511
|
+
const routineStore = new RoutineStore({ dir: directory });
|
|
512
|
+
const source = routineStore.get(entry.sourceId);
|
|
513
|
+
if (!source) {
|
|
514
|
+
throw rollbackAdminError(
|
|
515
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_NOT_FOUND",
|
|
516
|
+
`Routine source does not exist: ${entry.sourceId}`,
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
scope: { store: "routines", directory: locator.directory },
|
|
521
|
+
snapshot: routineMigrationSourceSnapshot(source),
|
|
522
|
+
nativeSourceDigest: routineMigrationSourceDigest(source),
|
|
523
|
+
marker: source.schedulerMigration || null,
|
|
524
|
+
hasRetirementFence:
|
|
525
|
+
source.enabled === false && source.trigger?.kind === "webhook",
|
|
526
|
+
retirementSource: source,
|
|
527
|
+
assertRollbackSafe: ({ entry: expected }) => {
|
|
528
|
+
const marker = source.schedulerMigration;
|
|
529
|
+
if (marker?.state === "retired") {
|
|
530
|
+
if (
|
|
531
|
+
source.enabled !== false ||
|
|
532
|
+
source.trigger?.kind !== "webhook" ||
|
|
533
|
+
marker.retirementToken !== expected.retirementToken
|
|
534
|
+
) {
|
|
535
|
+
throw rollbackAdminError(
|
|
536
|
+
"SCHEDULER_MIGRATION_ROLLBACK_FENCE_MISMATCH",
|
|
537
|
+
`Routine retirement fence changed for ${expected.sourceId}`,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
rollback: (schedulerStore) =>
|
|
543
|
+
rollbackRoutineMigration({
|
|
544
|
+
routineStore,
|
|
545
|
+
schedulerStore,
|
|
546
|
+
migrationId: migration.id,
|
|
547
|
+
}),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function resolveCoworkSource(migration, entry, options) {
|
|
552
|
+
const locator = requireLocator(entry, "cowork-workspace", "workspace");
|
|
553
|
+
const workspace = exactContext(
|
|
554
|
+
options.workspace,
|
|
555
|
+
locator.workspace,
|
|
556
|
+
"workspace",
|
|
557
|
+
{ path: true },
|
|
558
|
+
);
|
|
559
|
+
const {
|
|
560
|
+
coworkCronMigrationSourceDigest,
|
|
561
|
+
coworkCronMigrationSourceSnapshot,
|
|
562
|
+
getSchedule,
|
|
563
|
+
} = await import("../cowork-cron.js");
|
|
564
|
+
const { rollbackCoworkCronMigration } =
|
|
565
|
+
await import("./cowork-cron-adapter.js");
|
|
566
|
+
const source = getSchedule(workspace, entry.sourceId);
|
|
567
|
+
if (!source) {
|
|
568
|
+
throw rollbackAdminError(
|
|
569
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_NOT_FOUND",
|
|
570
|
+
`Cowork source does not exist: ${entry.sourceId}`,
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
return {
|
|
574
|
+
scope: { store: "cowork-schedules", workspace: locator.workspace },
|
|
575
|
+
snapshot: coworkCronMigrationSourceSnapshot(source),
|
|
576
|
+
nativeSourceDigest: coworkCronMigrationSourceDigest(source),
|
|
577
|
+
marker: source.schedulerMigration || null,
|
|
578
|
+
hasRetirementFence: String(source.activeDelivery?.ownerId || "").startsWith(
|
|
579
|
+
"scheduler-migration:",
|
|
580
|
+
),
|
|
581
|
+
retirementSource: source,
|
|
582
|
+
assertRollbackSafe: ({ entry: expected }) => {
|
|
583
|
+
const marker = source.schedulerMigration;
|
|
584
|
+
if (marker?.state === "retired") {
|
|
585
|
+
const leaseExpiresAt = Date.parse(
|
|
586
|
+
source.activeDelivery?.leaseExpiresAt || "",
|
|
587
|
+
);
|
|
588
|
+
if (
|
|
589
|
+
source.activeDelivery?.ownerId !==
|
|
590
|
+
`scheduler-migration:${expected.retirementToken}` ||
|
|
591
|
+
marker.retirementToken !== expected.retirementToken ||
|
|
592
|
+
!Number.isFinite(leaseExpiresAt) ||
|
|
593
|
+
leaseExpiresAt <= Date.now()
|
|
594
|
+
) {
|
|
595
|
+
throw rollbackAdminError(
|
|
596
|
+
"SCHEDULER_MIGRATION_ROLLBACK_FENCE_MISMATCH",
|
|
597
|
+
`Cowork retirement fence changed for ${expected.sourceId}`,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
} else if (
|
|
601
|
+
String(source.activeDelivery?.ownerId || "").startsWith(
|
|
602
|
+
"scheduler-migration:",
|
|
603
|
+
)
|
|
604
|
+
) {
|
|
605
|
+
throw rollbackAdminError(
|
|
606
|
+
"SCHEDULER_MIGRATION_ROLLBACK_FENCE_MISMATCH",
|
|
607
|
+
`Cowork rollback observed an orphan scheduler fence for ${expected.sourceId}`,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
rollback: (schedulerStore) =>
|
|
612
|
+
rollbackCoworkCronMigration({
|
|
613
|
+
cwd: workspace,
|
|
614
|
+
schedulerStore,
|
|
615
|
+
migrationId: migration.id,
|
|
616
|
+
}),
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function resolveAutomationSource(
|
|
621
|
+
migration,
|
|
622
|
+
entry,
|
|
623
|
+
options,
|
|
624
|
+
dependencies,
|
|
625
|
+
) {
|
|
626
|
+
const locator = requireLocator(entry, "automation-database", "database");
|
|
627
|
+
exactContext(
|
|
628
|
+
options.automationDatabase,
|
|
629
|
+
locator.database,
|
|
630
|
+
"automationDatabase",
|
|
631
|
+
{ path: !String(locator.database).startsWith("memory:") },
|
|
632
|
+
);
|
|
633
|
+
let automationDb = dependencies.automationDb;
|
|
634
|
+
let closeAutomationDb = async () => {};
|
|
635
|
+
if (!automationDb) {
|
|
636
|
+
if (String(locator.database).startsWith("memory:")) {
|
|
637
|
+
throw rollbackAdminError(
|
|
638
|
+
"SCHEDULER_MIGRATION_ROLLBACK_AUTOMATION_DB_REQUIRED",
|
|
639
|
+
"In-memory Automation rollback requires the owning database handle",
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const opened = await openAutomationSourceDatabase(
|
|
643
|
+
locator.database,
|
|
644
|
+
dependencies,
|
|
645
|
+
);
|
|
646
|
+
automationDb = opened.db;
|
|
647
|
+
let open = true;
|
|
648
|
+
closeAutomationDb = async () => {
|
|
649
|
+
if (!open) return;
|
|
650
|
+
open = false;
|
|
651
|
+
await opened.close();
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
if (automationDb?.__isSqlJsCompat === true) {
|
|
655
|
+
await closeAutomationDb();
|
|
656
|
+
throw automationSourceOpenError(
|
|
657
|
+
"SCHEDULER_MIGRATION_ROLLBACK_DURABILITY_UNAVAILABLE",
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
try {
|
|
661
|
+
const {
|
|
662
|
+
automationEffectiveSchedulerFlow,
|
|
663
|
+
automationMigrationSourceDigest,
|
|
664
|
+
automationMigrationSourceSnapshot,
|
|
665
|
+
FLOW_STATUS,
|
|
666
|
+
getAutomationSchedulerMigration,
|
|
667
|
+
getFlow,
|
|
668
|
+
} = await import("../automation-engine.js");
|
|
669
|
+
const { automationDatabaseIdentity, rollbackAutomationMigration } =
|
|
670
|
+
await import("./automation-adapter.js");
|
|
671
|
+
const database = automationDatabaseIdentity(automationDb);
|
|
672
|
+
if (database !== locator.database) {
|
|
673
|
+
throw rollbackAdminError(
|
|
674
|
+
"SCHEDULER_MIGRATION_ROLLBACK_CONTEXT_MISMATCH",
|
|
675
|
+
"Automation database handle does not match the migration source locator",
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
const source = getFlow(automationDb, entry.sourceId);
|
|
679
|
+
if (!source) {
|
|
680
|
+
throw rollbackAdminError(
|
|
681
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_NOT_FOUND",
|
|
682
|
+
`Automation source does not exist: ${entry.sourceId}`,
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
const effective = automationEffectiveSchedulerFlow(automationDb, source);
|
|
686
|
+
const evidence = {
|
|
687
|
+
scope: { store: "automation-engine", database: locator.database },
|
|
688
|
+
snapshot: automationMigrationSourceSnapshot(effective),
|
|
689
|
+
nativeSourceDigest: automationMigrationSourceDigest(effective),
|
|
690
|
+
marker:
|
|
691
|
+
getAutomationSchedulerMigration(automationDb, entry.sourceId) || null,
|
|
692
|
+
hasRetirementFence:
|
|
693
|
+
effective?.schedulerMigration?.state === "retired" &&
|
|
694
|
+
getFlow(automationDb, entry.sourceId)?.schedule === null,
|
|
695
|
+
retirementSource: effective,
|
|
696
|
+
assertRollbackSafe: ({ entry: expected }) => {
|
|
697
|
+
const marker = getAutomationSchedulerMigration(
|
|
698
|
+
automationDb,
|
|
699
|
+
expected.sourceId,
|
|
700
|
+
);
|
|
701
|
+
const current = getFlow(automationDb, expected.sourceId);
|
|
702
|
+
if (marker?.state === "retired") {
|
|
703
|
+
if (
|
|
704
|
+
marker.retirementToken !== expected.retirementToken ||
|
|
705
|
+
current?.schedule !== null ||
|
|
706
|
+
![FLOW_STATUS.PAUSED, FLOW_STATUS.ACTIVE].includes(current?.status)
|
|
707
|
+
) {
|
|
708
|
+
throw rollbackAdminError(
|
|
709
|
+
"SCHEDULER_MIGRATION_ROLLBACK_FENCE_MISMATCH",
|
|
710
|
+
`Automation retirement fence changed for ${expected.sourceId}`,
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
},
|
|
715
|
+
rollback: (schedulerStore) => {
|
|
716
|
+
return rollbackAutomationMigration({
|
|
717
|
+
db: automationDb,
|
|
718
|
+
schedulerStore,
|
|
719
|
+
migrationId: migration.id,
|
|
720
|
+
});
|
|
721
|
+
},
|
|
722
|
+
close: async () => {
|
|
723
|
+
try {
|
|
724
|
+
await closeAutomationDb();
|
|
725
|
+
} catch {
|
|
726
|
+
throw rollbackAdminError(
|
|
727
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_CLOSE_FAILED",
|
|
728
|
+
"Automation rollback could not close the recorded source database",
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
};
|
|
733
|
+
return evidence;
|
|
734
|
+
} catch (error) {
|
|
735
|
+
try {
|
|
736
|
+
await closeAutomationDb();
|
|
737
|
+
} catch {
|
|
738
|
+
// Preserve the inspection failure while keeping driver/path details out
|
|
739
|
+
// of the operator-facing error channel.
|
|
740
|
+
}
|
|
741
|
+
throw sanitizeAutomationSourceError(error);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async function resolveLoopSource(migration, entry, options, dependencies) {
|
|
746
|
+
const locator = requireLocator(entry, "jsonl-session", "sessionId");
|
|
747
|
+
const sessionId = exactContext(
|
|
748
|
+
options.sessionId,
|
|
749
|
+
locator.sessionId,
|
|
750
|
+
"sessionId",
|
|
751
|
+
);
|
|
752
|
+
const sessionStore = await import("../../harness/jsonl-session-store.js");
|
|
753
|
+
const resolveSessionPath =
|
|
754
|
+
dependencies.sessionPath || sessionStore.sessionPath;
|
|
755
|
+
const directory = normalizedPath(locator.directory, "sessionDirectory");
|
|
756
|
+
const actualDirectory = normalizedPath(
|
|
757
|
+
dirname(resolveSessionPath(sessionId)),
|
|
758
|
+
"sessionDirectory",
|
|
759
|
+
);
|
|
760
|
+
if (directory !== actualDirectory) {
|
|
761
|
+
throw rollbackAdminError(
|
|
762
|
+
"SCHEDULER_MIGRATION_ROLLBACK_CONTEXT_MISMATCH",
|
|
763
|
+
"Saved loop session directory does not match the migration source locator",
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const { summarizeLoopEvents } = await import("../loop.js");
|
|
767
|
+
const { loopMigrationSourceSnapshot, rollbackSavedLoopMigration } =
|
|
768
|
+
await import("./loop-adapter.js");
|
|
769
|
+
const readEvents = dependencies.readEvents || sessionStore.readEvents;
|
|
770
|
+
const appendEventIfHead =
|
|
771
|
+
dependencies.appendEventIfHead || sessionStore.appendEventIfHead;
|
|
772
|
+
const events = readEvents(sessionId);
|
|
773
|
+
const summary = summarizeLoopEvents(events);
|
|
774
|
+
const latestConfig = events
|
|
775
|
+
.filter((event) => event?.type === "loop_config")
|
|
776
|
+
.at(-1)?.data;
|
|
777
|
+
if (!summary.config) {
|
|
778
|
+
throw rollbackAdminError(
|
|
779
|
+
"SCHEDULER_MIGRATION_ROLLBACK_SOURCE_NOT_FOUND",
|
|
780
|
+
`Saved loop source does not exist: ${sessionId}`,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
return {
|
|
784
|
+
scope: { store: "jsonl-session", sessionId: locator.sessionId },
|
|
785
|
+
snapshot: loopMigrationSourceSnapshot({
|
|
786
|
+
sessionId,
|
|
787
|
+
config: summary.config,
|
|
788
|
+
}),
|
|
789
|
+
nativeSourceDigest: undefined,
|
|
790
|
+
marker: summary.schedulerMigration || null,
|
|
791
|
+
hasRetirementFence: Boolean(latestConfig?.schedulerMigrationFence),
|
|
792
|
+
retirementSource: summary.schedulerMigration || null,
|
|
793
|
+
assertRollbackSafe: ({ entry: expected }) => {
|
|
794
|
+
const marker = summary.schedulerMigration;
|
|
795
|
+
const fence = events
|
|
796
|
+
.filter((event) => event?.type === "loop_config")
|
|
797
|
+
.at(-1)?.data?.schedulerMigrationFence;
|
|
798
|
+
if (marker?.state === "retired") {
|
|
799
|
+
if (
|
|
800
|
+
fence?.schemaVersion !== 1 ||
|
|
801
|
+
fence.state !== "retired" ||
|
|
802
|
+
fence.migrationId !== migration.id ||
|
|
803
|
+
fence.targetJobId !== expected.targetJobId ||
|
|
804
|
+
fence.retirementToken !== expected.retirementToken
|
|
805
|
+
) {
|
|
806
|
+
throw rollbackAdminError(
|
|
807
|
+
"SCHEDULER_MIGRATION_ROLLBACK_FENCE_MISMATCH",
|
|
808
|
+
`Saved loop retirement fence changed for ${expected.sourceId}`,
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
rollback: (schedulerStore) =>
|
|
814
|
+
rollbackSavedLoopMigration({
|
|
815
|
+
schedulerStore,
|
|
816
|
+
sessionId,
|
|
817
|
+
config: summary.config,
|
|
818
|
+
migrationId: migration.id,
|
|
819
|
+
readEvents,
|
|
820
|
+
appendEventIfHead,
|
|
821
|
+
}),
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
async function resolveSource(migration, entry, options, dependencies) {
|
|
826
|
+
try {
|
|
827
|
+
if (typeof dependencies.resolveSource === "function") {
|
|
828
|
+
return await dependencies.resolveSource({ migration, entry, options });
|
|
829
|
+
}
|
|
830
|
+
switch (entry.domain) {
|
|
831
|
+
case "agenda":
|
|
832
|
+
return await resolveAgendaSource(migration, entry, options);
|
|
833
|
+
case "cowork-cron":
|
|
834
|
+
return await resolveCoworkSource(migration, entry, options);
|
|
835
|
+
case "routine":
|
|
836
|
+
return await resolveRoutineSource(migration, entry, options);
|
|
837
|
+
case "automation":
|
|
838
|
+
return await resolveAutomationSource(
|
|
839
|
+
migration,
|
|
840
|
+
entry,
|
|
841
|
+
options,
|
|
842
|
+
dependencies,
|
|
843
|
+
);
|
|
844
|
+
case "loop-iteration":
|
|
845
|
+
return await resolveLoopSource(migration, entry, options, dependencies);
|
|
846
|
+
default:
|
|
847
|
+
throw rollbackAdminError(
|
|
848
|
+
"SCHEDULER_MIGRATION_ROLLBACK_DOMAIN_UNSUPPORTED",
|
|
849
|
+
`Unsupported scheduler migration domain: ${entry.domain}`,
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
} catch (error) {
|
|
853
|
+
throw sanitizeSchedulerSourceError(error, entry.domain, "inspect");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export async function rollbackSchedulerMigration({
|
|
858
|
+
schedulerStore,
|
|
859
|
+
repository,
|
|
860
|
+
migrationId,
|
|
861
|
+
options = {},
|
|
862
|
+
dependencies = {},
|
|
863
|
+
}) {
|
|
864
|
+
const initial = showSchedulerMigrationRollback(repository, migrationId);
|
|
865
|
+
if (initial.journal.entries.length !== 1) {
|
|
866
|
+
throw rollbackAdminError(
|
|
867
|
+
"SCHEDULER_MIGRATION_ROLLBACK_MULTI_ENTRY_UNSUPPORTED",
|
|
868
|
+
"Operator rollback currently supports single-entry migrations only",
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
if (!initial.rollback.eligible) {
|
|
872
|
+
throw rollbackAdminError(
|
|
873
|
+
"SCHEDULER_MIGRATION_ROLLBACK_BLOCKED",
|
|
874
|
+
"Scheduler migration rollback is blocked by current journal or target evidence",
|
|
875
|
+
{ blockers: initial.rollback.blockers },
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
if (options.expectedEvidenceDigest !== initial.rollback.evidenceDigest) {
|
|
879
|
+
throw rollbackAdminError(
|
|
880
|
+
"SCHEDULER_MIGRATION_ROLLBACK_EVIDENCE_MISMATCH",
|
|
881
|
+
"Expected rollback evidence digest does not match the latest migration show",
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
const migration = repository.get(migrationId);
|
|
885
|
+
const entry = migration.entries[0];
|
|
886
|
+
let source = await resolveSource(migration, entry, options, dependencies);
|
|
887
|
+
try {
|
|
888
|
+
assertSourceEvidence(migration, entry, source);
|
|
889
|
+
await requireRollbackTTY(
|
|
890
|
+
{
|
|
891
|
+
migrationId,
|
|
892
|
+
evidenceDigest: initial.rollback.evidenceDigest,
|
|
893
|
+
sourceScopeDigest: entry.sourceScopeDigest,
|
|
894
|
+
},
|
|
895
|
+
dependencies,
|
|
896
|
+
);
|
|
897
|
+
} finally {
|
|
898
|
+
await source.close?.();
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// CAS both sides again after the operator has spent time at the prompt. No
|
|
902
|
+
// migration/target mutation has happened before these exact re-checks.
|
|
903
|
+
const latest = showSchedulerMigrationRollback(repository, migrationId);
|
|
904
|
+
if (latest.rollback.evidenceDigest !== initial.rollback.evidenceDigest) {
|
|
905
|
+
throw rollbackAdminError(
|
|
906
|
+
"SCHEDULER_MIGRATION_ROLLBACK_EVIDENCE_STALE",
|
|
907
|
+
"Scheduler migration evidence changed during rollback authorization",
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
const latestMigration = repository.get(migrationId);
|
|
911
|
+
source = await resolveSource(
|
|
912
|
+
latestMigration,
|
|
913
|
+
latestMigration.entries[0],
|
|
914
|
+
options,
|
|
915
|
+
dependencies,
|
|
916
|
+
);
|
|
917
|
+
try {
|
|
918
|
+
assertSourceEvidence(latestMigration, latestMigration.entries[0], source);
|
|
919
|
+
try {
|
|
920
|
+
await source.rollback(schedulerStore);
|
|
921
|
+
} catch (error) {
|
|
922
|
+
throw sanitizeSchedulerSourceError(
|
|
923
|
+
error,
|
|
924
|
+
latestMigration.entries[0].domain,
|
|
925
|
+
"restore",
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
} finally {
|
|
929
|
+
await source.close?.();
|
|
930
|
+
}
|
|
931
|
+
return showSchedulerMigrationRollback(repository, migrationId);
|
|
932
|
+
}
|