chainlesschain 0.162.198 → 0.162.200

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 (195) hide show
  1. package/bin/chainlesschain.js +14 -6
  2. package/package.json +4 -1
  3. package/scripts/command-lifecycle-report.mjs +94 -0
  4. package/src/assets/web-panel/assets/{AIOps-CSN-D2xb.js → AIOps-DcQ0bWKy.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-CBSMlGzv.js → ActionButton-GecwfFPt.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-3yr3Nt2t.js → Analytics-CD6iSt9v.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-1b-o3lMH.js → AppLayout-CDEtuEYf.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-Dr8rJi17.js → Artifacts-DS3jAjeW.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-2V689HC2.js → Audit-CcSJA048.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-DIxkNrD_.js → BackgroundAgents-DRo9Morb.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-Pg6BcwrE.js → Backup-Vw5MrWft.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-CLUpqyfH.js → BaseInput-DNHIu823.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-qVS8rD4G.js → Chat-DvAsO8-b.js} +5 -5
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-ti49X12Q.js → ChatBubbleRenderer-9aDuObay.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-Bs9AaEay.js → Checkbox-CKCa7tvn.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-Dvc1mq8q.js → Codegen-CIFvZdaT.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-C4_biO7A.js → Col-C79yz7bK.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-al25XQUV.js → Community-DVvZskLJ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-D-ZDB8M1.js → Compact-oaew61hV.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-6lIfDz1W.js → Compliance-DqA7wmRx.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DdI48XMJ.js → Cowork-B-qGadxa.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-DF5HZd2g.js → Cron-CUlCl4sS.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-DuxeSvt3.js → Crosschain-CNOlr9DU.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-B9yHkUTd.js → DID-DJqFCT_3.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-Z5EuI3IM.js → Dashboard-CznGpphf.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-vuRfO3dY.js → Dropdown-_cJw218E.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-QFLCc1uL.js → EmailListRenderer-Dr-1BZ9C.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BZ7EXkFJ.js → FamilyGuardDashboard-Dxf4xtSk.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-DycIEg2s.js → Federation-DOiG3i_1.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-Ba0H_7yG.js → FormItemContext-BzbMcFB9.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer--JgSTK-C.js → GenericCardRenderer-VbBrYttP.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DEvTbldE.js → Git-CLG8E6ED.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-BJIljy18.js → Governance-LGJQfk_q.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-CVyW3G3q.js → Inference-DlQKx9lF.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-v8-borge.js → KnowledgeGraph-DU8vEouV.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-BCdS7gGo.js → Logs-FZIQOdln.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-BJNWIrbw.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-AN6tGA2B.js → Marketplace-C_xd-zZI.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-DGViXASN.js → McpTools-q-YktphS.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-Bpvmx9X4.js → Memory-Cq9OhDin.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-CTP-DOTC.js → MobileBridge-B03_R98T.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-C8qfktxn.js → MobileProjects-DZrk-qmF.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-ndYr0M1s.js → Mtc-BidCKW28.js} +2 -2
  44. package/src/assets/web-panel/assets/{MtcAudit-Ci0-YK7W.js → MtcAudit-r-iaMVM7.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-BehSTFW7.js → Multisig-CgTUFvff.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-B91_YBLv.js → NLProgramming-B7NvzfAV.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-BQlJ0-gq.js → Notes-80CWNqJI.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-CDgYMjYK.js → NotificationSettings-0H6xdiY3.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-BJkSlKeH.js → OrderTableRenderer-CKP4AFWy.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-B-b3cWCp.js → Organization-B8sxCV_h.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-CILUL4nL.js → Overflow-CW9JBBhQ.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-BTCFBkF0.js → P2P-bdSQV7oZ.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-BAR6AUx5.js → PdhVaultBrowser-DQad42nW.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-RRf6HbWJ.js → Permissions-CueexxTR.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-BGTMiICa.js → PersonalDataHub-fqxflhR8.js} +6 -6
  56. package/src/assets/web-panel/assets/{Pipeline-945qY9YF.js → Pipeline-KFwwnIMC.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-DPZwI6lJ.js → Privacy-ClnD9buY.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-BPH2NRKc.js → ProjectInit-CZEz6sm3.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-BZCRNCvb.js → ProjectSettings-B6NzpmOI.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-BTI__tBt.js → Projects-DYyuwPCs.js} +1 -1
  61. package/src/assets/web-panel/assets/Providers-C3tz_jdh.js +1 -0
  62. package/src/assets/web-panel/assets/{QrScannerModal-N6rbqt1r.js → QrScannerModal-BzuOBbqB.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DrwjPS7M.js → QuickAsk-CmNO9qSj.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-Cog9Wd99.js → Recommend-VJNj3_NF.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-DC9pynjM.js → RemoteSession-CrbdbSBF.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-D4th4gKS.js → Reputation-DTyMd45j.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-ogiWR6v9.js → Row-DHrQ-Icz.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-uYlk-2Oj.js → RssFeed-DqWxY9c5.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-DYzzwvtM.js → Search-ctF1Ks3J.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Da7YOyX6.js → Security-BrQz8uo5.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-CLI1VM1X.js → Services-D4scVFar.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DK77dzDC.js → Skeleton-oUScaOjv.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-C_z7Lmhl.js → Skills-CL-y0eLt.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-D6413Q8L.js → Sla-DQExhwXj.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-C07ydbj2.js → SpeechSettings-iNp_I7kN.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-DGOm2W22.js → SyncSettings-CTDlMo0A.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-Cr_PVyLM.js → Tasks-4kb8l3sc.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-BhL2mZ0t.js → Templates-BXcL9oXW.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-CPk5oyUr.js → Tenant-DLE8eS0D.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-ClhqUsqV.js → Terminal-DGpUZPPK.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-BCC7MV5a.js → TimelineRenderer-C1pWXGPC.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-DD1Jt4oO.js → Tokens-BYfbfqhZ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-BCydFvN2.js → Trigger-K9YfLOCw.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-DDHT1noa.js → Trust-Ctkq77Hy.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-CWPZC7oI.js → UkeySign-C6_CSe9J.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-9Ea0nJR0.js → VideoEditing-Be3rpOWs.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-BjxhLxTf.js → Wallet-D0UIplxk.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-C6uCye67.js → WebAuthn-CF9bM9I3.js} +3 -3
  89. package/src/assets/web-panel/assets/{WorkflowEditor-d2g4reYo.js → WorkflowEditor-D0H1XVPY.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-frTc6ScX.js → chat-Cgzzbe0o.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-B5K6lG3v.js → colors-BwNxEVS0.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-Cdn46r8D.js → compact-item-D60Z4Hhs.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-DU1jqVQi.js → createContext-MFk58XB0.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-BJiw369i.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-DdNH-aW_.js → hasIn-CRj2SvGO.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DM5FmPZU.js → index--s45_1cb.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-D4l82Me1.js → index--tquQ2ZG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CQorP1Ng.js → index-1T1rhokn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CHmkkdfT.js → index-B-M82_rx.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CGiwfwB9.js → index-BE_el1Zs.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BwlIxAmX.js → index-BhRkR7IO.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cj6P9Nst.js → index-Bo0y96-K.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BoFak7le.js → index-CJgu5JUK.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dca_i7H8.js → index-CLWwdebO.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BVLL-mq_.js → index-CNAK5QG_.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-LaivuXyF.js → index-CQjJjPRD.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-t_OBzIP4.js → index-CTDbAfae.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BcGwGIaR.js → index-CVI_9sRK.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-D23xGYjM.js → index-CY14oMQK.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-VDM2KKo9.js → index-CbiTJkZf.js} +1 -1
  111. package/src/assets/web-panel/assets/index-CiMouosn.js +1 -0
  112. package/src/assets/web-panel/assets/{index-32ajXmTb.js → index-CqWpxMqx.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-PjAS0sIZ.js → index-CvB8lYUR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-7ehytj1K.js → index-D51XwtCY.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DtBCCkC4.js → index-D6HvK_1O.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Be4OtL-N.js → index-D9jVIwLJ.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CUoNAmE8.js → index-DA4tE98u.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-cYaWV4Mq.js → index-DBwOtXw0.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-MM4Zz2OE.js → index-DKdNzTIb.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DIVyTmZ3.js → index-DRySdmvN.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CtRPi4YC.js → index-DWzyTev7.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DXcKlwy7.js +1 -0
  123. package/src/assets/web-panel/assets/{index-kVO_MDm-.js → index-DYjs7jAP.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D4miNkWv.js → index-DbROzMBp.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-yZRuZpwK.js → index-DhFtk5PR.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BXKhhx9V.js → index-Di2AbMLg.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BC4eGkar.js → index-DvmocQsM.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-NmEXVjzt.js → index-Dw-o0zEF.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DRzBBdQ0.js → index-DysPoP38.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-HjCvXHf5.js → index-LX9pJxm_.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-gg3QPC3i.js → index-OI_fXWx0.js} +3 -3
  132. package/src/assets/web-panel/assets/{index-mgU3Q-lN.js → index-eDTOX8Xj.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-D1qIC4GR.js → index-gWKDsTzF.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DftZuyA7.js → index-q_GDUVAA.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-DwLTRNdo.js → initDefaultProps-C61kZqEd.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-BchZDXnh.js → motion-DZnT5U1L.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-e8M-zZFl.js → move-L8wzwbel.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-Ci40fQb3.js → mtc-parser-DLdYSejc.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-C7110_f2.js → omit-D53cbS59.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-skteMgnM.js → pickAttrs-Bbc5HPtu.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-MGZ-lh2B.js → placementArrow-BkvmO860.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-N4Sd7uy-.js → responsiveObserve-u2y1ryS1.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-C7HDGRaa.js → slide-BBqj7svU.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-BhRexlgy.js → statusUtils-GN0mqu2d.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-ChEoQkN2.js → styleChecker-BvwPKoBT.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-CDbG1s2Y.js → useFlexGapSupport-CCt8tnfY.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-CUIx0tcJ.js → useFs-DXAxl2SV.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-bkO7L7ec.js → usePersonalDataHub-DihlR2dj.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-B_ZvABIG.js → vnode-D7BOX2gi.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-D5shjpS8.js → zoom-CVbO-nL7.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +1 -1
  153. package/src/commands/mcp.js +119 -0
  154. package/src/commands/session-show.js +8 -10
  155. package/src/commands/session.js +60 -52
  156. package/src/data/changelog.json +27 -1
  157. package/src/gateways/ws/session-protocol.js +14 -4
  158. package/src/gateways/ws/ws-agent-handler.js +31 -0
  159. package/src/harness/jsonl-session-store.js +1147 -130
  160. package/src/harness/mcp-client.js +44 -11
  161. package/src/harness/session-index.js +80 -80
  162. package/src/harness/session-list-index.js +147 -2
  163. package/src/lib/background-interaction-journal.js +2 -2
  164. package/src/lib/command-lifecycle-report.js +516 -0
  165. package/src/lib/mcp-stdio-environment.js +48 -0
  166. package/src/lib/mcp-stdio-executable-identity.js +552 -17
  167. package/src/lib/mcp-stdio-package-materialization.js +1424 -0
  168. package/src/lib/packer/native-update-state.js +979 -1
  169. package/src/lib/packer/pack-update-applier.js +1050 -125
  170. package/src/lib/packer/pkg-config-generator.js +1 -0
  171. package/src/lib/paths.js +53 -0
  172. package/src/lib/process-execution-broker/index.js +180 -37
  173. package/src/lib/process-execution-broker/platform-sandbox.js +291 -24
  174. package/src/lib/recent-session.js +17 -17
  175. package/src/lib/secure-file-identity.js +47 -0
  176. package/src/lib/secure-fs.js +21 -2
  177. package/src/lib/session-anti-rollback-anchor.js +766 -0
  178. package/src/lib/session-host-lease.js +492 -0
  179. package/src/lib/session-persistence-failure.js +109 -0
  180. package/src/lib/session-picker.js +26 -3
  181. package/src/lib/session-search.js +25 -11
  182. package/src/lib/side-effect-ledger-store.js +2 -2
  183. package/src/lib/skill-execution-authority.js +13 -9
  184. package/src/lib/turn-binding-store.js +2 -2
  185. package/src/repl/agent-repl.js +309 -39
  186. package/src/runtime/agent-core.js +88 -5
  187. package/src/runtime/cleanup-deadline.js +143 -0
  188. package/src/runtime/headless-runner.js +357 -139
  189. package/src/runtime/headless-stream.js +207 -28
  190. package/src/runtime/output-backpressure.js +53 -0
  191. package/src/assets/web-panel/assets/MarkdownRenderer-CU-JB2Ns.js +0 -1
  192. package/src/assets/web-panel/assets/Providers-DDCf_nIw.js +0 -1
  193. package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +0 -1
  194. package/src/assets/web-panel/assets/index-CzrCHrFy.js +0 -1
  195. package/src/assets/web-panel/assets/index-OJsGskZw.js +0 -1
@@ -1,13 +1,28 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ import os from "node:os";
5
+ import { spawn } from "node:child_process";
6
+ import {
7
+ sameFileStatIdentity,
8
+ samePathHandleFileIdentity,
9
+ samePathHandleStableFileIdentity,
10
+ withTrustedFileParentSync,
11
+ } from "../secure-file-identity.js";
3
12
 
4
13
  export const NATIVE_UPDATE_LINEAGE_SCHEMA =
5
14
  "chainlesschain.native-update-lineage.v1";
6
15
  export const NATIVE_UPDATE_RESULT_SCHEMA =
7
16
  "chainlesschain.native-update-result.v1";
17
+ export const NATIVE_GENERATION_TRANSACTION_SCHEMA =
18
+ "chainlesschain.native-install-transaction.v1";
8
19
  export const SHA256_HEX = /^[0-9a-f]{64}$/;
9
20
  const UUID =
10
21
  /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
22
+ const NATIVE_LOCK_TOKEN = new RegExp(
23
+ `^([1-9][0-9]*):(?:[0-9a-f]{32}|${UUID.source.slice(1, -1)})$`,
24
+ "i",
25
+ );
11
26
  const RESULT_STATUS = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
12
27
 
13
28
  export class NativeUpdateStateError extends Error {
@@ -43,6 +58,7 @@ export function assertSafePathAncestors(filePath, options = {}) {
43
58
  platform = process.platform,
44
59
  leaf = "any",
45
60
  allowMissingLeaf = true,
61
+ allowPathAliases = false,
46
62
  } = options;
47
63
  const resolved = path.resolve(filePath);
48
64
  const parsed = path.parse(resolved);
@@ -73,7 +89,7 @@ export function assertSafePathAncestors(filePath, options = {}) {
73
89
  `${label} ancestor is not a directory: ${current}`,
74
90
  );
75
91
  }
76
- if (!isLeaf && stat.isDirectory()) {
92
+ if (!allowPathAliases && !isLeaf && stat.isDirectory()) {
77
93
  let real;
78
94
  try {
79
95
  real = fs.realpathSync.native(current);
@@ -139,6 +155,8 @@ export function nativeUpdatePaths(canonicalPath) {
139
155
  backupPath: `${canonicalPath}.previous`,
140
156
  lockPath: `${canonicalPath}.update.lock`,
141
157
  lineagePath: `${canonicalPath}.update-lineage.json`,
158
+ journalPath: `${canonicalPath}.update-transaction.json`,
159
+ lastJournalPath: `${canonicalPath}.update-transaction.json.last`,
142
160
  resultPath: `${canonicalPath}.update-result.json`,
143
161
  lastResultPath: `${canonicalPath}.update-result.last.json`,
144
162
  };
@@ -229,6 +247,966 @@ export function nativeResultRequiresRecovery(value) {
229
247
  );
230
248
  }
231
249
 
250
+ const GENERATION_PHASES = new Set([
251
+ "prepared",
252
+ "target-committed",
253
+ "backup-committed",
254
+ "alias-committed",
255
+ "verified",
256
+ "lineage-committed",
257
+ "committed",
258
+ ]);
259
+
260
+ function readStableRegular(filePath, options = {}) {
261
+ assertSafeRegularFile(filePath, {
262
+ label: options.label || "native generation state",
263
+ allowMissingLeaf: false,
264
+ platform: options.platform || process.platform,
265
+ });
266
+ return withTrustedFileParentSync(
267
+ fs,
268
+ filePath,
269
+ ({ canonicalPath, parentDevice }) =>
270
+ readStableRegularAtPath(canonicalPath, parentDevice, options),
271
+ );
272
+ }
273
+
274
+ function readStableRegularAtPath(filePath, parentDevice, options) {
275
+ const { capture = false, maxBytes = 1024 * 1024 } = options;
276
+ const fd = fs.openSync(filePath, "r");
277
+ try {
278
+ const before = fs.fstatSync(fd, { bigint: true });
279
+ const pathBefore = fs.lstatSync(filePath, { bigint: true });
280
+ const hash = crypto.createHash("sha256");
281
+ const chunks = [];
282
+ let capturedBytes = 0;
283
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
284
+ let offset = 0;
285
+ for (;;) {
286
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, offset);
287
+ if (bytesRead === 0) break;
288
+ const chunk = buffer.subarray(0, bytesRead);
289
+ hash.update(chunk);
290
+ offset += bytesRead;
291
+ if (capture) {
292
+ capturedBytes += bytesRead;
293
+ if (capturedBytes > maxBytes) {
294
+ throw new NativeUpdateStateError(
295
+ `${options.label || "native generation state"} is oversized`,
296
+ "TRANSACTION_INVALID",
297
+ );
298
+ }
299
+ chunks.push(Buffer.from(chunk));
300
+ }
301
+ }
302
+ const after = fs.fstatSync(fd, { bigint: true });
303
+ const pathAfter = fs.lstatSync(filePath, { bigint: true });
304
+ const stable =
305
+ before.isFile() &&
306
+ pathBefore.isFile() &&
307
+ after.isFile() &&
308
+ pathAfter.isFile() &&
309
+ before.nlink > 0n &&
310
+ samePathHandleFileIdentity(pathBefore, before, parentDevice) &&
311
+ sameFileStatIdentity(before, after) &&
312
+ samePathHandleFileIdentity(pathAfter, after, parentDevice);
313
+ if (!stable) {
314
+ throw new NativeUpdateStateError(
315
+ `${options.label || "native generation state"} changed while read`,
316
+ "TRANSACTION_CHANGED",
317
+ );
318
+ }
319
+ return {
320
+ bytes: capture ? Buffer.concat(chunks) : null,
321
+ sha256: hash.digest("hex"),
322
+ parentDevice,
323
+ stat: after,
324
+ };
325
+ } finally {
326
+ fs.closeSync(fd);
327
+ }
328
+ }
329
+
330
+ function fsyncNativeDirectory(directoryPath) {
331
+ let fd;
332
+ try {
333
+ fd = fs.openSync(directoryPath, "r");
334
+ fs.fsyncSync(fd);
335
+ } catch (error) {
336
+ if (
337
+ !["EACCES", "EBADF", "EINVAL", "EISDIR", "ENOTSUP", "EPERM"].includes(
338
+ error?.code,
339
+ )
340
+ ) {
341
+ throw error;
342
+ }
343
+ } finally {
344
+ if (fd !== undefined) fs.closeSync(fd);
345
+ }
346
+ }
347
+
348
+ function fsyncNativeFileDescriptor(fd) {
349
+ try {
350
+ fs.fsyncSync(fd);
351
+ } catch (error) {
352
+ if (!["EBADF", "EINVAL", "ENOTSUP", "EPERM"].includes(error?.code)) {
353
+ throw error;
354
+ }
355
+ }
356
+ }
357
+
358
+ function parseGenerationJournal(journalPath, platform) {
359
+ const { bytes } = readStableRegular(journalPath, {
360
+ capture: true,
361
+ label: "native generation transaction journal",
362
+ platform,
363
+ });
364
+ let value;
365
+ try {
366
+ value = JSON.parse(bytes.toString("utf8"));
367
+ } catch (error) {
368
+ throw new NativeUpdateStateError(
369
+ `native generation transaction journal is corrupt: ${error.message}`,
370
+ "TRANSACTION_INVALID",
371
+ );
372
+ }
373
+ const validBefore = (present, digest) =>
374
+ typeof present === "boolean" &&
375
+ (present ? SHA256_HEX.test(digest || "") : digest === null);
376
+ const valid =
377
+ value?.schema === NATIVE_GENERATION_TRANSACTION_SCHEMA &&
378
+ UUID.test(value.transactionId || "") &&
379
+ ["install", "update", "rescue"].includes(value.operation) &&
380
+ (platform !== "win32" ||
381
+ (Number.isSafeInteger(value.ownerPid) && value.ownerPid > 0)) &&
382
+ GENERATION_PHASES.has(value.phase) &&
383
+ ["rollback", "commit"].includes(value.decision) &&
384
+ (value.phase === "committed") === (value.decision === "commit") &&
385
+ SHA256_HEX.test(value.expectedSha256 || "") &&
386
+ validBefore(value.hadTarget, value.targetBeforeSha256) &&
387
+ validBefore(value.hadBackup, value.backupBeforeSha256) &&
388
+ validBefore(value.hadAlias, value.aliasBeforeSha256) &&
389
+ validBefore(value.hadLineage, value.lineageBeforeSha256);
390
+ if (!valid) {
391
+ throw new NativeUpdateStateError(
392
+ "native generation transaction journal failed schema validation",
393
+ "TRANSACTION_INVALID",
394
+ );
395
+ }
396
+ return value;
397
+ }
398
+
399
+ function stableRegularShaOrNull(filePath, platform, label) {
400
+ const stat = lstatOrNull(filePath);
401
+ if (!stat) return null;
402
+ if (stat.isSymbolicLink() || !stat.isFile()) {
403
+ throw new NativeUpdateStateError(`${label} has an unsafe file type`);
404
+ }
405
+ return readStableRegular(filePath, { label, platform }).sha256;
406
+ }
407
+
408
+ function stableAliasShaOrNull(aliasPath) {
409
+ if (!lstatOrNull(aliasPath)) return null;
410
+ const before = fs.lstatSync(aliasPath, { bigint: true });
411
+ if (!before.isSymbolicLink()) {
412
+ throw new NativeUpdateStateError(
413
+ "native generation alias has an unsafe file type",
414
+ );
415
+ }
416
+ const rawTarget = fs.readlinkSync(aliasPath);
417
+ const after = fs.lstatSync(aliasPath, { bigint: true });
418
+ if (
419
+ !after.isSymbolicLink() ||
420
+ before.dev !== after.dev ||
421
+ before.ino !== after.ino ||
422
+ before.size !== after.size ||
423
+ before.mtimeNs !== after.mtimeNs ||
424
+ before.ctimeNs !== after.ctimeNs
425
+ ) {
426
+ throw new NativeUpdateStateError(
427
+ "native generation alias changed while read",
428
+ "TRANSACTION_CHANGED",
429
+ );
430
+ }
431
+ return crypto
432
+ .createHash("sha256")
433
+ .update(Buffer.from(rawTarget))
434
+ .digest("hex");
435
+ }
436
+
437
+ function restoreRegularGeneration(
438
+ sourcePath,
439
+ destinationPath,
440
+ expectedSha,
441
+ transactionId,
442
+ platform,
443
+ label,
444
+ ) {
445
+ if (
446
+ stableRegularShaOrNull(sourcePath, platform, `${label} snapshot`) !==
447
+ expectedSha
448
+ ) {
449
+ throw new NativeUpdateStateError(
450
+ `${label} recovery snapshot is missing or changed`,
451
+ "RECOVERY_FAILED",
452
+ );
453
+ }
454
+ const stagingPath = `${destinationPath}.restart-recovery-${transactionId}`;
455
+ if (lstatOrNull(stagingPath)) {
456
+ throw new NativeUpdateStateError(
457
+ `${label} recovery staging path already exists`,
458
+ "RECOVERY_FAILED",
459
+ );
460
+ }
461
+ try {
462
+ fs.copyFileSync(sourcePath, stagingPath, fs.constants.COPYFILE_EXCL);
463
+ try {
464
+ fs.chmodSync(stagingPath, fs.statSync(sourcePath).mode);
465
+ } catch {
466
+ /* mode preservation is best effort on filesystems without POSIX modes */
467
+ }
468
+ const fd = fs.openSync(stagingPath, "r");
469
+ try {
470
+ fsyncNativeFileDescriptor(fd);
471
+ } finally {
472
+ fs.closeSync(fd);
473
+ }
474
+ if (
475
+ stableRegularShaOrNull(
476
+ stagingPath,
477
+ platform,
478
+ `${label} recovery staging`,
479
+ ) !== expectedSha
480
+ ) {
481
+ throw new NativeUpdateStateError(
482
+ `${label} recovery staging changed`,
483
+ "RECOVERY_FAILED",
484
+ );
485
+ }
486
+ fs.renameSync(stagingPath, destinationPath);
487
+ fsyncNativeDirectory(path.dirname(destinationPath));
488
+ if (
489
+ stableRegularShaOrNull(destinationPath, platform, label) !== expectedSha
490
+ ) {
491
+ throw new NativeUpdateStateError(
492
+ `${label} recovery verification failed`,
493
+ "RECOVERY_FAILED",
494
+ );
495
+ }
496
+ } finally {
497
+ try {
498
+ const stat = fs.lstatSync(stagingPath);
499
+ if (stat.isFile() && !stat.isSymbolicLink()) fs.unlinkSync(stagingPath);
500
+ } catch {
501
+ /* absent or unsafe staging is retained */
502
+ }
503
+ }
504
+ }
505
+
506
+ function retireKnownRegular(
507
+ filePath,
508
+ allowedHashes,
509
+ transactionId,
510
+ platform,
511
+ label,
512
+ ) {
513
+ const current = stableRegularShaOrNull(filePath, platform, label);
514
+ if (current === null) return;
515
+ if (!allowedHashes.filter(Boolean).includes(current)) {
516
+ throw new NativeUpdateStateError(
517
+ `${label} has an unknown generation`,
518
+ "RECOVERY_FAILED",
519
+ );
520
+ }
521
+ const retiredPath = `${filePath}.restart-retired-${transactionId}`;
522
+ if (lstatOrNull(retiredPath)) {
523
+ throw new NativeUpdateStateError(
524
+ `${label} retirement path already exists`,
525
+ "RECOVERY_FAILED",
526
+ );
527
+ }
528
+ fs.renameSync(filePath, retiredPath);
529
+ fsyncNativeDirectory(path.dirname(filePath));
530
+ }
531
+
532
+ function lineageMatchesGeneration(
533
+ lineagePath,
534
+ transactionId,
535
+ expectedSha,
536
+ platform,
537
+ ) {
538
+ if (
539
+ stableRegularShaOrNull(lineagePath, platform, "native update lineage") ===
540
+ null
541
+ ) {
542
+ return false;
543
+ }
544
+ let value;
545
+ try {
546
+ value = JSON.parse(
547
+ readStableRegular(lineagePath, {
548
+ capture: true,
549
+ label: "native update lineage",
550
+ platform,
551
+ }).bytes.toString("utf8"),
552
+ );
553
+ } catch {
554
+ return false;
555
+ }
556
+ return (
557
+ value?.schema === NATIVE_UPDATE_LINEAGE_SCHEMA &&
558
+ value.transactionId === transactionId &&
559
+ value.currentSha256 === expectedSha
560
+ );
561
+ }
562
+
563
+ function acquireGenerationRecoveryLock(lockPath, platform) {
564
+ const current = lstatOrNull(lockPath);
565
+ if (current) {
566
+ const { bytes, parentDevice, stat } = readStableRegular(lockPath, {
567
+ capture: true,
568
+ maxBytes: 128,
569
+ label: "stale native update lock",
570
+ platform,
571
+ });
572
+ const token = bytes.toString("utf8");
573
+ const match = NATIVE_LOCK_TOKEN.exec(token);
574
+ if (!match) {
575
+ throw new NativeUpdateStateError(
576
+ "stale native update lock token is invalid",
577
+ "RECOVERY_LOCK_INVALID",
578
+ );
579
+ }
580
+ try {
581
+ process.kill(Number(match[1]), 0);
582
+ throw new NativeUpdateStateError(
583
+ `native update lock owner PID ${match[1]} is still live`,
584
+ "RECOVERY_LOCK_LIVE",
585
+ );
586
+ } catch (error) {
587
+ if (error instanceof NativeUpdateStateError) throw error;
588
+ if (error?.code !== "ESRCH") {
589
+ throw new NativeUpdateStateError(
590
+ `could not prove stale native update lock ownership: ${error.message}`,
591
+ "RECOVERY_LOCK_LIVE",
592
+ );
593
+ }
594
+ }
595
+ const orphanPath = `${lockPath}.orphaned-${crypto.randomUUID()}`;
596
+ fs.renameSync(lockPath, orphanPath);
597
+ const orphan = fs.lstatSync(orphanPath, { bigint: true });
598
+ if (!samePathHandleStableFileIdentity(orphan, stat, parentDevice)) {
599
+ throw new NativeUpdateStateError(
600
+ "stale native update lock changed during quarantine",
601
+ "RECOVERY_LOCK_CHANGED",
602
+ );
603
+ }
604
+ fsyncNativeDirectory(path.dirname(lockPath));
605
+ }
606
+ const token = `${process.pid}:${crypto.randomUUID()}`;
607
+ const fd = fs.openSync(lockPath, "wx+", 0o600);
608
+ try {
609
+ fs.writeFileSync(fd, token, "utf8");
610
+ fsyncNativeFileDescriptor(fd);
611
+ return { fd, lockPath, token };
612
+ } catch (error) {
613
+ try {
614
+ const owned = fs.fstatSync(fd, { bigint: true });
615
+ const currentLock = fs.lstatSync(lockPath, { bigint: true });
616
+ if (
617
+ currentLock.isFile() &&
618
+ !currentLock.isSymbolicLink() &&
619
+ owned.dev === currentLock.dev &&
620
+ owned.ino === currentLock.ino
621
+ ) {
622
+ fs.unlinkSync(lockPath);
623
+ fsyncNativeDirectory(path.dirname(lockPath));
624
+ }
625
+ } catch {
626
+ /* retain ambiguous ownership evidence and fail closed */
627
+ }
628
+ try {
629
+ fs.closeSync(fd);
630
+ } catch {
631
+ /* the original persistence error is authoritative */
632
+ }
633
+ throw error;
634
+ }
635
+ }
636
+
637
+ function releaseGenerationRecoveryLock(lock, success) {
638
+ fs.closeSync(lock.fd);
639
+ if (!success) return;
640
+ const stat = lstatOrNull(lock.lockPath);
641
+ if (
642
+ stat?.isFile() &&
643
+ !stat.isSymbolicLink() &&
644
+ fs.readFileSync(lock.lockPath, "utf8") === lock.token
645
+ ) {
646
+ fs.unlinkSync(lock.lockPath);
647
+ fsyncNativeDirectory(path.dirname(lock.lockPath));
648
+ }
649
+ }
650
+
651
+ function windowsRecoveryWrapperSource() {
652
+ return [
653
+ "@echo off",
654
+ "setlocal EnableExtensions DisableDelayedExpansion",
655
+ 'set "CC_RECOVERY_EXIT=1"',
656
+ '> "%CC_RECOVERY_READY_TEMP%" echo %CC_RECOVERY_TRANSACTION_ID%',
657
+ "if errorlevel 1 goto cleanup",
658
+ 'move /Y "%CC_RECOVERY_READY_TEMP%" "%CC_RECOVERY_READY%" >NUL 2>&1',
659
+ "if errorlevel 1 goto cleanup",
660
+ "set /a CC_RECOVERY_ATTEMPTS=0",
661
+ ":waitloop",
662
+ '"%CC_RECOVERY_SYSTEM_ROOT%\\System32\\tasklist.exe" /FI "PID eq %CC_RECOVERY_PARENT_PID%" /FO CSV /NH > "%CC_RECOVERY_PARENT_PROBE%" 2>NUL',
663
+ "if errorlevel 1 goto cleanup",
664
+ '"%CC_RECOVERY_SYSTEM_ROOT%\\System32\\findstr.exe" /L /C:"%CC_RECOVERY_PARENT_PID%" "%CC_RECOVERY_PARENT_PROBE%" >NUL 2>&1',
665
+ "if errorlevel 1 goto runrecovery",
666
+ "set /a CC_RECOVERY_ATTEMPTS=%CC_RECOVERY_ATTEMPTS%+1",
667
+ "if %CC_RECOVERY_ATTEMPTS% GEQ 120 goto cleanup",
668
+ '"%CC_RECOVERY_SYSTEM_ROOT%\\System32\\ping.exe" -n 2 -w 1000 127.0.0.1 >NUL',
669
+ "if errorlevel 1 goto cleanup",
670
+ "goto waitloop",
671
+ ":runrecovery",
672
+ 'set "CC_CLI_INSTALL_DIR=%CC_RECOVERY_INSTALL_DIR%"',
673
+ 'set "CC_CLI_INSTALL_RECOVERY_ONLY=1"',
674
+ '"%CC_RECOVERY_POWERSHELL%" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%CC_RECOVERY_INSTALLER%"',
675
+ 'set "CC_RECOVERY_EXIT=%ERRORLEVEL%"',
676
+ ":cleanup",
677
+ 'del /F /Q "%CC_RECOVERY_READY_TEMP%" >NUL 2>&1',
678
+ 'del /F /Q "%CC_RECOVERY_READY%" >NUL 2>&1',
679
+ 'del /F /Q "%CC_RECOVERY_PARENT_PROBE%" >NUL 2>&1',
680
+ 'del /F /Q "%CC_RECOVERY_INSTALLER%" >NUL 2>&1',
681
+ '(goto) 2>NUL & del /F /Q "%~f0" & exit /b %CC_RECOVERY_EXIT%',
682
+ ].join("\r\n");
683
+ }
684
+
685
+ function waitForWindowsRecoveryReady(readyPath, transactionId, timeoutMs) {
686
+ const sleeper = new Int32Array(new SharedArrayBuffer(4));
687
+ const deadline = Date.now() + timeoutMs;
688
+ while (Date.now() < deadline) {
689
+ try {
690
+ const value = fs.readFileSync(readyPath, "utf8").trim();
691
+ if (value === transactionId) return true;
692
+ if (value) return false;
693
+ } catch (error) {
694
+ if (error?.code !== "ENOENT") throw error;
695
+ }
696
+ Atomics.wait(sleeper, 0, 0, 25);
697
+ }
698
+ return false;
699
+ }
700
+
701
+ function resolveTrustedWindowsPowerShell() {
702
+ if (process.platform !== "win32") {
703
+ return {
704
+ powershellPath: String.raw`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`,
705
+ systemRoot: String.raw`C:\Windows`,
706
+ };
707
+ }
708
+ let systemRoot;
709
+ try {
710
+ systemRoot = path.win32.resolve(
711
+ fs.realpathSync.native(String.raw`\\?\GLOBALROOT\SystemRoot`),
712
+ );
713
+ } catch (error) {
714
+ throw new NativeUpdateStateError(
715
+ `could not resolve the trusted Windows system root: ${error.message}`,
716
+ "WINDOWS_SYSTEM_ROOT_UNAVAILABLE",
717
+ );
718
+ }
719
+ if (!/^[A-Za-z]:\\/.test(systemRoot) || systemRoot.includes("\0")) {
720
+ throw new NativeUpdateStateError(
721
+ "the trusted Windows system root resolved to an invalid path",
722
+ "WINDOWS_SYSTEM_ROOT_UNAVAILABLE",
723
+ );
724
+ }
725
+ return {
726
+ powershellPath: path.win32.join(
727
+ systemRoot,
728
+ "System32",
729
+ "WindowsPowerShell",
730
+ "v1.0",
731
+ "powershell.exe",
732
+ ),
733
+ systemRoot,
734
+ };
735
+ }
736
+
737
+ function scheduleWindowsGenerationRecovery(options) {
738
+ const {
739
+ canonicalPath,
740
+ statePaths,
741
+ journal,
742
+ stderr,
743
+ spawnImpl = spawn,
744
+ tmpRoot = os.tmpdir(),
745
+ installerBytes,
746
+ powershellPath,
747
+ cmdPath,
748
+ readyTimeoutMs = 10_000,
749
+ waitForReadyImpl = waitForWindowsRecoveryReady,
750
+ } = options;
751
+ try {
752
+ process.kill(journal.ownerPid, 0);
753
+ throw new NativeUpdateStateError(
754
+ `native generation owner PID ${journal.ownerPid} is still live`,
755
+ "RECOVERY_OWNER_LIVE",
756
+ );
757
+ } catch (error) {
758
+ if (error instanceof NativeUpdateStateError) throw error;
759
+ if (error?.code !== "ESRCH") {
760
+ throw new NativeUpdateStateError(
761
+ `could not prove native generation owner retirement: ${error.message}`,
762
+ "RECOVERY_OWNER_LIVE",
763
+ );
764
+ }
765
+ }
766
+
767
+ const lock = acquireGenerationRecoveryLock(statePaths.lockPath, "win32");
768
+ let child = null;
769
+ let scheduled = false;
770
+ let wrapperPath = null;
771
+ let installerPath = null;
772
+ let readyPath = null;
773
+ let readyTempPath = null;
774
+ let parentProbePath = null;
775
+ try {
776
+ const suffix = journal.transactionId.replaceAll("-", "");
777
+ const rawTmpRoot = path.resolve(tmpRoot);
778
+ assertSafePathAncestors(rawTmpRoot, {
779
+ label: "raw Windows recovery temporary root",
780
+ allowMissingLeaf: false,
781
+ allowPathAliases: true,
782
+ leaf: "directory",
783
+ platform: "win32",
784
+ });
785
+ const canonicalTmpRoot = fs.realpathSync.native(rawTmpRoot);
786
+ assertSafePathAncestors(canonicalTmpRoot, {
787
+ label: "canonical Windows recovery temporary root",
788
+ allowMissingLeaf: false,
789
+ leaf: "directory",
790
+ platform: "win32",
791
+ });
792
+ wrapperPath = path.join(
793
+ canonicalTmpRoot,
794
+ `cc-native-recovery-${suffix}.cmd`,
795
+ );
796
+ installerPath = path.join(
797
+ canonicalTmpRoot,
798
+ `cc-native-recovery-installer-${suffix}.ps1`,
799
+ );
800
+ readyPath = `${wrapperPath}.ready`;
801
+ readyTempPath = `${readyPath}.tmp-${journal.transactionId}`;
802
+ parentProbePath = `${wrapperPath}.parent`;
803
+ const trustedPowerShell = resolveTrustedWindowsPowerShell();
804
+ const resolvedPowerShell =
805
+ powershellPath || trustedPowerShell.powershellPath;
806
+ const resolvedCmd =
807
+ cmdPath ||
808
+ path.win32.join(trustedPowerShell.systemRoot, "System32", "cmd.exe");
809
+ for (const [label, filePath] of Object.entries({
810
+ "Windows recovery wrapper": wrapperPath,
811
+ "Windows recovery installer": installerPath,
812
+ "Windows recovery readiness marker": readyPath,
813
+ "Windows recovery readiness staging": readyTempPath,
814
+ "Windows recovery parent probe": parentProbePath,
815
+ })) {
816
+ assertSafeRegularFile(filePath, {
817
+ label,
818
+ allowMissingLeaf: true,
819
+ platform: "win32",
820
+ });
821
+ if (lstatOrNull(filePath)) {
822
+ throw new NativeUpdateStateError(
823
+ `${label} already exists`,
824
+ "RECOVERY_SCHEDULE_FAILED",
825
+ );
826
+ }
827
+ }
828
+ assertSafeRegularFile(resolvedPowerShell, {
829
+ label: "trusted Windows PowerShell",
830
+ allowMissingLeaf: false,
831
+ platform: "win32",
832
+ });
833
+ assertSafeRegularFile(resolvedCmd, {
834
+ label: "trusted Windows command processor",
835
+ allowMissingLeaf: false,
836
+ platform: "win32",
837
+ });
838
+ const recoveryInstallerBytes =
839
+ installerBytes ||
840
+ fs.readFileSync(new URL("../../../install/install.ps1", import.meta.url));
841
+ fs.writeFileSync(installerPath, recoveryInstallerBytes, {
842
+ flag: "wx",
843
+ mode: 0o600,
844
+ });
845
+ const installerFd = fs.openSync(installerPath, "r+");
846
+ try {
847
+ fsyncNativeFileDescriptor(installerFd);
848
+ } finally {
849
+ fs.closeSync(installerFd);
850
+ }
851
+ fs.writeFileSync(wrapperPath, windowsRecoveryWrapperSource(), {
852
+ encoding: "utf8",
853
+ flag: "wx",
854
+ mode: 0o600,
855
+ });
856
+ const wrapperFd = fs.openSync(wrapperPath, "r+");
857
+ try {
858
+ fsyncNativeFileDescriptor(wrapperFd);
859
+ } finally {
860
+ fs.closeSync(wrapperFd);
861
+ }
862
+ fsyncNativeDirectory(canonicalTmpRoot);
863
+ child = spawnImpl(resolvedCmd, ["/d", "/c", wrapperPath], {
864
+ detached: true,
865
+ stdio: "ignore",
866
+ windowsHide: true,
867
+ shell: false,
868
+ env: {
869
+ ...process.env,
870
+ SystemRoot: trustedPowerShell.systemRoot,
871
+ windir: trustedPowerShell.systemRoot,
872
+ CC_RECOVERY_SYSTEM_ROOT: trustedPowerShell.systemRoot,
873
+ CC_RECOVERY_POWERSHELL: resolvedPowerShell,
874
+ CC_RECOVERY_PARENT_PID: String(process.pid),
875
+ CC_RECOVERY_INSTALL_DIR: path.dirname(canonicalPath),
876
+ CC_RECOVERY_INSTALLER: installerPath,
877
+ CC_RECOVERY_READY: readyPath,
878
+ CC_RECOVERY_READY_TEMP: readyTempPath,
879
+ CC_RECOVERY_PARENT_PROBE: parentProbePath,
880
+ CC_RECOVERY_TRANSACTION_ID: journal.transactionId,
881
+ },
882
+ });
883
+ child.on?.("error", () => {});
884
+ if (!waitForReadyImpl(readyPath, journal.transactionId, readyTimeoutMs)) {
885
+ child.kill?.();
886
+ child.unref?.();
887
+ throw new NativeUpdateStateError(
888
+ "Windows recovery sidecar did not publish readiness",
889
+ "RECOVERY_SCHEDULE_FAILED",
890
+ );
891
+ }
892
+ child.unref?.();
893
+ scheduled = true;
894
+ stderr.write(
895
+ `Scheduled Windows native generation recovery; rerun the CLI after transaction=${journal.transactionId}\n`,
896
+ );
897
+ return {
898
+ outcome: "scheduled",
899
+ transactionId: journal.transactionId,
900
+ requiresRestart: true,
901
+ };
902
+ } finally {
903
+ releaseGenerationRecoveryLock(lock, false);
904
+ if (!scheduled) {
905
+ for (const filePath of [readyPath, readyTempPath, parentProbePath].filter(
906
+ Boolean,
907
+ )) {
908
+ try {
909
+ if (lstatOrNull(filePath)?.isFile()) fs.unlinkSync(filePath);
910
+ } catch {
911
+ /* retain ambiguous transient evidence */
912
+ }
913
+ }
914
+ for (const filePath of [wrapperPath, installerPath].filter(Boolean)) {
915
+ try {
916
+ if (lstatOrNull(filePath)?.isFile()) fs.unlinkSync(filePath);
917
+ } catch {
918
+ /* retain ambiguous recovery evidence */
919
+ }
920
+ }
921
+ }
922
+ }
923
+ }
924
+
925
+ /** Resolve a durable installer/OTA generation decision before normal startup. */
926
+ export function recoverPendingNativeGeneration(options = {}) {
927
+ const {
928
+ targetExePath = process.execPath,
929
+ platform = process.platform,
930
+ packed = Boolean(process.pkg),
931
+ force = false,
932
+ stderr = process.stderr,
933
+ } = options;
934
+ if (!force && !packed) return null;
935
+ const normalizedPlatform = platform === "win32" ? "win32" : "posix";
936
+ const { canonicalPath } = resolveNativeLayout(
937
+ targetExePath,
938
+ normalizedPlatform,
939
+ );
940
+ const statePaths = nativeUpdatePaths(canonicalPath);
941
+ if (!lstatOrNull(statePaths.journalPath)) return null;
942
+ const journal = parseGenerationJournal(
943
+ statePaths.journalPath,
944
+ normalizedPlatform,
945
+ );
946
+ if (normalizedPlatform === "win32") {
947
+ return scheduleWindowsGenerationRecovery({
948
+ ...options,
949
+ canonicalPath,
950
+ statePaths,
951
+ journal,
952
+ stderr,
953
+ });
954
+ }
955
+ const directory = path.dirname(canonicalPath);
956
+ const aliasPath = path.join(directory, "cc");
957
+ const transactionId = journal.transactionId;
958
+ const priorTarget = path.join(
959
+ directory,
960
+ `.chainlesschain.target-prior-${transactionId}`,
961
+ );
962
+ const priorBackup = path.join(
963
+ directory,
964
+ `.chainlesschain.backup-prior-${transactionId}`,
965
+ );
966
+ const priorAlias = path.join(directory, `.cc.prior-${transactionId}`);
967
+ const priorLineage = path.join(
968
+ directory,
969
+ `.chainlesschain.lineage-prior-${transactionId}`,
970
+ );
971
+ const canonicalAliasSha = crypto
972
+ .createHash("sha256")
973
+ .update(Buffer.from("chainlesschain"))
974
+ .digest("hex");
975
+ const lock = acquireGenerationRecoveryLock(
976
+ statePaths.lockPath,
977
+ normalizedPlatform,
978
+ );
979
+ let success = false;
980
+ try {
981
+ const targetSha = stableRegularShaOrNull(
982
+ canonicalPath,
983
+ normalizedPlatform,
984
+ "native target",
985
+ );
986
+ if (journal.decision === "commit") {
987
+ if (targetSha !== journal.expectedSha256) {
988
+ throw new NativeUpdateStateError(
989
+ "committed native target does not match its durable decision",
990
+ "RECOVERY_FAILED",
991
+ );
992
+ }
993
+ const expectedBackup =
994
+ journal.operation === "rescue"
995
+ ? journal.hadBackup
996
+ ? journal.backupBeforeSha256
997
+ : null
998
+ : journal.hadTarget
999
+ ? journal.targetBeforeSha256
1000
+ : null;
1001
+ if (
1002
+ stableRegularShaOrNull(
1003
+ statePaths.backupPath,
1004
+ normalizedPlatform,
1005
+ "native backup",
1006
+ ) !== expectedBackup
1007
+ ) {
1008
+ throw new NativeUpdateStateError(
1009
+ "committed native backup does not match its durable decision",
1010
+ "RECOVERY_FAILED",
1011
+ );
1012
+ }
1013
+ if (
1014
+ journal.operation === "install" &&
1015
+ stableAliasShaOrNull(aliasPath) !== canonicalAliasSha
1016
+ ) {
1017
+ throw new NativeUpdateStateError(
1018
+ "committed native alias does not match its durable decision",
1019
+ "RECOVERY_FAILED",
1020
+ );
1021
+ }
1022
+ if (
1023
+ !lineageMatchesGeneration(
1024
+ statePaths.lineagePath,
1025
+ transactionId,
1026
+ journal.expectedSha256,
1027
+ normalizedPlatform,
1028
+ )
1029
+ ) {
1030
+ throw new NativeUpdateStateError(
1031
+ "committed native lineage does not match its durable decision",
1032
+ "RECOVERY_FAILED",
1033
+ );
1034
+ }
1035
+ } else {
1036
+ if (journal.hadTarget) {
1037
+ if (
1038
+ ![journal.targetBeforeSha256, journal.expectedSha256, null].includes(
1039
+ targetSha,
1040
+ )
1041
+ ) {
1042
+ throw new NativeUpdateStateError(
1043
+ "interrupted native target has an unknown generation",
1044
+ "RECOVERY_FAILED",
1045
+ );
1046
+ }
1047
+ if (targetSha !== journal.targetBeforeSha256) {
1048
+ restoreRegularGeneration(
1049
+ priorTarget,
1050
+ canonicalPath,
1051
+ journal.targetBeforeSha256,
1052
+ transactionId,
1053
+ normalizedPlatform,
1054
+ "native target",
1055
+ );
1056
+ }
1057
+ } else {
1058
+ retireKnownRegular(
1059
+ canonicalPath,
1060
+ [journal.expectedSha256],
1061
+ transactionId,
1062
+ normalizedPlatform,
1063
+ "fresh native target",
1064
+ );
1065
+ }
1066
+
1067
+ const backupSha = stableRegularShaOrNull(
1068
+ statePaths.backupPath,
1069
+ normalizedPlatform,
1070
+ "native backup",
1071
+ );
1072
+ if (journal.hadBackup) {
1073
+ if (
1074
+ ![
1075
+ journal.backupBeforeSha256,
1076
+ journal.targetBeforeSha256,
1077
+ null,
1078
+ ].includes(backupSha)
1079
+ ) {
1080
+ throw new NativeUpdateStateError(
1081
+ "interrupted native backup has an unknown generation",
1082
+ "RECOVERY_FAILED",
1083
+ );
1084
+ }
1085
+ if (backupSha !== journal.backupBeforeSha256) {
1086
+ restoreRegularGeneration(
1087
+ priorBackup,
1088
+ statePaths.backupPath,
1089
+ journal.backupBeforeSha256,
1090
+ transactionId,
1091
+ normalizedPlatform,
1092
+ "native backup",
1093
+ );
1094
+ }
1095
+ } else {
1096
+ retireKnownRegular(
1097
+ statePaths.backupPath,
1098
+ [journal.targetBeforeSha256],
1099
+ transactionId,
1100
+ normalizedPlatform,
1101
+ "fresh native backup",
1102
+ );
1103
+ }
1104
+
1105
+ if (journal.operation === "install") {
1106
+ const aliasSha = stableAliasShaOrNull(aliasPath);
1107
+ if (journal.hadAlias) {
1108
+ if (
1109
+ ![journal.aliasBeforeSha256, canonicalAliasSha, null].includes(
1110
+ aliasSha,
1111
+ )
1112
+ ) {
1113
+ throw new NativeUpdateStateError(
1114
+ "interrupted native alias has an unknown generation",
1115
+ "RECOVERY_FAILED",
1116
+ );
1117
+ }
1118
+ if (aliasSha !== journal.aliasBeforeSha256) {
1119
+ const rawTarget = fs.readlinkSync(priorAlias);
1120
+ const stagingAlias = `${aliasPath}.restart-recovery-${transactionId}`;
1121
+ fs.symlinkSync(rawTarget, stagingAlias);
1122
+ fs.renameSync(stagingAlias, aliasPath);
1123
+ fsyncNativeDirectory(directory);
1124
+ }
1125
+ } else if (aliasSha !== null) {
1126
+ if (aliasSha !== canonicalAliasSha) {
1127
+ throw new NativeUpdateStateError(
1128
+ "fresh native alias has an unknown generation",
1129
+ "RECOVERY_FAILED",
1130
+ );
1131
+ }
1132
+ const retiredAlias = `${aliasPath}.restart-retired-${transactionId}`;
1133
+ fs.renameSync(aliasPath, retiredAlias);
1134
+ fsyncNativeDirectory(directory);
1135
+ }
1136
+ }
1137
+
1138
+ const lineageSha = stableRegularShaOrNull(
1139
+ statePaths.lineagePath,
1140
+ normalizedPlatform,
1141
+ "native lineage",
1142
+ );
1143
+ if (journal.hadLineage) {
1144
+ if (lineageSha !== journal.lineageBeforeSha256) {
1145
+ if (
1146
+ lineageSha !== null &&
1147
+ !lineageMatchesGeneration(
1148
+ statePaths.lineagePath,
1149
+ transactionId,
1150
+ journal.expectedSha256,
1151
+ normalizedPlatform,
1152
+ )
1153
+ ) {
1154
+ throw new NativeUpdateStateError(
1155
+ "interrupted native lineage has an unknown generation",
1156
+ "RECOVERY_FAILED",
1157
+ );
1158
+ }
1159
+ restoreRegularGeneration(
1160
+ priorLineage,
1161
+ statePaths.lineagePath,
1162
+ journal.lineageBeforeSha256,
1163
+ transactionId,
1164
+ normalizedPlatform,
1165
+ "native lineage",
1166
+ );
1167
+ }
1168
+ } else if (lineageSha !== null) {
1169
+ if (
1170
+ !lineageMatchesGeneration(
1171
+ statePaths.lineagePath,
1172
+ transactionId,
1173
+ journal.expectedSha256,
1174
+ normalizedPlatform,
1175
+ )
1176
+ ) {
1177
+ throw new NativeUpdateStateError(
1178
+ "fresh native lineage has an unknown generation",
1179
+ "RECOVERY_FAILED",
1180
+ );
1181
+ }
1182
+ retireKnownRegular(
1183
+ statePaths.lineagePath,
1184
+ [lineageSha],
1185
+ transactionId,
1186
+ normalizedPlatform,
1187
+ "fresh native lineage",
1188
+ );
1189
+ }
1190
+ }
1191
+
1192
+ assertSafeRegularFile(statePaths.lastJournalPath, {
1193
+ label: "retired native generation journal",
1194
+ allowMissingLeaf: true,
1195
+ platform: normalizedPlatform,
1196
+ });
1197
+ fs.renameSync(statePaths.journalPath, statePaths.lastJournalPath);
1198
+ fsyncNativeDirectory(directory);
1199
+ success = true;
1200
+ const outcome = journal.decision === "commit" ? "committed" : "rolled-back";
1201
+ stderr.write(
1202
+ `Recovered interrupted native generation: ${outcome}, transaction=${transactionId}\n`,
1203
+ );
1204
+ return { outcome, transactionId, journal };
1205
+ } finally {
1206
+ releaseGenerationRecoveryLock(lock, success);
1207
+ }
1208
+ }
1209
+
232
1210
  /**
233
1211
  * Consume the detached Windows sidecar result on the next packed invocation.
234
1212
  * The consumed record is atomically retained as `.last.json` for diagnostics.