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.
- package/bin/chainlesschain.js +14 -6
- package/package.json +4 -1
- package/scripts/command-lifecycle-report.mjs +94 -0
- package/src/assets/web-panel/assets/{AIOps-CSN-D2xb.js → AIOps-DcQ0bWKy.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CBSMlGzv.js → ActionButton-GecwfFPt.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-3yr3Nt2t.js → Analytics-CD6iSt9v.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-1b-o3lMH.js → AppLayout-CDEtuEYf.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dr8rJi17.js → Artifacts-DS3jAjeW.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-2V689HC2.js → Audit-CcSJA048.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DIxkNrD_.js → BackgroundAgents-DRo9Morb.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Pg6BcwrE.js → Backup-Vw5MrWft.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CLUpqyfH.js → BaseInput-DNHIu823.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-qVS8rD4G.js → Chat-DvAsO8-b.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-ti49X12Q.js → ChatBubbleRenderer-9aDuObay.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Bs9AaEay.js → Checkbox-CKCa7tvn.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Dvc1mq8q.js → Codegen-CIFvZdaT.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C4_biO7A.js → Col-C79yz7bK.js} +1 -1
- package/src/assets/web-panel/assets/{Community-al25XQUV.js → Community-DVvZskLJ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-D-ZDB8M1.js → Compact-oaew61hV.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-6lIfDz1W.js → Compliance-DqA7wmRx.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DdI48XMJ.js → Cowork-B-qGadxa.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DF5HZd2g.js → Cron-CUlCl4sS.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DuxeSvt3.js → Crosschain-CNOlr9DU.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B9yHkUTd.js → DID-DJqFCT_3.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Z5EuI3IM.js → Dashboard-CznGpphf.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-vuRfO3dY.js → Dropdown-_cJw218E.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-QFLCc1uL.js → EmailListRenderer-Dr-1BZ9C.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BZ7EXkFJ.js → FamilyGuardDashboard-Dxf4xtSk.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DycIEg2s.js → Federation-DOiG3i_1.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Ba0H_7yG.js → FormItemContext-BzbMcFB9.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer--JgSTK-C.js → GenericCardRenderer-VbBrYttP.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DEvTbldE.js → Git-CLG8E6ED.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJIljy18.js → Governance-LGJQfk_q.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CVyW3G3q.js → Inference-DlQKx9lF.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-v8-borge.js → KnowledgeGraph-DU8vEouV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BCdS7gGo.js → Logs-FZIQOdln.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BJNWIrbw.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-AN6tGA2B.js → Marketplace-C_xd-zZI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DGViXASN.js → McpTools-q-YktphS.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-Bpvmx9X4.js → Memory-Cq9OhDin.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CTP-DOTC.js → MobileBridge-B03_R98T.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-C8qfktxn.js → MobileProjects-DZrk-qmF.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-ndYr0M1s.js → Mtc-BidCKW28.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Ci0-YK7W.js → MtcAudit-r-iaMVM7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BehSTFW7.js → Multisig-CgTUFvff.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-B91_YBLv.js → NLProgramming-B7NvzfAV.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BQlJ0-gq.js → Notes-80CWNqJI.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CDgYMjYK.js → NotificationSettings-0H6xdiY3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BJkSlKeH.js → OrderTableRenderer-CKP4AFWy.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B-b3cWCp.js → Organization-B8sxCV_h.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CILUL4nL.js → Overflow-CW9JBBhQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BTCFBkF0.js → P2P-bdSQV7oZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BAR6AUx5.js → PdhVaultBrowser-DQad42nW.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-RRf6HbWJ.js → Permissions-CueexxTR.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BGTMiICa.js → PersonalDataHub-fqxflhR8.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-945qY9YF.js → Pipeline-KFwwnIMC.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DPZwI6lJ.js → Privacy-ClnD9buY.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BPH2NRKc.js → ProjectInit-CZEz6sm3.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BZCRNCvb.js → ProjectSettings-B6NzpmOI.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BTI__tBt.js → Projects-DYyuwPCs.js} +1 -1
- package/src/assets/web-panel/assets/Providers-C3tz_jdh.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-N6rbqt1r.js → QrScannerModal-BzuOBbqB.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DrwjPS7M.js → QuickAsk-CmNO9qSj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cog9Wd99.js → Recommend-VJNj3_NF.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DC9pynjM.js → RemoteSession-CrbdbSBF.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D4th4gKS.js → Reputation-DTyMd45j.js} +1 -1
- package/src/assets/web-panel/assets/{Row-ogiWR6v9.js → Row-DHrQ-Icz.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-uYlk-2Oj.js → RssFeed-DqWxY9c5.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DYzzwvtM.js → Search-ctF1Ks3J.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Da7YOyX6.js → Security-BrQz8uo5.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CLI1VM1X.js → Services-D4scVFar.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DK77dzDC.js → Skeleton-oUScaOjv.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C_z7Lmhl.js → Skills-CL-y0eLt.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-D6413Q8L.js → Sla-DQExhwXj.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C07ydbj2.js → SpeechSettings-iNp_I7kN.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DGOm2W22.js → SyncSettings-CTDlMo0A.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cr_PVyLM.js → Tasks-4kb8l3sc.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BhL2mZ0t.js → Templates-BXcL9oXW.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CPk5oyUr.js → Tenant-DLE8eS0D.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-ClhqUsqV.js → Terminal-DGpUZPPK.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BCC7MV5a.js → TimelineRenderer-C1pWXGPC.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DD1Jt4oO.js → Tokens-BYfbfqhZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BCydFvN2.js → Trigger-K9YfLOCw.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DDHT1noa.js → Trust-Ctkq77Hy.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CWPZC7oI.js → UkeySign-C6_CSe9J.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-9Ea0nJR0.js → VideoEditing-Be3rpOWs.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BjxhLxTf.js → Wallet-D0UIplxk.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C6uCye67.js → WebAuthn-CF9bM9I3.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-d2g4reYo.js → WorkflowEditor-D0H1XVPY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-frTc6ScX.js → chat-Cgzzbe0o.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B5K6lG3v.js → colors-BwNxEVS0.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cdn46r8D.js → compact-item-D60Z4Hhs.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DU1jqVQi.js → createContext-MFk58XB0.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BJiw369i.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DdNH-aW_.js → hasIn-CRj2SvGO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM5FmPZU.js → index--s45_1cb.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4l82Me1.js → index--tquQ2ZG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQorP1Ng.js → index-1T1rhokn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHmkkdfT.js → index-B-M82_rx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGiwfwB9.js → index-BE_el1Zs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwlIxAmX.js → index-BhRkR7IO.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cj6P9Nst.js → index-Bo0y96-K.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoFak7le.js → index-CJgu5JUK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dca_i7H8.js → index-CLWwdebO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVLL-mq_.js → index-CNAK5QG_.js} +1 -1
- package/src/assets/web-panel/assets/{index-LaivuXyF.js → index-CQjJjPRD.js} +1 -1
- package/src/assets/web-panel/assets/{index-t_OBzIP4.js → index-CTDbAfae.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGwGIaR.js → index-CVI_9sRK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D23xGYjM.js → index-CY14oMQK.js} +1 -1
- package/src/assets/web-panel/assets/{index-VDM2KKo9.js → index-CbiTJkZf.js} +1 -1
- package/src/assets/web-panel/assets/index-CiMouosn.js +1 -0
- package/src/assets/web-panel/assets/{index-32ajXmTb.js → index-CqWpxMqx.js} +1 -1
- package/src/assets/web-panel/assets/{index-PjAS0sIZ.js → index-CvB8lYUR.js} +1 -1
- package/src/assets/web-panel/assets/{index-7ehytj1K.js → index-D51XwtCY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtBCCkC4.js → index-D6HvK_1O.js} +1 -1
- package/src/assets/web-panel/assets/{index-Be4OtL-N.js → index-D9jVIwLJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUoNAmE8.js → index-DA4tE98u.js} +1 -1
- package/src/assets/web-panel/assets/{index-cYaWV4Mq.js → index-DBwOtXw0.js} +1 -1
- package/src/assets/web-panel/assets/{index-MM4Zz2OE.js → index-DKdNzTIb.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIVyTmZ3.js → index-DRySdmvN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtRPi4YC.js → index-DWzyTev7.js} +1 -1
- package/src/assets/web-panel/assets/index-DXcKlwy7.js +1 -0
- package/src/assets/web-panel/assets/{index-kVO_MDm-.js → index-DYjs7jAP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4miNkWv.js → index-DbROzMBp.js} +1 -1
- package/src/assets/web-panel/assets/{index-yZRuZpwK.js → index-DhFtk5PR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXKhhx9V.js → index-Di2AbMLg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC4eGkar.js → index-DvmocQsM.js} +1 -1
- package/src/assets/web-panel/assets/{index-NmEXVjzt.js → index-Dw-o0zEF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzBBdQ0.js → index-DysPoP38.js} +1 -1
- package/src/assets/web-panel/assets/{index-HjCvXHf5.js → index-LX9pJxm_.js} +1 -1
- package/src/assets/web-panel/assets/{index-gg3QPC3i.js → index-OI_fXWx0.js} +3 -3
- package/src/assets/web-panel/assets/{index-mgU3Q-lN.js → index-eDTOX8Xj.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1qIC4GR.js → index-gWKDsTzF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DftZuyA7.js → index-q_GDUVAA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DwLTRNdo.js → initDefaultProps-C61kZqEd.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BchZDXnh.js → motion-DZnT5U1L.js} +1 -1
- package/src/assets/web-panel/assets/{move-e8M-zZFl.js → move-L8wzwbel.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Ci40fQb3.js → mtc-parser-DLdYSejc.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C7110_f2.js → omit-D53cbS59.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-skteMgnM.js → pickAttrs-Bbc5HPtu.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-MGZ-lh2B.js → placementArrow-BkvmO860.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-N4Sd7uy-.js → responsiveObserve-u2y1ryS1.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C7HDGRaa.js → slide-BBqj7svU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BhRexlgy.js → statusUtils-GN0mqu2d.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-ChEoQkN2.js → styleChecker-BvwPKoBT.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CDbG1s2Y.js → useFlexGapSupport-CCt8tnfY.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUIx0tcJ.js → useFs-DXAxl2SV.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-bkO7L7ec.js → usePersonalDataHub-DihlR2dj.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B_ZvABIG.js → vnode-D7BOX2gi.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D5shjpS8.js → zoom-CVbO-nL7.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/mcp.js +119 -0
- package/src/commands/session-show.js +8 -10
- package/src/commands/session.js +60 -52
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/session-protocol.js +14 -4
- package/src/gateways/ws/ws-agent-handler.js +31 -0
- package/src/harness/jsonl-session-store.js +1147 -130
- package/src/harness/mcp-client.js +44 -11
- package/src/harness/session-index.js +80 -80
- package/src/harness/session-list-index.js +147 -2
- package/src/lib/background-interaction-journal.js +2 -2
- package/src/lib/command-lifecycle-report.js +516 -0
- package/src/lib/mcp-stdio-environment.js +48 -0
- package/src/lib/mcp-stdio-executable-identity.js +552 -17
- package/src/lib/mcp-stdio-package-materialization.js +1424 -0
- package/src/lib/packer/native-update-state.js +979 -1
- package/src/lib/packer/pack-update-applier.js +1050 -125
- package/src/lib/packer/pkg-config-generator.js +1 -0
- package/src/lib/paths.js +53 -0
- package/src/lib/process-execution-broker/index.js +180 -37
- package/src/lib/process-execution-broker/platform-sandbox.js +291 -24
- package/src/lib/recent-session.js +17 -17
- package/src/lib/secure-file-identity.js +47 -0
- package/src/lib/secure-fs.js +21 -2
- package/src/lib/session-anti-rollback-anchor.js +766 -0
- package/src/lib/session-host-lease.js +492 -0
- package/src/lib/session-persistence-failure.js +109 -0
- package/src/lib/session-picker.js +26 -3
- package/src/lib/session-search.js +25 -11
- package/src/lib/side-effect-ledger-store.js +2 -2
- package/src/lib/skill-execution-authority.js +13 -9
- package/src/lib/turn-binding-store.js +2 -2
- package/src/repl/agent-repl.js +309 -39
- package/src/runtime/agent-core.js +88 -5
- package/src/runtime/cleanup-deadline.js +143 -0
- package/src/runtime/headless-runner.js +357 -139
- package/src/runtime/headless-stream.js +207 -28
- package/src/runtime/output-backpressure.js +53 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-CU-JB2Ns.js +0 -1
- package/src/assets/web-panel/assets/Providers-DDCf_nIw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +0 -1
- package/src/assets/web-panel/assets/index-CzrCHrFy.js +0 -1
- 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.
|