chainlesschain 0.162.188 → 0.162.189
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-Brr1LVcE.js → AIOps-DbZft8w0.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-DoOZpwfN.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-B0J0KLTW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-D9eIR0qq.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-BnZXEVeo.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-X1ce4Bjn.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-BvWR-y5v.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BublSGEd.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-CR_77Hso.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-DixW52tq.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DDHDpBBA.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CCv1C87K.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-B03Cc3dd.js} +1 -1
- package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CA06-6pU.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-Bj881J52.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-UZLTPoaL.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-kGwzRb_0.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-KundoYM8.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-BYZyoqDe.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BhrrppBX.js} +1 -1
- package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-CdC6EguZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-CcQx3J4y.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-BpijtEzQ.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-DfZjeZTe.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-BFYSf69C.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-Okdi3pJm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-mH-HebKW.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-xivV69x7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-1rh_YBwg.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-eAXkNxdY.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-CS6dMVTN.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-Bq_TnkvD.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-KchHtiHt.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-TabqHW9B.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-CsmGcX5X.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-BaPFnnyz.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-2m-sUUf7.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-D5nVZBRu.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bo8MLZvT.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-3GDvamGa.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-D2zGuZCH.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-BBVQ4t6t.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-BIOCyOGp.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DVwBGpzc.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-BDnHfIfX.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BU2Yqq5H.js → OrderTableRenderer-C7Ek20ag.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D3SRKaa7.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-C2B8ahyn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-2iwQzG65.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-69ca_WeR.js → PdhVaultBrowser-CoPiWqMN.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-yV5XNXo5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-uCDJhMUF.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-hN7ucMOA.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-CgU0ptN4.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-CfYPiT6j.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-B0A2C4nw.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-BE5C0ajn.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-CstQPf8W.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-Co3z_ynk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-DMTS6pQ6.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-ETw4odUa.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-B0rkE09d.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-BTVqQgKq.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CBKKwwtO.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BHUQ438I.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-B4NqeUsF.js} +1 -1
- package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-BRjoYG5_.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-CxQzu3p3.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton-BKzwwmWO.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-DrsFcgth.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-D21tmXCH.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-CjyjLfp8.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-Cu45-tnx.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-oMcILYOj.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-Bh-Zcr5R.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-68ns63aI.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BLJkFykE.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-xcrJLq-O.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-C6PpFJtf.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-BaKA7tdM.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-BzhA57Jc.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-4_U3JkIR.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-162AANxX.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-B1viuql_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-ZVWazhCy.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-tzdyUk-1.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-DmrHNuwq.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-DAJ_M-PK.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-uIzmYwLs.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-D_ZmuLwb.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DIrncrV1.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-CDt_shTL.js} +1 -1
- package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-BAvSmJXW.js} +1 -1
- package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-BDF6EQBs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-BHvlm0In.js} +1 -1
- package/src/assets/web-panel/assets/{index-COG61sRv.js → index-BRa4kJ_S.js} +1 -1
- package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-BTCXf1t-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Bbxjfstq.js} +1 -1
- package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-Bx4d6c1k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-ByzGvpR9.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-C16fYKFD.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-C5WcdJH2.js} +3 -3
- package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-CG0lY4h3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-CKyd472S.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-CaelyMgF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CbD_WW_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-CcZ9xJI9.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CeUpKjvi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-Cvak6Oe2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-CzXqvMuP.js} +1 -1
- package/src/assets/web-panel/assets/index-D-Swwqld.js +1 -0
- package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-D4n1lGTB.js} +1 -1
- package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-D9OE8B3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-DBFmcaCQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-DdUMcwRB.js} +1 -1
- package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-DeGBoLQA.js} +1 -1
- package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-DfSSy9uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-DqDSF2RW.js} +1 -1
- package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-DvFhhe6r.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaslYch6.js → index-DwfvPe-j.js} +1 -1
- package/src/assets/web-panel/assets/index-FMZAMKy8.js +1 -0
- package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-FyplHuNt.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-GcKOHD_c.js} +1 -1
- package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-OzTxFIJP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-QsgfSZrV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-Z8P4BexE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-clSX4PEc.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-ifxmu8SY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-rS3gl_68.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-rXdwUeE3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-saf3VNeP.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-pmfaD-aK.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-CaQ2CpYF.js} +1 -1
- package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-CkKTqNbI.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-Dd4xsgRI.js} +1 -1
- package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-BFuA5YEs.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-DDfxOAJc.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-Be595JCp.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-7byljUM3.js} +1 -1
- package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-CTWfOTaY.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-CNZOuIxM.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-DwOcBV78.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-B6MNwAO9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-CzyQjH9r.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-Dxh3KiVA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-DVfZiS5N.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-BYme7fbY.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +22 -0
- package/src/commands/checkpoint-managed.js +1059 -0
- package/src/commands/checkpoint.js +3 -0
- package/src/commands/team-distributed.js +3324 -0
- package/src/commands/team.js +1130 -74
- package/src/data/changelog.json +13 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/agent-team/task-lease.js +271 -3
- package/src/lib/agent-team/team-adjudication.js +1501 -0
- package/src/lib/agent-team/team-budget.js +4 -1
- package/src/lib/agent-team/team-control-store.js +582 -0
- package/src/lib/agent-team/team-distributed-queue.js +5246 -0
- package/src/lib/agent-team/team-process-checkpoint.js +211 -0
- package/src/lib/agent-team/team-runner.js +319 -22
- package/src/lib/agent-team/team-worktree.js +1403 -95
- package/src/lib/managed-checkpoint-render.js +60 -0
- package/src/lib/managed-tool-checkpoint.js +194 -0
- package/src/lib/process-execution-broker/index.js +438 -5
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
- package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
- package/src/lib/process-execution-broker/workspace-transaction.js +3556 -0
- package/src/lib/secure-file-identity.js +433 -0
- package/src/lib/turn-binding.js +160 -2
- package/src/lib/with-file-lock.js +140 -23
- package/src/repl/agent-repl.js +19 -1
- package/src/runtime/agent-core.js +366 -122
- package/src/runtime/headless-runner.js +12 -0
- package/src/runtime/headless-stream.js +3 -0
- package/src/runtime/policies/agent-policy.js +14 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-COBbEOlj.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-oOVIv9wq.js +0 -5
- package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
- package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
- package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed workspace transaction inspection and explicit rewind commands.
|
|
3
|
+
*
|
|
4
|
+
* These commands expose durable checkpoints created by ProcessExecutionBroker.
|
|
5
|
+
* They are deliberately separate from the git/copy checkpoints implemented by
|
|
6
|
+
* `checkpoint.js`: a managed transaction has coverage evidence, a write
|
|
7
|
+
* manifest, and mandatory evidence-digest authority for restore/undo.
|
|
8
|
+
*
|
|
9
|
+
* Registration:
|
|
10
|
+
* registerManagedCheckpointCommands(checkpointCommand)
|
|
11
|
+
*
|
|
12
|
+
* Resulting surface:
|
|
13
|
+
* cc checkpoint managed list
|
|
14
|
+
* cc checkpoint managed show <transaction-id>
|
|
15
|
+
* cc checkpoint managed run -- <command...>
|
|
16
|
+
* cc checkpoint managed restore <transaction-id>
|
|
17
|
+
* cc checkpoint managed undo <transaction-id>
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import crypto from "node:crypto";
|
|
22
|
+
import chalk from "chalk";
|
|
23
|
+
import { logger } from "../lib/logger.js";
|
|
24
|
+
import {
|
|
25
|
+
WorkspaceTransactionManager,
|
|
26
|
+
WORKSPACE_TRANSACTION_COVERAGE,
|
|
27
|
+
} from "../lib/process-execution-broker/workspace-transaction.js";
|
|
28
|
+
|
|
29
|
+
const EVIDENCE_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
30
|
+
const SAFE_TRANSACTION_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/;
|
|
31
|
+
const PROCESS_TREE_BOUNDARY = "process-tree";
|
|
32
|
+
const DEFAULT_MAX_CAPTURE_BYTES = 4 * 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
function commandError(code, message, details = {}) {
|
|
35
|
+
const error = new Error(message);
|
|
36
|
+
error.name = "ManagedCheckpointCommandError";
|
|
37
|
+
error.code = code;
|
|
38
|
+
Object.assign(error, details);
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeTransactionId(id) {
|
|
43
|
+
if (typeof id !== "string" || !SAFE_TRANSACTION_ID.test(id)) {
|
|
44
|
+
throw commandError(
|
|
45
|
+
"MANAGED_CHECKPOINT_INVALID_ID",
|
|
46
|
+
"transaction id must be a stable filesystem-safe identifier",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return id;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeEvidenceDigest(value, label) {
|
|
53
|
+
if (typeof value !== "string" || !EVIDENCE_DIGEST.test(value)) {
|
|
54
|
+
throw commandError(
|
|
55
|
+
"MANAGED_CHECKPOINT_INVALID_EVIDENCE_DIGEST",
|
|
56
|
+
`${label} must be a sha256:<64 lowercase hex> digest`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeCommand(command, args) {
|
|
63
|
+
if (
|
|
64
|
+
typeof command !== "string" ||
|
|
65
|
+
command.trim() === "" ||
|
|
66
|
+
command.includes("\0")
|
|
67
|
+
) {
|
|
68
|
+
throw commandError(
|
|
69
|
+
"MANAGED_CHECKPOINT_COMMAND_REQUIRED",
|
|
70
|
+
"managed run requires a literal foreground command after `--`",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
!Array.isArray(args) ||
|
|
75
|
+
args.some((value) => typeof value !== "string" || value.includes("\0"))
|
|
76
|
+
) {
|
|
77
|
+
throw commandError(
|
|
78
|
+
"MANAGED_CHECKPOINT_INVALID_COMMAND",
|
|
79
|
+
"managed run command arguments must be NUL-free strings",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return { command, args: [...args] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeCaptureLimit(value) {
|
|
86
|
+
const resolved =
|
|
87
|
+
value === undefined ? DEFAULT_MAX_CAPTURE_BYTES : Number(value);
|
|
88
|
+
if (
|
|
89
|
+
!Number.isSafeInteger(resolved) ||
|
|
90
|
+
resolved <= 0 ||
|
|
91
|
+
resolved > 64 * 1024 * 1024
|
|
92
|
+
) {
|
|
93
|
+
throw commandError(
|
|
94
|
+
"MANAGED_CHECKPOINT_INVALID_OUTPUT_LIMIT",
|
|
95
|
+
"max output bytes must be an integer between 1 and 67108864",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return resolved;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeInspection(raw) {
|
|
102
|
+
// The public manager API returns a verified state snapshot. Accepting the
|
|
103
|
+
// `{ state: snapshot }` envelope as well keeps this command compatible with
|
|
104
|
+
// embedders that attach baseline metadata without ever reading it here.
|
|
105
|
+
if (
|
|
106
|
+
raw?.state &&
|
|
107
|
+
typeof raw.state === "object" &&
|
|
108
|
+
!Array.isArray(raw.state)
|
|
109
|
+
) {
|
|
110
|
+
return raw.state;
|
|
111
|
+
}
|
|
112
|
+
return raw;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function coverageWarnings(state) {
|
|
116
|
+
const warnings = [];
|
|
117
|
+
const overall =
|
|
118
|
+
state?.coverage ||
|
|
119
|
+
state?.evidence?.coverage ||
|
|
120
|
+
WORKSPACE_TRANSACTION_COVERAGE.NONE;
|
|
121
|
+
const files =
|
|
122
|
+
state?.fileCoverage ||
|
|
123
|
+
state?.evidence?.fileCoverage ||
|
|
124
|
+
WORKSPACE_TRANSACTION_COVERAGE.NONE;
|
|
125
|
+
const uncovered = Array.isArray(state?.uncoveredPaths)
|
|
126
|
+
? state.uncoveredPaths
|
|
127
|
+
: Array.isArray(state?.evidence?.uncoveredPaths)
|
|
128
|
+
? state.evidence.uncoveredPaths
|
|
129
|
+
: [];
|
|
130
|
+
|
|
131
|
+
if (overall !== WORKSPACE_TRANSACTION_COVERAGE.FULL) {
|
|
132
|
+
warnings.push(
|
|
133
|
+
`Overall rollback coverage is ${overall}; this checkpoint must not be treated as a full side-effect rollback.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (files !== WORKSPACE_TRANSACTION_COVERAGE.FULL) {
|
|
137
|
+
warnings.push(
|
|
138
|
+
`Workspace file coverage is ${files}; some file writes may be outside the checkpoint.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (state?.externalSideEffects !== false) {
|
|
142
|
+
warnings.push(
|
|
143
|
+
"External effects such as databases, messages, deployments, payments, and writes outside the workspace are not reversible by this checkpoint.",
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (uncovered.length > 0) {
|
|
147
|
+
warnings.push(`Uncovered paths: ${uncovered.join(", ")}`);
|
|
148
|
+
}
|
|
149
|
+
return warnings;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function transactionSummary(raw) {
|
|
153
|
+
const state = normalizeInspection(raw);
|
|
154
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) {
|
|
155
|
+
throw commandError(
|
|
156
|
+
"MANAGED_CHECKPOINT_INVALID_INSPECTION",
|
|
157
|
+
"workspace transaction inspection returned an invalid record",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const id = normalizeTransactionId(state.id);
|
|
161
|
+
const overall =
|
|
162
|
+
state.coverage ||
|
|
163
|
+
state.evidence?.coverage ||
|
|
164
|
+
WORKSPACE_TRANSACTION_COVERAGE.NONE;
|
|
165
|
+
const files =
|
|
166
|
+
state.fileCoverage ||
|
|
167
|
+
state.evidence?.fileCoverage ||
|
|
168
|
+
WORKSPACE_TRANSACTION_COVERAGE.NONE;
|
|
169
|
+
return {
|
|
170
|
+
id,
|
|
171
|
+
checkpointId: state.checkpointId || state.checkpoint?.id || null,
|
|
172
|
+
state: typeof state.state === "string" ? state.state : "unknown",
|
|
173
|
+
runId: state.runId || null,
|
|
174
|
+
taskKey: state.taskKey || null,
|
|
175
|
+
workspaceRoot: state.workspaceRoot || null,
|
|
176
|
+
createdAt: state.createdAt || null,
|
|
177
|
+
updatedAt: state.updatedAt || null,
|
|
178
|
+
coverage: overall,
|
|
179
|
+
fileCoverage: files,
|
|
180
|
+
externalSideEffects: state.externalSideEffects !== false,
|
|
181
|
+
writerIsolation: state.writerIsolation || "unknown",
|
|
182
|
+
uncoveredPaths: Array.isArray(state.uncoveredPaths)
|
|
183
|
+
? [...state.uncoveredPaths]
|
|
184
|
+
: Array.isArray(state.evidence?.uncoveredPaths)
|
|
185
|
+
? [...state.evidence.uncoveredPaths]
|
|
186
|
+
: [],
|
|
187
|
+
evidenceDigest: state.evidence?.evidenceDigest || null,
|
|
188
|
+
restoreEvidenceDigest: state.restoreEvidence?.evidenceDigest || null,
|
|
189
|
+
undoRestoreEvidenceDigest:
|
|
190
|
+
state.undoRestoreEvidence?.evidenceDigest || null,
|
|
191
|
+
warnings: coverageWarnings(state),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function transactionDetails(raw) {
|
|
196
|
+
const state = normalizeInspection(raw);
|
|
197
|
+
const summary = transactionSummary(state);
|
|
198
|
+
return {
|
|
199
|
+
...summary,
|
|
200
|
+
checkpoint: state.checkpoint
|
|
201
|
+
? {
|
|
202
|
+
id: state.checkpoint.id || null,
|
|
203
|
+
digest: state.checkpoint.digest || null,
|
|
204
|
+
entries: state.checkpoint.entries ?? null,
|
|
205
|
+
files: state.checkpoint.files ?? null,
|
|
206
|
+
bytes: state.checkpoint.bytes ?? null,
|
|
207
|
+
}
|
|
208
|
+
: null,
|
|
209
|
+
writeManifest: state.writeManifest
|
|
210
|
+
? {
|
|
211
|
+
digest: state.writeManifest.digest || null,
|
|
212
|
+
summary: state.writeManifest.summary || null,
|
|
213
|
+
}
|
|
214
|
+
: null,
|
|
215
|
+
evidence: state.evidence || null,
|
|
216
|
+
restoreEvidence: state.restoreEvidence || null,
|
|
217
|
+
undoRestoreEvidence: state.undoRestoreEvidence || null,
|
|
218
|
+
failureEvidence: Array.isArray(state.failureEvidence)
|
|
219
|
+
? state.failureEvidence
|
|
220
|
+
: [],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function managerOptions(options) {
|
|
225
|
+
return {
|
|
226
|
+
...(options.stateDir ? { stateDir: path.resolve(options.stateDir) } : {}),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function resolveWorkspaceFilter(value) {
|
|
231
|
+
return value ? path.resolve(value) : undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function listManagedCheckpoints(manager, options = {}) {
|
|
235
|
+
if (!manager || typeof manager.list !== "function") {
|
|
236
|
+
throw commandError(
|
|
237
|
+
"MANAGED_CHECKPOINT_API_UNAVAILABLE",
|
|
238
|
+
"workspace transaction manager does not expose the verified list API",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const records = manager.list({
|
|
242
|
+
...(options.workspaceRoot
|
|
243
|
+
? { workspaceRoot: path.resolve(options.workspaceRoot) }
|
|
244
|
+
: {}),
|
|
245
|
+
});
|
|
246
|
+
if (!Array.isArray(records)) {
|
|
247
|
+
throw commandError(
|
|
248
|
+
"MANAGED_CHECKPOINT_INVALID_INSPECTION",
|
|
249
|
+
"workspace transaction list returned an invalid result",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return records
|
|
253
|
+
.map(transactionSummary)
|
|
254
|
+
.sort((left, right) =>
|
|
255
|
+
String(right.updatedAt || "").localeCompare(String(left.updatedAt || "")),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function showManagedCheckpoint(manager, id) {
|
|
260
|
+
normalizeTransactionId(id);
|
|
261
|
+
if (!manager || typeof manager.inspect !== "function") {
|
|
262
|
+
throw commandError(
|
|
263
|
+
"MANAGED_CHECKPOINT_API_UNAVAILABLE",
|
|
264
|
+
"workspace transaction manager does not expose the verified inspect API",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return transactionDetails(manager.inspect(id));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function restoreManagedCheckpoint(manager, id, options = {}) {
|
|
271
|
+
normalizeTransactionId(id);
|
|
272
|
+
if (!manager || typeof manager.restore !== "function") {
|
|
273
|
+
throw commandError(
|
|
274
|
+
"MANAGED_CHECKPOINT_API_UNAVAILABLE",
|
|
275
|
+
"workspace transaction manager does not expose restore",
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const expectedEvidenceDigest = normalizeEvidenceDigest(
|
|
279
|
+
options.expectedEvidenceDigest,
|
|
280
|
+
"expected evidence digest",
|
|
281
|
+
);
|
|
282
|
+
return manager.restore(id, {
|
|
283
|
+
expectedEvidenceDigest,
|
|
284
|
+
force: options.force === true,
|
|
285
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function undoManagedCheckpointRestore(manager, id, options = {}) {
|
|
290
|
+
normalizeTransactionId(id);
|
|
291
|
+
if (!manager || typeof manager.undoRestore !== "function") {
|
|
292
|
+
throw commandError(
|
|
293
|
+
"MANAGED_CHECKPOINT_API_UNAVAILABLE",
|
|
294
|
+
"workspace transaction manager does not expose restore undo",
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const expectedRestoreEvidenceDigest = normalizeEvidenceDigest(
|
|
298
|
+
options.expectedRestoreEvidenceDigest,
|
|
299
|
+
"expected restore evidence digest",
|
|
300
|
+
);
|
|
301
|
+
return manager.undoRestore(id, {
|
|
302
|
+
expectedRestoreEvidenceDigest,
|
|
303
|
+
force: options.force === true,
|
|
304
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function appendBoundedOutput(state, stream, chunk) {
|
|
309
|
+
const value = Buffer.isBuffer(chunk)
|
|
310
|
+
? chunk
|
|
311
|
+
: Buffer.from(String(chunk), "utf8");
|
|
312
|
+
const nextBytes = state[`${stream}Bytes`] + value.byteLength;
|
|
313
|
+
state[`${stream}Bytes`] = nextBytes;
|
|
314
|
+
if (state.captureOutput && nextBytes <= state.maxOutputBytes) {
|
|
315
|
+
state[`${stream}Chunks`].push(value);
|
|
316
|
+
}
|
|
317
|
+
if (!state.captureOutput) {
|
|
318
|
+
state[`${stream}Writer`]?.(value);
|
|
319
|
+
}
|
|
320
|
+
return nextBytes <= state.maxOutputBytes;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function executeForegroundProcess(broker, command, args, options = {}) {
|
|
324
|
+
const output = {
|
|
325
|
+
captureOutput: options.captureOutput === true,
|
|
326
|
+
maxOutputBytes: normalizeCaptureLimit(options.maxOutputBytes),
|
|
327
|
+
stdoutBytes: 0,
|
|
328
|
+
stderrBytes: 0,
|
|
329
|
+
stdoutChunks: [],
|
|
330
|
+
stderrChunks: [],
|
|
331
|
+
stdoutWriter: options.stdoutWriter,
|
|
332
|
+
stderrWriter: options.stderrWriter,
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
return new Promise((resolve, reject) => {
|
|
336
|
+
let proc;
|
|
337
|
+
try {
|
|
338
|
+
proc = broker.spawn(command, args, {
|
|
339
|
+
cwd: options.cwd,
|
|
340
|
+
shell: false,
|
|
341
|
+
detached: false,
|
|
342
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
343
|
+
origin: "checkpoint:managed-run",
|
|
344
|
+
scope: "checkpoint",
|
|
345
|
+
policy: "allow",
|
|
346
|
+
requiredBoundaries: [PROCESS_TREE_BOUNDARY],
|
|
347
|
+
});
|
|
348
|
+
} catch (error) {
|
|
349
|
+
// A post-spawn sandbox failure may occur after the OS process exists.
|
|
350
|
+
// The Broker exposes a close promise in that case. Never race rollback
|
|
351
|
+
// against that writer, even though the transaction will still refuse to
|
|
352
|
+
// claim a tree guarantee that was not established.
|
|
353
|
+
if (
|
|
354
|
+
error?.workspaceProcessClosed &&
|
|
355
|
+
typeof error.workspaceProcessClosed.then === "function"
|
|
356
|
+
) {
|
|
357
|
+
Promise.resolve(error.workspaceProcessClosed).then(
|
|
358
|
+
() => reject(error),
|
|
359
|
+
(closeError) => {
|
|
360
|
+
error.workspaceCloseError = String(
|
|
361
|
+
closeError?.message || closeError,
|
|
362
|
+
);
|
|
363
|
+
reject(error);
|
|
364
|
+
},
|
|
365
|
+
);
|
|
366
|
+
} else {
|
|
367
|
+
reject(error);
|
|
368
|
+
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (!proc || typeof proc.once !== "function") {
|
|
372
|
+
reject(
|
|
373
|
+
commandError(
|
|
374
|
+
"MANAGED_CHECKPOINT_INVALID_PROCESS_HANDLE",
|
|
375
|
+
"Process Broker did not return a close-observable foreground process",
|
|
376
|
+
),
|
|
377
|
+
);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let processError = null;
|
|
382
|
+
let outputLimitError = null;
|
|
383
|
+
let closed = false;
|
|
384
|
+
const collect = (stream, name) => {
|
|
385
|
+
if (!stream || typeof stream.on !== "function") return;
|
|
386
|
+
stream.on("data", (chunk) => {
|
|
387
|
+
if (!appendBoundedOutput(output, name, chunk) && !outputLimitError) {
|
|
388
|
+
outputLimitError = commandError(
|
|
389
|
+
"MANAGED_CHECKPOINT_OUTPUT_LIMIT",
|
|
390
|
+
`${name} exceeded the ${output.maxOutputBytes} byte safety limit`,
|
|
391
|
+
{ stream: name, maxOutputBytes: output.maxOutputBytes },
|
|
392
|
+
);
|
|
393
|
+
try {
|
|
394
|
+
proc.kill("SIGTERM");
|
|
395
|
+
} catch {
|
|
396
|
+
// The close event remains mandatory; rollback cannot race a writer.
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
collect(proc.stdout, "stdout");
|
|
402
|
+
collect(proc.stderr, "stderr");
|
|
403
|
+
proc.once("error", (error) => {
|
|
404
|
+
// Node normally emits `close` after `error`. Do not let rollback start on
|
|
405
|
+
// `error`, because inherited/duplicated stdio and descendants may still
|
|
406
|
+
// be alive until `close`.
|
|
407
|
+
processError = error;
|
|
408
|
+
});
|
|
409
|
+
proc.once("close", (exitCode, signal) => {
|
|
410
|
+
if (closed) return;
|
|
411
|
+
closed = true;
|
|
412
|
+
const result = {
|
|
413
|
+
exitCode: Number.isInteger(exitCode) ? exitCode : null,
|
|
414
|
+
signal: typeof signal === "string" ? signal : null,
|
|
415
|
+
stdoutBytes: output.stdoutBytes,
|
|
416
|
+
stderrBytes: output.stderrBytes,
|
|
417
|
+
stdout: output.captureOutput
|
|
418
|
+
? Buffer.concat(output.stdoutChunks).toString("utf8")
|
|
419
|
+
: null,
|
|
420
|
+
stderr: output.captureOutput
|
|
421
|
+
? Buffer.concat(output.stderrChunks).toString("utf8")
|
|
422
|
+
: null,
|
|
423
|
+
};
|
|
424
|
+
if (outputLimitError || processError) {
|
|
425
|
+
const error = outputLimitError || processError;
|
|
426
|
+
error.processResult = result;
|
|
427
|
+
reject(error);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
resolve(result);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function managedRunResult(transaction, evidence, processResult, ok) {
|
|
436
|
+
const state = transaction.snapshot();
|
|
437
|
+
const coverageState = {
|
|
438
|
+
coverage:
|
|
439
|
+
evidence?.coverage ||
|
|
440
|
+
state.coverage ||
|
|
441
|
+
WORKSPACE_TRANSACTION_COVERAGE.NONE,
|
|
442
|
+
fileCoverage:
|
|
443
|
+
evidence?.fileCoverage ||
|
|
444
|
+
state.fileCoverage ||
|
|
445
|
+
WORKSPACE_TRANSACTION_COVERAGE.NONE,
|
|
446
|
+
externalSideEffects:
|
|
447
|
+
evidence?.externalSideEffects ?? state.externalSideEffects ?? true,
|
|
448
|
+
uncoveredPaths: evidence?.uncoveredPaths || state.uncoveredPaths || [],
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
ok,
|
|
452
|
+
transactionId: transaction.id,
|
|
453
|
+
checkpointId: transaction.checkpointId,
|
|
454
|
+
state: state.state,
|
|
455
|
+
outcome: evidence?.outcome || (ok ? "committed" : "rolled_back"),
|
|
456
|
+
exitCode: processResult?.exitCode ?? null,
|
|
457
|
+
signal: processResult?.signal ?? null,
|
|
458
|
+
evidenceDigest: evidence?.evidenceDigest || null,
|
|
459
|
+
checkpointDigest:
|
|
460
|
+
evidence?.checkpointDigest || state.checkpoint?.digest || null,
|
|
461
|
+
writeManifestDigest: evidence?.writeManifestDigest || null,
|
|
462
|
+
coverage: coverageState.coverage,
|
|
463
|
+
fileCoverage: coverageState.fileCoverage,
|
|
464
|
+
externalSideEffects: coverageState.externalSideEffects,
|
|
465
|
+
uncoveredPaths: [...coverageState.uncoveredPaths],
|
|
466
|
+
stdout: processResult?.stdout ?? null,
|
|
467
|
+
stderr: processResult?.stderr ?? null,
|
|
468
|
+
stdoutBytes: processResult?.stdoutBytes ?? 0,
|
|
469
|
+
stderrBytes: processResult?.stderrBytes ?? 0,
|
|
470
|
+
warnings: coverageWarnings(coverageState),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function attachRollbackFailure(primaryError, transaction, rollbackError) {
|
|
475
|
+
const snapshot = transaction.snapshot();
|
|
476
|
+
const error = commandError(
|
|
477
|
+
"MANAGED_CHECKPOINT_ROLLBACK_FAILED",
|
|
478
|
+
`managed command did not commit, but workspace rollback could not be proven: ${rollbackError.message}`,
|
|
479
|
+
{
|
|
480
|
+
transactionId: transaction.id,
|
|
481
|
+
checkpointId: transaction.checkpointId,
|
|
482
|
+
transactionState: {
|
|
483
|
+
id: snapshot.id,
|
|
484
|
+
state: snapshot.state,
|
|
485
|
+
workspaceRoot: snapshot.workspaceRoot,
|
|
486
|
+
coverage: snapshot.coverage || WORKSPACE_TRANSACTION_COVERAGE.NONE,
|
|
487
|
+
fileCoverage:
|
|
488
|
+
snapshot.fileCoverage || WORKSPACE_TRANSACTION_COVERAGE.NONE,
|
|
489
|
+
uncoveredPaths: Array.isArray(snapshot.uncoveredPaths)
|
|
490
|
+
? snapshot.uncoveredPaths
|
|
491
|
+
: [],
|
|
492
|
+
evidenceDigest: snapshot.evidence?.evidenceDigest || null,
|
|
493
|
+
failureEvidence: Array.isArray(snapshot.failureEvidence)
|
|
494
|
+
? snapshot.failureEvidence
|
|
495
|
+
: [],
|
|
496
|
+
},
|
|
497
|
+
primaryError: {
|
|
498
|
+
code: primaryError?.code || "MANAGED_COMMAND_FAILED",
|
|
499
|
+
message: String(primaryError?.message || primaryError),
|
|
500
|
+
},
|
|
501
|
+
rollbackError: {
|
|
502
|
+
code: rollbackError?.code || "WORKSPACE_TRANSACTION_ROLLBACK_FAILED",
|
|
503
|
+
message: String(rollbackError?.message || rollbackError),
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
);
|
|
507
|
+
error.cause = rollbackError;
|
|
508
|
+
return error;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function rollbackAfterError(transaction, error, reason) {
|
|
512
|
+
try {
|
|
513
|
+
const evidence = transaction.rollback({ reason });
|
|
514
|
+
error.managedTransactionResult = managedRunResult(
|
|
515
|
+
transaction,
|
|
516
|
+
evidence,
|
|
517
|
+
error.processResult || null,
|
|
518
|
+
false,
|
|
519
|
+
);
|
|
520
|
+
return error;
|
|
521
|
+
} catch (rollbackError) {
|
|
522
|
+
return attachRollbackFailure(error, transaction, rollbackError);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Execute one literal foreground command inside a partial workspace
|
|
528
|
+
* transaction. The command always passes through ProcessExecutionBroker with a
|
|
529
|
+
* mandatory process-tree boundary. A missing boundary is therefore denied by
|
|
530
|
+
* the Broker before command execution; it is never downgraded.
|
|
531
|
+
*/
|
|
532
|
+
export async function runManagedWorkspaceCommand(options = {}) {
|
|
533
|
+
const broker = options.broker;
|
|
534
|
+
if (
|
|
535
|
+
!broker ||
|
|
536
|
+
typeof broker.beginWorkspaceTransaction !== "function" ||
|
|
537
|
+
typeof broker.spawn !== "function"
|
|
538
|
+
) {
|
|
539
|
+
throw commandError(
|
|
540
|
+
"MANAGED_CHECKPOINT_BROKER_REQUIRED",
|
|
541
|
+
"managed run requires ProcessExecutionBroker",
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
if (options.detached === true || options.background === true) {
|
|
545
|
+
throw commandError(
|
|
546
|
+
"MANAGED_CHECKPOINT_FOREGROUND_REQUIRED",
|
|
547
|
+
"managed run rejects detached/background execution",
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const normalized = normalizeCommand(options.command, options.args || []);
|
|
551
|
+
const workspaceRoot = path.resolve(options.workspaceRoot || ".");
|
|
552
|
+
const runId = options.runId || `managed-run-${crypto.randomUUID()}`;
|
|
553
|
+
const taskKey = options.taskKey || "managed-command";
|
|
554
|
+
const captureOutput = options.captureOutput === true;
|
|
555
|
+
const maxOutputBytes = normalizeCaptureLimit(options.maxOutputBytes);
|
|
556
|
+
|
|
557
|
+
const transaction = broker.beginWorkspaceTransaction({
|
|
558
|
+
workspaceRoot,
|
|
559
|
+
runId,
|
|
560
|
+
taskKey,
|
|
561
|
+
coverageTarget: WORKSPACE_TRANSACTION_COVERAGE.PARTIAL,
|
|
562
|
+
externalSideEffects: true,
|
|
563
|
+
writerIsolation: "unknown",
|
|
564
|
+
exclusions: Array.isArray(options.exclusions) ? options.exclusions : [],
|
|
565
|
+
...(options.stateDir ? { stateDir: path.resolve(options.stateDir) } : {}),
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
let processResult;
|
|
569
|
+
try {
|
|
570
|
+
processResult = await executeForegroundProcess(
|
|
571
|
+
broker,
|
|
572
|
+
normalized.command,
|
|
573
|
+
normalized.args,
|
|
574
|
+
{
|
|
575
|
+
cwd: workspaceRoot,
|
|
576
|
+
captureOutput,
|
|
577
|
+
maxOutputBytes,
|
|
578
|
+
stdoutWriter: options.stdoutWriter,
|
|
579
|
+
stderrWriter: options.stderrWriter,
|
|
580
|
+
},
|
|
581
|
+
);
|
|
582
|
+
} catch (error) {
|
|
583
|
+
throw rollbackAfterError(
|
|
584
|
+
transaction,
|
|
585
|
+
error,
|
|
586
|
+
`managed command failed before a clean exit: ${error.message}`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (processResult.exitCode !== 0) {
|
|
591
|
+
try {
|
|
592
|
+
const evidence = transaction.rollback({
|
|
593
|
+
reason:
|
|
594
|
+
processResult.signal != null
|
|
595
|
+
? `managed command terminated by ${processResult.signal}`
|
|
596
|
+
: `managed command exited ${processResult.exitCode ?? "without a status"}`,
|
|
597
|
+
});
|
|
598
|
+
return managedRunResult(transaction, evidence, processResult, false);
|
|
599
|
+
} catch (rollbackError) {
|
|
600
|
+
throw attachRollbackFailure(
|
|
601
|
+
commandError(
|
|
602
|
+
"MANAGED_CHECKPOINT_COMMAND_FAILED",
|
|
603
|
+
`managed command exited ${processResult.exitCode ?? "without a status"}`,
|
|
604
|
+
{ processResult },
|
|
605
|
+
),
|
|
606
|
+
transaction,
|
|
607
|
+
rollbackError,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
try {
|
|
613
|
+
const evidence = transaction.accept();
|
|
614
|
+
return managedRunResult(transaction, evidence, processResult, true);
|
|
615
|
+
} catch (acceptError) {
|
|
616
|
+
throw rollbackAfterError(
|
|
617
|
+
transaction,
|
|
618
|
+
acceptError,
|
|
619
|
+
`managed command commit failed: ${acceptError.message}`,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function safeErrorDetails(error) {
|
|
625
|
+
const details = {};
|
|
626
|
+
for (const key of [
|
|
627
|
+
"transactionId",
|
|
628
|
+
"restoreId",
|
|
629
|
+
"undoId",
|
|
630
|
+
"state",
|
|
631
|
+
"expectedEvidenceDigest",
|
|
632
|
+
"actualEvidenceDigest",
|
|
633
|
+
"expectedRestoreEvidenceDigest",
|
|
634
|
+
"actualRestoreEvidenceDigest",
|
|
635
|
+
"conflictDigest",
|
|
636
|
+
"conflicts",
|
|
637
|
+
"unsafe",
|
|
638
|
+
"checkpointId",
|
|
639
|
+
"transactionState",
|
|
640
|
+
"primaryError",
|
|
641
|
+
"rollbackError",
|
|
642
|
+
"managedTransactionResult",
|
|
643
|
+
]) {
|
|
644
|
+
if (error?.[key] !== undefined) details[key] = error[key];
|
|
645
|
+
}
|
|
646
|
+
return details;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function defaultOutput() {
|
|
650
|
+
return {
|
|
651
|
+
json(value) {
|
|
652
|
+
console.log(JSON.stringify(value, null, 2));
|
|
653
|
+
},
|
|
654
|
+
line(value) {
|
|
655
|
+
logger.log(value);
|
|
656
|
+
},
|
|
657
|
+
error(error, json) {
|
|
658
|
+
if (json) {
|
|
659
|
+
console.error(
|
|
660
|
+
JSON.stringify(
|
|
661
|
+
{
|
|
662
|
+
ok: false,
|
|
663
|
+
code: error?.code || "MANAGED_CHECKPOINT_ERROR",
|
|
664
|
+
message: String(error?.message || error),
|
|
665
|
+
...safeErrorDetails(error),
|
|
666
|
+
},
|
|
667
|
+
null,
|
|
668
|
+
2,
|
|
669
|
+
),
|
|
670
|
+
);
|
|
671
|
+
} else {
|
|
672
|
+
logger.error(
|
|
673
|
+
chalk.red(
|
|
674
|
+
`managed checkpoint failed [${error?.code || "MANAGED_CHECKPOINT_ERROR"}]: ${error?.message || error}`,
|
|
675
|
+
),
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async function confirmMutation(kind, id, options, dependencies) {
|
|
683
|
+
if (options.yes === true) return true;
|
|
684
|
+
const isTTY =
|
|
685
|
+
dependencies.isTTY === undefined
|
|
686
|
+
? process.stdin.isTTY === true
|
|
687
|
+
: dependencies.isTTY === true;
|
|
688
|
+
if (!isTTY) {
|
|
689
|
+
throw commandError(
|
|
690
|
+
"MANAGED_CHECKPOINT_CONFIRMATION_REQUIRED",
|
|
691
|
+
`refusing to ${kind} in non-interactive mode without --yes`,
|
|
692
|
+
{ transactionId: id },
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
const confirm =
|
|
696
|
+
dependencies.confirm ||
|
|
697
|
+
(async (message) => {
|
|
698
|
+
const prompts = await import("@inquirer/prompts");
|
|
699
|
+
return prompts.confirm({ message, default: false }).catch(() => false);
|
|
700
|
+
});
|
|
701
|
+
return (
|
|
702
|
+
(await confirm(
|
|
703
|
+
`${kind === "restore" ? "Restore" : "Undo restore for"} managed transaction ${id}? This rewrites workspace files, takes/uses a safety checkpoint, and does not reverse external side effects.${options.force ? " --force also authorizes overwriting post-checkpoint conflicts." : ""}`,
|
|
704
|
+
)) === true
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function writeSummary(output, item) {
|
|
709
|
+
output.line(
|
|
710
|
+
`${item.id} ${item.state} overall=${item.coverage} files=${item.fileCoverage}`,
|
|
711
|
+
);
|
|
712
|
+
output.line(` workspace: ${item.workspaceRoot || "(unknown)"}`);
|
|
713
|
+
if (item.updatedAt) output.line(` updated: ${item.updatedAt}`);
|
|
714
|
+
if (item.evidenceDigest) {
|
|
715
|
+
output.line(` evidence: ${item.evidenceDigest}`);
|
|
716
|
+
}
|
|
717
|
+
for (const warning of item.warnings) {
|
|
718
|
+
output.line(chalk.yellow(` warning: ${warning}`));
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Register managed transaction commands under an existing `checkpoint`
|
|
724
|
+
* Commander command. `dependencies` is intentionally injectable so the
|
|
725
|
+
* destructive command policy can be tested without mutating global state.
|
|
726
|
+
*/
|
|
727
|
+
export function registerManagedCheckpointCommands(
|
|
728
|
+
checkpointCommand,
|
|
729
|
+
dependencies = {},
|
|
730
|
+
) {
|
|
731
|
+
const managerFactory =
|
|
732
|
+
dependencies.managerFactory ||
|
|
733
|
+
((options) => new WorkspaceTransactionManager(options));
|
|
734
|
+
const output = dependencies.output || defaultOutput();
|
|
735
|
+
|
|
736
|
+
const managed = checkpointCommand
|
|
737
|
+
.command("managed")
|
|
738
|
+
.description(
|
|
739
|
+
"Run, inspect, and explicitly restore Process Broker workspace transactions",
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
const addStoreOptions = (command) =>
|
|
743
|
+
command.option(
|
|
744
|
+
"--state-dir <dir>",
|
|
745
|
+
"Managed transaction state directory (outside the workspace)",
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
addStoreOptions(
|
|
749
|
+
managed
|
|
750
|
+
.command("list")
|
|
751
|
+
.alias("ls")
|
|
752
|
+
.description("List verified managed workspace transactions")
|
|
753
|
+
.option("--workspace <dir>", "Only transactions for this workspace")
|
|
754
|
+
.option("--json", "Output machine-readable JSON"),
|
|
755
|
+
).action(async (options) => {
|
|
756
|
+
try {
|
|
757
|
+
const manager = managerFactory(managerOptions(options));
|
|
758
|
+
const records = listManagedCheckpoints(manager, {
|
|
759
|
+
workspaceRoot: resolveWorkspaceFilter(options.workspace),
|
|
760
|
+
});
|
|
761
|
+
if (options.json) {
|
|
762
|
+
output.json(records);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
if (records.length === 0) {
|
|
766
|
+
output.line("No managed workspace transactions.");
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
records.forEach((item) => writeSummary(output, item));
|
|
770
|
+
} catch (error) {
|
|
771
|
+
output.error(error, options.json === true);
|
|
772
|
+
process.exitCode = 1;
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
addStoreOptions(
|
|
777
|
+
managed
|
|
778
|
+
.command("show <transaction-id>")
|
|
779
|
+
.description("Show verified transaction, coverage, and restore evidence")
|
|
780
|
+
.option("--json", "Output machine-readable JSON"),
|
|
781
|
+
).action(async (id, options) => {
|
|
782
|
+
try {
|
|
783
|
+
const manager = managerFactory(managerOptions(options));
|
|
784
|
+
const details = showManagedCheckpoint(manager, id);
|
|
785
|
+
if (options.json) {
|
|
786
|
+
output.json(details);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
writeSummary(output, details);
|
|
790
|
+
if (details.checkpoint) {
|
|
791
|
+
output.line(
|
|
792
|
+
` checkpoint: ${details.checkpoint.id} ${details.checkpoint.digest}`,
|
|
793
|
+
);
|
|
794
|
+
output.line(
|
|
795
|
+
` captured: ${details.checkpoint.files ?? "?"} files, ${details.checkpoint.bytes ?? "?"} bytes`,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
if (details.restoreEvidenceDigest) {
|
|
799
|
+
output.line(` restore evidence: ${details.restoreEvidenceDigest}`);
|
|
800
|
+
}
|
|
801
|
+
if (details.undoRestoreEvidenceDigest) {
|
|
802
|
+
output.line(` undo evidence: ${details.undoRestoreEvidenceDigest}`);
|
|
803
|
+
}
|
|
804
|
+
} catch (error) {
|
|
805
|
+
output.error(error, options.json === true);
|
|
806
|
+
process.exitCode = 1;
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
addStoreOptions(
|
|
811
|
+
managed
|
|
812
|
+
.command("run [command...]")
|
|
813
|
+
.description(
|
|
814
|
+
"Run one literal foreground command in a partial managed transaction (use -- before the command)",
|
|
815
|
+
)
|
|
816
|
+
.option("-d, --dir <dir>", "Workspace root", ".")
|
|
817
|
+
.option("--run-id <id>", "Durable run binding")
|
|
818
|
+
.option("--task-key <key>", "Durable task binding", "managed-command")
|
|
819
|
+
.option(
|
|
820
|
+
"--exclude <paths...>",
|
|
821
|
+
"Explicit uncovered workspace-relative paths",
|
|
822
|
+
)
|
|
823
|
+
.option(
|
|
824
|
+
"--max-output-bytes <bytes>",
|
|
825
|
+
"Per-stream output safety limit (max 67108864)",
|
|
826
|
+
String(DEFAULT_MAX_CAPTURE_BYTES),
|
|
827
|
+
)
|
|
828
|
+
.option(
|
|
829
|
+
"--background",
|
|
830
|
+
"Rejected: managed transactions require foreground execution",
|
|
831
|
+
)
|
|
832
|
+
.option(
|
|
833
|
+
"--json",
|
|
834
|
+
"Capture command output and emit machine-readable JSON",
|
|
835
|
+
),
|
|
836
|
+
).action(async (tokens, options) => {
|
|
837
|
+
try {
|
|
838
|
+
const broker =
|
|
839
|
+
dependencies.broker ||
|
|
840
|
+
(await import("../lib/process-execution-broker/index.js"))
|
|
841
|
+
.executionBroker;
|
|
842
|
+
const command = Array.isArray(tokens) ? tokens[0] : null;
|
|
843
|
+
const args = Array.isArray(tokens) ? tokens.slice(1) : [];
|
|
844
|
+
const result = await runManagedWorkspaceCommand({
|
|
845
|
+
broker,
|
|
846
|
+
command,
|
|
847
|
+
args,
|
|
848
|
+
workspaceRoot: options.dir,
|
|
849
|
+
runId: options.runId,
|
|
850
|
+
taskKey: options.taskKey,
|
|
851
|
+
stateDir: options.stateDir,
|
|
852
|
+
exclusions: options.exclude || [],
|
|
853
|
+
background: options.background === true,
|
|
854
|
+
captureOutput: options.json === true,
|
|
855
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
856
|
+
stdoutWriter:
|
|
857
|
+
dependencies.stdoutWriter || ((chunk) => process.stdout.write(chunk)),
|
|
858
|
+
stderrWriter:
|
|
859
|
+
dependencies.stderrWriter || ((chunk) => process.stderr.write(chunk)),
|
|
860
|
+
});
|
|
861
|
+
if (options.json) {
|
|
862
|
+
output.json(result);
|
|
863
|
+
} else {
|
|
864
|
+
output.line(
|
|
865
|
+
result.ok
|
|
866
|
+
? chalk.green(
|
|
867
|
+
`Committed managed transaction ${result.transactionId}.`,
|
|
868
|
+
)
|
|
869
|
+
: chalk.yellow(
|
|
870
|
+
`Command failed; rolled back managed transaction ${result.transactionId}.`,
|
|
871
|
+
),
|
|
872
|
+
);
|
|
873
|
+
output.line(` checkpoint: ${result.checkpointId}`);
|
|
874
|
+
output.line(` evidence: ${result.evidenceDigest}`);
|
|
875
|
+
output.line(
|
|
876
|
+
` coverage: overall=${result.coverage} files=${result.fileCoverage}`,
|
|
877
|
+
);
|
|
878
|
+
for (const warning of result.warnings) {
|
|
879
|
+
output.line(chalk.yellow(` warning: ${warning}`));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (!result.ok) {
|
|
883
|
+
process.exitCode =
|
|
884
|
+
Number.isInteger(result.exitCode) &&
|
|
885
|
+
result.exitCode > 0 &&
|
|
886
|
+
result.exitCode <= 255
|
|
887
|
+
? result.exitCode
|
|
888
|
+
: 1;
|
|
889
|
+
}
|
|
890
|
+
} catch (error) {
|
|
891
|
+
output.error(error, options.json === true);
|
|
892
|
+
process.exitCode = 1;
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
addStoreOptions(
|
|
897
|
+
managed
|
|
898
|
+
.command("restore <transaction-id>")
|
|
899
|
+
.description(
|
|
900
|
+
"Restore a committed managed checkpoint (evidence digest required)",
|
|
901
|
+
)
|
|
902
|
+
.requiredOption(
|
|
903
|
+
"--expected-evidence-digest <digest>",
|
|
904
|
+
"Exact committed evidence digest shown by `managed show`",
|
|
905
|
+
)
|
|
906
|
+
.option("--reason <text>", "Human restore reason")
|
|
907
|
+
.option(
|
|
908
|
+
"--force",
|
|
909
|
+
"Overwrite post-commit conflicts; never bypasses evidence/unsafe checks",
|
|
910
|
+
)
|
|
911
|
+
.option("--yes", "Approve the destructive operation without a prompt")
|
|
912
|
+
.option("--json", "Output machine-readable JSON"),
|
|
913
|
+
).action(async (id, options) => {
|
|
914
|
+
try {
|
|
915
|
+
if (!(await confirmMutation("restore", id, options, dependencies))) {
|
|
916
|
+
output.line("Aborted.");
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const manager = managerFactory(managerOptions(options));
|
|
920
|
+
const evidence = restoreManagedCheckpoint(manager, id, {
|
|
921
|
+
expectedEvidenceDigest: options.expectedEvidenceDigest,
|
|
922
|
+
force: options.force,
|
|
923
|
+
reason: options.reason,
|
|
924
|
+
});
|
|
925
|
+
const fileCoverage =
|
|
926
|
+
evidence?.fileCoverage || WORKSPACE_TRANSACTION_COVERAGE.NONE;
|
|
927
|
+
const externalSideEffects = evidence?.externalSideEffects !== false;
|
|
928
|
+
const coverage =
|
|
929
|
+
evidence?.coverage ||
|
|
930
|
+
(fileCoverage === WORKSPACE_TRANSACTION_COVERAGE.FULL &&
|
|
931
|
+
!externalSideEffects
|
|
932
|
+
? WORKSPACE_TRANSACTION_COVERAGE.FULL
|
|
933
|
+
: WORKSPACE_TRANSACTION_COVERAGE.PARTIAL);
|
|
934
|
+
const uncoveredPaths = Array.isArray(evidence?.uncoveredPaths)
|
|
935
|
+
? evidence.uncoveredPaths
|
|
936
|
+
: [];
|
|
937
|
+
const result = {
|
|
938
|
+
ok: true,
|
|
939
|
+
transactionId: id,
|
|
940
|
+
outcome: evidence?.outcome || "restored",
|
|
941
|
+
forced: evidence?.forced === true,
|
|
942
|
+
coverage,
|
|
943
|
+
fileCoverage,
|
|
944
|
+
externalSideEffects,
|
|
945
|
+
uncoveredPaths,
|
|
946
|
+
restoreEvidenceDigest: evidence?.evidenceDigest || null,
|
|
947
|
+
safetyCheckpoint: evidence?.safetyCheckpoint || null,
|
|
948
|
+
warnings: coverageWarnings({
|
|
949
|
+
coverage,
|
|
950
|
+
fileCoverage,
|
|
951
|
+
externalSideEffects,
|
|
952
|
+
uncoveredPaths,
|
|
953
|
+
}),
|
|
954
|
+
};
|
|
955
|
+
if (options.json) {
|
|
956
|
+
output.json(result);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
output.line(chalk.green(`Restored managed transaction ${id}.`));
|
|
960
|
+
output.line(
|
|
961
|
+
` restore evidence: ${result.restoreEvidenceDigest || "(unavailable)"}`,
|
|
962
|
+
);
|
|
963
|
+
output.line(
|
|
964
|
+
` coverage: overall=${result.coverage} files=${result.fileCoverage}`,
|
|
965
|
+
);
|
|
966
|
+
result.warnings.forEach((warning) =>
|
|
967
|
+
output.line(chalk.yellow(` warning: ${warning}`)),
|
|
968
|
+
);
|
|
969
|
+
if (result.restoreEvidenceDigest) {
|
|
970
|
+
output.line(
|
|
971
|
+
` undo with: cc checkpoint managed undo ${id} --expected-restore-evidence-digest ${result.restoreEvidenceDigest} --yes`,
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
} catch (error) {
|
|
975
|
+
output.error(error, options.json === true);
|
|
976
|
+
process.exitCode = 1;
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
addStoreOptions(
|
|
981
|
+
managed
|
|
982
|
+
.command("undo <transaction-id>")
|
|
983
|
+
.description(
|
|
984
|
+
"Undo a completed managed restore using its safety checkpoint",
|
|
985
|
+
)
|
|
986
|
+
.requiredOption(
|
|
987
|
+
"--expected-restore-evidence-digest <digest>",
|
|
988
|
+
"Exact restore evidence digest returned by `managed restore`",
|
|
989
|
+
)
|
|
990
|
+
.option("--reason <text>", "Human undo reason")
|
|
991
|
+
.option(
|
|
992
|
+
"--force",
|
|
993
|
+
"Overwrite post-restore conflicts; never bypasses evidence/unsafe checks",
|
|
994
|
+
)
|
|
995
|
+
.option("--yes", "Approve the destructive operation without a prompt")
|
|
996
|
+
.option("--json", "Output machine-readable JSON"),
|
|
997
|
+
).action(async (id, options) => {
|
|
998
|
+
try {
|
|
999
|
+
if (!(await confirmMutation("undo", id, options, dependencies))) {
|
|
1000
|
+
output.line("Aborted.");
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const manager = managerFactory(managerOptions(options));
|
|
1004
|
+
const evidence = undoManagedCheckpointRestore(manager, id, {
|
|
1005
|
+
expectedRestoreEvidenceDigest: options.expectedRestoreEvidenceDigest,
|
|
1006
|
+
force: options.force,
|
|
1007
|
+
reason: options.reason,
|
|
1008
|
+
});
|
|
1009
|
+
const fileCoverage =
|
|
1010
|
+
evidence?.fileCoverage || WORKSPACE_TRANSACTION_COVERAGE.NONE;
|
|
1011
|
+
const externalSideEffects = evidence?.externalSideEffects !== false;
|
|
1012
|
+
const coverage =
|
|
1013
|
+
evidence?.coverage ||
|
|
1014
|
+
(fileCoverage === WORKSPACE_TRANSACTION_COVERAGE.FULL &&
|
|
1015
|
+
!externalSideEffects
|
|
1016
|
+
? WORKSPACE_TRANSACTION_COVERAGE.FULL
|
|
1017
|
+
: WORKSPACE_TRANSACTION_COVERAGE.PARTIAL);
|
|
1018
|
+
const uncoveredPaths = Array.isArray(evidence?.uncoveredPaths)
|
|
1019
|
+
? evidence.uncoveredPaths
|
|
1020
|
+
: [];
|
|
1021
|
+
const result = {
|
|
1022
|
+
ok: true,
|
|
1023
|
+
transactionId: id,
|
|
1024
|
+
outcome: evidence?.outcome || "restore_undone",
|
|
1025
|
+
forced: evidence?.forced === true,
|
|
1026
|
+
coverage,
|
|
1027
|
+
fileCoverage,
|
|
1028
|
+
externalSideEffects,
|
|
1029
|
+
uncoveredPaths,
|
|
1030
|
+
undoRestoreEvidenceDigest: evidence?.evidenceDigest || null,
|
|
1031
|
+
warnings: coverageWarnings({
|
|
1032
|
+
coverage,
|
|
1033
|
+
fileCoverage,
|
|
1034
|
+
externalSideEffects,
|
|
1035
|
+
uncoveredPaths,
|
|
1036
|
+
}),
|
|
1037
|
+
};
|
|
1038
|
+
if (options.json) {
|
|
1039
|
+
output.json(result);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
output.line(chalk.green(`Undid managed restore for transaction ${id}.`));
|
|
1043
|
+
output.line(
|
|
1044
|
+
` undo evidence: ${result.undoRestoreEvidenceDigest || "(unavailable)"}`,
|
|
1045
|
+
);
|
|
1046
|
+
output.line(
|
|
1047
|
+
` coverage: overall=${result.coverage} files=${result.fileCoverage}`,
|
|
1048
|
+
);
|
|
1049
|
+
result.warnings.forEach((warning) =>
|
|
1050
|
+
output.line(chalk.yellow(` warning: ${warning}`)),
|
|
1051
|
+
);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
output.error(error, options.json === true);
|
|
1054
|
+
process.exitCode = 1;
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
return managed;
|
|
1059
|
+
}
|