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
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
sameOpenedFileIdentity,
|
|
24
24
|
} from "./file-identity.js";
|
|
25
25
|
import {
|
|
26
|
+
NATIVE_GENERATION_TRANSACTION_SCHEMA,
|
|
26
27
|
NATIVE_UPDATE_LINEAGE_SCHEMA,
|
|
27
28
|
NATIVE_UPDATE_RESULT_SCHEMA,
|
|
28
29
|
SHA256_HEX,
|
|
@@ -101,6 +102,8 @@ export async function scheduleReplace(ctx = {}) {
|
|
|
101
102
|
"last-known-good backup": statePaths.backupPath,
|
|
102
103
|
"update lock": statePaths.lockPath,
|
|
103
104
|
"update lineage": statePaths.lineagePath,
|
|
105
|
+
"generation transaction journal": statePaths.journalPath,
|
|
106
|
+
"retired generation transaction journal": statePaths.lastJournalPath,
|
|
104
107
|
"update result": statePaths.resultPath,
|
|
105
108
|
"last consumed update result": statePaths.lastResultPath,
|
|
106
109
|
})) {
|
|
@@ -145,6 +148,12 @@ export async function scheduleReplace(ctx = {}) {
|
|
|
145
148
|
"RESULT_PENDING",
|
|
146
149
|
);
|
|
147
150
|
}
|
|
151
|
+
if (lstatOrNull(statePaths.journalPath)) {
|
|
152
|
+
throw new ApplyError(
|
|
153
|
+
`an interrupted native generation requires restart recovery: ${statePaths.journalPath}`,
|
|
154
|
+
"RECOVERY_REQUIRED",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
148
157
|
|
|
149
158
|
const updateLock = acquireUpdateLock(statePaths.lockPath, platform);
|
|
150
159
|
const transactionId = crypto.randomUUID();
|
|
@@ -160,6 +169,8 @@ export async function scheduleReplace(ctx = {}) {
|
|
|
160
169
|
for (const [label, filePath] of Object.entries({
|
|
161
170
|
"last-known-good backup": statePaths.backupPath,
|
|
162
171
|
"update lineage": statePaths.lineagePath,
|
|
172
|
+
"generation transaction journal": statePaths.journalPath,
|
|
173
|
+
"retired generation transaction journal": statePaths.lastJournalPath,
|
|
163
174
|
"update result": statePaths.resultPath,
|
|
164
175
|
"last consumed update result": statePaths.lastResultPath,
|
|
165
176
|
})) {
|
|
@@ -183,6 +194,10 @@ export async function scheduleReplace(ctx = {}) {
|
|
|
183
194
|
const priorBackupSha = hadPriorBackup
|
|
184
195
|
? stableSha256(statePaths.backupPath, "existing last-known-good backup")
|
|
185
196
|
: null;
|
|
197
|
+
const hadPriorLineage = Boolean(lstatOrNull(statePaths.lineagePath));
|
|
198
|
+
const priorLineageSha = hadPriorLineage
|
|
199
|
+
? stableSha256(statePaths.lineagePath, "existing native update lineage")
|
|
200
|
+
: null;
|
|
186
201
|
|
|
187
202
|
if (platform === "win32") {
|
|
188
203
|
return scheduleWindowsTransaction({
|
|
@@ -198,7 +213,9 @@ export async function scheduleReplace(ctx = {}) {
|
|
|
198
213
|
hadTarget,
|
|
199
214
|
hadAlias,
|
|
200
215
|
hadPriorBackup,
|
|
216
|
+
hadPriorLineage,
|
|
201
217
|
priorBackupSha,
|
|
218
|
+
priorLineageSha,
|
|
202
219
|
parentPid,
|
|
203
220
|
restart: Boolean(restart),
|
|
204
221
|
verify: Boolean(verify),
|
|
@@ -218,6 +235,9 @@ export async function scheduleReplace(ctx = {}) {
|
|
|
218
235
|
targetBeforeSha,
|
|
219
236
|
hadTarget,
|
|
220
237
|
hadPriorBackup,
|
|
238
|
+
hadPriorLineage,
|
|
239
|
+
priorBackupSha,
|
|
240
|
+
priorLineageSha,
|
|
221
241
|
restart: Boolean(restart),
|
|
222
242
|
verify: Boolean(verify),
|
|
223
243
|
spawnImpl,
|
|
@@ -245,6 +265,9 @@ function applyPosixUpdate(ctx) {
|
|
|
245
265
|
targetBeforeSha,
|
|
246
266
|
hadTarget,
|
|
247
267
|
hadPriorBackup,
|
|
268
|
+
hadPriorLineage,
|
|
269
|
+
priorBackupSha,
|
|
270
|
+
priorLineageSha,
|
|
248
271
|
restart,
|
|
249
272
|
verify,
|
|
250
273
|
spawnImpl,
|
|
@@ -253,7 +276,61 @@ function applyPosixUpdate(ctx) {
|
|
|
253
276
|
} = ctx;
|
|
254
277
|
const targetPath = layout.canonicalPath;
|
|
255
278
|
let backupStagingPath = null;
|
|
256
|
-
|
|
279
|
+
const snapshotPaths = generationSnapshotPaths(targetPath, transactionId);
|
|
280
|
+
const journal = makeGenerationJournal({
|
|
281
|
+
transactionId,
|
|
282
|
+
operation: "update",
|
|
283
|
+
expectedSha,
|
|
284
|
+
hadTarget,
|
|
285
|
+
targetBeforeSha,
|
|
286
|
+
hadBackup: hadPriorBackup,
|
|
287
|
+
backupBeforeSha: priorBackupSha,
|
|
288
|
+
hadLineage: hadPriorLineage,
|
|
289
|
+
lineageBeforeSha: priorLineageSha,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
if (hadTarget) {
|
|
294
|
+
atomicCopyFile(targetPath, snapshotPaths.priorTargetPath, {
|
|
295
|
+
preserveModeFrom: targetPath,
|
|
296
|
+
updateLock,
|
|
297
|
+
});
|
|
298
|
+
assertExpectedFileHash(
|
|
299
|
+
snapshotPaths.priorTargetPath,
|
|
300
|
+
targetBeforeSha,
|
|
301
|
+
"target generation snapshot",
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (hadPriorBackup) {
|
|
305
|
+
atomicCopyFile(statePaths.backupPath, snapshotPaths.priorBackupPath, {
|
|
306
|
+
preserveModeFrom: statePaths.backupPath,
|
|
307
|
+
updateLock,
|
|
308
|
+
});
|
|
309
|
+
assertExpectedFileHash(
|
|
310
|
+
snapshotPaths.priorBackupPath,
|
|
311
|
+
priorBackupSha,
|
|
312
|
+
"backup generation snapshot",
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
if (hadPriorLineage) {
|
|
316
|
+
atomicCopyFile(statePaths.lineagePath, snapshotPaths.priorLineagePath, {
|
|
317
|
+
preserveModeFrom: statePaths.lineagePath,
|
|
318
|
+
updateLock,
|
|
319
|
+
});
|
|
320
|
+
assertExpectedFileHash(
|
|
321
|
+
snapshotPaths.priorLineagePath,
|
|
322
|
+
priorLineageSha,
|
|
323
|
+
"lineage generation snapshot",
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
cleanupGenerationSnapshots(snapshotPaths);
|
|
328
|
+
throw asApplyError(
|
|
329
|
+
error,
|
|
330
|
+
"GENERATION_SNAPSHOT_FAILED",
|
|
331
|
+
"could not preserve the native generation pre-state",
|
|
332
|
+
);
|
|
333
|
+
}
|
|
257
334
|
|
|
258
335
|
if (hadTarget) {
|
|
259
336
|
backupStagingPath = uniqueSibling(statePaths.backupPath, "pending");
|
|
@@ -269,10 +346,35 @@ function applyPosixUpdate(ctx) {
|
|
|
269
346
|
);
|
|
270
347
|
} catch (error) {
|
|
271
348
|
removeRegularFileIfPresent(backupStagingPath);
|
|
349
|
+
cleanupGenerationSnapshots(snapshotPaths);
|
|
272
350
|
throw asApplyError(error, "BACKUP_FAILED", "could not create backup");
|
|
273
351
|
}
|
|
274
352
|
}
|
|
275
353
|
|
|
354
|
+
try {
|
|
355
|
+
writeGenerationJournalPhase(
|
|
356
|
+
statePaths.journalPath,
|
|
357
|
+
journal,
|
|
358
|
+
"prepared",
|
|
359
|
+
"rollback",
|
|
360
|
+
updateLock,
|
|
361
|
+
);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
removeRegularFileIfPresent(backupStagingPath);
|
|
364
|
+
if (lstatOrNull(statePaths.journalPath)) {
|
|
365
|
+
throw new ApplyError(
|
|
366
|
+
`native generation intent persistence is unknown: ${error.message}`,
|
|
367
|
+
"ROLLBACK_FAILED",
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
cleanupGenerationSnapshots(snapshotPaths);
|
|
371
|
+
throw asApplyError(
|
|
372
|
+
error,
|
|
373
|
+
"JOURNAL_WRITE_FAILED",
|
|
374
|
+
"could not persist native generation intent",
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
276
378
|
try {
|
|
277
379
|
fs.chmodSync(resolvedNewPath, 0o755);
|
|
278
380
|
} catch {
|
|
@@ -294,8 +396,36 @@ function applyPosixUpdate(ctx) {
|
|
|
294
396
|
assertUpdateLockOwned(updateLock);
|
|
295
397
|
fs.renameSync(resolvedNewPath, targetPath);
|
|
296
398
|
fsyncDirectory(path.dirname(targetPath));
|
|
399
|
+
writeGenerationJournalPhase(
|
|
400
|
+
statePaths.journalPath,
|
|
401
|
+
journal,
|
|
402
|
+
"target-committed",
|
|
403
|
+
"rollback",
|
|
404
|
+
updateLock,
|
|
405
|
+
);
|
|
297
406
|
} catch (error) {
|
|
298
407
|
removeRegularFileIfPresent(backupStagingPath);
|
|
408
|
+
try {
|
|
409
|
+
rollbackFailedPosixUpdate({
|
|
410
|
+
targetPath,
|
|
411
|
+
statePaths,
|
|
412
|
+
transactionId,
|
|
413
|
+
hadTarget,
|
|
414
|
+
targetBeforeSha,
|
|
415
|
+
hadPriorBackup,
|
|
416
|
+
hadPriorLineage,
|
|
417
|
+
priorBackupSha,
|
|
418
|
+
priorLineageSha,
|
|
419
|
+
snapshotPaths,
|
|
420
|
+
journal,
|
|
421
|
+
updateLock,
|
|
422
|
+
});
|
|
423
|
+
} catch (recoveryError) {
|
|
424
|
+
throw new ApplyError(
|
|
425
|
+
`target commit failed and its generation journal could not be retired: ${recoveryError.message}`,
|
|
426
|
+
"ROLLBACK_FAILED",
|
|
427
|
+
);
|
|
428
|
+
}
|
|
299
429
|
if (error instanceof ApplyError) throw error;
|
|
300
430
|
throw new ApplyError(`rename failed: ${error.message}`, "RENAME_FAILED");
|
|
301
431
|
}
|
|
@@ -316,15 +446,37 @@ function applyPosixUpdate(ctx) {
|
|
|
316
446
|
if (!commitFailure && verify) {
|
|
317
447
|
commitFailure = verificationFailure(targetPath, verifyImpl);
|
|
318
448
|
}
|
|
449
|
+
if (!commitFailure) {
|
|
450
|
+
try {
|
|
451
|
+
writeGenerationJournalPhase(
|
|
452
|
+
statePaths.journalPath,
|
|
453
|
+
journal,
|
|
454
|
+
"verified",
|
|
455
|
+
"rollback",
|
|
456
|
+
updateLock,
|
|
457
|
+
);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
commitFailure = new ApplyError(
|
|
460
|
+
`could not persist native generation verification: ${error.message}`,
|
|
461
|
+
"JOURNAL_WRITE_FAILED",
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
319
465
|
if (!commitFailure) {
|
|
320
466
|
try {
|
|
321
467
|
if (hadTarget) {
|
|
322
468
|
assertUpdateLockOwned(updateLock);
|
|
323
469
|
fs.renameSync(backupStagingPath, statePaths.backupPath);
|
|
324
470
|
backupStagingPath = null;
|
|
325
|
-
backupCommitted = true;
|
|
326
471
|
fsyncDirectory(path.dirname(targetPath));
|
|
327
472
|
}
|
|
473
|
+
writeGenerationJournalPhase(
|
|
474
|
+
statePaths.journalPath,
|
|
475
|
+
journal,
|
|
476
|
+
"backup-committed",
|
|
477
|
+
"rollback",
|
|
478
|
+
updateLock,
|
|
479
|
+
);
|
|
328
480
|
} catch (error) {
|
|
329
481
|
commitFailure = new ApplyError(
|
|
330
482
|
`could not persist last-known-good backup: ${error.message}`,
|
|
@@ -344,6 +496,13 @@ function applyPosixUpdate(ctx) {
|
|
|
344
496
|
}),
|
|
345
497
|
updateLock,
|
|
346
498
|
);
|
|
499
|
+
writeGenerationJournalPhase(
|
|
500
|
+
statePaths.journalPath,
|
|
501
|
+
journal,
|
|
502
|
+
"lineage-committed",
|
|
503
|
+
"rollback",
|
|
504
|
+
updateLock,
|
|
505
|
+
);
|
|
347
506
|
} catch (error) {
|
|
348
507
|
commitFailure = new ApplyError(
|
|
349
508
|
`could not persist update lineage: ${error.message}`,
|
|
@@ -359,15 +518,42 @@ function applyPosixUpdate(ctx) {
|
|
|
359
518
|
transactionId,
|
|
360
519
|
hadTarget,
|
|
361
520
|
targetBeforeSha,
|
|
362
|
-
rollbackSourcePath: backupStagingPath || statePaths.backupPath,
|
|
363
|
-
backupCommitted,
|
|
364
521
|
hadPriorBackup,
|
|
522
|
+
hadPriorLineage,
|
|
523
|
+
priorBackupSha,
|
|
524
|
+
priorLineageSha,
|
|
525
|
+
snapshotPaths,
|
|
526
|
+
journal,
|
|
365
527
|
updateLock,
|
|
366
528
|
});
|
|
367
529
|
removeRegularFileIfPresent(backupStagingPath);
|
|
368
530
|
throw commitFailure;
|
|
369
531
|
}
|
|
370
532
|
|
|
533
|
+
try {
|
|
534
|
+
writeGenerationJournalPhase(
|
|
535
|
+
statePaths.journalPath,
|
|
536
|
+
journal,
|
|
537
|
+
"committed",
|
|
538
|
+
"commit",
|
|
539
|
+
updateLock,
|
|
540
|
+
);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw new ApplyError(
|
|
543
|
+
`native generation commit decision is unknown: ${error.message}`,
|
|
544
|
+
"ROLLBACK_FAILED",
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
retireGenerationJournal(statePaths, journal, updateLock);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
throw new ApplyError(
|
|
551
|
+
`native generation committed but journal retirement failed: ${error.message}`,
|
|
552
|
+
"ROLLBACK_FAILED",
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
cleanupGenerationSnapshots(snapshotPaths);
|
|
556
|
+
|
|
371
557
|
restartVerifiedExecutable(targetPath, restart, spawnImpl);
|
|
372
558
|
return buildPlan({
|
|
373
559
|
platform: "posix",
|
|
@@ -390,9 +576,12 @@ function rollbackFailedPosixUpdate(ctx) {
|
|
|
390
576
|
transactionId,
|
|
391
577
|
hadTarget,
|
|
392
578
|
targetBeforeSha,
|
|
393
|
-
rollbackSourcePath,
|
|
394
|
-
backupCommitted,
|
|
395
579
|
hadPriorBackup,
|
|
580
|
+
hadPriorLineage,
|
|
581
|
+
priorBackupSha,
|
|
582
|
+
priorLineageSha,
|
|
583
|
+
snapshotPaths,
|
|
584
|
+
journal,
|
|
396
585
|
updateLock,
|
|
397
586
|
} = ctx;
|
|
398
587
|
const failedPath = uniqueSibling(targetPath, "failed");
|
|
@@ -406,8 +595,8 @@ function rollbackFailedPosixUpdate(ctx) {
|
|
|
406
595
|
} catch {
|
|
407
596
|
/* preserving rejected bytes must never block restoration */
|
|
408
597
|
}
|
|
409
|
-
atomicCopyFile(
|
|
410
|
-
preserveModeFrom:
|
|
598
|
+
atomicCopyFile(snapshotPaths.priorTargetPath, targetPath, {
|
|
599
|
+
preserveModeFrom: snapshotPaths.priorTargetPath,
|
|
411
600
|
updateLock,
|
|
412
601
|
});
|
|
413
602
|
assertExpectedFileHash(
|
|
@@ -415,43 +604,68 @@ function rollbackFailedPosixUpdate(ctx) {
|
|
|
415
604
|
targetBeforeSha,
|
|
416
605
|
"restored executable",
|
|
417
606
|
);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
makeLineage({
|
|
426
|
-
transactionId,
|
|
427
|
-
operation: "rolled-back",
|
|
428
|
-
currentSha256: targetBeforeSha,
|
|
429
|
-
previousSha256: targetBeforeSha,
|
|
430
|
-
}),
|
|
431
|
-
updateLock,
|
|
607
|
+
} else {
|
|
608
|
+
const currentTarget = lstatOrNull(targetPath);
|
|
609
|
+
if (currentTarget) {
|
|
610
|
+
assertExpectedFileHash(
|
|
611
|
+
targetPath,
|
|
612
|
+
journal.expectedSha256,
|
|
613
|
+
"failed fresh target",
|
|
432
614
|
);
|
|
433
|
-
} else if (!hadPriorBackup) {
|
|
434
615
|
assertUpdateLockOwned(updateLock);
|
|
435
|
-
fs.renameSync(
|
|
616
|
+
fs.renameSync(targetPath, failedPath);
|
|
436
617
|
fsyncDirectory(path.dirname(targetPath));
|
|
437
|
-
atomicWriteJson(
|
|
438
|
-
statePaths.lineagePath,
|
|
439
|
-
makeLineage({
|
|
440
|
-
transactionId,
|
|
441
|
-
operation: "rolled-back",
|
|
442
|
-
currentSha256: targetBeforeSha,
|
|
443
|
-
previousSha256: targetBeforeSha,
|
|
444
|
-
}),
|
|
445
|
-
updateLock,
|
|
446
|
-
);
|
|
447
618
|
}
|
|
448
|
-
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (hadPriorBackup) {
|
|
622
|
+
atomicCopyFile(snapshotPaths.priorBackupPath, statePaths.backupPath, {
|
|
623
|
+
preserveModeFrom: snapshotPaths.priorBackupPath,
|
|
624
|
+
updateLock,
|
|
625
|
+
});
|
|
626
|
+
assertExpectedFileHash(
|
|
627
|
+
statePaths.backupPath,
|
|
628
|
+
priorBackupSha,
|
|
629
|
+
"restored last-known-good backup",
|
|
630
|
+
);
|
|
631
|
+
} else if (lstatOrNull(statePaths.backupPath)) {
|
|
632
|
+
if (!hadTarget) {
|
|
633
|
+
throw new Error("fresh update created an unexpected backup");
|
|
634
|
+
}
|
|
635
|
+
assertExpectedFileHash(
|
|
636
|
+
statePaths.backupPath,
|
|
637
|
+
targetBeforeSha,
|
|
638
|
+
"transaction-created last-known-good backup",
|
|
639
|
+
);
|
|
449
640
|
assertUpdateLockOwned(updateLock);
|
|
450
|
-
|
|
641
|
+
removeRegularFileIfPresent(statePaths.backupPath);
|
|
451
642
|
fsyncDirectory(path.dirname(targetPath));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (hadPriorLineage) {
|
|
646
|
+
atomicCopyFile(snapshotPaths.priorLineagePath, statePaths.lineagePath, {
|
|
647
|
+
preserveModeFrom: snapshotPaths.priorLineagePath,
|
|
648
|
+
updateLock,
|
|
649
|
+
});
|
|
650
|
+
assertExpectedFileHash(
|
|
651
|
+
statePaths.lineagePath,
|
|
652
|
+
priorLineageSha,
|
|
653
|
+
"restored native update lineage",
|
|
654
|
+
);
|
|
655
|
+
} else if (lstatOrNull(statePaths.lineagePath)) {
|
|
656
|
+
const lineage = readNativeLineage(statePaths.lineagePath);
|
|
657
|
+
if (
|
|
658
|
+
lineage.transactionId !== transactionId ||
|
|
659
|
+
lineage.currentSha256 !== journal.expectedSha256
|
|
660
|
+
) {
|
|
661
|
+
throw new Error("transaction-created lineage changed before rollback");
|
|
662
|
+
}
|
|
452
663
|
assertUpdateLockOwned(updateLock);
|
|
453
664
|
removeRegularFileIfPresent(statePaths.lineagePath);
|
|
665
|
+
fsyncDirectory(path.dirname(targetPath));
|
|
454
666
|
}
|
|
667
|
+
retireGenerationJournal(statePaths, journal, updateLock);
|
|
668
|
+
cleanupGenerationSnapshots(snapshotPaths);
|
|
455
669
|
} catch (error) {
|
|
456
670
|
throw new ApplyError(
|
|
457
671
|
`new binary failed and rollback failed: ${error.message}`,
|
|
@@ -474,7 +688,9 @@ function scheduleWindowsTransaction(ctx) {
|
|
|
474
688
|
hadTarget,
|
|
475
689
|
hadAlias,
|
|
476
690
|
hadPriorBackup = false,
|
|
691
|
+
hadPriorLineage = false,
|
|
477
692
|
priorBackupSha = null,
|
|
693
|
+
priorLineageSha = null,
|
|
478
694
|
parentPid,
|
|
479
695
|
restart,
|
|
480
696
|
verify,
|
|
@@ -492,6 +708,7 @@ function scheduleWindowsTransaction(ctx) {
|
|
|
492
708
|
aliasPath: layout.aliasPath,
|
|
493
709
|
backupPath: statePaths.backupPath,
|
|
494
710
|
lineagePath: statePaths.lineagePath,
|
|
711
|
+
journalPath: statePaths.journalPath,
|
|
495
712
|
lockPath: statePaths.lockPath,
|
|
496
713
|
resultPath: statePaths.resultPath,
|
|
497
714
|
lockToken: updateLock.token,
|
|
@@ -502,7 +719,9 @@ function scheduleWindowsTransaction(ctx) {
|
|
|
502
719
|
hadTarget,
|
|
503
720
|
hadAlias,
|
|
504
721
|
hadPriorBackup,
|
|
722
|
+
hadPriorLineage,
|
|
505
723
|
priorBackupSha256: priorBackupSha,
|
|
724
|
+
priorLineageSha256: priorLineageSha,
|
|
506
725
|
parentPid,
|
|
507
726
|
restart,
|
|
508
727
|
verify,
|
|
@@ -551,11 +770,6 @@ function scheduleWindowsTransaction(ctx) {
|
|
|
551
770
|
// post-transfer paths always consume the ChildProcess error event.
|
|
552
771
|
child.once("error", (error) => {
|
|
553
772
|
childError = error;
|
|
554
|
-
if (ready) {
|
|
555
|
-
removeOwnedLockFile(updateLock);
|
|
556
|
-
removeRegularFileIfPresent(sidecarPath);
|
|
557
|
-
removeRegularFileIfPresent(readyPath);
|
|
558
|
-
}
|
|
559
773
|
});
|
|
560
774
|
}
|
|
561
775
|
try {
|
|
@@ -606,6 +820,7 @@ function scheduleWindowsTransaction(ctx) {
|
|
|
606
820
|
});
|
|
607
821
|
} catch (error) {
|
|
608
822
|
if (sidecarPath) removeRegularFileIfPresent(sidecarPath);
|
|
823
|
+
if (sidecarPath) removeRegularFileIfPresent(`${sidecarPath}.journal.ps1`);
|
|
609
824
|
if (sidecarPath) removeRegularFileIfPresent(`${sidecarPath}.ready`);
|
|
610
825
|
if (error instanceof ApplyError) throw error;
|
|
611
826
|
throw new ApplyError(
|
|
@@ -615,6 +830,167 @@ function scheduleWindowsTransaction(ctx) {
|
|
|
615
830
|
}
|
|
616
831
|
}
|
|
617
832
|
|
|
833
|
+
function windowsGenerationJournalHelperSource() {
|
|
834
|
+
return String.raw`param()
|
|
835
|
+
$ErrorActionPreference = 'Stop'
|
|
836
|
+
$Action = [string]$env:JOURNAL_ACTION
|
|
837
|
+
$JournalPath = [string]$env:JOURNAL_FILE
|
|
838
|
+
$RetiredPath = [string]$env:JOURNAL_RETIRED
|
|
839
|
+
$TransactionId = [string]$env:TRANSACTION_ID
|
|
840
|
+
$Phase = [string]$env:JOURNAL_PHASE
|
|
841
|
+
$Decision = [string]$env:JOURNAL_DECISION
|
|
842
|
+
$HashPattern = '^[a-f0-9]{64}$'
|
|
843
|
+
$AllowedPhases = @('prepared','target-committed','alias-committed','verified','backup-committed','lineage-committed','committed')
|
|
844
|
+
$AllowedActions = @('write','retire')
|
|
845
|
+
$ParsedId = [guid]::Empty
|
|
846
|
+
if ($AllowedActions -notcontains $Action) { throw 'Invalid journal action' }
|
|
847
|
+
if (-not [guid]::TryParse($TransactionId, [ref]$ParsedId)) { throw 'Invalid transaction identifier' }
|
|
848
|
+
if ($AllowedPhases -notcontains $Phase) { throw 'Invalid transaction phase' }
|
|
849
|
+
if (@('rollback','commit') -notcontains $Decision) { throw 'Invalid transaction decision' }
|
|
850
|
+
if (($Phase -eq 'committed') -ne ($Decision -eq 'commit')) { throw 'Invalid phase decision' }
|
|
851
|
+
|
|
852
|
+
if ($Action -eq 'write' -and $Phase -eq 'prepared') {
|
|
853
|
+
if ([IO.File]::Exists($JournalPath)) { throw 'A generation journal already exists' }
|
|
854
|
+
$Bytes = [Convert]::FromBase64String([string]$env:JOURNAL_INITIAL_BASE64)
|
|
855
|
+
$Journal = [Text.Encoding]::UTF8.GetString($Bytes) | ConvertFrom-Json
|
|
856
|
+
$ParentSource = @'
|
|
857
|
+
using System;
|
|
858
|
+
using System.Runtime.InteropServices;
|
|
859
|
+
public static class CcParentProcess {
|
|
860
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
861
|
+
private struct PBI {
|
|
862
|
+
public IntPtr Reserved1;
|
|
863
|
+
public IntPtr PebBaseAddress;
|
|
864
|
+
public IntPtr Reserved2_0;
|
|
865
|
+
public IntPtr Reserved2_1;
|
|
866
|
+
public IntPtr UniqueProcessId;
|
|
867
|
+
public IntPtr InheritedFromUniqueProcessId;
|
|
868
|
+
}
|
|
869
|
+
[DllImport("ntdll.dll")]
|
|
870
|
+
private static extern int NtQueryInformationProcess(IntPtr process, int infoClass, ref PBI info, int size, out int returned);
|
|
871
|
+
[DllImport("kernel32.dll")]
|
|
872
|
+
private static extern IntPtr GetCurrentProcess();
|
|
873
|
+
public static int Get() {
|
|
874
|
+
PBI info = new PBI();
|
|
875
|
+
int returned;
|
|
876
|
+
int status = NtQueryInformationProcess(GetCurrentProcess(), 0, ref info, Marshal.SizeOf(info), out returned);
|
|
877
|
+
if (status != 0) { throw new InvalidOperationException("Parent process query failed"); }
|
|
878
|
+
return info.InheritedFromUniqueProcessId.ToInt32();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
'@
|
|
882
|
+
Add-Type -TypeDefinition $ParentSource -Language CSharp
|
|
883
|
+
$Journal | Add-Member -NotePropertyName ownerPid -NotePropertyValue ([CcParentProcess]::Get())
|
|
884
|
+
} else {
|
|
885
|
+
if (-not [IO.File]::Exists($JournalPath)) { throw 'Generation journal is missing' }
|
|
886
|
+
$Journal = Get-Content -Raw -LiteralPath $JournalPath | ConvertFrom-Json
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
$ValidBefore = {
|
|
890
|
+
param($Present, $Digest)
|
|
891
|
+
return (($Present -is [bool]) -and ((-not $Present -and $null -eq $Digest) -or ($Present -and [string]$Digest -match $HashPattern)))
|
|
892
|
+
}
|
|
893
|
+
$Valid = (
|
|
894
|
+
[string]$Journal.schema -eq 'chainlesschain.native-install-transaction.v1' -and
|
|
895
|
+
[string]$Journal.transactionId -eq $TransactionId -and
|
|
896
|
+
@('update','rescue') -contains [string]$Journal.operation -and
|
|
897
|
+
($Journal.ownerPid -is [int] -or $Journal.ownerPid -is [long]) -and
|
|
898
|
+
[int64]$Journal.ownerPid -gt 0 -and
|
|
899
|
+
$AllowedPhases -contains [string]$Journal.phase -and
|
|
900
|
+
@('rollback','commit') -contains [string]$Journal.decision -and
|
|
901
|
+
[string]$Journal.expectedSha256 -match $HashPattern -and
|
|
902
|
+
(& $ValidBefore $Journal.hadTarget $Journal.targetBeforeSha256) -and
|
|
903
|
+
(& $ValidBefore $Journal.hadAlias $Journal.aliasBeforeSha256) -and
|
|
904
|
+
(& $ValidBefore $Journal.hadBackup $Journal.backupBeforeSha256) -and
|
|
905
|
+
(& $ValidBefore $Journal.hadLineage $Journal.lineageBeforeSha256)
|
|
906
|
+
)
|
|
907
|
+
if (-not $Valid) { throw 'Generation journal failed schema validation' }
|
|
908
|
+
if ([string]$Journal.transactionId -ne $TransactionId) { throw 'Generation journal ownership changed' }
|
|
909
|
+
|
|
910
|
+
if ($Action -eq 'retire') {
|
|
911
|
+
if ([string]$Journal.phase -ne $Phase -or [string]$Journal.decision -ne $Decision) {
|
|
912
|
+
throw 'Generation journal decision changed before retirement'
|
|
913
|
+
}
|
|
914
|
+
$ReplacedPath = Join-Path (Split-Path -Parent $JournalPath) ('.chainlesschain.journal-retired-previous-' + [guid]::NewGuid().ToString('N'))
|
|
915
|
+
try {
|
|
916
|
+
if ([IO.File]::Exists($RetiredPath)) {
|
|
917
|
+
[IO.File]::Replace($JournalPath, $RetiredPath, $ReplacedPath, $true)
|
|
918
|
+
} else {
|
|
919
|
+
[IO.File]::Move($JournalPath, $RetiredPath)
|
|
920
|
+
}
|
|
921
|
+
$Final = [IO.File]::Open($RetiredPath, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::Read)
|
|
922
|
+
try { $Final.Flush($true) } finally { $Final.Dispose() }
|
|
923
|
+
} finally {
|
|
924
|
+
if ([IO.File]::Exists($ReplacedPath)) {
|
|
925
|
+
try { [IO.File]::Delete($ReplacedPath) } catch { }
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
exit 0
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if ($Phase -eq 'prepared') {
|
|
932
|
+
$Snapshots = [Collections.Generic.Dictionary[string,string]]::new([StringComparer]::OrdinalIgnoreCase)
|
|
933
|
+
if ($Journal.hadTarget) { $Snapshots.Add([string]$env:TARGET_SNAPSHOT, [string]$Journal.targetBeforeSha256) }
|
|
934
|
+
if ($Journal.hadAlias) { $Snapshots.Add([string]$env:ALIAS_BACKUP, [string]$Journal.aliasBeforeSha256) }
|
|
935
|
+
if ($Journal.hadBackup) { $Snapshots.Add([string]$env:BACKUP_SNAPSHOT, [string]$Journal.backupBeforeSha256) }
|
|
936
|
+
if ($Journal.hadLineage) { $Snapshots.Add([string]$env:LINEAGE_SNAPSHOT, [string]$Journal.lineageBeforeSha256) }
|
|
937
|
+
foreach ($Snapshot in $Snapshots.GetEnumerator()) {
|
|
938
|
+
if (-not [IO.File]::Exists($Snapshot.Key)) { throw 'A recovery snapshot is missing' }
|
|
939
|
+
$SnapshotStream = [IO.File]::Open($Snapshot.Key, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::Read)
|
|
940
|
+
try {
|
|
941
|
+
$Hasher = [Security.Cryptography.SHA256]::Create()
|
|
942
|
+
try {
|
|
943
|
+
$Observed = ([BitConverter]::ToString($Hasher.ComputeHash($SnapshotStream))).Replace('-', '').ToLowerInvariant()
|
|
944
|
+
} finally { $Hasher.Dispose() }
|
|
945
|
+
if ($Observed -ne $Snapshot.Value) { throw 'A recovery snapshot changed while staging' }
|
|
946
|
+
$SnapshotStream.Flush($true)
|
|
947
|
+
} finally { $SnapshotStream.Dispose() }
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if ($Phase -ne 'prepared') {
|
|
952
|
+
$Transitions = @{
|
|
953
|
+
'prepared' = @('target-committed')
|
|
954
|
+
'target-committed' = @('alias-committed','verified')
|
|
955
|
+
'alias-committed' = @('verified')
|
|
956
|
+
'verified' = @('backup-committed','lineage-committed')
|
|
957
|
+
'backup-committed' = @('lineage-committed')
|
|
958
|
+
'lineage-committed' = @('committed')
|
|
959
|
+
}
|
|
960
|
+
if ($Transitions[[string]$Journal.phase] -notcontains $Phase) {
|
|
961
|
+
throw 'Generation journal phase transition is invalid'
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
$Journal.phase = $Phase
|
|
966
|
+
$Journal.decision = $Decision
|
|
967
|
+
$Journal.updatedAt = [DateTimeOffset]::UtcNow.ToString('o')
|
|
968
|
+
$Directory = Split-Path -Parent $JournalPath
|
|
969
|
+
$StagingPath = Join-Path $Directory ('.chainlesschain.journal-' + [guid]::NewGuid().ToString('N'))
|
|
970
|
+
$ReplacedPath = Join-Path $Directory ('.chainlesschain.journal-previous-' + [guid]::NewGuid().ToString('N'))
|
|
971
|
+
try {
|
|
972
|
+
$Payload = $Journal | ConvertTo-Json -Compress -Depth 8
|
|
973
|
+
$PayloadBytes = [Text.UTF8Encoding]::new($false).GetBytes($Payload + [Environment]::NewLine)
|
|
974
|
+
$Stream = [IO.File]::Open($StagingPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
|
|
975
|
+
try {
|
|
976
|
+
$Stream.Write($PayloadBytes, 0, $PayloadBytes.Length)
|
|
977
|
+
$Stream.Flush($true)
|
|
978
|
+
} finally { $Stream.Dispose() }
|
|
979
|
+
if ([IO.File]::Exists($JournalPath)) {
|
|
980
|
+
[IO.File]::Replace($StagingPath, $JournalPath, $ReplacedPath, $true)
|
|
981
|
+
[IO.File]::Delete($ReplacedPath)
|
|
982
|
+
} else {
|
|
983
|
+
[IO.File]::Move($StagingPath, $JournalPath)
|
|
984
|
+
}
|
|
985
|
+
$Final = [IO.File]::Open($JournalPath, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::Read)
|
|
986
|
+
try { $Final.Flush($true) } finally { $Final.Dispose() }
|
|
987
|
+
} finally {
|
|
988
|
+
if ([IO.File]::Exists($StagingPath)) { [IO.File]::Delete($StagingPath) }
|
|
989
|
+
if ([IO.File]::Exists($ReplacedPath)) { [IO.File]::Delete($ReplacedPath) }
|
|
990
|
+
}
|
|
991
|
+
`;
|
|
992
|
+
}
|
|
993
|
+
|
|
618
994
|
/**
|
|
619
995
|
* Windows sidecar for both forward update and independent rescue.
|
|
620
996
|
* Every caller must pass the observed pre-state and lock token.
|
|
@@ -627,6 +1003,7 @@ export function writeWindowsSidecar(ctx) {
|
|
|
627
1003
|
aliasPath = null,
|
|
628
1004
|
backupPath = `${targetExePath}.previous`,
|
|
629
1005
|
lineagePath = `${targetExePath}.update-lineage.json`,
|
|
1006
|
+
journalPath = `${targetExePath}.update-transaction.json`,
|
|
630
1007
|
lockPath = `${targetExePath}.update.lock`,
|
|
631
1008
|
resultPath = `${targetExePath}.update-result.json`,
|
|
632
1009
|
lockToken,
|
|
@@ -637,7 +1014,9 @@ export function writeWindowsSidecar(ctx) {
|
|
|
637
1014
|
hadTarget,
|
|
638
1015
|
hadAlias = false,
|
|
639
1016
|
hadPriorBackup = false,
|
|
1017
|
+
hadPriorLineage = false,
|
|
640
1018
|
priorBackupSha256 = null,
|
|
1019
|
+
priorLineageSha256 = null,
|
|
641
1020
|
parentPid,
|
|
642
1021
|
restart,
|
|
643
1022
|
verify = true,
|
|
@@ -665,19 +1044,37 @@ export function writeWindowsSidecar(ctx) {
|
|
|
665
1044
|
"MISSING_PRESTATE",
|
|
666
1045
|
);
|
|
667
1046
|
}
|
|
1047
|
+
if (typeof hadPriorLineage !== "boolean") {
|
|
1048
|
+
throw new ApplyError(
|
|
1049
|
+
"sidecar requires explicit prior lineage state",
|
|
1050
|
+
"MISSING_PRESTATE",
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
668
1053
|
const expectedSha = validateExpectedSha(expectedSha256);
|
|
669
1054
|
if (hadTarget) validateExpectedSha(targetBeforeSha256, "targetBeforeSha256");
|
|
670
1055
|
if (hadAlias) validateExpectedSha(aliasBeforeSha256, "aliasBeforeSha256");
|
|
671
1056
|
if (hadPriorBackup) {
|
|
672
1057
|
validateExpectedSha(priorBackupSha256, "priorBackupSha256");
|
|
673
1058
|
}
|
|
674
|
-
if (
|
|
1059
|
+
if (hadPriorLineage) {
|
|
1060
|
+
validateExpectedSha(priorLineageSha256, "priorLineageSha256");
|
|
1061
|
+
}
|
|
1062
|
+
if (
|
|
1063
|
+
!lockToken ||
|
|
1064
|
+
!/^[1-9][0-9]*:(?:[0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/i.test(
|
|
1065
|
+
lockToken,
|
|
1066
|
+
)
|
|
1067
|
+
) {
|
|
675
1068
|
throw new ApplyError(
|
|
676
1069
|
"a valid lock ownership token is required",
|
|
677
1070
|
"BAD_LOCK_TOKEN",
|
|
678
1071
|
);
|
|
679
1072
|
}
|
|
680
|
-
if (
|
|
1073
|
+
if (
|
|
1074
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
|
|
1075
|
+
transactionId,
|
|
1076
|
+
)
|
|
1077
|
+
) {
|
|
681
1078
|
throw new ApplyError(
|
|
682
1079
|
"a valid transactionId is required",
|
|
683
1080
|
"BAD_TRANSACTION_ID",
|
|
@@ -721,16 +1118,31 @@ export function writeWindowsSidecar(ctx) {
|
|
|
721
1118
|
}
|
|
722
1119
|
const readyPath = `${sidecarPath}.ready`;
|
|
723
1120
|
const readyTempPath = `${readyPath}.tmp-${suffix}`;
|
|
1121
|
+
const journalHelperPath = `${sidecarPath}.journal.ps1`;
|
|
724
1122
|
const parentProbePath = `${sidecarPath}.parent`;
|
|
1123
|
+
const targetSnapshotPath = path.join(
|
|
1124
|
+
path.dirname(targetExePath),
|
|
1125
|
+
`.chainlesschain.target-prior-${transactionId}.exe`,
|
|
1126
|
+
);
|
|
1127
|
+
const backupSnapshotPath = path.join(
|
|
1128
|
+
path.dirname(targetExePath),
|
|
1129
|
+
`.chainlesschain.backup-prior-${transactionId}.exe`,
|
|
1130
|
+
);
|
|
1131
|
+
const lineageSnapshotPath = path.join(
|
|
1132
|
+
path.dirname(targetExePath),
|
|
1133
|
+
`.chainlesschain.lineage-prior-${transactionId}.json`,
|
|
1134
|
+
);
|
|
1135
|
+
const journalRetiredPath = `${journalPath}.last`;
|
|
725
1136
|
const backupTempPath = `${backupPath}.tmp-${suffix}`;
|
|
726
|
-
const currentTempPath =
|
|
1137
|
+
const currentTempPath = targetSnapshotPath;
|
|
727
1138
|
const rollbackTempPath = `${targetExePath}.rollback-${suffix}`;
|
|
1139
|
+
const backupRollbackPath = `${backupPath}.rollback-${suffix}`;
|
|
728
1140
|
const failedPath = `${targetExePath}.failed-${suffix}`;
|
|
729
1141
|
const aliasCandidatePath = aliasPath
|
|
730
1142
|
? `${aliasPath}.candidate-${suffix}`
|
|
731
1143
|
: targetExePath;
|
|
732
1144
|
const aliasBackupPath = aliasPath
|
|
733
|
-
?
|
|
1145
|
+
? path.join(path.dirname(aliasPath), `.cc.previous-${transactionId}.exe`)
|
|
734
1146
|
: targetExePath;
|
|
735
1147
|
const aliasRollbackPath = aliasPath
|
|
736
1148
|
? `${aliasPath}.rollback-${suffix}`
|
|
@@ -746,6 +1158,7 @@ export function writeWindowsSidecar(ctx) {
|
|
|
746
1158
|
// spelling before READY_FILE is published and lock ownership transfers.
|
|
747
1159
|
rawTempRoot,
|
|
748
1160
|
sidecarPath,
|
|
1161
|
+
journalHelperPath,
|
|
749
1162
|
readyPath,
|
|
750
1163
|
readyTempPath,
|
|
751
1164
|
parentProbePath,
|
|
@@ -753,11 +1166,17 @@ export function writeWindowsSidecar(ctx) {
|
|
|
753
1166
|
targetExePath,
|
|
754
1167
|
backupPath,
|
|
755
1168
|
lineagePath,
|
|
1169
|
+
journalPath,
|
|
1170
|
+
journalRetiredPath,
|
|
756
1171
|
lockPath,
|
|
757
1172
|
resultPath,
|
|
758
1173
|
backupTempPath,
|
|
759
1174
|
currentTempPath,
|
|
1175
|
+
targetSnapshotPath,
|
|
1176
|
+
backupSnapshotPath,
|
|
1177
|
+
lineageSnapshotPath,
|
|
760
1178
|
rollbackTempPath,
|
|
1179
|
+
backupRollbackPath,
|
|
761
1180
|
failedPath,
|
|
762
1181
|
lineageTempPath,
|
|
763
1182
|
resultTempPath,
|
|
@@ -785,16 +1204,32 @@ export function writeWindowsSidecar(ctx) {
|
|
|
785
1204
|
: null
|
|
786
1205
|
: expectedSha,
|
|
787
1206
|
});
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
:
|
|
796
|
-
|
|
797
|
-
|
|
1207
|
+
const rollbackSourcePath = targetSnapshotPath;
|
|
1208
|
+
const initialJournal = {
|
|
1209
|
+
schema: NATIVE_GENERATION_TRANSACTION_SCHEMA,
|
|
1210
|
+
transactionId,
|
|
1211
|
+
operation,
|
|
1212
|
+
phase: "prepared",
|
|
1213
|
+
decision: "rollback",
|
|
1214
|
+
expectedSha256: expectedSha,
|
|
1215
|
+
hadTarget,
|
|
1216
|
+
targetBeforeSha256: hadTarget ? targetBeforeSha256 : null,
|
|
1217
|
+
hadAlias,
|
|
1218
|
+
aliasBeforeSha256: hadAlias ? aliasBeforeSha256 : null,
|
|
1219
|
+
hadBackup: hadPriorBackup,
|
|
1220
|
+
backupBeforeSha256: hadPriorBackup ? priorBackupSha256 : null,
|
|
1221
|
+
hadLineage: hadPriorLineage,
|
|
1222
|
+
lineageBeforeSha256: hadPriorLineage ? priorLineageSha256 : null,
|
|
1223
|
+
updatedAt: null,
|
|
1224
|
+
};
|
|
1225
|
+
const initialJournalBase64 = Buffer.from(
|
|
1226
|
+
JSON.stringify(initialJournal),
|
|
1227
|
+
"utf8",
|
|
1228
|
+
).toString("base64");
|
|
1229
|
+
const successLineageSha = crypto
|
|
1230
|
+
.createHash("sha256")
|
|
1231
|
+
.update(`${JSON.stringify(successLineage)}\r\n`, "utf8")
|
|
1232
|
+
.digest("hex");
|
|
798
1233
|
const reparseChecks = buildWindowsReparseChecks(Object.values(allPaths));
|
|
799
1234
|
const startupCheck = buildWindowsTimedStartupCheckLines();
|
|
800
1235
|
|
|
@@ -814,12 +1249,23 @@ export function writeWindowsSidecar(ctx) {
|
|
|
814
1249
|
`set "CURRENT_TEMP=${currentTempPath}"`,
|
|
815
1250
|
`set "ROLLBACK_SOURCE=${rollbackSourcePath}"`,
|
|
816
1251
|
`set "ROLLBACK_TEMP=${rollbackTempPath}"`,
|
|
1252
|
+
`set "BACKUP_ROLLBACK=${backupRollbackPath}"`,
|
|
817
1253
|
`set "FAILED_EXE=${failedPath}"`,
|
|
818
1254
|
`set "ALIAS_CANDIDATE=${aliasCandidatePath}"`,
|
|
819
1255
|
`set "ALIAS_BACKUP=${aliasBackupPath}"`,
|
|
820
1256
|
`set "ALIAS_ROLLBACK=${aliasRollbackPath}"`,
|
|
821
1257
|
`set "LINEAGE_FILE=${lineagePath}"`,
|
|
822
1258
|
`set "LINEAGE_TEMP=${lineageTempPath}"`,
|
|
1259
|
+
`set "LINEAGE_SNAPSHOT=${lineageSnapshotPath}"`,
|
|
1260
|
+
`set "TARGET_SNAPSHOT=${targetSnapshotPath}"`,
|
|
1261
|
+
`set "BACKUP_SNAPSHOT=${backupSnapshotPath}"`,
|
|
1262
|
+
`set "JOURNAL_FILE=${journalPath}"`,
|
|
1263
|
+
`set "JOURNAL_RETIRED=${journalRetiredPath}"`,
|
|
1264
|
+
`set "JOURNAL_HELPER=${journalHelperPath}"`,
|
|
1265
|
+
`set "JOURNAL_INITIAL_BASE64=${initialJournalBase64}"`,
|
|
1266
|
+
'set "JOURNAL_ACTION=write"',
|
|
1267
|
+
'set "JOURNAL_PHASE=prepared"',
|
|
1268
|
+
'set "JOURNAL_DECISION=rollback"',
|
|
823
1269
|
`set "LOCK_FILE=${lockPath}"`,
|
|
824
1270
|
`set "LOCK_TOKEN=${lockToken}"`,
|
|
825
1271
|
`set "LOCK_TOKEN_SHA=${lockTokenSha}"`,
|
|
@@ -835,6 +1281,9 @@ export function writeWindowsSidecar(ctx) {
|
|
|
835
1281
|
`set "HAD_ALIAS=${hadAlias ? "1" : "0"}"`,
|
|
836
1282
|
`set "HAD_BACKUP=${hadPriorBackup ? "1" : "0"}"`,
|
|
837
1283
|
`set "BACKUP_BEFORE_SHA=${priorBackupSha256 || ""}"`,
|
|
1284
|
+
`set "HAD_LINEAGE=${hadPriorLineage ? "1" : "0"}"`,
|
|
1285
|
+
`set "LINEAGE_BEFORE_SHA=${priorLineageSha256 || ""}"`,
|
|
1286
|
+
`set "LINEAGE_TRANSACTION_SHA=${successLineageSha}"`,
|
|
838
1287
|
`set "MANAGE_ALIAS=${aliasPath ? "1" : "0"}"`,
|
|
839
1288
|
'set "TARGET_COMMITTED=0"',
|
|
840
1289
|
'set "ALIAS_COMMITTED=0"',
|
|
@@ -889,27 +1338,53 @@ export function writeWindowsSidecar(ctx) {
|
|
|
889
1338
|
"if errorlevel 1 goto hashfailed",
|
|
890
1339
|
'if /I not "%OBSERVED_SHA%"=="%EXPECTED_SHA%" goto hashmismatch',
|
|
891
1340
|
'if "%OPERATION%"=="rescue" goto stagerescue',
|
|
892
|
-
'if "%HAD_TARGET%"=="0" goto
|
|
893
|
-
'copy /B /Y "%TARGET_EXE%" "%
|
|
1341
|
+
'if "%HAD_TARGET%"=="0" goto stagepriorstate',
|
|
1342
|
+
'copy /B /Y "%TARGET_EXE%" "%TARGET_SNAPSHOT%" >NUL',
|
|
1343
|
+
"if errorlevel 1 goto backupfailed",
|
|
1344
|
+
'set "HASH_PATH=%TARGET_SNAPSHOT%"',
|
|
1345
|
+
"call :hashpath",
|
|
1346
|
+
"if errorlevel 1 goto backupfailed",
|
|
1347
|
+
'if /I not "%OBSERVED_SHA%"=="%TARGET_BEFORE_SHA%" goto backupfailed',
|
|
1348
|
+
'copy /B /Y "%TARGET_SNAPSHOT%" "%BACKUP_TEMP%" >NUL',
|
|
894
1349
|
"if errorlevel 1 goto backupfailed",
|
|
895
1350
|
'set "HASH_PATH=%BACKUP_TEMP%"',
|
|
896
1351
|
"call :hashpath",
|
|
897
1352
|
"if errorlevel 1 goto backupfailed",
|
|
898
1353
|
'if /I not "%OBSERVED_SHA%"=="%TARGET_BEFORE_SHA%" goto backupfailed',
|
|
899
|
-
"goto
|
|
1354
|
+
"goto stagepriorstate",
|
|
900
1355
|
":stagerescue",
|
|
901
|
-
'if "%HAD_TARGET%"=="0" goto
|
|
1356
|
+
'if "%HAD_TARGET%"=="0" goto stagepriorstate',
|
|
902
1357
|
'copy /B /Y "%TARGET_EXE%" "%CURRENT_TEMP%" >NUL',
|
|
903
1358
|
"if errorlevel 1 goto backupfailed",
|
|
904
1359
|
'set "HASH_PATH=%CURRENT_TEMP%"',
|
|
905
1360
|
"call :hashpath",
|
|
906
1361
|
"if errorlevel 1 goto backupfailed",
|
|
907
1362
|
'if /I not "%OBSERVED_SHA%"=="%TARGET_BEFORE_SHA%" goto backupfailed',
|
|
1363
|
+
":stagepriorstate",
|
|
1364
|
+
'if "%HAD_BACKUP%"=="0" goto stagepriorlineage',
|
|
1365
|
+
'copy /B /Y "%BACKUP_EXE%" "%BACKUP_SNAPSHOT%" >NUL',
|
|
1366
|
+
"if errorlevel 1 goto backupfailed",
|
|
1367
|
+
'set "HASH_PATH=%BACKUP_SNAPSHOT%"',
|
|
1368
|
+
"call :hashpath",
|
|
1369
|
+
"if errorlevel 1 goto backupfailed",
|
|
1370
|
+
'if /I not "%OBSERVED_SHA%"=="%BACKUP_BEFORE_SHA%" goto backupfailed',
|
|
1371
|
+
":stagepriorlineage",
|
|
1372
|
+
'if "%HAD_LINEAGE%"=="0" goto stagealias',
|
|
1373
|
+
'copy /B /Y "%LINEAGE_FILE%" "%LINEAGE_SNAPSHOT%" >NUL',
|
|
1374
|
+
"if errorlevel 1 goto backupfailed",
|
|
1375
|
+
'set "HASH_PATH=%LINEAGE_SNAPSHOT%"',
|
|
1376
|
+
"call :hashpath",
|
|
1377
|
+
"if errorlevel 1 goto backupfailed",
|
|
1378
|
+
'if /I not "%OBSERVED_SHA%"=="%LINEAGE_BEFORE_SHA%" goto backupfailed',
|
|
908
1379
|
":stagealias",
|
|
909
|
-
'if "%MANAGE_ALIAS%"=="0" goto
|
|
1380
|
+
'if "%MANAGE_ALIAS%"=="0" goto persistprepared',
|
|
910
1381
|
'if "%HAD_ALIAS%"=="0" goto makealiascandidate',
|
|
911
1382
|
'copy /B /Y "%ALIAS_EXE%" "%ALIAS_BACKUP%" >NUL',
|
|
912
1383
|
"if errorlevel 1 goto aliasstagefailed",
|
|
1384
|
+
'set "HASH_PATH=%ALIAS_BACKUP%"',
|
|
1385
|
+
"call :hashpath",
|
|
1386
|
+
"if errorlevel 1 goto aliasstagefailed",
|
|
1387
|
+
'if /I not "%OBSERVED_SHA%"=="%ALIAS_BEFORE_SHA%" goto aliasstagefailed',
|
|
913
1388
|
":makealiascandidate",
|
|
914
1389
|
'copy /B /Y "%NEW_EXE%" "%ALIAS_CANDIDATE%" >NUL',
|
|
915
1390
|
"if errorlevel 1 goto aliasstagefailed",
|
|
@@ -917,6 +1392,12 @@ export function writeWindowsSidecar(ctx) {
|
|
|
917
1392
|
"call :hashpath",
|
|
918
1393
|
"if errorlevel 1 goto aliasstagefailed",
|
|
919
1394
|
'if /I not "%OBSERVED_SHA%"=="%EXPECTED_SHA%" goto aliasstagefailed',
|
|
1395
|
+
":persistprepared",
|
|
1396
|
+
'set "JOURNAL_PHASE=prepared"',
|
|
1397
|
+
'set "JOURNAL_DECISION=rollback"',
|
|
1398
|
+
"call :writejournal",
|
|
1399
|
+
"if errorlevel 1 goto journalpreparefailed",
|
|
1400
|
+
'set "JOURNAL_INITIAL_BASE64="',
|
|
920
1401
|
":precommitcheck",
|
|
921
1402
|
// Re-hash after all potentially slow backup work, immediately before move.
|
|
922
1403
|
'set "HASH_PATH=%NEW_EXE%"',
|
|
@@ -933,10 +1414,16 @@ export function writeWindowsSidecar(ctx) {
|
|
|
933
1414
|
'move /Y "%NEW_EXE%" "%TARGET_EXE%" >NUL',
|
|
934
1415
|
"if errorlevel 1 goto movefailed",
|
|
935
1416
|
'set "TARGET_COMMITTED=1"',
|
|
1417
|
+
'set "JOURNAL_PHASE=target-committed"',
|
|
1418
|
+
"call :writejournal",
|
|
1419
|
+
"if errorlevel 1 goto journalphasefailed",
|
|
936
1420
|
'if "%MANAGE_ALIAS%"=="0" goto verifytransaction',
|
|
937
1421
|
'move /Y "%ALIAS_CANDIDATE%" "%ALIAS_EXE%" >NUL',
|
|
938
1422
|
"if errorlevel 1 goto aliascommitfailed",
|
|
939
1423
|
'set "ALIAS_COMMITTED=1"',
|
|
1424
|
+
'set "JOURNAL_PHASE=alias-committed"',
|
|
1425
|
+
"call :writejournal",
|
|
1426
|
+
"if errorlevel 1 goto journalphasefailed",
|
|
940
1427
|
":verifytransaction",
|
|
941
1428
|
// Hash committed pathnames before executing either binary or persisting
|
|
942
1429
|
// backup/lineage. This catches a candidate pathname exchange in the final
|
|
@@ -955,22 +1442,46 @@ export function writeWindowsSidecar(ctx) {
|
|
|
955
1442
|
? [
|
|
956
1443
|
'set "VERIFY_PATH=%TARGET_EXE%"',
|
|
957
1444
|
...startupCheck,
|
|
958
|
-
'if "%MANAGE_ALIAS%"=="0" goto
|
|
1445
|
+
'if "%MANAGE_ALIAS%"=="0" goto persistverified',
|
|
959
1446
|
'set "VERIFY_PATH=%ALIAS_EXE%"',
|
|
960
1447
|
...startupCheck,
|
|
961
1448
|
]
|
|
962
1449
|
: ["REM post-replace verification disabled"]),
|
|
1450
|
+
":persistverified",
|
|
1451
|
+
'set "JOURNAL_PHASE=verified"',
|
|
1452
|
+
"call :writejournal",
|
|
1453
|
+
"if errorlevel 1 goto journalphasefailed",
|
|
963
1454
|
":writelineage",
|
|
964
1455
|
'if not "%OPERATION%"=="update" goto persistlineage',
|
|
965
1456
|
'if "%HAD_TARGET%"=="0" goto persistlineage',
|
|
966
1457
|
'move /Y "%BACKUP_TEMP%" "%BACKUP_EXE%" >NUL',
|
|
967
1458
|
"if errorlevel 1 goto backupcommitfailed",
|
|
968
1459
|
'set "BACKUP_COMMITTED=1"',
|
|
969
|
-
'set "
|
|
1460
|
+
'set "HASH_PATH=%BACKUP_EXE%"',
|
|
1461
|
+
"call :hashpath",
|
|
1462
|
+
"if errorlevel 1 goto backupcommitfailed",
|
|
1463
|
+
'if /I not "%OBSERVED_SHA%"=="%TARGET_BEFORE_SHA%" goto backupcommitfailed',
|
|
1464
|
+
'set "JOURNAL_PHASE=backup-committed"',
|
|
1465
|
+
"call :writejournal",
|
|
1466
|
+
"if errorlevel 1 goto journalphasefailed",
|
|
970
1467
|
":persistlineage",
|
|
971
1468
|
`> "%LINEAGE_TEMP%" echo ${JSON.stringify(successLineage)}`,
|
|
972
1469
|
'move /Y "%LINEAGE_TEMP%" "%LINEAGE_FILE%" >NUL',
|
|
973
1470
|
"if errorlevel 1 goto lineagefailed",
|
|
1471
|
+
'set "HASH_PATH=%LINEAGE_FILE%"',
|
|
1472
|
+
"call :hashpath",
|
|
1473
|
+
"if errorlevel 1 goto lineagefailed",
|
|
1474
|
+
'if /I not "%OBSERVED_SHA%"=="%LINEAGE_TRANSACTION_SHA%" goto lineagefailed',
|
|
1475
|
+
'set "JOURNAL_PHASE=lineage-committed"',
|
|
1476
|
+
"call :writejournal",
|
|
1477
|
+
"if errorlevel 1 goto journalphasefailed",
|
|
1478
|
+
'set "JOURNAL_PHASE=committed"',
|
|
1479
|
+
'set "JOURNAL_DECISION=commit"',
|
|
1480
|
+
"call :writejournal",
|
|
1481
|
+
"if errorlevel 1 goto journalcommitfailed",
|
|
1482
|
+
'set "JOURNAL_ACTION=retire"',
|
|
1483
|
+
"call :writejournal",
|
|
1484
|
+
"if errorlevel 1 goto journalretirefailed",
|
|
974
1485
|
restart
|
|
975
1486
|
? "REM restart deferred until result persistence"
|
|
976
1487
|
: "REM restart not requested",
|
|
@@ -989,8 +1500,36 @@ export function writeWindowsSidecar(ctx) {
|
|
|
989
1500
|
":lineagefailed",
|
|
990
1501
|
'set "FAIL_STATUS=lineage-write-failed"',
|
|
991
1502
|
"goto rollbacktransaction",
|
|
1503
|
+
":journalphasefailed",
|
|
1504
|
+
'set "FAIL_STATUS=journal-phase-write-failed"',
|
|
1505
|
+
"goto rollbacktransaction",
|
|
1506
|
+
":journalpreparefailed",
|
|
1507
|
+
'if exist "%JOURNAL_FILE%" goto journalrecoveryrequired',
|
|
1508
|
+
'set "RESULT_STATUS=journal-prepare-failed"',
|
|
1509
|
+
"goto cleanup",
|
|
1510
|
+
":journalcommitfailed",
|
|
1511
|
+
'set "RESULT_STATUS=journal-commit-write-failed"',
|
|
1512
|
+
"goto journalrecoveryrequired",
|
|
1513
|
+
":journalretirefailed",
|
|
1514
|
+
'set "RESULT_STATUS=journal-retirement-failed"',
|
|
1515
|
+
"goto journalrecoveryrequired",
|
|
992
1516
|
":rollbacktransaction",
|
|
993
|
-
'if
|
|
1517
|
+
'if "%TARGET_COMMITTED%"=="1" goto rollbackcommittedtarget',
|
|
1518
|
+
'if exist "%TARGET_EXE%" goto inspectrollbacktarget',
|
|
1519
|
+
'if "%HAD_TARGET%"=="1" goto targetrollbackfailed',
|
|
1520
|
+
"goto rollbackalias",
|
|
1521
|
+
":inspectrollbacktarget",
|
|
1522
|
+
'set "HASH_PATH=%TARGET_EXE%"',
|
|
1523
|
+
"call :hashpath",
|
|
1524
|
+
"if errorlevel 1 goto targetrollbackfailed",
|
|
1525
|
+
'if /I "%OBSERVED_SHA%"=="%TARGET_BEFORE_SHA%" goto rollbackalias',
|
|
1526
|
+
'if /I not "%OBSERVED_SHA%"=="%EXPECTED_SHA%" goto targetrollbackfailed',
|
|
1527
|
+
'set "TARGET_COMMITTED=1"',
|
|
1528
|
+
":rollbackcommittedtarget",
|
|
1529
|
+
'set "HASH_PATH=%TARGET_EXE%"',
|
|
1530
|
+
"call :hashpath",
|
|
1531
|
+
"if errorlevel 1 goto targetrollbackfailed",
|
|
1532
|
+
'if /I not "%OBSERVED_SHA%"=="%EXPECTED_SHA%" goto targetrollbackfailed',
|
|
994
1533
|
'copy /B /Y "%TARGET_EXE%" "%FAILED_EXE%" >NUL 2>&1',
|
|
995
1534
|
'if "%HAD_TARGET%"=="0" goto removefreshcanonical',
|
|
996
1535
|
'copy /B /Y "%ROLLBACK_SOURCE%" "%ROLLBACK_TEMP%" >NUL',
|
|
@@ -1009,7 +1548,23 @@ export function writeWindowsSidecar(ctx) {
|
|
|
1009
1548
|
":targetrollbackfailed",
|
|
1010
1549
|
'set "ROLLBACK_OK=0"',
|
|
1011
1550
|
":rollbackalias",
|
|
1012
|
-
'if
|
|
1551
|
+
'if "%MANAGE_ALIAS%"=="0" goto finishrollback',
|
|
1552
|
+
'if "%ALIAS_COMMITTED%"=="1" goto rollbackcommittedalias',
|
|
1553
|
+
'if exist "%ALIAS_EXE%" goto inspectrollbackalias',
|
|
1554
|
+
'if "%HAD_ALIAS%"=="1" goto aliasrollbackfailed',
|
|
1555
|
+
"goto finishrollback",
|
|
1556
|
+
":inspectrollbackalias",
|
|
1557
|
+
'set "HASH_PATH=%ALIAS_EXE%"',
|
|
1558
|
+
"call :hashpath",
|
|
1559
|
+
"if errorlevel 1 goto aliasrollbackfailed",
|
|
1560
|
+
'if /I "%OBSERVED_SHA%"=="%ALIAS_BEFORE_SHA%" goto finishrollback',
|
|
1561
|
+
'if /I not "%OBSERVED_SHA%"=="%EXPECTED_SHA%" goto aliasrollbackfailed',
|
|
1562
|
+
'set "ALIAS_COMMITTED=1"',
|
|
1563
|
+
":rollbackcommittedalias",
|
|
1564
|
+
'set "HASH_PATH=%ALIAS_EXE%"',
|
|
1565
|
+
"call :hashpath",
|
|
1566
|
+
"if errorlevel 1 goto aliasrollbackfailed",
|
|
1567
|
+
'if /I not "%OBSERVED_SHA%"=="%EXPECTED_SHA%" goto aliasrollbackfailed',
|
|
1013
1568
|
'if "%HAD_ALIAS%"=="0" goto removefreshalias',
|
|
1014
1569
|
'copy /B /Y "%ALIAS_BACKUP%" "%ALIAS_ROLLBACK%" >NUL',
|
|
1015
1570
|
"if errorlevel 1 goto aliasrollbackfailed",
|
|
@@ -1028,29 +1583,29 @@ export function writeWindowsSidecar(ctx) {
|
|
|
1028
1583
|
'set "ROLLBACK_OK=0"',
|
|
1029
1584
|
":finishrollback",
|
|
1030
1585
|
'if "%ROLLBACK_OK%"=="0" goto rollbackfailed',
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
":writerolledbacklineage",
|
|
1039
|
-
`> "%LINEAGE_TEMP%" echo ${JSON.stringify(rolledBackLineage)}`,
|
|
1040
|
-
'move /Y "%LINEAGE_TEMP%" "%LINEAGE_FILE%" >NUL 2>&1',
|
|
1041
|
-
"if errorlevel 1 goto rollbacklineagefailed",
|
|
1042
|
-
":rollbackstatecomplete",
|
|
1043
|
-
]
|
|
1044
|
-
: []),
|
|
1586
|
+
"call :restorebackupstate",
|
|
1587
|
+
"if errorlevel 1 goto rollbackstatefailed",
|
|
1588
|
+
"call :restorelineagestate",
|
|
1589
|
+
"if errorlevel 1 goto rollbacklineagefailed",
|
|
1590
|
+
'set "JOURNAL_ACTION=retire"',
|
|
1591
|
+
"call :writejournal",
|
|
1592
|
+
"if errorlevel 1 goto rollbackjournalretirefailed",
|
|
1045
1593
|
'set "RESULT_STATUS=%FAIL_STATUS%-rolled-back"',
|
|
1046
1594
|
"goto cleanup",
|
|
1047
1595
|
":rollbackfailed",
|
|
1048
1596
|
'set "RESULT_STATUS=%FAIL_STATUS%-rollback-failed"',
|
|
1049
|
-
"goto
|
|
1597
|
+
"goto journalrecoveryrequired",
|
|
1050
1598
|
":rollbacklineagefailed",
|
|
1051
1599
|
'set "ROLLBACK_OK=0"',
|
|
1052
1600
|
'set "FAIL_STATUS=rollback-lineage-write-failed"',
|
|
1053
1601
|
"goto rollbackfailed",
|
|
1602
|
+
":rollbackstatefailed",
|
|
1603
|
+
'set "ROLLBACK_OK=0"',
|
|
1604
|
+
'set "FAIL_STATUS=rollback-state-restore-failed"',
|
|
1605
|
+
"goto rollbackfailed",
|
|
1606
|
+
":rollbackjournalretirefailed",
|
|
1607
|
+
'set "FAIL_STATUS=rollback-journal-retirement-failed"',
|
|
1608
|
+
"goto rollbackfailed",
|
|
1054
1609
|
":parenttimeout",
|
|
1055
1610
|
'set "RESULT_STATUS=parent-timeout"',
|
|
1056
1611
|
"goto cleanup",
|
|
@@ -1069,13 +1624,22 @@ export function writeWindowsSidecar(ctx) {
|
|
|
1069
1624
|
"goto cleanup",
|
|
1070
1625
|
":stateconflict",
|
|
1071
1626
|
'set "RESULT_STATUS=prestate-conflict"',
|
|
1627
|
+
'if exist "%JOURNAL_FILE%" goto journalrecoveryrequired',
|
|
1072
1628
|
"goto cleanup",
|
|
1073
1629
|
":hashfailed",
|
|
1074
1630
|
'set "RESULT_STATUS=hash-check-failed"',
|
|
1631
|
+
'if exist "%JOURNAL_FILE%" goto hashfailedafterjournal',
|
|
1075
1632
|
"goto cleanup",
|
|
1633
|
+
":hashfailedafterjournal",
|
|
1634
|
+
'set "FAIL_STATUS=hash-check-failed"',
|
|
1635
|
+
"goto rollbacktransaction",
|
|
1076
1636
|
":hashmismatch",
|
|
1077
1637
|
'set "RESULT_STATUS=sha256-mismatch"',
|
|
1638
|
+
'if exist "%JOURNAL_FILE%" goto hashmismatchafterjournal',
|
|
1078
1639
|
"goto cleanup",
|
|
1640
|
+
":hashmismatchafterjournal",
|
|
1641
|
+
'set "FAIL_STATUS=sha256-mismatch"',
|
|
1642
|
+
"goto rollbacktransaction",
|
|
1079
1643
|
":backupfailed",
|
|
1080
1644
|
'set "RESULT_STATUS=backup-failed"',
|
|
1081
1645
|
"goto cleanup",
|
|
@@ -1086,23 +1650,35 @@ export function writeWindowsSidecar(ctx) {
|
|
|
1086
1650
|
'set "RESULT_STATUS=alias-stage-failed"',
|
|
1087
1651
|
"goto cleanup",
|
|
1088
1652
|
":movefailed",
|
|
1089
|
-
'set "
|
|
1090
|
-
"goto
|
|
1653
|
+
'set "FAIL_STATUS=move-failed"',
|
|
1654
|
+
"goto rollbacktransaction",
|
|
1091
1655
|
":locklost",
|
|
1092
1656
|
'set "RESULT_STATUS=lock-ownership-lost"',
|
|
1657
|
+
"goto journalrecoveryrequired",
|
|
1658
|
+
":readyfailed",
|
|
1093
1659
|
'set "WRITE_RESULT=0"',
|
|
1660
|
+
'set "SKIP_PATH_CLEANUP=1"',
|
|
1094
1661
|
"goto cleanup",
|
|
1095
|
-
":
|
|
1662
|
+
":journalrecoveryrequired",
|
|
1663
|
+
'set "EXIT_CODE=1"',
|
|
1096
1664
|
'set "WRITE_RESULT=0"',
|
|
1097
1665
|
'set "SKIP_PATH_CLEANUP=1"',
|
|
1666
|
+
'set "ROLLBACK_OK=0"',
|
|
1098
1667
|
"goto cleanup",
|
|
1099
1668
|
":cleanup",
|
|
1100
1669
|
'if "%SKIP_PATH_CLEANUP%"=="1" goto unsafeexit',
|
|
1101
|
-
'if
|
|
1102
|
-
'
|
|
1670
|
+
'if exist "%JOURNAL_FILE%" goto cleanupvolatile',
|
|
1671
|
+
'del /F /Q "%BACKUP_TEMP%" >NUL 2>&1',
|
|
1672
|
+
'del /F /Q "%CURRENT_TEMP%" >NUL 2>&1',
|
|
1673
|
+
'del /F /Q "%TARGET_SNAPSHOT%" >NUL 2>&1',
|
|
1674
|
+
'del /F /Q "%BACKUP_SNAPSHOT%" >NUL 2>&1',
|
|
1675
|
+
'del /F /Q "%LINEAGE_SNAPSHOT%" >NUL 2>&1',
|
|
1676
|
+
'if "%MANAGE_ALIAS%"=="1" del /F /Q "%ALIAS_BACKUP%" >NUL 2>&1',
|
|
1677
|
+
'del /F /Q "%JOURNAL_HELPER%" >NUL 2>&1',
|
|
1678
|
+
":cleanupvolatile",
|
|
1103
1679
|
'if "%ROLLBACK_OK%"=="1" del /F /Q "%ROLLBACK_TEMP%" >NUL 2>&1',
|
|
1680
|
+
'if "%ROLLBACK_OK%"=="1" del /F /Q "%BACKUP_ROLLBACK%" >NUL 2>&1',
|
|
1104
1681
|
'if "%MANAGE_ALIAS%"=="1" del /F /Q "%ALIAS_CANDIDATE%" >NUL 2>&1',
|
|
1105
|
-
'if "%ROLLBACK_OK%"=="1" if "%MANAGE_ALIAS%"=="1" del /F /Q "%ALIAS_BACKUP%" >NUL 2>&1',
|
|
1106
1682
|
'if "%ROLLBACK_OK%"=="1" if "%MANAGE_ALIAS%"=="1" del /F /Q "%ALIAS_ROLLBACK%" >NUL 2>&1',
|
|
1107
1683
|
'del /F /Q "%LINEAGE_TEMP%" >NUL 2>&1',
|
|
1108
1684
|
'del /F /Q "%READY_TEMP%" >NUL 2>&1',
|
|
@@ -1125,6 +1701,76 @@ export function writeWindowsSidecar(ctx) {
|
|
|
1125
1701
|
'if not errorlevel 1 del /F /Q "%LOCK_FILE%" >NUL 2>&1',
|
|
1126
1702
|
'if "%EXIT_CODE%"=="0" if "%RESTART_REQUESTED%"=="1" start "" "%TARGET_EXE%"',
|
|
1127
1703
|
'(goto) 2>NUL & del /F /Q "%~f0" & exit /b %EXIT_CODE%',
|
|
1704
|
+
":writejournal",
|
|
1705
|
+
'"%CC_SYSTEM_ROOT%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%JOURNAL_HELPER%" >NUL 2>&1',
|
|
1706
|
+
"exit /b %ERRORLEVEL%",
|
|
1707
|
+
":restorebackupstate",
|
|
1708
|
+
'if "%HAD_BACKUP%"=="0" goto restorebackupabsent',
|
|
1709
|
+
'if not exist "%BACKUP_EXE%" goto restorebackupfromsnapshot',
|
|
1710
|
+
'set "HASH_PATH=%BACKUP_EXE%"',
|
|
1711
|
+
"call :hashpath",
|
|
1712
|
+
"if errorlevel 1 exit /b 1",
|
|
1713
|
+
'if /I "%OBSERVED_SHA%"=="%BACKUP_BEFORE_SHA%" exit /b 0',
|
|
1714
|
+
'if "%OPERATION%"=="rescue" exit /b 1',
|
|
1715
|
+
'if /I not "%OBSERVED_SHA%"=="%TARGET_BEFORE_SHA%" exit /b 1',
|
|
1716
|
+
":restorebackupfromsnapshot",
|
|
1717
|
+
'if not exist "%BACKUP_SNAPSHOT%" exit /b 1',
|
|
1718
|
+
'set "HASH_PATH=%BACKUP_SNAPSHOT%"',
|
|
1719
|
+
"call :hashpath",
|
|
1720
|
+
"if errorlevel 1 exit /b 1",
|
|
1721
|
+
'if /I not "%OBSERVED_SHA%"=="%BACKUP_BEFORE_SHA%" exit /b 1',
|
|
1722
|
+
'copy /B /Y "%BACKUP_SNAPSHOT%" "%BACKUP_ROLLBACK%" >NUL',
|
|
1723
|
+
"if errorlevel 1 exit /b 1",
|
|
1724
|
+
'move /Y "%BACKUP_ROLLBACK%" "%BACKUP_EXE%" >NUL',
|
|
1725
|
+
"if errorlevel 1 exit /b 1",
|
|
1726
|
+
'set "HASH_PATH=%BACKUP_EXE%"',
|
|
1727
|
+
"call :hashpath",
|
|
1728
|
+
"if errorlevel 1 exit /b 1",
|
|
1729
|
+
'if /I not "%OBSERVED_SHA%"=="%BACKUP_BEFORE_SHA%" exit /b 1',
|
|
1730
|
+
"exit /b 0",
|
|
1731
|
+
":restorebackupabsent",
|
|
1732
|
+
'if not exist "%BACKUP_EXE%" exit /b 0',
|
|
1733
|
+
'if "%OPERATION%"=="rescue" exit /b 1',
|
|
1734
|
+
'if "%HAD_TARGET%"=="0" exit /b 1',
|
|
1735
|
+
'set "HASH_PATH=%BACKUP_EXE%"',
|
|
1736
|
+
"call :hashpath",
|
|
1737
|
+
"if errorlevel 1 exit /b 1",
|
|
1738
|
+
'if /I not "%OBSERVED_SHA%"=="%TARGET_BEFORE_SHA%" exit /b 1',
|
|
1739
|
+
'del /F /Q "%BACKUP_EXE%" >NUL 2>&1',
|
|
1740
|
+
'if exist "%BACKUP_EXE%" exit /b 1',
|
|
1741
|
+
"exit /b 0",
|
|
1742
|
+
":restorelineagestate",
|
|
1743
|
+
'if "%HAD_LINEAGE%"=="0" goto restorelineageabsent',
|
|
1744
|
+
'if not exist "%LINEAGE_FILE%" goto restorelineagefromsnapshot',
|
|
1745
|
+
'set "HASH_PATH=%LINEAGE_FILE%"',
|
|
1746
|
+
"call :hashpath",
|
|
1747
|
+
"if errorlevel 1 exit /b 1",
|
|
1748
|
+
'if /I "%OBSERVED_SHA%"=="%LINEAGE_BEFORE_SHA%" exit /b 0',
|
|
1749
|
+
'if /I not "%OBSERVED_SHA%"=="%LINEAGE_TRANSACTION_SHA%" exit /b 1',
|
|
1750
|
+
":restorelineagefromsnapshot",
|
|
1751
|
+
'if not exist "%LINEAGE_SNAPSHOT%" exit /b 1',
|
|
1752
|
+
'set "HASH_PATH=%LINEAGE_SNAPSHOT%"',
|
|
1753
|
+
"call :hashpath",
|
|
1754
|
+
"if errorlevel 1 exit /b 1",
|
|
1755
|
+
'if /I not "%OBSERVED_SHA%"=="%LINEAGE_BEFORE_SHA%" exit /b 1',
|
|
1756
|
+
'copy /B /Y "%LINEAGE_SNAPSHOT%" "%LINEAGE_TEMP%" >NUL',
|
|
1757
|
+
"if errorlevel 1 exit /b 1",
|
|
1758
|
+
'move /Y "%LINEAGE_TEMP%" "%LINEAGE_FILE%" >NUL',
|
|
1759
|
+
"if errorlevel 1 exit /b 1",
|
|
1760
|
+
'set "HASH_PATH=%LINEAGE_FILE%"',
|
|
1761
|
+
"call :hashpath",
|
|
1762
|
+
"if errorlevel 1 exit /b 1",
|
|
1763
|
+
'if /I not "%OBSERVED_SHA%"=="%LINEAGE_BEFORE_SHA%" exit /b 1',
|
|
1764
|
+
"exit /b 0",
|
|
1765
|
+
":restorelineageabsent",
|
|
1766
|
+
'if not exist "%LINEAGE_FILE%" exit /b 0',
|
|
1767
|
+
'set "HASH_PATH=%LINEAGE_FILE%"',
|
|
1768
|
+
"call :hashpath",
|
|
1769
|
+
"if errorlevel 1 exit /b 1",
|
|
1770
|
+
'if /I not "%OBSERVED_SHA%"=="%LINEAGE_TRANSACTION_SHA%" exit /b 1',
|
|
1771
|
+
'del /F /Q "%LINEAGE_FILE%" >NUL 2>&1',
|
|
1772
|
+
'if exist "%LINEAGE_FILE%" exit /b 1',
|
|
1773
|
+
"exit /b 0",
|
|
1128
1774
|
":hashpath",
|
|
1129
1775
|
'set "OBSERVED_SHA="',
|
|
1130
1776
|
'for /f "usebackq delims=" %%H in (`%CC_SYSTEM_ROOT%\\System32\\certutil.exe -hashfile "%HASH_PATH%" SHA256 ^| %CC_SYSTEM_ROOT%\\System32\\findstr.exe /R /X /I "[0-9A-F][0-9A-F]*"`) do if not defined OBSERVED_SHA set "OBSERVED_SHA=%%H"',
|
|
@@ -1160,21 +1806,46 @@ export function writeWindowsSidecar(ctx) {
|
|
|
1160
1806
|
":prestate_backup",
|
|
1161
1807
|
'if "%HAD_BACKUP%"=="1" goto prestate_backup_present',
|
|
1162
1808
|
'if exist "%BACKUP_EXE%" exit /b 1',
|
|
1163
|
-
"
|
|
1809
|
+
"goto prestate_lineage",
|
|
1164
1810
|
":prestate_backup_present",
|
|
1165
1811
|
'if not exist "%BACKUP_EXE%" exit /b 1',
|
|
1166
1812
|
'set "HASH_PATH=%BACKUP_EXE%"',
|
|
1167
1813
|
"call :hashpath",
|
|
1168
1814
|
"if errorlevel 1 exit /b 1",
|
|
1169
1815
|
'if /I not "%OBSERVED_SHA%"=="%BACKUP_BEFORE_SHA%" exit /b 1',
|
|
1816
|
+
":prestate_lineage",
|
|
1817
|
+
'if "%HAD_LINEAGE%"=="1" goto prestate_lineage_present',
|
|
1818
|
+
'if exist "%LINEAGE_FILE%" exit /b 1',
|
|
1819
|
+
"exit /b 0",
|
|
1820
|
+
":prestate_lineage_present",
|
|
1821
|
+
'if not exist "%LINEAGE_FILE%" exit /b 1',
|
|
1822
|
+
'set "HASH_PATH=%LINEAGE_FILE%"',
|
|
1823
|
+
"call :hashpath",
|
|
1824
|
+
"if errorlevel 1 exit /b 1",
|
|
1825
|
+
'if /I not "%OBSERVED_SHA%"=="%LINEAGE_BEFORE_SHA%" exit /b 1',
|
|
1170
1826
|
"exit /b 0",
|
|
1171
1827
|
].join("\r\n");
|
|
1172
1828
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1829
|
+
try {
|
|
1830
|
+
fs.writeFileSync(
|
|
1831
|
+
journalHelperPath,
|
|
1832
|
+
windowsGenerationJournalHelperSource(),
|
|
1833
|
+
{
|
|
1834
|
+
encoding: "utf8",
|
|
1835
|
+
flag: "wx",
|
|
1836
|
+
mode: 0o600,
|
|
1837
|
+
},
|
|
1838
|
+
);
|
|
1839
|
+
fs.writeFileSync(sidecarPath, `\uFEFF${cmd}`, {
|
|
1840
|
+
encoding: "utf8",
|
|
1841
|
+
flag: "wx",
|
|
1842
|
+
mode: 0o600,
|
|
1843
|
+
});
|
|
1844
|
+
} catch (error) {
|
|
1845
|
+
removeRegularFileIfPresent(sidecarPath);
|
|
1846
|
+
removeRegularFileIfPresent(journalHelperPath);
|
|
1847
|
+
throw error;
|
|
1848
|
+
}
|
|
1178
1849
|
return sidecarPath;
|
|
1179
1850
|
}
|
|
1180
1851
|
|
|
@@ -1219,12 +1890,27 @@ export async function rollbackLastKnownGood(ctx = {}) {
|
|
|
1219
1890
|
"BACKUP_NOT_SIBLING",
|
|
1220
1891
|
);
|
|
1221
1892
|
}
|
|
1893
|
+
if (
|
|
1894
|
+
platform === "win32" &&
|
|
1895
|
+
!samePath(backupPath, statePaths.backupPath, platform)
|
|
1896
|
+
) {
|
|
1897
|
+
throw new ApplyError(
|
|
1898
|
+
"Windows restart recovery requires the canonical .previous backup",
|
|
1899
|
+
"BACKUP_NOT_CANONICAL",
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1222
1902
|
assertApplyPath(backupPath, "last-known-good backup", false, platform);
|
|
1223
1903
|
assertApplyPath(statePaths.lineagePath, "update lineage", false, platform);
|
|
1224
1904
|
assertApplyPath(layout.canonicalPath, "canonical executable", true, platform);
|
|
1225
1905
|
if (layout.aliasPath) {
|
|
1226
1906
|
assertApplyPath(layout.aliasPath, "managed CLI alias", true, platform);
|
|
1227
1907
|
}
|
|
1908
|
+
if (lstatOrNull(statePaths.journalPath)) {
|
|
1909
|
+
throw new ApplyError(
|
|
1910
|
+
`an interrupted native generation requires restart recovery: ${statePaths.journalPath}`,
|
|
1911
|
+
"RECOVERY_REQUIRED",
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1228
1914
|
|
|
1229
1915
|
const updateLock = acquireUpdateLock(statePaths.lockPath, platform);
|
|
1230
1916
|
const transactionId = crypto.randomUUID();
|
|
@@ -1263,6 +1949,14 @@ export async function rollbackLastKnownGood(ctx = {}) {
|
|
|
1263
1949
|
lineage.previousSha256,
|
|
1264
1950
|
"last-known-good backup",
|
|
1265
1951
|
);
|
|
1952
|
+
const hadPriorBackup = Boolean(lstatOrNull(statePaths.backupPath));
|
|
1953
|
+
const priorBackupSha = hadPriorBackup
|
|
1954
|
+
? stableSha256(statePaths.backupPath, "existing native backup")
|
|
1955
|
+
: null;
|
|
1956
|
+
const priorLineageSha = stableSha256(
|
|
1957
|
+
statePaths.lineagePath,
|
|
1958
|
+
"native update lineage before rescue",
|
|
1959
|
+
);
|
|
1266
1960
|
|
|
1267
1961
|
rescueStagingPath = uniqueSibling(layout.canonicalPath, "rescue");
|
|
1268
1962
|
atomicCopyFile(backupPath, rescueStagingPath, {
|
|
@@ -1293,8 +1987,10 @@ export async function rollbackLastKnownGood(ctx = {}) {
|
|
|
1293
1987
|
aliasBeforeSha,
|
|
1294
1988
|
hadTarget,
|
|
1295
1989
|
hadAlias,
|
|
1296
|
-
hadPriorBackup
|
|
1297
|
-
|
|
1990
|
+
hadPriorBackup,
|
|
1991
|
+
hadPriorLineage: true,
|
|
1992
|
+
priorBackupSha,
|
|
1993
|
+
priorLineageSha,
|
|
1298
1994
|
parentPid,
|
|
1299
1995
|
restart: Boolean(restart),
|
|
1300
1996
|
verify: Boolean(verify),
|
|
@@ -1314,6 +2010,9 @@ export async function rollbackLastKnownGood(ctx = {}) {
|
|
|
1314
2010
|
rescueSha: lineage.previousSha256,
|
|
1315
2011
|
hadTarget,
|
|
1316
2012
|
targetBeforeSha,
|
|
2013
|
+
hadPriorBackup,
|
|
2014
|
+
priorBackupSha,
|
|
2015
|
+
priorLineageSha,
|
|
1317
2016
|
restart: Boolean(restart),
|
|
1318
2017
|
verify: Boolean(verify),
|
|
1319
2018
|
spawnImpl,
|
|
@@ -1342,22 +2041,89 @@ function applyPosixRescue(ctx) {
|
|
|
1342
2041
|
rescueSha,
|
|
1343
2042
|
hadTarget,
|
|
1344
2043
|
targetBeforeSha,
|
|
2044
|
+
hadPriorBackup,
|
|
2045
|
+
priorBackupSha,
|
|
2046
|
+
priorLineageSha,
|
|
1345
2047
|
restart,
|
|
1346
2048
|
verify,
|
|
1347
2049
|
spawnImpl,
|
|
1348
2050
|
verifyImpl,
|
|
1349
2051
|
} = ctx;
|
|
1350
2052
|
const targetPath = layout.canonicalPath;
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
2053
|
+
const snapshotPaths = generationSnapshotPaths(targetPath, transactionId);
|
|
2054
|
+
const journal = makeGenerationJournal({
|
|
2055
|
+
transactionId,
|
|
2056
|
+
operation: "rescue",
|
|
2057
|
+
expectedSha: rescueSha,
|
|
2058
|
+
hadTarget,
|
|
2059
|
+
targetBeforeSha,
|
|
2060
|
+
hadBackup: hadPriorBackup,
|
|
2061
|
+
backupBeforeSha: priorBackupSha,
|
|
2062
|
+
hadLineage: true,
|
|
2063
|
+
lineageBeforeSha: priorLineageSha,
|
|
2064
|
+
});
|
|
2065
|
+
|
|
2066
|
+
try {
|
|
2067
|
+
if (hadTarget) {
|
|
2068
|
+
atomicCopyFile(targetPath, snapshotPaths.priorTargetPath, {
|
|
2069
|
+
preserveModeFrom: targetPath,
|
|
2070
|
+
updateLock,
|
|
2071
|
+
});
|
|
2072
|
+
assertExpectedFileHash(
|
|
2073
|
+
snapshotPaths.priorTargetPath,
|
|
2074
|
+
targetBeforeSha,
|
|
2075
|
+
"pre-rescue target generation snapshot",
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
if (hadPriorBackup) {
|
|
2079
|
+
atomicCopyFile(statePaths.backupPath, snapshotPaths.priorBackupPath, {
|
|
2080
|
+
preserveModeFrom: statePaths.backupPath,
|
|
2081
|
+
updateLock,
|
|
2082
|
+
});
|
|
2083
|
+
assertExpectedFileHash(
|
|
2084
|
+
snapshotPaths.priorBackupPath,
|
|
2085
|
+
priorBackupSha,
|
|
2086
|
+
"pre-rescue backup generation snapshot",
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
atomicCopyFile(statePaths.lineagePath, snapshotPaths.priorLineagePath, {
|
|
2090
|
+
preserveModeFrom: statePaths.lineagePath,
|
|
1355
2091
|
updateLock,
|
|
1356
2092
|
});
|
|
1357
2093
|
assertExpectedFileHash(
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
"pre-rescue
|
|
2094
|
+
snapshotPaths.priorLineagePath,
|
|
2095
|
+
priorLineageSha,
|
|
2096
|
+
"pre-rescue lineage generation snapshot",
|
|
2097
|
+
);
|
|
2098
|
+
} catch (error) {
|
|
2099
|
+
cleanupGenerationSnapshots(snapshotPaths);
|
|
2100
|
+
throw asApplyError(
|
|
2101
|
+
error,
|
|
2102
|
+
"GENERATION_SNAPSHOT_FAILED",
|
|
2103
|
+
"could not preserve the native rescue pre-state",
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
try {
|
|
2108
|
+
writeGenerationJournalPhase(
|
|
2109
|
+
statePaths.journalPath,
|
|
2110
|
+
journal,
|
|
2111
|
+
"prepared",
|
|
2112
|
+
"rollback",
|
|
2113
|
+
updateLock,
|
|
2114
|
+
);
|
|
2115
|
+
} catch (error) {
|
|
2116
|
+
if (lstatOrNull(statePaths.journalPath)) {
|
|
2117
|
+
throw new ApplyError(
|
|
2118
|
+
`native rescue intent persistence is unknown: ${error.message}`,
|
|
2119
|
+
"RESCUE_ROLLBACK_FAILED",
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
cleanupGenerationSnapshots(snapshotPaths);
|
|
2123
|
+
throw asApplyError(
|
|
2124
|
+
error,
|
|
2125
|
+
"JOURNAL_WRITE_FAILED",
|
|
2126
|
+
"could not persist native rescue intent",
|
|
1361
2127
|
);
|
|
1362
2128
|
}
|
|
1363
2129
|
|
|
@@ -1377,8 +2143,35 @@ function applyPosixRescue(ctx) {
|
|
|
1377
2143
|
assertUpdateLockOwned(updateLock);
|
|
1378
2144
|
fs.renameSync(rescueStagingPath, targetPath);
|
|
1379
2145
|
fsyncDirectory(path.dirname(targetPath));
|
|
2146
|
+
writeGenerationJournalPhase(
|
|
2147
|
+
statePaths.journalPath,
|
|
2148
|
+
journal,
|
|
2149
|
+
"target-committed",
|
|
2150
|
+
"rollback",
|
|
2151
|
+
updateLock,
|
|
2152
|
+
);
|
|
1380
2153
|
} catch (error) {
|
|
1381
|
-
|
|
2154
|
+
try {
|
|
2155
|
+
rollbackFailedPosixUpdate({
|
|
2156
|
+
targetPath,
|
|
2157
|
+
statePaths,
|
|
2158
|
+
transactionId,
|
|
2159
|
+
hadTarget,
|
|
2160
|
+
targetBeforeSha,
|
|
2161
|
+
hadPriorBackup,
|
|
2162
|
+
hadPriorLineage: true,
|
|
2163
|
+
priorBackupSha,
|
|
2164
|
+
priorLineageSha,
|
|
2165
|
+
snapshotPaths,
|
|
2166
|
+
journal,
|
|
2167
|
+
updateLock,
|
|
2168
|
+
});
|
|
2169
|
+
} catch (recoveryError) {
|
|
2170
|
+
throw new ApplyError(
|
|
2171
|
+
`rescue target commit failed and its generation journal could not be retired: ${recoveryError.message}`,
|
|
2172
|
+
"RESCUE_ROLLBACK_FAILED",
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
1382
2175
|
throw new ApplyError(
|
|
1383
2176
|
`rescue rename failed: ${error.message}`,
|
|
1384
2177
|
"RESCUE_RENAME_FAILED",
|
|
@@ -1401,6 +2194,22 @@ function applyPosixRescue(ctx) {
|
|
|
1401
2194
|
if (!failure && verify) {
|
|
1402
2195
|
failure = verificationFailure(targetPath, verifyImpl);
|
|
1403
2196
|
}
|
|
2197
|
+
if (!failure) {
|
|
2198
|
+
try {
|
|
2199
|
+
writeGenerationJournalPhase(
|
|
2200
|
+
statePaths.journalPath,
|
|
2201
|
+
journal,
|
|
2202
|
+
"verified",
|
|
2203
|
+
"rollback",
|
|
2204
|
+
updateLock,
|
|
2205
|
+
);
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
failure = new ApplyError(
|
|
2208
|
+
`could not persist native rescue verification: ${error.message}`,
|
|
2209
|
+
"JOURNAL_WRITE_FAILED",
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
1404
2213
|
if (!failure) {
|
|
1405
2214
|
try {
|
|
1406
2215
|
atomicWriteJson(
|
|
@@ -1413,6 +2222,13 @@ function applyPosixRescue(ctx) {
|
|
|
1413
2222
|
}),
|
|
1414
2223
|
updateLock,
|
|
1415
2224
|
);
|
|
2225
|
+
writeGenerationJournalPhase(
|
|
2226
|
+
statePaths.journalPath,
|
|
2227
|
+
journal,
|
|
2228
|
+
"lineage-committed",
|
|
2229
|
+
"rollback",
|
|
2230
|
+
updateLock,
|
|
2231
|
+
);
|
|
1416
2232
|
} catch (error) {
|
|
1417
2233
|
failure = new ApplyError(
|
|
1418
2234
|
`could not persist rescue lineage: ${error.message}`,
|
|
@@ -1422,39 +2238,53 @@ function applyPosixRescue(ctx) {
|
|
|
1422
2238
|
}
|
|
1423
2239
|
|
|
1424
2240
|
if (failure) {
|
|
1425
|
-
let restored = false;
|
|
1426
2241
|
try {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
}
|
|
1441
|
-
restored = true;
|
|
2242
|
+
rollbackFailedPosixUpdate({
|
|
2243
|
+
targetPath,
|
|
2244
|
+
statePaths,
|
|
2245
|
+
transactionId,
|
|
2246
|
+
hadTarget,
|
|
2247
|
+
targetBeforeSha,
|
|
2248
|
+
hadPriorBackup,
|
|
2249
|
+
hadPriorLineage: true,
|
|
2250
|
+
priorBackupSha,
|
|
2251
|
+
priorLineageSha,
|
|
2252
|
+
snapshotPaths,
|
|
2253
|
+
journal,
|
|
2254
|
+
updateLock,
|
|
2255
|
+
});
|
|
1442
2256
|
} catch (error) {
|
|
1443
2257
|
throw new ApplyError(
|
|
1444
|
-
`rescue verification failed and pre-rescue
|
|
2258
|
+
`rescue verification failed and pre-rescue restoration failed: ${error.message}`,
|
|
1445
2259
|
"RESCUE_ROLLBACK_FAILED",
|
|
1446
2260
|
);
|
|
1447
|
-
} finally {
|
|
1448
|
-
if (restored) {
|
|
1449
|
-
assertUpdateLockOwned(updateLock);
|
|
1450
|
-
removeRegularFileIfPresent(currentSnapshotPath);
|
|
1451
|
-
}
|
|
1452
2261
|
}
|
|
1453
2262
|
throw failure;
|
|
1454
2263
|
}
|
|
1455
2264
|
|
|
1456
|
-
|
|
1457
|
-
|
|
2265
|
+
try {
|
|
2266
|
+
writeGenerationJournalPhase(
|
|
2267
|
+
statePaths.journalPath,
|
|
2268
|
+
journal,
|
|
2269
|
+
"committed",
|
|
2270
|
+
"commit",
|
|
2271
|
+
updateLock,
|
|
2272
|
+
);
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
throw new ApplyError(
|
|
2275
|
+
`native rescue commit decision is unknown: ${error.message}`,
|
|
2276
|
+
"RESCUE_ROLLBACK_FAILED",
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
try {
|
|
2280
|
+
retireGenerationJournal(statePaths, journal, updateLock);
|
|
2281
|
+
} catch (error) {
|
|
2282
|
+
throw new ApplyError(
|
|
2283
|
+
`native rescue committed but journal retirement failed: ${error.message}`,
|
|
2284
|
+
"RESCUE_ROLLBACK_FAILED",
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
cleanupGenerationSnapshots(snapshotPaths);
|
|
1458
2288
|
restartVerifiedExecutable(targetPath, restart, spawnImpl);
|
|
1459
2289
|
return buildPlan({
|
|
1460
2290
|
platform: "posix",
|
|
@@ -1485,6 +2315,91 @@ function makeLineage({
|
|
|
1485
2315
|
};
|
|
1486
2316
|
}
|
|
1487
2317
|
|
|
2318
|
+
function generationSnapshotPaths(targetPath, transactionId) {
|
|
2319
|
+
const directory = path.dirname(targetPath);
|
|
2320
|
+
return {
|
|
2321
|
+
priorTargetPath: path.join(
|
|
2322
|
+
directory,
|
|
2323
|
+
`.chainlesschain.target-prior-${transactionId}`,
|
|
2324
|
+
),
|
|
2325
|
+
priorBackupPath: path.join(
|
|
2326
|
+
directory,
|
|
2327
|
+
`.chainlesschain.backup-prior-${transactionId}`,
|
|
2328
|
+
),
|
|
2329
|
+
priorLineagePath: path.join(
|
|
2330
|
+
directory,
|
|
2331
|
+
`.chainlesschain.lineage-prior-${transactionId}`,
|
|
2332
|
+
),
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
function makeGenerationJournal({
|
|
2337
|
+
transactionId,
|
|
2338
|
+
operation,
|
|
2339
|
+
expectedSha,
|
|
2340
|
+
hadTarget,
|
|
2341
|
+
targetBeforeSha,
|
|
2342
|
+
hadBackup,
|
|
2343
|
+
backupBeforeSha,
|
|
2344
|
+
hadLineage,
|
|
2345
|
+
lineageBeforeSha,
|
|
2346
|
+
}) {
|
|
2347
|
+
return {
|
|
2348
|
+
schema: NATIVE_GENERATION_TRANSACTION_SCHEMA,
|
|
2349
|
+
transactionId,
|
|
2350
|
+
operation,
|
|
2351
|
+
phase: "prepared",
|
|
2352
|
+
decision: "rollback",
|
|
2353
|
+
expectedSha256: expectedSha,
|
|
2354
|
+
hadTarget,
|
|
2355
|
+
targetBeforeSha256: hadTarget ? targetBeforeSha : null,
|
|
2356
|
+
hadBackup,
|
|
2357
|
+
backupBeforeSha256: hadBackup ? backupBeforeSha : null,
|
|
2358
|
+
hadAlias: false,
|
|
2359
|
+
aliasBeforeSha256: null,
|
|
2360
|
+
hadLineage,
|
|
2361
|
+
lineageBeforeSha256: hadLineage ? lineageBeforeSha : null,
|
|
2362
|
+
updatedAt: new Date().toISOString(),
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
function writeGenerationJournalPhase(
|
|
2367
|
+
journalPath,
|
|
2368
|
+
journal,
|
|
2369
|
+
phase,
|
|
2370
|
+
decision,
|
|
2371
|
+
updateLock,
|
|
2372
|
+
) {
|
|
2373
|
+
journal.phase = phase;
|
|
2374
|
+
journal.decision = decision;
|
|
2375
|
+
journal.updatedAt = new Date().toISOString();
|
|
2376
|
+
atomicWriteJson(journalPath, journal, updateLock);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function retireGenerationJournal(statePaths, journal, updateLock) {
|
|
2380
|
+
assertUpdateLockOwned(updateLock);
|
|
2381
|
+
const current = JSON.parse(fs.readFileSync(statePaths.journalPath, "utf8"));
|
|
2382
|
+
if (
|
|
2383
|
+
current.schema !== NATIVE_GENERATION_TRANSACTION_SCHEMA ||
|
|
2384
|
+
current.transactionId !== journal.transactionId ||
|
|
2385
|
+
current.decision !== journal.decision ||
|
|
2386
|
+
(current.phase === "committed") !== (current.decision === "commit")
|
|
2387
|
+
) {
|
|
2388
|
+
throw new ApplyError(
|
|
2389
|
+
"native generation journal changed before retirement",
|
|
2390
|
+
"JOURNAL_CHANGED",
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
fs.renameSync(statePaths.journalPath, statePaths.lastJournalPath);
|
|
2394
|
+
fsyncDirectory(path.dirname(statePaths.journalPath));
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
function cleanupGenerationSnapshots(snapshotPaths) {
|
|
2398
|
+
for (const snapshotPath of Object.values(snapshotPaths)) {
|
|
2399
|
+
removeRegularFileIfPresent(snapshotPath);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
1488
2403
|
function buildPlan({
|
|
1489
2404
|
platform,
|
|
1490
2405
|
action,
|
|
@@ -1595,6 +2510,16 @@ function sameDirectory(firstPath, secondPath, platform = process.platform) {
|
|
|
1595
2510
|
return first === second;
|
|
1596
2511
|
}
|
|
1597
2512
|
|
|
2513
|
+
function samePath(firstPath, secondPath, platform = process.platform) {
|
|
2514
|
+
let first = path.resolve(firstPath);
|
|
2515
|
+
let second = path.resolve(secondPath);
|
|
2516
|
+
if (platform === "win32") {
|
|
2517
|
+
first = first.toLowerCase();
|
|
2518
|
+
second = second.toLowerCase();
|
|
2519
|
+
}
|
|
2520
|
+
return first === second;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
1598
2523
|
function assertApplyPath(filePath, label, allowMissingLeaf, platform) {
|
|
1599
2524
|
try {
|
|
1600
2525
|
assertStateSafeRegularFile(filePath, {
|