chainlesschain 0.162.188 → 0.162.193

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/README.md +61 -11
  2. package/bin/chainlesschain.js +7 -52
  3. package/completions/_cc +209 -0
  4. package/completions/cc.bash +18 -0
  5. package/completions/cc.fish +13 -0
  6. package/completions/cc.ps1 +226 -0
  7. package/package.json +10 -2
  8. package/src/assets/web-panel/assets/{AIOps-Brr1LVcE.js → AIOps-DBLkaTje.js} +1 -1
  9. package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-BAy-Qebo.js} +1 -1
  10. package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-Dfb1Mlau.js} +3 -3
  11. package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-BbFkN8m0.js} +5 -5
  12. package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-kM4-cEEH.js} +1 -1
  13. package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-BSQfYWf3.js} +1 -1
  14. package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-CWDbI_fG.js} +1 -1
  15. package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BxLPbq_Q.js} +1 -1
  16. package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-DuIbdrp6.js} +1 -1
  17. package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-NEmjGahU.js} +5 -5
  18. package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
  19. package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CaoeG3Ns.js} +1 -1
  20. package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-BFf_g3xY.js} +1 -1
  21. package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CNOd8mdQ.js} +1 -1
  22. package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-BhpDqz-i.js} +1 -1
  23. package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-CQ42T7Z6.js} +1 -1
  24. package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-DI5PcLMN.js} +1 -1
  25. package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-wYYZe0wR.js} +3 -3
  26. package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-Dlq03DD_.js} +2 -2
  27. package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BzqS8PP3.js} +1 -1
  28. package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-It55Yem7.js} +2 -2
  29. package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-9HkH6oxn.js} +2 -2
  30. package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-00e2YWRB.js} +1 -1
  31. package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-By-ugRDL.js} +1 -1
  32. package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
  33. package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-BME5sbZY.js} +1 -1
  34. package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-B4TEZCr-.js} +1 -1
  35. package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-BrS6WVSr.js} +2 -2
  37. package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-BMdCWnKF.js} +1 -1
  38. package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-Bcp3vzcA.js} +1 -1
  39. package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
  40. package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-D-W_6x09.js} +2 -2
  41. package/src/assets/web-panel/assets/MarkdownRenderer-DElGPxCZ.js +1 -0
  42. package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-Dzn2guvI.js} +1 -1
  43. package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-uPict-KL.js} +6 -6
  44. package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-BCw43kOb.js} +2 -2
  45. package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-DmV8Wv7F.js} +1 -1
  46. package/src/assets/web-panel/assets/{MobileProjects-COBbEOlj.js → MobileProjects-C-HaCmSM.js} +1 -1
  47. package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-C6jxJ34Y.js} +5 -5
  48. package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-Za8ExwhQ.js} +5 -5
  49. package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-KKDbuEgb.js} +3 -3
  50. package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-Dj97zbCk.js} +1 -1
  51. package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DBXvMraU.js} +4 -4
  52. package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-DOxITLYl.js} +1 -1
  53. package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
  54. package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D1i73mjb.js} +4 -4
  55. package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-kW6Q3rvg.js} +1 -1
  56. package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-C-v_8p2v.js} +2 -2
  57. package/src/assets/web-panel/assets/PdhVaultBrowser-tUbbY64W.js +7 -0
  58. package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-DkHxEZ58.js} +4 -4
  59. package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-DWuMPGrK.js} +5 -5
  60. package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-30IYXWWm.js} +1 -1
  61. package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-DZNQ4RrH.js} +1 -1
  62. package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-De2MCElf.js} +2 -2
  63. package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-5uHk9wsC.js} +2 -2
  64. package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-XjDfrFLb.js} +1 -1
  65. package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-Bgwj6gcE.js} +1 -1
  66. package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-gKql9umv.js} +1 -1
  67. package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-BWvmwkh7.js} +1 -1
  68. package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-Uok4hZPd.js} +1 -1
  69. package/src/assets/web-panel/assets/{RemoteSession-oOVIv9wq.js → RemoteSession-DLJzTk9a.js} +3 -3
  70. package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-ZVwIPMSO.js} +1 -1
  71. package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CN04HxWR.js} +1 -1
  72. package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BdSrEG4g.js} +3 -3
  73. package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-5PwVd0IR.js} +1 -1
  74. package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-DdBo4zjN.js} +3 -3
  75. package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-D3letU7M.js} +2 -2
  76. package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton--Jb83f1v.js} +1 -1
  77. package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-Bg-CulHX.js} +1 -1
  78. package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-7QOa7S-X.js} +1 -1
  79. package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-DQwppjRw.js} +1 -1
  80. package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-dNPQGL-s.js} +2 -2
  81. package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-Ggs-Z48q.js} +1 -1
  82. package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-78TYHgRO.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-xHwwv9vO.js} +1 -1
  84. package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BJXSJfm4.js} +2 -2
  85. package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-fLbjRhJw.js} +1 -1
  86. package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-oMSBSBmB.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-CfnY_wX_.js} +1 -1
  88. package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-AdyTgYKO.js} +1 -1
  89. package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-C5K_81dS.js} +1 -1
  90. package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-vcGVDqki.js} +1 -1
  91. package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-hhOc3hlY.js} +4 -4
  92. package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-CVloC11s.js} +3 -3
  93. package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-CYeE32R5.js} +1 -1
  94. package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-CNROR5bN.js} +1 -1
  95. package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-Cjx-cnwU.js} +1 -1
  96. package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-DOIfelvD.js} +1 -1
  97. package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-DfA7UZ8u.js} +1 -1
  98. package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
  99. package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-BMSCzcfM.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-5-HXgOX5.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-AWg-0M1S.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-B4iYzpkL.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-B5Udeb_6.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-B7gf77CJ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-BEMwBBHk.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-BM1y_9hM.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-BUonmKSE.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-BbQ2eQ8H.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-BemJ2u_E.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-BolaOQxx.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-BxOk9vkG.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-By61ojUx.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-C2K9SNVX.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-C3L5gdiD.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-C7xn0Nub.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-COG61sRv.js → index-CAnvhwg6.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CEKMcOyU.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-CNC5onm4.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CVL27kXC.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-CVm1_i3t.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-CdYBye8B.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-ClNMrqEY.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-Cs6l7uRu.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-CshQbilz.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-Cyweh0Hy.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-DBpWXFed.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-DHtpHZAQ.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-DLmn0BoK.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-DPAWBrMf.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-DjEsxO5h.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-FBc_CJBd.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Icdoqmzt.js} +1 -1
  133. package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
  134. package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
  135. package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-dCd8cSUq.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-m2vG9gG_.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-CaslYch6.js → index-mTpHoEzr.js} +1 -1
  138. package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-ti_lnTWl.js} +1 -1
  139. package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
  140. package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-s3iVK41k.js} +1 -1
  141. package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-B6B-E5Jz.js} +1 -1
  142. package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-BiYgTidA.js} +1 -1
  143. package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-pU1RdUDA.js} +1 -1
  144. package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-Cdoq_SeM.js} +1 -1
  145. package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-DIuvAn2K.js} +1 -1
  146. package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-8w16YR2D.js} +1 -1
  147. package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-VZ01cZe3.js} +1 -1
  148. package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-lh8YMN8Z.js} +1 -1
  149. package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-CmDyreec.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
  151. package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-Thnu-Iaj.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
  153. package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-Cg6lFY_X.js} +1 -1
  154. package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-DVfsR1YO.js} +1 -1
  155. package/src/assets/web-panel/index.html +1 -1
  156. package/src/cli-help.js +181 -0
  157. package/src/command-help-index.json +181 -0
  158. package/src/command-manifest.json +1107 -175
  159. package/src/command-surface-policy.js +197 -0
  160. package/src/commands/agent.js +98 -33
  161. package/src/commands/artifacts.js +400 -2
  162. package/src/commands/background-session.js +41 -3
  163. package/src/commands/checkpoint-managed.js +1059 -0
  164. package/src/commands/checkpoint-restore-recovery.js +1476 -0
  165. package/src/commands/checkpoint.js +950 -22
  166. package/src/commands/compact.js +101 -25
  167. package/src/commands/config.js +506 -20
  168. package/src/commands/doctor.js +4 -1
  169. package/src/commands/hub.js +1 -1
  170. package/src/commands/mcp.js +688 -24
  171. package/src/commands/pack.js +16 -2
  172. package/src/commands/plugin.js +20 -12
  173. package/src/commands/session-mcp-recovery.js +218 -0
  174. package/src/commands/session.js +470 -110
  175. package/src/commands/setup.js +36 -10
  176. package/src/commands/skill.js +181 -110
  177. package/src/commands/status.js +21 -5
  178. package/src/commands/team-distributed.js +3324 -0
  179. package/src/commands/team.js +1130 -74
  180. package/src/commands/update.js +68 -6
  181. package/src/constants.js +11 -0
  182. package/src/data/changelog.json +26 -0
  183. package/src/gateways/ws/action-protocol.js +10 -0
  184. package/src/gateways/ws/session-protocol.js +555 -27
  185. package/src/gateways/ws/ws-agent-handler.js +541 -14
  186. package/src/gateways/ws/ws-session-gateway.js +129 -22
  187. package/src/gateways/ws/ws-session-state.js +431 -0
  188. package/src/harness/background-task-manager.js +233 -36
  189. package/src/harness/jsonl-session-store.js +2551 -167
  190. package/src/harness/mcp-client.js +802 -61
  191. package/src/harness/prompt-compressor.js +227 -27
  192. package/src/harness/session-index.js +89 -38
  193. package/src/harness/session-list-index.js +254 -0
  194. package/src/harness/structured-handoff.js +410 -0
  195. package/src/lazy-dispatch.js +747 -84
  196. package/src/lib/__tests__/logger.test.js +50 -2
  197. package/src/lib/advisor-runtime.js +1047 -0
  198. package/src/lib/agent-core.js +2 -0
  199. package/src/lib/agent-sandbox.js +120 -26
  200. package/src/lib/agent-session-export.js +20 -0
  201. package/src/lib/agent-team/task-lease.js +271 -3
  202. package/src/lib/agent-team/team-adjudication.js +1501 -0
  203. package/src/lib/agent-team/team-budget.js +4 -1
  204. package/src/lib/agent-team/team-control-store.js +582 -0
  205. package/src/lib/agent-team/team-distributed-queue.js +5246 -0
  206. package/src/lib/agent-team/team-process-checkpoint.js +211 -0
  207. package/src/lib/agent-team/team-runner.js +543 -24
  208. package/src/lib/agent-team/team-worktree.js +1403 -95
  209. package/src/lib/artifact-store.js +131 -8
  210. package/src/lib/background-agent-supervisor.js +185 -13
  211. package/src/lib/background-interaction-journal.js +37 -9
  212. package/src/lib/background-launch-profile.js +1058 -0
  213. package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
  214. package/src/lib/checkpoint-restore-orchestrator.js +987 -0
  215. package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
  216. package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
  217. package/src/lib/checkpoint-restore-recovery.js +884 -0
  218. package/src/lib/checkpoint-restore-retention.js +126 -0
  219. package/src/lib/checkpoint-restore-saga.js +5079 -0
  220. package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
  221. package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
  222. package/src/lib/checkpoint-store.js +1688 -97
  223. package/src/lib/checkpoint-timeline-authority.js +522 -0
  224. package/src/lib/checkpoint-timeline.js +642 -0
  225. package/src/lib/config-keys.js +18 -119
  226. package/src/lib/config-manager.js +446 -27
  227. package/src/lib/config-redaction.js +57 -0
  228. package/src/lib/config-schema.js +853 -0
  229. package/src/lib/cowork-mcp-tools.js +5 -0
  230. package/src/lib/cowork-task-runner.js +458 -63
  231. package/src/lib/delivery-coordinator.js +1234 -0
  232. package/src/lib/delivery-evidence.js +450 -0
  233. package/src/lib/doctor-checkup.js +199 -14
  234. package/src/lib/executable-path.js +34 -0
  235. package/src/lib/fatal-handler.js +16 -0
  236. package/src/lib/file-checkpoint.js +4909 -186
  237. package/src/lib/file-lines.js +125 -0
  238. package/src/lib/headless-config-command.js +16 -5
  239. package/src/lib/hook-runner.cjs +139 -38
  240. package/src/lib/hooks-v2-workspace-context.js +10 -0
  241. package/src/lib/impacted-gate-selector.js +424 -0
  242. package/src/lib/interaction-adapter.js +62 -0
  243. package/src/lib/jsonl-session-store.js +1 -35
  244. package/src/lib/logger.js +66 -14
  245. package/src/lib/managed-checkpoint-render.js +60 -0
  246. package/src/lib/managed-tool-checkpoint.js +194 -0
  247. package/src/lib/mcp-call-ledger-store.js +1069 -0
  248. package/src/lib/mcp-call-ledger.js +747 -0
  249. package/src/lib/mcp-client.js +5 -0
  250. package/src/lib/mcp-conflict-scheduler.js +468 -0
  251. package/src/lib/mcp-effect-contract.js +102 -0
  252. package/src/lib/mcp-headers-helper-trust.js +145 -0
  253. package/src/lib/mcp-headers-helper.js +616 -0
  254. package/src/lib/mcp-host-recovery-runtime.js +131 -0
  255. package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
  256. package/src/lib/mcp-recovery-adjudication.js +443 -0
  257. package/src/lib/output-context.js +86 -0
  258. package/src/lib/packer/file-identity.js +170 -0
  259. package/src/lib/packer/index.js +3 -0
  260. package/src/lib/packer/native-update-state.js +277 -0
  261. package/src/lib/packer/pack-update-applier.js +1983 -136
  262. package/src/lib/packer/pack-update-checker.js +34 -0
  263. package/src/lib/packer/pack-update-downloader.js +875 -86
  264. package/src/lib/packer/pack-update-signature.js +86 -0
  265. package/src/lib/packer/pkg-config-generator.js +6 -0
  266. package/src/lib/paths.js +65 -10
  267. package/src/lib/plan-mode.js +831 -14
  268. package/src/lib/plan-persistence.js +540 -0
  269. package/src/lib/plugin-runtime/hooks.js +227 -69
  270. package/src/lib/plugin-runtime/plugin-options.js +189 -44
  271. package/src/lib/pr-automation-policy.js +146 -8
  272. package/src/lib/process-execution-broker/credential-agent.js +7 -0
  273. package/src/lib/process-execution-broker/index.js +491 -13
  274. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
  275. package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
  276. package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
  277. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  278. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  279. package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
  280. package/src/lib/process-execution-broker/workspace-transaction.js +4459 -0
  281. package/src/lib/project-instructions.js +165 -29
  282. package/src/lib/project-mcp-trust.js +67 -0
  283. package/src/lib/public-ide-capability-manifest.js +272 -0
  284. package/src/lib/public-ide-capability-validation.js +351 -0
  285. package/src/lib/secret-store.js +267 -43
  286. package/src/lib/secure-file-identity.js +433 -0
  287. package/src/lib/secure-fs.js +1089 -0
  288. package/src/lib/security-audit.js +58 -0
  289. package/src/lib/service-manager.js +34 -13
  290. package/src/lib/session-budget-runtime.js +1452 -0
  291. package/src/lib/session-host-snapshot.js +388 -0
  292. package/src/lib/session-message-provenance.js +429 -0
  293. package/src/lib/session-projection.js +804 -0
  294. package/src/lib/session-resource-budget.js +1495 -0
  295. package/src/lib/settings-hooks.cjs +138 -22
  296. package/src/lib/settings-loader.cjs +25 -2
  297. package/src/lib/side-effect-ledger-store.js +45 -23
  298. package/src/lib/skill-execution-identity.js +217 -0
  299. package/src/lib/skill-loader.js +299 -18
  300. package/src/lib/sub-agent-context.js +236 -28
  301. package/src/lib/sub-agent-registry.js +5 -0
  302. package/src/lib/subagent-contract.js +94 -0
  303. package/src/lib/todo-manager.js +185 -18
  304. package/src/lib/todo-persistence.js +417 -0
  305. package/src/lib/turn-binding-store.js +41 -23
  306. package/src/lib/turn-binding.js +160 -2
  307. package/src/lib/with-file-lock.js +165 -24
  308. package/src/lib/ws-chat-handler.js +21 -0
  309. package/src/program-base.js +18 -0
  310. package/src/repl/advisor-command.js +103 -0
  311. package/src/repl/agent-repl.js +2175 -146
  312. package/src/repl/btw-command.js +93 -10
  313. package/src/repl/clipboard-image.js +119 -0
  314. package/src/repl/config-summary.js +64 -108
  315. package/src/repl/prompt-editor.js +233 -0
  316. package/src/repl/prompt-interactions.js +344 -0
  317. package/src/repl/prompt-stash.js +354 -0
  318. package/src/repl/prompt-suggestions.js +312 -0
  319. package/src/repl/repl-keybindings.js +227 -0
  320. package/src/repl/session-recap.js +217 -0
  321. package/src/repl/slash-command-registry.js +22 -0
  322. package/src/repl/terminal-layout.js +88 -0
  323. package/src/runtime/agent-core.js +1630 -447
  324. package/src/runtime/coding-agent-contract-shared.cjs +13 -1
  325. package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
  326. package/src/runtime/diagnostics.js +32 -13
  327. package/src/runtime/headless-runner.js +310 -118
  328. package/src/runtime/headless-stream.js +356 -95
  329. package/src/runtime/mcp-config.js +148 -14
  330. package/src/runtime/policies/agent-policy.js +14 -0
  331. package/src/runtime/status-diagnostics-lite.js +137 -0
  332. package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
  333. package/src/assets/web-panel/assets/OrderTableRenderer-BU2Yqq5H.js +0 -1
  334. package/src/assets/web-panel/assets/PdhVaultBrowser-69ca_WeR.js +0 -7
  335. package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
  336. package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
  337. package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
@@ -0,0 +1,1953 @@
1
+ /**
2
+ * Durable partial-mutation rollback controller.
3
+ *
4
+ * The controller owns only orchestration and authority validation. Engine
5
+ * adapters prepare and execute an exact rollback plan; session helpers read or
6
+ * append the timeline settlement. Lock order is always:
7
+ *
8
+ * workspace recovery lease -> session authority -> short saga CAS
9
+ */
10
+
11
+ import path from "node:path";
12
+ import { randomUUID } from "node:crypto";
13
+ import {
14
+ CheckpointRestoreSagaStore,
15
+ computeCheckpointRestoreWorkspaceLockOwnerDigest,
16
+ } from "./checkpoint-restore-saga.js";
17
+ import { computeCheckpointRestoreDigest } from "./checkpoint-restore-orchestrator.js";
18
+ import {
19
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION,
20
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_OUTCOME,
21
+ CHECKPOINT_RESTORE_ROLLBACK_CONVERSATION_DISPOSITION,
22
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION,
23
+ CHECKPOINT_RESTORE_SESSION_RECOVERY_SCHEMA,
24
+ CHECKPOINT_RESTORE_SESSION_RECOVERY_VERSION,
25
+ appendCheckpointRestoreRecoveryResolution,
26
+ computeCheckpointRestoreSessionRollbackCommitDigest,
27
+ reconcileCheckpointRestoreRecoveryResolutionProjection,
28
+ } from "./checkpoint-restore-session-recovery.js";
29
+ import {
30
+ inspectWorkspaceLockOwnerSync,
31
+ withWorkspaceRecoveryLockSync,
32
+ } from "./process-execution-broker/workspace-transaction.js";
33
+
34
+ const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
35
+ const RAW_HASH_PATTERN = /^[a-f0-9]{64}$/;
36
+ const OPERATION_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
37
+ const REQUEST_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/;
38
+ const GIT_IDENTITY_PATTERN = /^git:(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
39
+ const GIT_TREE_PATTERN = /^git-tree:(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
40
+ const COPY_IDENTITY_PATTERN = /^sha256:[a-f0-9]{64}$/;
41
+ const WORKSPACE_BINDING_SCHEMA = "cc-checkpoint-workspace-binding/v1";
42
+ const CHECKPOINT_RESTORE_PURPOSE = "checkpoint-restore";
43
+ const RECOVERY_ADMIN_PHASES = new Set([
44
+ "recovery_required",
45
+ "recovery_started",
46
+ ]);
47
+ const ROLLBACK_PROTOCOL_PHASES = new Set([
48
+ ...RECOVERY_ADMIN_PHASES,
49
+ "rollback_prepared",
50
+ "rollback_started",
51
+ "workspace_rolled_back",
52
+ "session_rollback_committed",
53
+ ]);
54
+ const NON_SETTLED_BASE_PHASES = new Set([
55
+ "mutation_started",
56
+ "rollback_prepared",
57
+ "rollback_started",
58
+ ]);
59
+ const TIMELINE_RESUMABLE_CLASSIFICATIONS = new Set([
60
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION.CODE_SETTLEMENT_RESUMABLE,
61
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION.BOTH_SETTLEMENT_RESUMABLE,
62
+ ]);
63
+ const ACTIONS = new Set(["restore-code", "restore-both"]);
64
+
65
+ export const CHECKPOINT_ROLLBACK_PLAN_SCHEMA =
66
+ "chainlesschain.checkpoint-rollback-plan";
67
+ export const CHECKPOINT_ROLLBACK_PLAN_VERSION = 1;
68
+ export const CHECKPOINT_ROLLBACK_RESULT_SCHEMA =
69
+ "chainlesschain.checkpoint-rollback-result";
70
+ export const CHECKPOINT_ROLLBACK_RESULT_VERSION = 1;
71
+ export const CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_RESULT_SCHEMA =
72
+ "chainlesschain.checkpoint-restore-partial-rollback-result";
73
+ export const CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_RESULT_VERSION = 1;
74
+
75
+ export { CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION };
76
+
77
+ export const CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES = Object.freeze({
78
+ INVALID_ARGUMENT: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_INVALID_ARGUMENT",
79
+ SAGA_CONFLICT: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_SAGA_CONFLICT",
80
+ ACTION_NOT_ALLOWED: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION_NOT_ALLOWED",
81
+ OWNER_CONFLICT: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_OWNER_CONFLICT",
82
+ PLAN_INVALID: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_PLAN_INVALID",
83
+ RESULT_INVALID: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_RESULT_INVALID",
84
+ SESSION_CONFLICT: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_SESSION_CONFLICT",
85
+ ASYNC_UNSUPPORTED: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ASYNC_UNSUPPORTED",
86
+ });
87
+
88
+ export class CheckpointRestorePartialRollbackError extends Error {
89
+ constructor(code, message, details = {}, cause = null) {
90
+ super(message, cause ? { cause } : undefined);
91
+ this.name = "CheckpointRestorePartialRollbackError";
92
+ this.code = code;
93
+ Object.assign(this, details);
94
+ }
95
+ }
96
+
97
+ function rollbackError(code, message, details = {}, cause = null) {
98
+ return new CheckpointRestorePartialRollbackError(
99
+ code,
100
+ message,
101
+ details,
102
+ cause,
103
+ );
104
+ }
105
+
106
+ function isPlainObject(value) {
107
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
108
+ const prototype = Object.getPrototypeOf(value);
109
+ return prototype === Object.prototype || prototype === null;
110
+ }
111
+
112
+ function exactKeys(value, keys) {
113
+ if (!isPlainObject(value)) return false;
114
+ const actual = Object.keys(value).sort();
115
+ const expected = [...keys].sort();
116
+ return (
117
+ actual.length === expected.length &&
118
+ actual.every((key, index) => key === expected[index])
119
+ );
120
+ }
121
+
122
+ function immutable(value) {
123
+ if (Array.isArray(value)) {
124
+ return Object.freeze(value.map((entry) => immutable(entry)));
125
+ }
126
+ if (isPlainObject(value)) {
127
+ const result = {};
128
+ for (const [key, entry] of Object.entries(value)) {
129
+ result[key] = immutable(entry);
130
+ }
131
+ return Object.freeze(result);
132
+ }
133
+ return value;
134
+ }
135
+
136
+ function canonicalJson(value) {
137
+ if (value === undefined || value === null) return "null";
138
+ if (typeof value === "string" || typeof value === "boolean") {
139
+ return JSON.stringify(value);
140
+ }
141
+ if (typeof value === "number" && Number.isFinite(value)) {
142
+ return JSON.stringify(value);
143
+ }
144
+ if (Array.isArray(value)) {
145
+ return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
146
+ }
147
+ if (isPlainObject(value)) {
148
+ return `{${Object.keys(value)
149
+ .sort()
150
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
151
+ .join(",")}}`;
152
+ }
153
+ throw rollbackError(
154
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.INVALID_ARGUMENT,
155
+ "Checkpoint rollback evidence is not canonical JSON",
156
+ );
157
+ }
158
+
159
+ function boundedText(value, fallback, maximum = 2_048) {
160
+ let sanitized = "";
161
+ for (const character of String(value || fallback)) {
162
+ const code = character.charCodeAt(0);
163
+ sanitized += code < 32 || code === 127 ? " " : character;
164
+ }
165
+ return (sanitized.trim() || fallback).slice(0, maximum);
166
+ }
167
+
168
+ function validText(value, maximum = 1_024) {
169
+ return (
170
+ typeof value === "string" &&
171
+ value.length > 0 &&
172
+ value.length <= maximum &&
173
+ !value.includes("\0") &&
174
+ value.trim() === value
175
+ );
176
+ }
177
+
178
+ function asError(value) {
179
+ return value && typeof value === "object"
180
+ ? value
181
+ : new Error(String(value || "checkpoint rollback failed"));
182
+ }
183
+
184
+ function isThenable(value) {
185
+ if (
186
+ value === null ||
187
+ (typeof value !== "object" && typeof value !== "function")
188
+ ) {
189
+ return false;
190
+ }
191
+ try {
192
+ return typeof value.then === "function";
193
+ } catch (cause) {
194
+ throw rollbackError(
195
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.ASYNC_UNSUPPORTED,
196
+ "Checkpoint rollback result exposed an unsafe thenable",
197
+ {},
198
+ cause,
199
+ );
200
+ }
201
+ }
202
+
203
+ function requireSynchronous(value, boundary) {
204
+ if (isThenable(value)) {
205
+ throw rollbackError(
206
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.ASYNC_UNSUPPORTED,
207
+ `${boundary} must be synchronous while workspace authority is held`,
208
+ );
209
+ }
210
+ return value;
211
+ }
212
+
213
+ function assertLeaseOwnedSync(lease, boundary = "workspace lease assertion") {
214
+ if (!lease || typeof lease.assertOwned !== "function") {
215
+ throw rollbackError(
216
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.OWNER_CONFLICT,
217
+ "Checkpoint rollback requires an active workspace recovery lease",
218
+ );
219
+ }
220
+ return requireSynchronous(lease.assertOwned(), boundary);
221
+ }
222
+
223
+ function pathKey(value, platform) {
224
+ if (!validText(value, 4_096) || !path.isAbsolute(value)) return null;
225
+ const resolved = path.resolve(value);
226
+ return platform === "win32" ? resolved.toLowerCase() : resolved;
227
+ }
228
+
229
+ function samePath(left, right, platform) {
230
+ const leftKey = pathKey(left, platform);
231
+ return leftKey !== null && leftKey === pathKey(right, platform);
232
+ }
233
+
234
+ function uniqueEvent(saga, phase) {
235
+ const matches = saga.events.filter((event) => event?.phase === phase);
236
+ return matches.length === 1 ? matches[0] : null;
237
+ }
238
+
239
+ function currentRecoveryBaseEvent(saga) {
240
+ return (
241
+ [...saga.events]
242
+ .reverse()
243
+ .find((event) => !RECOVERY_ADMIN_PHASES.has(event?.phase)) || null
244
+ );
245
+ }
246
+
247
+ function originalDurableBaseEvent(saga) {
248
+ return (
249
+ [...saga.events]
250
+ .reverse()
251
+ .find((event) => !ROLLBACK_PROTOCOL_PHASES.has(event?.phase)) || null
252
+ );
253
+ }
254
+
255
+ function validateExpectedAuthority({
256
+ operationId,
257
+ expectedSeq,
258
+ expectedHash,
259
+ expectedOwnerDigest,
260
+ }) {
261
+ if (
262
+ !OPERATION_ID_PATTERN.test(String(operationId || "")) ||
263
+ !Number.isSafeInteger(expectedSeq) ||
264
+ expectedSeq < 1 ||
265
+ !DIGEST_PATTERN.test(String(expectedHash || "")) ||
266
+ !DIGEST_PATTERN.test(String(expectedOwnerDigest || ""))
267
+ ) {
268
+ throw rollbackError(
269
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.INVALID_ARGUMENT,
270
+ "Partial rollback requires an exact saga head and retained owner digest",
271
+ );
272
+ }
273
+ }
274
+
275
+ function exactSaga(saga, operationId, expectedSeq, expectedHash) {
276
+ if (
277
+ !saga ||
278
+ saga.operationId !== operationId ||
279
+ saga.seq !== expectedSeq ||
280
+ saga.headHash !== expectedHash
281
+ ) {
282
+ throw rollbackError(
283
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.SAGA_CONFLICT,
284
+ "Checkpoint restore saga changed after rollback preview",
285
+ {
286
+ operationId,
287
+ expectedSeq,
288
+ expectedHash,
289
+ actualSeq: saga?.seq ?? null,
290
+ actualHash: saga?.headHash ?? null,
291
+ },
292
+ );
293
+ }
294
+ return saga;
295
+ }
296
+
297
+ function stateDigest(domain, engine, binding, stateIdentity) {
298
+ return computeCheckpointRestoreDigest(domain, {
299
+ engine,
300
+ scopeIdentity: binding.scopeIdentity,
301
+ stateIdentity,
302
+ });
303
+ }
304
+
305
+ function originalPrestateDigest(engine, binding) {
306
+ return stateDigest(
307
+ "cc-checkpoint-restore-prestate-v1",
308
+ engine,
309
+ binding,
310
+ binding.prestateIdentity,
311
+ );
312
+ }
313
+
314
+ function rollbackPrestateDigest(engine, binding) {
315
+ return stateDigest(
316
+ "cc-checkpoint-restore-rollback-prestate-v1",
317
+ engine,
318
+ binding,
319
+ binding.prestateIdentity,
320
+ );
321
+ }
322
+
323
+ function rollbackStateDigest(engine, binding) {
324
+ return stateDigest(
325
+ "cc-checkpoint-restore-rollback-state-v1",
326
+ engine,
327
+ binding,
328
+ binding.targetPoststateIdentity,
329
+ );
330
+ }
331
+
332
+ function mutationSetIdentity(engine, paths) {
333
+ return computeCheckpointRestoreDigest(
334
+ "cc-checkpoint-restore-original-mutation-set-v1",
335
+ { engine, paths },
336
+ );
337
+ }
338
+
339
+ function rollbackResultDigest(operationId, recoveryRequestId, plan, result) {
340
+ return computeCheckpointRestoreDigest(
341
+ "cc-checkpoint-restore-rollback-result-v1",
342
+ {
343
+ operationId,
344
+ recoveryRequestId,
345
+ restoreKind: plan.engine,
346
+ safetyCheckpoint: {
347
+ id: plan.safetyCheckpoint.id,
348
+ identity: plan.safetyCheckpoint.identity,
349
+ planIdentity: plan.safetyCheckpoint.planIdentity,
350
+ },
351
+ originalMutationTargetCount: plan.originalMutationTargetCount,
352
+ targetCount: plan.targetCount,
353
+ rolledBackCount: result.rolledBackCount,
354
+ rollbackPlanIdentity: plan.rollbackPlanIdentity,
355
+ rollbackStateDigest: result.rollbackStateDigest,
356
+ },
357
+ );
358
+ }
359
+
360
+ function settledRollbackResultDigest(authority, settlement) {
361
+ return computeCheckpointRestoreDigest(
362
+ "cc-checkpoint-restore-rollback-result-v1",
363
+ {
364
+ operationId: authority.operationId,
365
+ recoveryRequestId: settlement.recoveryRequestId,
366
+ restoreKind: authority.restoreKind,
367
+ safetyCheckpoint: {
368
+ id: authority.safetyId,
369
+ identity: authority.safetyIdentity,
370
+ planIdentity: authority.safetyPlanIdentity,
371
+ },
372
+ originalMutationTargetCount: settlement.originalMutationTargetCount,
373
+ targetCount: settlement.targetCount,
374
+ rolledBackCount: settlement.rolledBackCount,
375
+ rollbackPlanIdentity: settlement.rollbackPlanIdentity,
376
+ rollbackStateDigest: settlement.rollbackStateDigest,
377
+ },
378
+ );
379
+ }
380
+
381
+ function validWorkspaceBinding(value, engine, workspaceRoot, platform) {
382
+ const statePattern = engine === "git" ? GIT_TREE_PATTERN : DIGEST_PATTERN;
383
+ return (
384
+ exactKeys(value, [
385
+ "engine",
386
+ "prestateIdentity",
387
+ "schema",
388
+ "scopeIdentity",
389
+ "targetPoststateIdentity",
390
+ "version",
391
+ "workspaceRoot",
392
+ "writePlanIdentity",
393
+ ]) &&
394
+ value.schema === WORKSPACE_BINDING_SCHEMA &&
395
+ value.version === 1 &&
396
+ value.engine === engine &&
397
+ samePath(value.workspaceRoot, workspaceRoot, platform) &&
398
+ DIGEST_PATTERN.test(String(value.scopeIdentity || "")) &&
399
+ statePattern.test(String(value.prestateIdentity || "")) &&
400
+ DIGEST_PATTERN.test(String(value.writePlanIdentity || "")) &&
401
+ statePattern.test(String(value.targetPoststateIdentity || ""))
402
+ );
403
+ }
404
+
405
+ function validCanonicalPaths(value) {
406
+ if (!Array.isArray(value)) return false;
407
+ const normalized = [];
408
+ for (const entry of value) {
409
+ if (
410
+ typeof entry !== "string" ||
411
+ entry.length === 0 ||
412
+ entry.length > 4_096 ||
413
+ entry.includes("\0") ||
414
+ entry.includes("\\") ||
415
+ path.posix.isAbsolute(entry) ||
416
+ path.win32.isAbsolute(entry)
417
+ ) {
418
+ return false;
419
+ }
420
+ const segments = entry.split("/");
421
+ if (
422
+ segments.some(
423
+ (segment) =>
424
+ segment.length === 0 || segment === "." || segment === "..",
425
+ )
426
+ ) {
427
+ return false;
428
+ }
429
+ normalized.push(entry);
430
+ }
431
+ const canonical = [...new Set(normalized)].sort();
432
+ return (
433
+ canonical.length === value.length &&
434
+ canonical.every((v, i) => v === value[i])
435
+ );
436
+ }
437
+
438
+ function canonicalPathSubset(values, candidates) {
439
+ const candidateSet = new Set(candidates);
440
+ return values.every((entry) => candidateSet.has(entry));
441
+ }
442
+
443
+ function partialRollbackSagaAuthority(saga, workspaceRoot, platform) {
444
+ const created = uniqueEvent(saga, "created");
445
+ const prepared = uniqueEvent(saga, "prepared");
446
+ const intent = uniqueEvent(saga, "intent_committed");
447
+ const safety = uniqueEvent(saga, "safety_ready");
448
+ const mutation = uniqueEvent(saga, "mutation_started");
449
+ const originalBase = originalDurableBaseEvent(saga);
450
+ const c = created?.evidence;
451
+ const p = prepared?.evidence;
452
+ const i = intent?.evidence;
453
+ const s = safety?.evidence;
454
+ const m = mutation?.evidence;
455
+ const direct = c?.restoreSurface === "direct";
456
+ const timeline = c?.restoreSurface === "timeline";
457
+ const timelineAuthority =
458
+ timeline &&
459
+ i?.intentAuthority === "session" &&
460
+ validText(c?.sessionId, 256) &&
461
+ validText(c?.timelineEntryId, 256) &&
462
+ i.sessionId === c.sessionId &&
463
+ i.timelineEntryId === c.timelineEntryId;
464
+ const directAuthority =
465
+ direct &&
466
+ i?.intentAuthority === "operation" &&
467
+ c?.sessionId == null &&
468
+ c?.timelineEntryId == null &&
469
+ i?.sessionId == null &&
470
+ i?.timelineEntryId == null;
471
+
472
+ if (
473
+ saga.terminal ||
474
+ originalBase?.phase !== "mutation_started" ||
475
+ !created ||
476
+ !prepared ||
477
+ !intent ||
478
+ !safety ||
479
+ !mutation ||
480
+ !["git", "copy"].includes(c?.restoreKind) ||
481
+ (!directAuthority && !timelineAuthority) ||
482
+ !samePath(c?.workspaceRoot, workspaceRoot, platform) ||
483
+ !validText(c?.checkpointId, 256) ||
484
+ !validText(c?.checkpointIdentity) ||
485
+ (c.restoreKind === "git" && !validText(c?.checkpointNamespace, 256)) ||
486
+ (c.restoreKind === "copy" && c?.checkpointNamespace != null) ||
487
+ !DIGEST_PATTERN.test(String(c?.workspaceBinding || "")) ||
488
+ !DIGEST_PATTERN.test(String(c?.confirmationDigest || "")) ||
489
+ !DIGEST_PATTERN.test(String(p?.prestateDigest || "")) ||
490
+ p?.workspaceBinding !== c.workspaceBinding ||
491
+ !Number.isSafeInteger(p?.targetCount) ||
492
+ p.targetCount <= 0 ||
493
+ !DIGEST_PATTERN.test(String(i?.intentCommitDigest || "")) ||
494
+ s?.safetyCoverage !== "full" ||
495
+ !validText(s?.safetyId, 256) ||
496
+ !validText(s?.safetyIdentity) ||
497
+ !DIGEST_PATTERN.test(String(s?.safetyPlanIdentity || "")) ||
498
+ m?.targetCount !== p.targetCount
499
+ ) {
500
+ throw rollbackError(
501
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.ACTION_NOT_ALLOWED,
502
+ "Partial rollback requires one exact mutation_started restore with full safety authority",
503
+ {
504
+ operationId: saga.operationId,
505
+ phase: saga.phase,
506
+ originalBasePhase: originalBase?.phase || null,
507
+ },
508
+ );
509
+ }
510
+
511
+ return immutable({
512
+ restoreKind: c.restoreKind,
513
+ restoreSurface: c.restoreSurface,
514
+ checkpointNamespace: c.checkpointNamespace || null,
515
+ checkpointId: c.checkpointId,
516
+ checkpointIdentity: c.checkpointIdentity,
517
+ workspaceWritePlanIdentity: c.workspaceBinding,
518
+ confirmationDigest: c.confirmationDigest,
519
+ originalPrestateDigest: p.prestateDigest,
520
+ originalMutationTargetCount: p.targetCount,
521
+ intentCommitDigest: i.intentCommitDigest,
522
+ sessionId: c.sessionId || null,
523
+ timelineEntryId: c.timelineEntryId || null,
524
+ safetyId: s.safetyId,
525
+ safetyIdentity: s.safetyIdentity,
526
+ safetyPlanIdentity: s.safetyPlanIdentity,
527
+ });
528
+ }
529
+
530
+ const PLAN_KEYS = Object.freeze([
531
+ "checkpointNamespace",
532
+ "currentRollbackPaths",
533
+ "engine",
534
+ "expectedRollbackStateDigest",
535
+ "expectedWorkspaceBinding",
536
+ "originalBindingVerification",
537
+ "originalCheckpoint",
538
+ "originalMutationPaths",
539
+ "originalMutationTargetCount",
540
+ "originalPlanAuthority",
541
+ "originalWorkspaceBinding",
542
+ "rollbackPlanIdentity",
543
+ "rollbackPrestateDigest",
544
+ "safetyCheckpoint",
545
+ "schema",
546
+ "targetCount",
547
+ "version",
548
+ "workspaceRoot",
549
+ ]);
550
+
551
+ function validateCheckpointAuthority(plan, authority) {
552
+ const original = plan.originalCheckpoint;
553
+ const safety = plan.safetyCheckpoint;
554
+ const originalIdentityPattern =
555
+ plan.engine === "git" ? GIT_IDENTITY_PATTERN : COPY_IDENTITY_PATTERN;
556
+ const treeIdentityPattern = plan.engine === "git" ? GIT_TREE_PATTERN : null;
557
+ return (
558
+ exactKeys(original, ["id", "identity", "treeIdentity"]) &&
559
+ exactKeys(safety, ["id", "identity", "planIdentity", "treeIdentity"]) &&
560
+ original.id === authority.checkpointId &&
561
+ original.identity === authority.checkpointIdentity &&
562
+ originalIdentityPattern.test(String(original.identity || "")) &&
563
+ safety.id === authority.safetyId &&
564
+ safety.identity === authority.safetyIdentity &&
565
+ originalIdentityPattern.test(String(safety.identity || "")) &&
566
+ safety.planIdentity === authority.safetyPlanIdentity &&
567
+ DIGEST_PATTERN.test(String(safety.planIdentity || "")) &&
568
+ (plan.engine === "git"
569
+ ? treeIdentityPattern.test(String(original.treeIdentity || "")) &&
570
+ treeIdentityPattern.test(String(safety.treeIdentity || ""))
571
+ : original.treeIdentity === null && safety.treeIdentity === null)
572
+ );
573
+ }
574
+
575
+ function validateRollbackPlan(
576
+ rawPlan,
577
+ authority,
578
+ workspaceRoot,
579
+ platform,
580
+ sessionIntentAuthority = null,
581
+ ) {
582
+ const plan = rawPlan;
583
+ const expectedBinding = plan?.expectedWorkspaceBinding;
584
+ const originalPlanAuthority = plan?.originalPlanAuthority;
585
+ if (
586
+ !exactKeys(plan, PLAN_KEYS) ||
587
+ plan.schema !== CHECKPOINT_ROLLBACK_PLAN_SCHEMA ||
588
+ plan.version !== CHECKPOINT_ROLLBACK_PLAN_VERSION ||
589
+ plan.engine !== authority.restoreKind ||
590
+ !samePath(plan.workspaceRoot, workspaceRoot, platform) ||
591
+ (plan.engine === "git"
592
+ ? plan.checkpointNamespace !== authority.checkpointNamespace
593
+ : plan.checkpointNamespace !== null) ||
594
+ !validateCheckpointAuthority(plan, authority) ||
595
+ !validCanonicalPaths(plan.originalMutationPaths) ||
596
+ !validCanonicalPaths(plan.currentRollbackPaths) ||
597
+ !Number.isSafeInteger(plan.originalMutationTargetCount) ||
598
+ plan.originalMutationTargetCount !==
599
+ authority.originalMutationTargetCount ||
600
+ plan.originalMutationPaths.length !== plan.originalMutationTargetCount ||
601
+ !Number.isSafeInteger(plan.targetCount) ||
602
+ plan.targetCount < 0 ||
603
+ plan.targetCount > plan.originalMutationTargetCount ||
604
+ plan.currentRollbackPaths.length !== plan.targetCount ||
605
+ !canonicalPathSubset(
606
+ plan.currentRollbackPaths,
607
+ plan.originalMutationPaths,
608
+ ) ||
609
+ !validWorkspaceBinding(
610
+ expectedBinding,
611
+ plan.engine,
612
+ workspaceRoot,
613
+ platform,
614
+ ) ||
615
+ plan.rollbackPlanIdentity !== expectedBinding.writePlanIdentity ||
616
+ plan.rollbackPrestateDigest !==
617
+ rollbackPrestateDigest(plan.engine, expectedBinding) ||
618
+ plan.expectedRollbackStateDigest !==
619
+ rollbackStateDigest(plan.engine, expectedBinding) ||
620
+ !exactKeys(originalPlanAuthority, [
621
+ "bindingReconstructable",
622
+ "mutationSetIdentity",
623
+ "safetyPlanIdentity",
624
+ "sourceCheckpointId",
625
+ "sourceCheckpointIdentity",
626
+ ]) ||
627
+ originalPlanAuthority.sourceCheckpointId !== authority.checkpointId ||
628
+ originalPlanAuthority.sourceCheckpointIdentity !==
629
+ authority.checkpointIdentity ||
630
+ originalPlanAuthority.safetyPlanIdentity !== authority.safetyPlanIdentity ||
631
+ originalPlanAuthority.mutationSetIdentity !==
632
+ mutationSetIdentity(plan.engine, plan.originalMutationPaths)
633
+ ) {
634
+ throw rollbackError(
635
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.PLAN_INVALID,
636
+ "Workspace rollback adapter returned an invalid authority-bound plan",
637
+ { operationId: authority.operationId || null },
638
+ );
639
+ }
640
+
641
+ if (plan.engine === "git") {
642
+ const originalBinding = plan.originalWorkspaceBinding;
643
+ if (
644
+ plan.originalBindingVerification !==
645
+ "exact-checkpoint-tree-reconstruction" ||
646
+ originalPlanAuthority.bindingReconstructable !== true ||
647
+ !validWorkspaceBinding(originalBinding, "git", workspaceRoot, platform) ||
648
+ originalBinding.writePlanIdentity !==
649
+ authority.workspaceWritePlanIdentity ||
650
+ originalBinding.targetPoststateIdentity !==
651
+ plan.originalCheckpoint.treeIdentity ||
652
+ originalBinding.prestateIdentity !== plan.safetyCheckpoint.treeIdentity ||
653
+ originalPrestateDigest("git", originalBinding) !==
654
+ authority.originalPrestateDigest
655
+ ) {
656
+ throw rollbackError(
657
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.PLAN_INVALID,
658
+ "Git rollback did not reconstruct the exact original workspace binding",
659
+ );
660
+ }
661
+ } else if (
662
+ plan.originalBindingVerification !== "durable-safety-plan-v2" ||
663
+ originalPlanAuthority.bindingReconstructable !== false ||
664
+ plan.originalWorkspaceBinding !== null
665
+ ) {
666
+ throw rollbackError(
667
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.PLAN_INVALID,
668
+ "Copy rollback must use durable safety-plan v2 authority",
669
+ );
670
+ }
671
+
672
+ if (sessionIntentAuthority) {
673
+ if (
674
+ sessionIntentAuthority.workspaceWritePlanIdentity !==
675
+ authority.workspaceWritePlanIdentity ||
676
+ sessionIntentAuthority.checkpointId !== authority.checkpointId ||
677
+ sessionIntentAuthority.checkpointIdentity !==
678
+ authority.checkpointIdentity ||
679
+ !samePath(sessionIntentAuthority.workspaceDir, workspaceRoot, platform) ||
680
+ originalPrestateDigest(plan.engine, {
681
+ scopeIdentity: sessionIntentAuthority.workspaceScopeIdentity,
682
+ prestateIdentity: sessionIntentAuthority.workspacePrestateIdentity,
683
+ }) !== authority.originalPrestateDigest
684
+ ) {
685
+ throw rollbackError(
686
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.PLAN_INVALID,
687
+ "Rollback plan does not match the verified timeline intent",
688
+ );
689
+ }
690
+ }
691
+
692
+ return immutable(plan);
693
+ }
694
+
695
+ function validateRollbackResult(rawResult, plan) {
696
+ if (
697
+ !exactKeys(rawResult, [
698
+ "engine",
699
+ "rollbackStateDigest",
700
+ "rolledBackCount",
701
+ "schema",
702
+ "version",
703
+ ]) ||
704
+ rawResult.schema !== CHECKPOINT_ROLLBACK_RESULT_SCHEMA ||
705
+ rawResult.version !== CHECKPOINT_ROLLBACK_RESULT_VERSION ||
706
+ rawResult.engine !== plan.engine ||
707
+ rawResult.rolledBackCount !== plan.targetCount ||
708
+ rawResult.rollbackStateDigest !== plan.expectedRollbackStateDigest
709
+ ) {
710
+ throw rollbackError(
711
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.RESULT_INVALID,
712
+ "Workspace rollback adapter did not prove the complete rollback state",
713
+ );
714
+ }
715
+ return immutable(rawResult);
716
+ }
717
+
718
+ function rollbackBindingEvidence(authority, recoveryRequestId, plan) {
719
+ return Object.freeze({
720
+ recoveryAction: CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION,
721
+ recoveryRequestId,
722
+ safetyId: authority.safetyId,
723
+ safetyIdentity: authority.safetyIdentity,
724
+ safetyPlanIdentity: authority.safetyPlanIdentity,
725
+ safetyCoverage: "full",
726
+ rollbackPrestateDigest: plan.rollbackPrestateDigest,
727
+ rollbackPlanIdentity: plan.rollbackPlanIdentity,
728
+ originalMutationTargetCount: authority.originalMutationTargetCount,
729
+ targetCount: plan.targetCount,
730
+ });
731
+ }
732
+
733
+ function rollbackSettlementEvidence(
734
+ authority,
735
+ recoveryRequestId,
736
+ plan,
737
+ result,
738
+ ) {
739
+ return Object.freeze({
740
+ ...rollbackBindingEvidence(authority, recoveryRequestId, plan),
741
+ rolledBackCount: result.rolledBackCount,
742
+ rollbackStateDigest: result.rollbackStateDigest,
743
+ resultDigest: rollbackResultDigest(
744
+ authority.operationId,
745
+ recoveryRequestId,
746
+ plan,
747
+ result,
748
+ ),
749
+ });
750
+ }
751
+
752
+ function validateSettledPlan(plan, authority, settlement) {
753
+ if (
754
+ plan.targetCount !== 0 ||
755
+ plan.currentRollbackPaths.length !== 0 ||
756
+ !exactKeys(settlement, [
757
+ "originalMutationTargetCount",
758
+ "recoveryAction",
759
+ "recoveryRequestId",
760
+ "resultDigest",
761
+ "rollbackPlanIdentity",
762
+ "rollbackPrestateDigest",
763
+ "rollbackStateDigest",
764
+ "rolledBackCount",
765
+ "safetyCoverage",
766
+ "safetyId",
767
+ "safetyIdentity",
768
+ "safetyPlanIdentity",
769
+ "targetCount",
770
+ ]) ||
771
+ plan.originalMutationTargetCount !==
772
+ settlement.originalMutationTargetCount ||
773
+ plan.originalPlanAuthority.mutationSetIdentity !==
774
+ mutationSetIdentity(plan.engine, plan.originalMutationPaths) ||
775
+ plan.expectedRollbackStateDigest !== settlement.rollbackStateDigest ||
776
+ settlement.safetyId !== authority.safetyId ||
777
+ settlement.safetyIdentity !== authority.safetyIdentity ||
778
+ settlement.safetyPlanIdentity !== authority.safetyPlanIdentity ||
779
+ settlement.safetyCoverage !== "full" ||
780
+ settlement.recoveryAction !== CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION ||
781
+ !REQUEST_ID_PATTERN.test(String(settlement.recoveryRequestId || "")) ||
782
+ !DIGEST_PATTERN.test(String(settlement.rollbackPrestateDigest || "")) ||
783
+ !DIGEST_PATTERN.test(String(settlement.rollbackPlanIdentity || "")) ||
784
+ !DIGEST_PATTERN.test(String(settlement.resultDigest || "")) ||
785
+ !Number.isSafeInteger(settlement.targetCount) ||
786
+ settlement.targetCount < 0 ||
787
+ settlement.targetCount > settlement.originalMutationTargetCount ||
788
+ settlement.rolledBackCount !== settlement.targetCount ||
789
+ settlement.resultDigest !==
790
+ settledRollbackResultDigest(authority, settlement)
791
+ ) {
792
+ throw rollbackError(
793
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.PLAN_INVALID,
794
+ "Settled rollback no longer matches the exact durable safety state",
795
+ );
796
+ }
797
+ return plan;
798
+ }
799
+
800
+ function sessionConflict(message, details = {}, cause = null) {
801
+ return rollbackError(
802
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.SESSION_CONFLICT,
803
+ message,
804
+ details,
805
+ cause,
806
+ );
807
+ }
808
+
809
+ function validTranscriptAuthority(value) {
810
+ return (
811
+ isPlainObject(value) &&
812
+ RAW_HASH_PATTERN.test(String(value.headHash || "")) &&
813
+ Number.isSafeInteger(value.eventCount) &&
814
+ value.eventCount > 0 &&
815
+ Number.isSafeInteger(value.operationEventCount) &&
816
+ value.operationEventCount > 0 &&
817
+ RAW_HASH_PATTERN.test(String(value.operationTailHash || "")) &&
818
+ Number.isSafeInteger(value.eventsAfterOperationTail) &&
819
+ value.eventsAfterOperationTail >= 0
820
+ );
821
+ }
822
+
823
+ function validIntentStateIdentity(engine, value) {
824
+ return engine === "git"
825
+ ? GIT_TREE_PATTERN.test(String(value || ""))
826
+ : DIGEST_PATTERN.test(String(value || ""));
827
+ }
828
+
829
+ function sameIntentAuthority(left, right, workspaceRoot, platform) {
830
+ return (
831
+ isPlainObject(left) &&
832
+ isPlainObject(right) &&
833
+ left.revision === right.revision &&
834
+ left.action === right.action &&
835
+ left.turnId === right.turnId &&
836
+ left.checkpointId === right.checkpointId &&
837
+ left.checkpointIdentity === right.checkpointIdentity &&
838
+ samePath(left.workspaceDir, right.workspaceDir, platform) &&
839
+ samePath(left.workspaceDir, workspaceRoot, platform) &&
840
+ left.workspaceScopeIdentity === right.workspaceScopeIdentity &&
841
+ left.workspacePrestateIdentity === right.workspacePrestateIdentity &&
842
+ left.workspaceWritePlanIdentity === right.workspaceWritePlanIdentity &&
843
+ left.workspaceTargetPoststateIdentity ===
844
+ right.workspaceTargetPoststateIdentity &&
845
+ left.confirmationDigest === right.confirmationDigest
846
+ );
847
+ }
848
+
849
+ function validateTimelineProjection(
850
+ projection,
851
+ authority,
852
+ workspaceRoot,
853
+ platform,
854
+ allowedClassifications,
855
+ ) {
856
+ const intent = projection?.intent;
857
+ const intentAuthority = intent?.authority;
858
+ const failedAudit = projection?.audit?.failed ?? null;
859
+ const classification = projection?.classification;
860
+ const commonValid =
861
+ isPlainObject(projection) &&
862
+ projection.schema === CHECKPOINT_RESTORE_SESSION_RECOVERY_SCHEMA &&
863
+ projection.version === CHECKPOINT_RESTORE_SESSION_RECOVERY_VERSION &&
864
+ projection.operationId === authority.operationId &&
865
+ projection.sessionId === authority.sessionId &&
866
+ projection.restoreSurface === "timeline" &&
867
+ projection.failClosed === false &&
868
+ projection.safeToMutate === false &&
869
+ Array.isArray(projection.issues) &&
870
+ projection.issues.length === 0 &&
871
+ allowedClassifications.has(classification) &&
872
+ validTranscriptAuthority(projection.transcript) &&
873
+ isPlainObject(intent) &&
874
+ RAW_HASH_PATTERN.test(String(intent.eventHash || "")) &&
875
+ intent.intentCommitDigest === authority.intentCommitDigest &&
876
+ intent.intentCommitDigest ===
877
+ computeCheckpointRestoreDigest(
878
+ "cc-checkpoint-restore-intent-commit-v1",
879
+ intent.eventHash,
880
+ ) &&
881
+ isPlainObject(intentAuthority) &&
882
+ ACTIONS.has(intentAuthority.action) &&
883
+ intentAuthority.turnId === authority.timelineEntryId &&
884
+ intentAuthority.checkpointId === authority.checkpointId &&
885
+ intentAuthority.checkpointIdentity === authority.checkpointIdentity &&
886
+ samePath(intentAuthority.workspaceDir, workspaceRoot, platform) &&
887
+ DIGEST_PATTERN.test(String(intentAuthority.workspaceScopeIdentity || "")) &&
888
+ validIntentStateIdentity(
889
+ authority.restoreKind,
890
+ intentAuthority.workspacePrestateIdentity,
891
+ ) &&
892
+ intentAuthority.workspaceWritePlanIdentity ===
893
+ authority.workspaceWritePlanIdentity &&
894
+ validIntentStateIdentity(
895
+ authority.restoreKind,
896
+ intentAuthority.workspaceTargetPoststateIdentity,
897
+ ) &&
898
+ intentAuthority.confirmationDigest === authority.confirmationDigest &&
899
+ originalPrestateDigest(authority.restoreKind, {
900
+ scopeIdentity: intentAuthority.workspaceScopeIdentity,
901
+ prestateIdentity: intentAuthority.workspacePrestateIdentity,
902
+ }) === authority.originalPrestateDigest &&
903
+ projection.conversationCommit === null &&
904
+ projection.audit?.completed === null &&
905
+ (failedAudit === null ||
906
+ (isPlainObject(failedAudit) &&
907
+ RAW_HASH_PATTERN.test(String(failedAudit.eventHash || "")) &&
908
+ failedAudit.conversationSideEffectPresent === false &&
909
+ sameIntentAuthority(
910
+ failedAudit.authority,
911
+ intentAuthority,
912
+ workspaceRoot,
913
+ platform,
914
+ )));
915
+
916
+ const resumable = TIMELINE_RESUMABLE_CLASSIFICATIONS.has(classification);
917
+ const resolution = projection?.resolution;
918
+ const resolutionShapeValid =
919
+ classification === CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK &&
920
+ isPlainObject(resolution) &&
921
+ Number.isSafeInteger(resolution.index) &&
922
+ resolution.index > intent.index &&
923
+ RAW_HASH_PATTERN.test(String(resolution.eventHash || "")) &&
924
+ RAW_HASH_PATTERN.test(String(resolution.prevHash || "")) &&
925
+ projection.transcript.operationTailHash === resolution.eventHash &&
926
+ resolution.sessionRollbackCommitDigest ===
927
+ computeCheckpointRestoreSessionRollbackCommitDigest(
928
+ resolution.eventHash,
929
+ ) &&
930
+ isPlainObject(resolution.data);
931
+
932
+ if (
933
+ !commonValid ||
934
+ (resumable && resolution !== null) ||
935
+ (!resumable && !resolutionShapeValid)
936
+ ) {
937
+ throw sessionConflict(
938
+ "Timeline rollback requires one exact resumable or rolled-back session authority",
939
+ {
940
+ operationId: authority.operationId,
941
+ classification: classification || null,
942
+ issues: Array.isArray(projection?.issues)
943
+ ? projection.issues.slice(0, 20)
944
+ : [],
945
+ },
946
+ );
947
+ }
948
+ return projection;
949
+ }
950
+
951
+ function resolutionInput(
952
+ authority,
953
+ projection,
954
+ settlement,
955
+ sagaWorkspaceRolledBackHash,
956
+ ) {
957
+ return immutable({
958
+ operationId: authority.operationId,
959
+ recoveryRequestId: settlement.recoveryRequestId,
960
+ action: projection.intent.authority.action,
961
+ recoveryAction: CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION,
962
+ outcome: CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_OUTCOME,
963
+ intentEventHash: projection.intent.eventHash,
964
+ intentCommitDigest: authority.intentCommitDigest,
965
+ failedAuditEventHash: projection.audit.failed?.eventHash ?? null,
966
+ conversationDisposition:
967
+ CHECKPOINT_RESTORE_ROLLBACK_CONVERSATION_DISPOSITION,
968
+ checkpointId: authority.checkpointId,
969
+ checkpointIdentity: authority.checkpointIdentity,
970
+ workspaceScopeIdentity: projection.intent.authority.workspaceScopeIdentity,
971
+ workspaceWritePlanIdentity: authority.workspaceWritePlanIdentity,
972
+ safetyId: authority.safetyId,
973
+ safetyIdentity: authority.safetyIdentity,
974
+ safetyPlanIdentity: authority.safetyPlanIdentity,
975
+ rollbackPlanIdentity: settlement.rollbackPlanIdentity,
976
+ rollbackStateDigest: settlement.rollbackStateDigest,
977
+ sagaWorkspaceRolledBackHash,
978
+ });
979
+ }
980
+
981
+ function exactHistoricalWorkspaceSettlement(saga, eventHash, settlement) {
982
+ const matches = saga.events.filter(
983
+ (event) =>
984
+ event.phase === "workspace_rolled_back" &&
985
+ event.hash === eventHash &&
986
+ canonicalJson(event.evidence) === canonicalJson(settlement),
987
+ );
988
+ if (matches.length !== 1) {
989
+ throw sessionConflict(
990
+ "Session rollback resolution is not bound to the exact historical workspace settlement",
991
+ { operationId: saga.operationId, sagaWorkspaceRolledBackHash: eventHash },
992
+ );
993
+ }
994
+ return matches[0];
995
+ }
996
+
997
+ function workspaceSettlementFromSessionSettlement(settlement) {
998
+ return immutable({
999
+ recoveryAction: settlement.recoveryAction,
1000
+ recoveryRequestId: settlement.recoveryRequestId,
1001
+ safetyId: settlement.safetyId,
1002
+ safetyIdentity: settlement.safetyIdentity,
1003
+ safetyPlanIdentity: settlement.safetyPlanIdentity,
1004
+ safetyCoverage: settlement.safetyCoverage,
1005
+ rollbackPrestateDigest: settlement.rollbackPrestateDigest,
1006
+ rollbackPlanIdentity: settlement.rollbackPlanIdentity,
1007
+ originalMutationTargetCount: settlement.originalMutationTargetCount,
1008
+ targetCount: settlement.targetCount,
1009
+ rolledBackCount: settlement.rolledBackCount,
1010
+ rollbackStateDigest: settlement.rollbackStateDigest,
1011
+ resultDigest: settlement.resultDigest,
1012
+ });
1013
+ }
1014
+
1015
+ function validateResolutionReceipt(receipt, expectedHeadHash, input) {
1016
+ if (
1017
+ !isPlainObject(receipt) ||
1018
+ !RAW_HASH_PATTERN.test(String(receipt.eventHash || "")) ||
1019
+ receipt.prevHash !== expectedHeadHash ||
1020
+ receipt.sessionRollbackCommitDigest !==
1021
+ computeCheckpointRestoreSessionRollbackCommitDigest(receipt.eventHash) ||
1022
+ canonicalJson(receipt.resolution) !==
1023
+ canonicalJson({
1024
+ schema: "chainlesschain.checkpoint-restore-recovery-resolution",
1025
+ version: 1,
1026
+ ...input,
1027
+ })
1028
+ ) {
1029
+ throw sessionConflict(
1030
+ "Session rollback resolution append did not return exact durable authority",
1031
+ { commitState: "unknown" },
1032
+ );
1033
+ }
1034
+ return immutable(receipt);
1035
+ }
1036
+
1037
+ function terminalSummary(saga) {
1038
+ return Object.freeze({
1039
+ operationId: saga.operationId,
1040
+ phase: saga.phase,
1041
+ seq: saga.seq,
1042
+ headHash: saga.headHash,
1043
+ });
1044
+ }
1045
+
1046
+ function archiveWarning(error) {
1047
+ return Object.freeze({
1048
+ code: boundedText(
1049
+ error?.code,
1050
+ "CHECKPOINT_RESTORE_SAGA_ARCHIVE_PENDING",
1051
+ 128,
1052
+ ),
1053
+ message: boundedText(
1054
+ error?.message,
1055
+ "Checkpoint restore saga archive is pending",
1056
+ 512,
1057
+ ),
1058
+ commitState:
1059
+ typeof error?.commitState === "string"
1060
+ ? boundedText(error.commitState, "unknown", 128)
1061
+ : null,
1062
+ });
1063
+ }
1064
+
1065
+ function propagateRecoveryDiagnostics(error) {
1066
+ if (!error || typeof error !== "object") return error;
1067
+ const nested = error.cause;
1068
+ if (!nested || typeof nested !== "object") return error;
1069
+ for (const field of [
1070
+ "checkpointRestoreRecoveryCause",
1071
+ "checkpointRestoreOperationId",
1072
+ "checkpointRestoreSagaPhase",
1073
+ "checkpointRestoreSagaSeq",
1074
+ "checkpointRestoreSagaHeadHash",
1075
+ "checkpointRestoreRecoveryRequired",
1076
+ ]) {
1077
+ if (error[field] === undefined && nested[field] !== undefined) {
1078
+ error[field] = nested[field];
1079
+ }
1080
+ }
1081
+ return error;
1082
+ }
1083
+
1084
+ export class CheckpointRestorePartialRollbackController {
1085
+ constructor(options = {}) {
1086
+ if (!isPlainObject(options) || !validText(options.workspaceRoot, 4_096)) {
1087
+ throw rollbackError(
1088
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.INVALID_ARGUMENT,
1089
+ "Partial rollback controller requires a workspaceRoot",
1090
+ );
1091
+ }
1092
+ if (
1093
+ typeof options.prepareWorkspaceRollback !== "function" ||
1094
+ typeof options.executeWorkspaceRollback !== "function"
1095
+ ) {
1096
+ throw rollbackError(
1097
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.INVALID_ARGUMENT,
1098
+ "Partial rollback controller requires prepare and execute adapters",
1099
+ );
1100
+ }
1101
+ this.workspaceRoot = options.workspaceRoot;
1102
+ this.store =
1103
+ options.store ||
1104
+ new CheckpointRestoreSagaStore({ workspaceRoot: this.workspaceRoot });
1105
+ this._prepareWorkspaceRollback = options.prepareWorkspaceRollback;
1106
+ this._executeWorkspaceRollback = options.executeWorkspaceRollback;
1107
+ this._readSessionRecovery =
1108
+ options.readSessionRecovery ||
1109
+ (typeof options.sessionRecoveryReader?.read === "function"
1110
+ ? options.sessionRecoveryReader.read.bind(options.sessionRecoveryReader)
1111
+ : null);
1112
+ this._withSessionAuthorityTransaction =
1113
+ options.withSessionAuthorityTransaction || null;
1114
+ this._appendSessionResolution =
1115
+ options.appendSessionResolution ||
1116
+ appendCheckpointRestoreRecoveryResolution;
1117
+ this._reconcileSessionResolution =
1118
+ options.reconcileSessionResolution ||
1119
+ reconcileCheckpointRestoreRecoveryResolutionProjection;
1120
+ this._inspectWorkspaceLockOwnerSync =
1121
+ options.inspectWorkspaceLockOwnerSync || inspectWorkspaceLockOwnerSync;
1122
+ this._withWorkspaceRecoveryLockSync =
1123
+ options.withWorkspaceRecoveryLockSync || withWorkspaceRecoveryLockSync;
1124
+ this._workspaceLockOwnerDigest =
1125
+ options.computeWorkspaceLockOwnerDigest ||
1126
+ computeCheckpointRestoreWorkspaceLockOwnerDigest;
1127
+ this._createRecoveryRequestId =
1128
+ options.createRecoveryRequestId || (() => `rollback-${randomUUID()}`);
1129
+ this._workspaceLockOptions = Object.freeze({
1130
+ ...(options.workspaceLockOptions || {}),
1131
+ });
1132
+ this._platform = options.platform || process.platform;
1133
+ }
1134
+
1135
+ _lockOptions(operationId) {
1136
+ return {
1137
+ ...this._workspaceLockOptions,
1138
+ workspaceRoot: this.workspaceRoot,
1139
+ operationId,
1140
+ purpose: CHECKPOINT_RESTORE_PURPOSE,
1141
+ };
1142
+ }
1143
+
1144
+ _loadExact(operationId, expectedSeq, expectedHash) {
1145
+ return exactSaga(
1146
+ requireSynchronous(
1147
+ this.store.load(operationId),
1148
+ "checkpoint rollback saga load",
1149
+ ),
1150
+ operationId,
1151
+ expectedSeq,
1152
+ expectedHash,
1153
+ );
1154
+ }
1155
+
1156
+ _advanceExact(saga, phase, evidence) {
1157
+ try {
1158
+ return requireSynchronous(
1159
+ this.store.advance(saga.operationId, {
1160
+ expectedSeq: saga.seq,
1161
+ expectedHash: saga.headHash,
1162
+ phase,
1163
+ evidence,
1164
+ }),
1165
+ "checkpoint rollback saga advance",
1166
+ );
1167
+ } catch (caught) {
1168
+ const error = asError(caught);
1169
+ try {
1170
+ const observed = requireSynchronous(
1171
+ this.store.load(saga.operationId),
1172
+ "checkpoint rollback saga reconciliation load",
1173
+ );
1174
+ const latest = observed.events?.at(-1);
1175
+ if (
1176
+ observed.seq === saga.seq + 1 &&
1177
+ observed.phase === phase &&
1178
+ latest?.prevHash === saga.headHash &&
1179
+ canonicalJson(latest.evidence) === canonicalJson(evidence)
1180
+ ) {
1181
+ return observed;
1182
+ }
1183
+ } catch (loadError) {
1184
+ error.checkpointRestoreRecoveryLoadError = loadError;
1185
+ }
1186
+ throw error;
1187
+ }
1188
+ }
1189
+
1190
+ _inspectBoundOwner(operationId, expectedOwnerDigest) {
1191
+ const owner = this._inspectWorkspaceLockOwnerSync(
1192
+ this._lockOptions(operationId),
1193
+ );
1194
+ if (!owner) {
1195
+ throw rollbackError(
1196
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.OWNER_CONFLICT,
1197
+ "Checkpoint restore has no retained workspace owner to take over",
1198
+ { operationId, expectedOwnerDigest, actualOwnerDigest: null },
1199
+ );
1200
+ }
1201
+ const actualOwnerDigest = this._workspaceLockOwnerDigest(owner);
1202
+ if (actualOwnerDigest !== expectedOwnerDigest) {
1203
+ throw rollbackError(
1204
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.OWNER_CONFLICT,
1205
+ "Checkpoint restore workspace owner changed after rollback preview",
1206
+ { operationId, expectedOwnerDigest, actualOwnerDigest },
1207
+ );
1208
+ }
1209
+ return owner;
1210
+ }
1211
+
1212
+ _archiveTerminal(saga) {
1213
+ try {
1214
+ const archived = requireSynchronous(
1215
+ this.store.archiveTerminal(saga.operationId, {
1216
+ expectedSeq: saga.seq,
1217
+ expectedHash: saga.headHash,
1218
+ }),
1219
+ "checkpoint rollback saga archive",
1220
+ );
1221
+ return Object.freeze({
1222
+ archived: true,
1223
+ alreadyArchived: archived.alreadyArchived === true,
1224
+ warning: null,
1225
+ });
1226
+ } catch (error) {
1227
+ return Object.freeze({
1228
+ archived: false,
1229
+ alreadyArchived: false,
1230
+ warning: archiveWarning(error),
1231
+ });
1232
+ }
1233
+ }
1234
+
1235
+ _newRequestId() {
1236
+ const value = requireSynchronous(
1237
+ this._createRecoveryRequestId(),
1238
+ "checkpoint rollback request id creation",
1239
+ );
1240
+ if (!REQUEST_ID_PATTERN.test(String(value || ""))) {
1241
+ throw rollbackError(
1242
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.INVALID_ARGUMENT,
1243
+ "Checkpoint rollback recoveryRequestId is invalid",
1244
+ );
1245
+ }
1246
+ return value;
1247
+ }
1248
+
1249
+ _prepareRequest(operationId, recoveryRequestId, authority, lease) {
1250
+ return Object.freeze({
1251
+ operationId,
1252
+ recoveryRequestId,
1253
+ workspaceRoot: this.workspaceRoot,
1254
+ workspaceLease: lease,
1255
+ expected: immutable({
1256
+ engine: authority.restoreKind,
1257
+ restoreSurface: authority.restoreSurface,
1258
+ checkpointNamespace: authority.checkpointNamespace,
1259
+ originalCheckpoint: {
1260
+ id: authority.checkpointId,
1261
+ identity: authority.checkpointIdentity,
1262
+ },
1263
+ safetyCheckpoint: {
1264
+ id: authority.safetyId,
1265
+ identity: authority.safetyIdentity,
1266
+ planIdentity: authority.safetyPlanIdentity,
1267
+ },
1268
+ originalWorkspaceWritePlanIdentity:
1269
+ authority.workspaceWritePlanIdentity,
1270
+ originalPrestateDigest: authority.originalPrestateDigest,
1271
+ originalMutationTargetCount: authority.originalMutationTargetCount,
1272
+ }),
1273
+ });
1274
+ }
1275
+
1276
+ _preparePlan(operationId, recoveryRequestId, authority, lease, session) {
1277
+ assertLeaseOwnedSync(lease);
1278
+ const rawPlan = requireSynchronous(
1279
+ this._prepareWorkspaceRollback(
1280
+ this._prepareRequest(operationId, recoveryRequestId, authority, lease),
1281
+ ),
1282
+ "checkpoint workspace rollback preparation",
1283
+ );
1284
+ assertLeaseOwnedSync(lease);
1285
+ return validateRollbackPlan(
1286
+ rawPlan,
1287
+ { ...authority, operationId },
1288
+ this.workspaceRoot,
1289
+ this._platform,
1290
+ session?.intent?.authority || null,
1291
+ );
1292
+ }
1293
+
1294
+ _executePlan(operationId, recoveryRequestId, plan, lease) {
1295
+ assertLeaseOwnedSync(lease);
1296
+ const rawResult = requireSynchronous(
1297
+ this._executeWorkspaceRollback(
1298
+ Object.freeze({
1299
+ operationId,
1300
+ recoveryRequestId,
1301
+ workspaceRoot: this.workspaceRoot,
1302
+ workspaceLease: lease,
1303
+ plan,
1304
+ }),
1305
+ ),
1306
+ "checkpoint workspace rollback execution",
1307
+ );
1308
+ assertLeaseOwnedSync(lease);
1309
+ return validateRollbackResult(rawResult, plan);
1310
+ }
1311
+
1312
+ _readTimelineProjection(
1313
+ authority,
1314
+ allowedClassifications,
1315
+ expectedSessionRollbackCommitDigest = null,
1316
+ ) {
1317
+ if (typeof this._readSessionRecovery !== "function") {
1318
+ throw sessionConflict(
1319
+ "Timeline rollback requires a verified session recovery reader",
1320
+ { operationId: authority.operationId },
1321
+ );
1322
+ }
1323
+ const options = {
1324
+ operationId: authority.operationId,
1325
+ sessionId: authority.sessionId,
1326
+ restoreSurface: "timeline",
1327
+ expectedIntentCommitDigest: authority.intentCommitDigest,
1328
+ expectedTimelineEntryId: authority.timelineEntryId,
1329
+ expectedCheckpointId: authority.checkpointId,
1330
+ expectedCheckpointIdentity: authority.checkpointIdentity,
1331
+ expectedConfirmationDigest: authority.confirmationDigest,
1332
+ ...(expectedSessionRollbackCommitDigest
1333
+ ? { expectedSessionRollbackCommitDigest }
1334
+ : {}),
1335
+ };
1336
+ let projection;
1337
+ try {
1338
+ projection = requireSynchronous(
1339
+ this._readSessionRecovery(Object.freeze(options)),
1340
+ "checkpoint timeline recovery read",
1341
+ );
1342
+ } catch (cause) {
1343
+ throw sessionConflict(
1344
+ "Timeline rollback session authority could not be read",
1345
+ { operationId: authority.operationId },
1346
+ cause,
1347
+ );
1348
+ }
1349
+ return validateTimelineProjection(
1350
+ projection,
1351
+ authority,
1352
+ this.workspaceRoot,
1353
+ this._platform,
1354
+ allowedClassifications,
1355
+ );
1356
+ }
1357
+
1358
+ _reconcileTimelineResolution(saga, authority, projection, settlement) {
1359
+ const resolution = projection.resolution;
1360
+ const historicalHash = resolution.data.sagaWorkspaceRolledBackHash;
1361
+ exactHistoricalWorkspaceSettlement(saga, historicalHash, settlement);
1362
+ const input = resolutionInput(
1363
+ authority,
1364
+ projection,
1365
+ settlement,
1366
+ historicalHash,
1367
+ );
1368
+ let receipt;
1369
+ try {
1370
+ receipt = requireSynchronous(
1371
+ this._reconcileSessionResolution(projection, input, {
1372
+ expectedHeadHash: resolution.prevHash,
1373
+ expectedSessionId: authority.sessionId,
1374
+ }),
1375
+ "checkpoint timeline rollback resolution reconciliation",
1376
+ );
1377
+ } catch (cause) {
1378
+ throw sessionConflict(
1379
+ "Timeline rollback resolution reconciliation failed closed",
1380
+ { operationId: authority.operationId },
1381
+ cause,
1382
+ );
1383
+ }
1384
+ if (!receipt) {
1385
+ throw sessionConflict(
1386
+ "Timeline rollback resolution does not exactly match saga settlement authority",
1387
+ { operationId: authority.operationId },
1388
+ );
1389
+ }
1390
+ return validateResolutionReceipt(receipt, resolution.prevHash, input);
1391
+ }
1392
+
1393
+ _appendTimelineResolution(
1394
+ saga,
1395
+ authority,
1396
+ projection,
1397
+ settlement,
1398
+ workspaceSettlementEvent,
1399
+ lease,
1400
+ ) {
1401
+ if (typeof this._withSessionAuthorityTransaction !== "function") {
1402
+ throw sessionConflict(
1403
+ "Timeline rollback requires a session authority transaction",
1404
+ { operationId: authority.operationId },
1405
+ );
1406
+ }
1407
+ const expectedHeadHash = projection.transcript.headHash;
1408
+ const input = resolutionInput(
1409
+ authority,
1410
+ projection,
1411
+ settlement,
1412
+ workspaceSettlementEvent.hash,
1413
+ );
1414
+ try {
1415
+ assertLeaseOwnedSync(lease);
1416
+ const receipt = requireSynchronous(
1417
+ this._withSessionAuthorityTransaction(
1418
+ authority.sessionId,
1419
+ expectedHeadHash,
1420
+ (transaction) => {
1421
+ assertLeaseOwnedSync(lease);
1422
+ return requireSynchronous(
1423
+ this._appendSessionResolution(transaction, input, {
1424
+ expectedHeadHash,
1425
+ }),
1426
+ "checkpoint timeline rollback resolution append",
1427
+ );
1428
+ },
1429
+ ),
1430
+ "checkpoint timeline authority transaction",
1431
+ );
1432
+ assertLeaseOwnedSync(lease);
1433
+ return validateResolutionReceipt(receipt, expectedHeadHash, input);
1434
+ } catch (caught) {
1435
+ const appendError = asError(caught);
1436
+ try {
1437
+ assertLeaseOwnedSync(lease);
1438
+ const observed = this._readTimelineProjection(
1439
+ authority,
1440
+ new Set([
1441
+ ...TIMELINE_RESUMABLE_CLASSIFICATIONS,
1442
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK,
1443
+ ]),
1444
+ );
1445
+ if (
1446
+ observed.classification ===
1447
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK
1448
+ ) {
1449
+ const receipt = requireSynchronous(
1450
+ this._reconcileSessionResolution(observed, input, {
1451
+ expectedHeadHash,
1452
+ expectedSessionId: authority.sessionId,
1453
+ }),
1454
+ "checkpoint timeline unknown append reconciliation",
1455
+ );
1456
+ if (receipt) {
1457
+ assertLeaseOwnedSync(lease);
1458
+ return validateResolutionReceipt(receipt, expectedHeadHash, input);
1459
+ }
1460
+ }
1461
+ } catch (reconciliationError) {
1462
+ appendError.checkpointRestoreSessionReconciliationError =
1463
+ reconciliationError;
1464
+ }
1465
+ throw sessionConflict(
1466
+ "Timeline rollback resolution append outcome is not exactly reconciled",
1467
+ { operationId: authority.operationId, commitState: "unknown" },
1468
+ appendError,
1469
+ );
1470
+ }
1471
+ }
1472
+
1473
+ _settleTimeline(
1474
+ saga,
1475
+ authority,
1476
+ projection,
1477
+ workspaceSettlement,
1478
+ workspaceSettlementEvent,
1479
+ lease,
1480
+ reconciledReceipt = null,
1481
+ ) {
1482
+ const receipt =
1483
+ reconciledReceipt ||
1484
+ (projection.classification ===
1485
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK
1486
+ ? this._reconcileTimelineResolution(
1487
+ saga,
1488
+ authority,
1489
+ projection,
1490
+ workspaceSettlement,
1491
+ )
1492
+ : this._appendTimelineResolution(
1493
+ saga,
1494
+ authority,
1495
+ projection,
1496
+ workspaceSettlement,
1497
+ workspaceSettlementEvent,
1498
+ lease,
1499
+ ));
1500
+ assertLeaseOwnedSync(lease);
1501
+ const sessionSettlement = immutable({
1502
+ ...workspaceSettlement,
1503
+ sessionRollbackCommitDigest: receipt.sessionRollbackCommitDigest,
1504
+ });
1505
+ saga = this._advanceExact(
1506
+ saga,
1507
+ "session_rollback_committed",
1508
+ sessionSettlement,
1509
+ );
1510
+ return Object.freeze({
1511
+ saga,
1512
+ sessionSettlement,
1513
+ receipt,
1514
+ });
1515
+ }
1516
+
1517
+ _retainFailure(operationError, lease, terminalEvidence = null) {
1518
+ const error = asError(operationError);
1519
+ let observed = null;
1520
+ let leaseOwned = false;
1521
+ try {
1522
+ assertLeaseOwnedSync(
1523
+ lease,
1524
+ "checkpoint rollback failure workspace lease assertion",
1525
+ );
1526
+ leaseOwned = true;
1527
+ } catch (leaseError) {
1528
+ error.checkpointRestoreRecoveryLeaseError = leaseError;
1529
+ }
1530
+ try {
1531
+ if (!leaseOwned) throw error.checkpointRestoreRecoveryLeaseError;
1532
+ observed = requireSynchronous(
1533
+ this.store.load(lease.recoveryOfOperationId),
1534
+ "checkpoint rollback failure reconciliation load",
1535
+ );
1536
+ const latest = observed.events?.at(-1);
1537
+ if (
1538
+ terminalEvidence &&
1539
+ observed.terminal &&
1540
+ observed.phase === "rolled_back" &&
1541
+ canonicalJson(latest?.evidence) === canonicalJson(terminalEvidence)
1542
+ ) {
1543
+ return Object.freeze({ saga: observed, reconciledFromError: true });
1544
+ }
1545
+ if (!observed.terminal) {
1546
+ partialRollbackSagaAuthority(
1547
+ observed,
1548
+ this.workspaceRoot,
1549
+ this._platform,
1550
+ );
1551
+ if (observed.phase !== "recovery_required") {
1552
+ observed = this._advanceExact(observed, "recovery_required", {
1553
+ reason: boundedText(
1554
+ error.message,
1555
+ "Partial rollback requires another recovery attempt",
1556
+ ),
1557
+ errorCode: boundedText(
1558
+ error.code,
1559
+ "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_FAILED",
1560
+ 128,
1561
+ ),
1562
+ });
1563
+ }
1564
+ }
1565
+ } catch (journalError) {
1566
+ error.checkpointRestoreRecoveryJournalError = journalError;
1567
+ }
1568
+
1569
+ try {
1570
+ requireSynchronous(
1571
+ lease.retainForRecovery(
1572
+ boundedText(
1573
+ `${error.code || "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_FAILED"}: ${error.message || "partial rollback outcome is unknown"}`,
1574
+ "Partial rollback outcome is unknown",
1575
+ 512,
1576
+ ),
1577
+ ),
1578
+ "checkpoint rollback workspace lease retention",
1579
+ );
1580
+ } catch (retentionError) {
1581
+ if (retentionError && typeof retentionError === "object") {
1582
+ retentionError.checkpointRestoreRecoveryCause = error;
1583
+ retentionError.checkpointRestoreOperationId =
1584
+ lease.recoveryOfOperationId;
1585
+ retentionError.checkpointRestoreSagaPhase = observed?.phase || null;
1586
+ retentionError.checkpointRestoreSagaSeq = observed?.seq || null;
1587
+ retentionError.checkpointRestoreSagaHeadHash =
1588
+ observed?.headHash || null;
1589
+ retentionError.checkpointRestoreRecoveryRequired = true;
1590
+ }
1591
+ throw retentionError;
1592
+ }
1593
+ throw error;
1594
+ }
1595
+
1596
+ rollback(
1597
+ operationId,
1598
+ { expectedSeq, expectedHash, expectedOwnerDigest } = {},
1599
+ ) {
1600
+ validateExpectedAuthority({
1601
+ operationId,
1602
+ expectedSeq,
1603
+ expectedHash,
1604
+ expectedOwnerDigest,
1605
+ });
1606
+ const previewSaga = this._loadExact(operationId, expectedSeq, expectedHash);
1607
+ partialRollbackSagaAuthority(
1608
+ previewSaga,
1609
+ this.workspaceRoot,
1610
+ this._platform,
1611
+ );
1612
+ const observedOwner = this._inspectBoundOwner(
1613
+ operationId,
1614
+ expectedOwnerDigest,
1615
+ );
1616
+
1617
+ let settled;
1618
+ try {
1619
+ settled = requireSynchronous(
1620
+ this._withWorkspaceRecoveryLockSync(
1621
+ {
1622
+ ...this._lockOptions(operationId),
1623
+ expectedOwner: observedOwner,
1624
+ },
1625
+ (lease) => {
1626
+ let terminalEvidence = null;
1627
+ try {
1628
+ assertLeaseOwnedSync(lease);
1629
+ let saga = this._loadExact(
1630
+ operationId,
1631
+ expectedSeq,
1632
+ expectedHash,
1633
+ );
1634
+ const authority = {
1635
+ ...partialRollbackSagaAuthority(
1636
+ saga,
1637
+ this.workspaceRoot,
1638
+ this._platform,
1639
+ ),
1640
+ operationId,
1641
+ };
1642
+ const currentBase = currentRecoveryBaseEvent(saga);
1643
+ const nonSettled = NON_SETTLED_BASE_PHASES.has(
1644
+ currentBase?.phase,
1645
+ );
1646
+ const workspaceSettled =
1647
+ currentBase?.phase === "workspace_rolled_back";
1648
+ const sessionSettled =
1649
+ currentBase?.phase === "session_rollback_committed";
1650
+ if (!nonSettled && !workspaceSettled && !sessionSettled) {
1651
+ throw rollbackError(
1652
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.ACTION_NOT_ALLOWED,
1653
+ "Partial rollback requires a mutation or rollback settlement base",
1654
+ { operationId, basePhase: currentBase?.phase || null },
1655
+ );
1656
+ }
1657
+ if (authority.restoreSurface === "direct" && sessionSettled) {
1658
+ throw rollbackError(
1659
+ CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ERROR_CODES.ACTION_NOT_ALLOWED,
1660
+ "Direct rollback cannot claim a session settlement",
1661
+ { operationId },
1662
+ );
1663
+ }
1664
+
1665
+ let sessionProjection = null;
1666
+ if (authority.restoreSurface === "timeline") {
1667
+ const classifications = sessionSettled
1668
+ ? new Set([
1669
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK,
1670
+ ])
1671
+ : workspaceSettled
1672
+ ? new Set([
1673
+ ...TIMELINE_RESUMABLE_CLASSIFICATIONS,
1674
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK,
1675
+ ])
1676
+ : TIMELINE_RESUMABLE_CLASSIFICATIONS;
1677
+ sessionProjection = this._readTimelineProjection(
1678
+ authority,
1679
+ classifications,
1680
+ sessionSettled
1681
+ ? currentBase.evidence.sessionRollbackCommitDigest
1682
+ : null,
1683
+ );
1684
+ }
1685
+
1686
+ const publishRecoveryStarted = (recoveryRequestId) => {
1687
+ if (saga.phase !== "recovery_required") {
1688
+ saga = this._advanceExact(saga, "recovery_required", {
1689
+ reason:
1690
+ "Partial workspace mutation requires exact rollback",
1691
+ errorCode: "CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_REQUESTED",
1692
+ });
1693
+ }
1694
+ assertLeaseOwnedSync(lease);
1695
+ saga = this._advanceExact(saga, "recovery_started", {
1696
+ workspaceLockOwner: lease.owner,
1697
+ lockOwnerDigest: this._workspaceLockOwnerDigest(lease.owner),
1698
+ recoveryAction: CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION,
1699
+ recoveryRequestId,
1700
+ });
1701
+ assertLeaseOwnedSync(lease);
1702
+ };
1703
+
1704
+ let recoveryRequestId;
1705
+ let result;
1706
+ let sessionRollbackCommitDigest = null;
1707
+ let reconciledFromError = false;
1708
+
1709
+ if (nonSettled) {
1710
+ recoveryRequestId = this._newRequestId();
1711
+ publishRecoveryStarted(recoveryRequestId);
1712
+ const plan = this._preparePlan(
1713
+ operationId,
1714
+ recoveryRequestId,
1715
+ authority,
1716
+ lease,
1717
+ sessionProjection,
1718
+ );
1719
+ const bindingEvidence = rollbackBindingEvidence(
1720
+ authority,
1721
+ recoveryRequestId,
1722
+ plan,
1723
+ );
1724
+ saga = this._advanceExact(
1725
+ saga,
1726
+ "rollback_prepared",
1727
+ bindingEvidence,
1728
+ );
1729
+ assertLeaseOwnedSync(lease);
1730
+ if (plan.targetCount > 0) {
1731
+ saga = this._advanceExact(
1732
+ saga,
1733
+ "rollback_started",
1734
+ bindingEvidence,
1735
+ );
1736
+ result = this._executePlan(
1737
+ operationId,
1738
+ recoveryRequestId,
1739
+ plan,
1740
+ lease,
1741
+ );
1742
+ } else {
1743
+ result = immutable({
1744
+ schema: CHECKPOINT_ROLLBACK_RESULT_SCHEMA,
1745
+ version: CHECKPOINT_ROLLBACK_RESULT_VERSION,
1746
+ engine: plan.engine,
1747
+ rolledBackCount: 0,
1748
+ rollbackStateDigest: plan.expectedRollbackStateDigest,
1749
+ });
1750
+ }
1751
+ const workspaceSettlement = rollbackSettlementEvidence(
1752
+ authority,
1753
+ recoveryRequestId,
1754
+ plan,
1755
+ result,
1756
+ );
1757
+ assertLeaseOwnedSync(lease);
1758
+ saga = this._advanceExact(
1759
+ saga,
1760
+ "workspace_rolled_back",
1761
+ workspaceSettlement,
1762
+ );
1763
+ const workspaceSettlementEvent = saga.events.at(-1);
1764
+ if (authority.restoreSurface === "timeline") {
1765
+ const session = this._settleTimeline(
1766
+ saga,
1767
+ authority,
1768
+ sessionProjection,
1769
+ workspaceSettlement,
1770
+ workspaceSettlementEvent,
1771
+ lease,
1772
+ );
1773
+ saga = session.saga;
1774
+ terminalEvidence = session.sessionSettlement;
1775
+ sessionRollbackCommitDigest =
1776
+ session.receipt.sessionRollbackCommitDigest;
1777
+ reconciledFromError =
1778
+ session.receipt.reconciledFromError === true;
1779
+ } else {
1780
+ terminalEvidence = workspaceSettlement;
1781
+ }
1782
+ } else if (workspaceSettled) {
1783
+ const workspaceSettlement = immutable(currentBase.evidence);
1784
+ recoveryRequestId = workspaceSettlement.recoveryRequestId;
1785
+ let existingReceipt = null;
1786
+ if (
1787
+ sessionProjection?.classification ===
1788
+ CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK
1789
+ ) {
1790
+ // Inspect and bind an existing resolution before any saga
1791
+ // republication can change the current workspace event hash.
1792
+ existingReceipt = this._reconcileTimelineResolution(
1793
+ saga,
1794
+ authority,
1795
+ sessionProjection,
1796
+ workspaceSettlement,
1797
+ );
1798
+ }
1799
+ const plan = this._preparePlan(
1800
+ operationId,
1801
+ recoveryRequestId,
1802
+ authority,
1803
+ lease,
1804
+ sessionProjection,
1805
+ );
1806
+ validateSettledPlan(plan, authority, workspaceSettlement);
1807
+ result = immutable({
1808
+ schema: CHECKPOINT_ROLLBACK_RESULT_SCHEMA,
1809
+ version: CHECKPOINT_ROLLBACK_RESULT_VERSION,
1810
+ engine: plan.engine,
1811
+ rolledBackCount: workspaceSettlement.rolledBackCount,
1812
+ rollbackStateDigest: workspaceSettlement.rollbackStateDigest,
1813
+ });
1814
+ let workspaceSettlementEvent = currentBase;
1815
+ if (saga.phase !== "workspace_rolled_back") {
1816
+ publishRecoveryStarted(recoveryRequestId);
1817
+ saga = this._advanceExact(
1818
+ saga,
1819
+ "workspace_rolled_back",
1820
+ workspaceSettlement,
1821
+ );
1822
+ workspaceSettlementEvent = saga.events.at(-1);
1823
+ }
1824
+ if (authority.restoreSurface === "timeline") {
1825
+ const session = this._settleTimeline(
1826
+ saga,
1827
+ authority,
1828
+ sessionProjection,
1829
+ workspaceSettlement,
1830
+ workspaceSettlementEvent,
1831
+ lease,
1832
+ existingReceipt,
1833
+ );
1834
+ saga = session.saga;
1835
+ terminalEvidence = session.sessionSettlement;
1836
+ sessionRollbackCommitDigest =
1837
+ session.receipt.sessionRollbackCommitDigest;
1838
+ reconciledFromError =
1839
+ session.receipt.reconciledFromError === true;
1840
+ } else {
1841
+ terminalEvidence = workspaceSettlement;
1842
+ }
1843
+ } else {
1844
+ const sessionSettlement = immutable(currentBase.evidence);
1845
+ const workspaceSettlement =
1846
+ workspaceSettlementFromSessionSettlement(sessionSettlement);
1847
+ recoveryRequestId = sessionSettlement.recoveryRequestId;
1848
+ const existingReceipt = this._reconcileTimelineResolution(
1849
+ saga,
1850
+ authority,
1851
+ sessionProjection,
1852
+ workspaceSettlement,
1853
+ );
1854
+ const plan = this._preparePlan(
1855
+ operationId,
1856
+ recoveryRequestId,
1857
+ authority,
1858
+ lease,
1859
+ sessionProjection,
1860
+ );
1861
+ validateSettledPlan(plan, authority, workspaceSettlement);
1862
+ if (
1863
+ existingReceipt.sessionRollbackCommitDigest !==
1864
+ sessionSettlement.sessionRollbackCommitDigest
1865
+ ) {
1866
+ throw sessionConflict(
1867
+ "Saga session settlement does not match the verified transcript resolution",
1868
+ { operationId },
1869
+ );
1870
+ }
1871
+ result = immutable({
1872
+ schema: CHECKPOINT_ROLLBACK_RESULT_SCHEMA,
1873
+ version: CHECKPOINT_ROLLBACK_RESULT_VERSION,
1874
+ engine: plan.engine,
1875
+ rolledBackCount: workspaceSettlement.rolledBackCount,
1876
+ rollbackStateDigest: workspaceSettlement.rollbackStateDigest,
1877
+ });
1878
+ if (saga.phase !== "session_rollback_committed") {
1879
+ publishRecoveryStarted(recoveryRequestId);
1880
+ saga = this._advanceExact(
1881
+ saga,
1882
+ "session_rollback_committed",
1883
+ sessionSettlement,
1884
+ );
1885
+ }
1886
+ terminalEvidence = sessionSettlement;
1887
+ sessionRollbackCommitDigest =
1888
+ sessionSettlement.sessionRollbackCommitDigest;
1889
+ reconciledFromError =
1890
+ existingReceipt.reconciledFromError === true;
1891
+ }
1892
+
1893
+ assertLeaseOwnedSync(lease);
1894
+ saga = this._advanceExact(saga, "rolled_back", terminalEvidence);
1895
+ assertLeaseOwnedSync(lease);
1896
+ return Object.freeze({
1897
+ saga,
1898
+ recoveryRequestId,
1899
+ rolledBackCount: result.rolledBackCount,
1900
+ rollbackStateDigest: result.rollbackStateDigest,
1901
+ resultDigest: terminalEvidence.resultDigest,
1902
+ sessionRollbackCommitDigest,
1903
+ reconciledFromError,
1904
+ });
1905
+ } catch (error) {
1906
+ const reconciled = this._retainFailure(
1907
+ error,
1908
+ lease,
1909
+ terminalEvidence,
1910
+ );
1911
+ if (reconciled?.saga) {
1912
+ return Object.freeze({
1913
+ saga: reconciled.saga,
1914
+ recoveryRequestId: terminalEvidence.recoveryRequestId,
1915
+ rolledBackCount: terminalEvidence.rolledBackCount,
1916
+ rollbackStateDigest: terminalEvidence.rollbackStateDigest,
1917
+ resultDigest: terminalEvidence.resultDigest,
1918
+ sessionRollbackCommitDigest:
1919
+ terminalEvidence.sessionRollbackCommitDigest || null,
1920
+ reconciledFromError: true,
1921
+ });
1922
+ }
1923
+ return reconciled;
1924
+ }
1925
+ },
1926
+ ),
1927
+ "checkpoint rollback workspace recovery transaction",
1928
+ );
1929
+ } catch (error) {
1930
+ throw propagateRecoveryDiagnostics(error);
1931
+ }
1932
+
1933
+ const archive = this._archiveTerminal(settled.saga);
1934
+ return Object.freeze({
1935
+ schema: CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_RESULT_SCHEMA,
1936
+ version: CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_RESULT_VERSION,
1937
+ ok: true,
1938
+ action: CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION,
1939
+ ...terminalSummary(settled.saga),
1940
+ recoveryRequestId: settled.recoveryRequestId,
1941
+ rolledBackCount: settled.rolledBackCount,
1942
+ rollbackStateDigest: settled.rollbackStateDigest,
1943
+ resultDigest: settled.resultDigest,
1944
+ sessionRollbackCommitDigest: settled.sessionRollbackCommitDigest,
1945
+ reconciledFromError: settled.reconciledFromError,
1946
+ ...archive,
1947
+ });
1948
+ }
1949
+ }
1950
+
1951
+ export function createCheckpointRestorePartialRollbackController(options = {}) {
1952
+ return new CheckpointRestorePartialRollbackController(options);
1953
+ }