deadbyte-mcp 0.15.44 → 0.15.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/MANIFEST.SHA256 +69 -131
  2. package/PROVENANCE.json +257 -243
  3. package/README.txt +18 -18
  4. package/RELEASE.json +348 -334
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +503 -491
  7. package/bridge/package.json +1 -1
  8. package/controller/deadbyte-controller.ps1 +1857 -1769
  9. package/controller/deadbyte-desktop-policy.json +6 -6
  10. package/controller/deadbyte-process-policy.json +4 -4
  11. package/docs/R68-COMPAT-HOST-EXEC-CANDIDATE.md +63 -0
  12. package/docs/R69-LIVE-OUTCOME-R70-POLICY-TRANSFER.md +136 -0
  13. package/docs/R70-CODING-DIGEST-SUPERVISOR-CHECKPOINT-20261013.md +42 -0
  14. package/docs/R70-FINITE-GRANT-OWNER-BOUNDARY-20261013.md +80 -0
  15. package/docs/R70-OWNER-BOOTSTRAP-CHECKPOINT-20261013.md +60 -0
  16. package/docs/R70-REFERENCE-MECHANISMS.json +95 -0
  17. package/docs/R70-TRUST-GUARD-EXPIRY-CHECKPOINT-20261013.md +47 -0
  18. package/package.json +2 -2
  19. package/scripts/benchmark-l0-r71.mjs +76 -0
  20. package/scripts/gate-windows-r71.ps1 +64 -0
  21. package/scripts/npm-release-r48.mjs +2 -0
  22. package/scripts/npm-release-readiness-r71.mjs +21 -0
  23. package/scripts/verify-r70-full-regression.ps1 +72 -0
  24. package/src/authority-continuity-r65.mjs +28 -8
  25. package/src/autonomous-mcp-tools.mjs +1 -1
  26. package/src/autonomous-output-contracts.mjs +4 -1
  27. package/src/autonomous-policy.mjs +54 -7
  28. package/src/capability-ledger.mjs +1 -1
  29. package/src/capability-resolver.mjs +2 -1
  30. package/src/commander-compat-runtime.mjs +30 -2
  31. package/src/deadbyte-cli.mjs +8 -6
  32. package/src/native-diff-v2.mjs +18 -5
  33. package/src/native-diff.mjs +21 -5
  34. package/src/r70-owner-approval.mjs +78 -0
  35. package/src/remote-live-log.mjs +48 -14
  36. package/src/remote-result-journal.mjs +1 -1
  37. package/src/runtime-update.mjs +2051 -1891
  38. package/src/tool-audit.mjs +2 -0
  39. package/src/version.mjs +1 -1
  40. package/test/autonomous-local-supervisor.test.mjs +2 -1
  41. package/test/autonomous-output-contract.test.mjs +17 -1
  42. package/test/autonomous-supervisor-integration.test.mjs +2 -1
  43. package/test/capability-resolver.test.mjs +17 -0
  44. package/test/codex-autonomous-r44.test.mjs +1 -1
  45. package/test/commander-compat-runtime.test.mjs +42 -0
  46. package/test/controller-policy-preflight-r59.test.mjs +1 -0
  47. package/test/native-diff-topology-r36.test.mjs +35 -2
  48. package/test/native-diff.test.mjs +83 -3
  49. package/test/r59-tool-surface-compat.test.mjs +9 -5
  50. package/test/r65-authority-runtime-epoch.test.mjs +9 -1
  51. package/test/r67-isolated-slot-rollback-restore.test.mjs +2 -1
  52. package/test/r67-npm-publish-gate.test.mjs +4 -2
  53. package/test/r67-release-contract.test.mjs +8 -7
  54. package/test/r69-live-outcome.test.mjs +137 -0
  55. package/test/r70-autonomous-grant-expiry.test.mjs +89 -0
  56. package/test/r70-autonomous-policy-bootstrap.test.mjs +166 -0
  57. package/test/r70-controller-autonomous-root.test.mjs +104 -0
  58. package/test/r70-controller-grant-subset.test.mjs +28 -0
  59. package/test/r70-controller-owner-issue-boundary.test.mjs +80 -0
  60. package/test/r70-controller-policy-snapshot.test.mjs +66 -0
  61. package/test/r70-owner-approval.test.mjs +67 -0
  62. package/test/r71-full-verifier-artifact.test.mjs +28 -0
  63. package/test/r71-isolated-slot-rollback-restore.test.mjs +62 -0
  64. package/test/r71-native-diff-mcp-wire.test.mjs +75 -0
  65. package/test/r71-publish-fence.test.mjs +38 -0
  66. package/test/r71-release-identity.test.mjs +31 -0
  67. package/test/remote-result-journal.test.mjs +12 -0
  68. package/test/remote-session-cli.test.mjs +2 -1
  69. package/test/standing-grants.test.mjs +2 -2
  70. package/test/tool-audit.test.mjs +14 -0
  71. package/bin/appcontainer-stage.obj +0 -0
  72. package/bin/bootstrap-advapi32.obj +0 -0
  73. package/bin/bootstrap-exitcode.obj +0 -0
  74. package/bin/bootstrap-kernel32.obj +0 -0
  75. package/bin/child-control-probe.obj +0 -0
  76. package/bin/child-control-stage.obj +0 -0
  77. package/bin/contained-reverse-worker.obj +0 -0
  78. package/bin/contained-transform-worker.obj +0 -0
  79. package/bin/containment-probe.obj +0 -0
  80. package/bin/deadbyte-contain.obj +0 -0
  81. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  82. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  83. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  84. package/bin/deadbyte-exec.obj +0 -0
  85. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  86. package/bin/deadbyte-native-inspect-r67.obj +0 -0
  87. package/bin/deadbyte-process-host.obj +0 -0
  88. package/bin/deadbyte-tunnel-host.obj +0 -0
  89. package/conpty-input-observer.obj +0 -0
  90. package/miniconpty.obj +0 -0
  91. package/proof/CONTAINMENT-BUILD.txt +0 -163
  92. package/proof/MCP-SMOKE-STATUS.txt +0 -9
  93. package/proof/NODE-SYNTAX.txt +0 -4
  94. package/proof/PROOF.txt +0 -6
  95. package/proof/R34-TASK8-SUPERVISOR-CHECKPOINT.json +0 -29
  96. package/proof/R34-TASK9-MCP-SURFACE-CHECKPOINT.json +0 -28
  97. package/proof/SECRET-SCAN.txt +0 -4
  98. package/proof/SOURCE-GATES.txt +0 -16
  99. package/proof/STATUS.txt +0 -11
  100. package/proof/TARGET-V0313-CONTAINMENT-PASS.sha256 +0 -1
  101. package/proof/TARGET-V0313-CONTAINMENT-PASS.txt +0 -215
  102. package/proof/TARGET-V040-CONTAINED-IPC-FAIL.sha256 +0 -1
  103. package/proof/TARGET-V040-CONTAINED-IPC-FAIL.txt +0 -299
  104. package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  105. package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.txt +0 -311
  106. package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  107. package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.txt +0 -313
  108. package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  109. package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.txt +0 -308
  110. package/proof/TARGET-V044-CONTAINED-PASS.sha256 +0 -1
  111. package/proof/TARGET-V044-CONTAINED-PASS.txt +0 -432
  112. package/proof/TARGET-V045-DUAL-OPERATION-PASS.sha256 +0 -1
  113. package/proof/TARGET-V045-DUAL-OPERATION-PASS.txt +0 -481
  114. package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.sha256 +0 -1
  115. package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.txt +0 -610
  116. package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.sha256 +0 -1
  117. package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.txt +0 -642
  118. package/proof/TARGET-V050-BATCH-GATE-PASS.sha256 +0 -1
  119. package/proof/TARGET-V050-BATCH-GATE-PASS.txt +0 -713
  120. package/proof/TESTS.txt +0 -686
  121. package/proof/TRUST-EPHEMERAL.txt +0 -4
  122. package/proof/V03-FREEZE.txt +0 -27
  123. package/proof/V041-IPC-HOTFIX.txt +0 -16
  124. package/proof/V042-ENV-HOTFIX.txt +0 -15
  125. package/proof/V043-DRIVE-CWD-HOTFIX.txt +0 -14
  126. package/proof/V044-LOCALAPPDATA-HOTFIX.txt +0 -18
  127. package/proof/V045-OPERATION-REGISTRY.txt +0 -7
  128. package/proof/V046-WORKER-REGISTRY.txt +0 -12
  129. package/proof/V047-WORKER-MANIFEST.txt +0 -18
  130. package/proof/V050-BATCH-TRUST-CLOSURE.txt +0 -21
  131. package/proof/V060-ACTIVE-NETWORK-AUTHORITY.txt +0 -29
  132. package/proof/V070-NODE-SYNTAX.txt +0 -36
  133. package/proof/V070-NODE-TESTS.txt +0 -752
  134. package/proof/V070-PACK-DRY-RUN.txt +0 -126
  135. package/proof/V070-SOURCE-GATE.txt +0 -30
  136. package/proof/V070-STATIC-SCAN.txt +0 -4
  137. package/proof/V070-WINDOWS-GATE-PENDING.txt +0 -25
  138. package/proof/V071-NODE-SYNTAX.txt +0 -1
  139. package/proof/V071-NODE-TESTS.txt +0 -796
  140. package/proof/V071-PACK-DRY-RUN.json +0 -656
  141. package/proof/V071-SOURCE-GATE.txt +0 -16
  142. package/proof/V071-WINDOWS-GATE-PENDING.txt +0 -24
  143. package/proof/V073-NODE-SYNTAX.txt +0 -4
  144. package/proof/V073-SOURCE-GATE.txt +0 -21
  145. package/proof/V073-STATIC-SCAN.txt +0 -10
  146. package/proof/V073-WINDOWS-GATE-PENDING.txt +0 -5
  147. package/proof/VERIFY.txt +0 -7
  148. package/proof/WINDOWS-CONTAINED-STATUS.txt +0 -5
  149. package/proof/WINDOWS-CONTAINMENT-STATUS.txt +0 -3
  150. package/proof/commander-closure-20260927/CONTINUATION.json +0 -43
  151. package/proof/commander-closure-20260927/RESULT.json +0 -30
  152. package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +0 -30
  153. package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +0 -13
  154. package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +0 -122
  155. package/proof/r59-full-regression-current.json +0 -13
  156. package/proof/r59-full-regression-current.stderr.log +0 -0
  157. package/proof/r59-full-regression-current.stdout.log +0 -1268
@@ -1,1896 +1,2056 @@
1
- import { createHash, createPrivateKey, createPublicKey, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
2
- import { spawn, spawnSync } from 'node:child_process';
3
- import { existsSync } from 'node:fs';
4
- import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm, writeFile } from 'node:fs/promises';
5
- import os from 'node:os';
6
- import path from 'node:path';
7
- import { fileURLToPath } from 'node:url';
8
- import {
9
- atomicWriteActivePointer,
10
- materializeReleaseSlot,
11
- parseActivePointer,
12
- parseManifest,
13
- releaseId,
14
- renderStableLauncherCmd,
15
- renderStableLauncherPs1,
16
- sha256Bytes,
17
- verifyManifestTree
18
- } from './release-slot.mjs';
19
- import { createGenerationBundle, verifyGenerationBundle } from './release-generation.mjs';
20
- import { resolveStateRoot } from './state-root.mjs';
21
-
22
- const HEX64=/^[0-9a-f]{64}$/;
23
- const SEMVER=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
24
- const RECEIPT_DOMAIN=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0','utf8');
25
- const UPDATE_SCHEMA='deadbyte.r27-slot-update-receipt.v1';
26
- const HANDOFF_SCHEMA='deadbyte.r27-slot-update-handoff.v1';
27
- const FAILURE_SCHEMA='deadbyte.r27-slot-update-failure.v1';
28
- const ROLLBACK_SCHEMA='deadbyte.r31-slot-rollback-receipt.v1';
29
- const ROLLBACK_FAILURE_SCHEMA='deadbyte.r31-slot-rollback-failure.v1';
30
- const DEFAULT_STATE_ROOT=undefined;
31
- const TOP_LAUNCHER=path.join(os.homedir(),'Downloads','DEADBYTE-MCP.cmd');
32
- const RUNTIME_STABILITY_MS=12000;
33
- const RUNTIME_STABILITY_POLL_MS=500;
34
-
35
- const sha256File=async file=>sha256Bytes(await readFile(file));
36
- const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
37
- const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
38
- ?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
39
- const canonicalJson=value=>JSON.stringify(canonical(value));
40
- function normalizePersistentPublicOrigin(value){
41
- let publicOrigin;
42
- try { publicOrigin=new URL(String(value)); }
43
- catch { throw new Error('named tunnel public origin must be one persistent HTTPS origin'); }
44
- if(publicOrigin.protocol!=='https:'||publicOrigin.username||publicOrigin.password||publicOrigin.search||publicOrigin.hash||publicOrigin.pathname!=='/'||!publicOrigin.hostname) {
45
- throw new Error('named tunnel public origin must be one persistent HTTPS origin without path, query, fragment, or credentials');
46
- }
47
- if(publicOrigin.hostname.toLowerCase().endsWith('.trycloudflare.com')) throw new Error('named tunnel public origin must be persistent; trycloudflare is forbidden');
48
- return publicOrigin.origin;
49
- }
50
- export function parseUpdateArgs(argv){
51
- const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableDesktop:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,hostPolicyBootstrap:null,codingPolicyBootstrap:null,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
52
- const cloud={},bridge={},processPolicy={},hostPolicy={},codingPolicy={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
53
- const bridgeFlags=new Map([
54
- ['--bridge-root','root'],['--bridge-script-sha256','scriptSha256']
55
- ]);
56
- const processPolicyFlags=new Map([
57
- ['--process-policy','path'],['--process-policy-sha256','sha256']
58
- ]);
59
- const hostPolicyFlags=new Map([
60
- ['--host-policy','path'],['--host-policy-sha256','sha256']
61
- ]);
62
- const codingPolicyFlags=new Map([
63
- ['--coding-policy','path'],['--coding-policy-sha256','sha256']
64
- ]);
65
- const desktopPolicyFlags=new Map([
66
- ['--desktop-policy','path'],['--desktop-policy-sha256','sha256']
67
- ]);
68
- const semanticLspFlags=new Map([
69
- ['--semantic-lsp-config','path'],['--semantic-lsp-config-sha256','sha256']
70
- ]);
71
- const cloudFlags=new Map([
72
- ['--cloud-cloudflared','cloudflaredPath'],['--cloud-cloudflared-sha256','cloudflaredSha256'],
73
- ['--cloud-token-file','tokenFilePath'],['--cloud-token-sha256','tokenFileSha256'],
74
- ['--cloud-child-working-directory','childWorkingDirectory'],['--cloud-public-origin','publicOrigin']
75
- ]);
76
- const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...hostPolicyFlags.keys(),...codingPolicyFlags.keys(),...desktopPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
77
- for(let index=0;index<argv.length;index++){
78
- const arg=argv[index];
79
- if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-desktop'||arg==='--enable-autonomous-supervisor'){
80
- if(seen.has(arg)) throw new Error(`duplicate self-update argument '${arg}'`);
81
- seen.add(arg);
82
- if(arg==='--restore-standing-grants') out.restoreStandingGrants=true;
83
- else if(arg==='--recover-offline-predecessor') out.recoverOfflinePredecessor=true;
84
- else if(arg==='--enable-machine-fs') out.enableMachineFs=true;
85
- else if(arg==='--enable-observation') out.enableObservation=true;
86
- else if(arg==='--enable-desktop') out.enableDesktop=true;
87
- else out.enableAutonomousSupervisor=true;
88
- continue;
89
- }
90
- if(arg==='--host-command-pin'){
91
- const value=argv[++index];
92
- if(!value) throw new Error('--host-command-pin requires command_id=sha256');
93
- const match=/^([A-Za-z0-9._-]{1,64})=([0-9a-f]{64})$/.exec(value);
94
- if(!match) throw new Error('host command pin must be command_id=lowercase_sha256');
95
- const [,commandId,sha256]=match;
96
- if(hostPinIds.has(commandId)) throw new Error(`duplicate host command pin '${commandId}'`);
97
- hostPinIds.add(commandId); out.hostCommandPins.push({commandId,sha256}); continue;
98
- }
99
- if(!valued.has(arg)) throw new Error(`unknown self-update argument '${arg}'`);
100
- if(seen.has(arg)) throw new Error(`duplicate self-update argument '${arg}'`);
101
- seen.add(arg);
102
- const value=argv[++index];
103
- if(!value) throw new Error(`${arg} requires a value`);
104
- if(arg==='--stage') out.stage=path.resolve(value);
105
- else if(arg==='--development-root') out.developmentRoot=path.resolve(value);
106
- else if(arg==='--expected-version') out.expectedVersion=value;
107
- else if(arg==='--expected-manifest') out.expectedManifest=value.toLowerCase();
108
- else if(arg==='--source-root') out.sourceRoot=path.resolve(value);
109
- else if(bridgeFlags.has(arg)) bridge[bridgeFlags.get(arg)]=value;
110
- else if(processPolicyFlags.has(arg)) processPolicy[processPolicyFlags.get(arg)]=value;
111
- else if(hostPolicyFlags.has(arg)) hostPolicy[hostPolicyFlags.get(arg)]=value;
112
- else if(codingPolicyFlags.has(arg)) codingPolicy[codingPolicyFlags.get(arg)]=value;
113
- else if(desktopPolicyFlags.has(arg)) desktopPolicy[desktopPolicyFlags.get(arg)]=value;
114
- else if(semanticLspFlags.has(arg)) semanticLsp[semanticLspFlags.get(arg)]=value;
115
- else cloud[cloudFlags.get(arg)]=value;
116
- }
117
- if(!out.stage) throw new Error('--stage is required');
118
- if(!out.developmentRoot) throw new Error('--development-root is required');
119
- if(!SEMVER.test(String(out.expectedVersion??''))) throw new Error('--expected-version must be SemVer');
120
- if(!HEX64.test(String(out.expectedManifest??''))) throw new Error('--expected-manifest must be lowercase SHA-256 hex');
121
- const bridgeFields=[...bridgeFlags.values()],bridgePresent=bridgeFields.filter(key=>bridge[key]!==undefined);
122
- if(bridgePresent.length!==0&&bridgePresent.length!==bridgeFields.length) throw new Error('bridge bootstrap requires root plus script SHA-256 as one pair');
123
- if(bridgePresent.length===bridgeFields.length){
124
- const scriptSha256=String(bridge.scriptSha256).toLowerCase();
125
- if(!HEX64.test(scriptSha256)) throw new Error('bridge script SHA-256 must be lowercase hex');
126
- out.bridgeBootstrap={root:path.resolve(bridge.root),scriptSha256};
127
- }
128
- const processPolicyFields=[...processPolicyFlags.values()],processPolicyPresent=processPolicyFields.filter(key=>processPolicy[key]!==undefined);
129
- if(processPolicyPresent.length!==0&&processPolicyPresent.length!==processPolicyFields.length) throw new Error('process policy bootstrap requires path plus SHA-256 as one pair');
130
- if(processPolicyPresent.length===processPolicyFields.length){
131
- const sha256=String(processPolicy.sha256).toLowerCase();
132
- if(!HEX64.test(sha256)) throw new Error('process policy SHA-256 must be lowercase hex');
133
- out.processPolicyBootstrap={path:path.resolve(processPolicy.path),sha256};
134
- }
135
- const hostPolicyFields=[...hostPolicyFlags.values()],hostPolicyPresent=hostPolicyFields.filter(key=>hostPolicy[key]!==undefined);
136
- if(hostPolicyPresent.length!==0&&hostPolicyPresent.length!==hostPolicyFields.length) throw new Error('host policy bootstrap requires path plus SHA-256 as one pair');
137
- if(hostPolicyPresent.length===hostPolicyFields.length){
138
- const sha256=String(hostPolicy.sha256).toLowerCase();
139
- if(!HEX64.test(sha256)) throw new Error('host policy SHA-256 must be lowercase hex');
140
- out.hostPolicyBootstrap={path:path.resolve(hostPolicy.path),sha256};
141
- }
142
- const codingPolicyFields=[...codingPolicyFlags.values()],codingPolicyPresent=codingPolicyFields.filter(key=>codingPolicy[key]!==undefined);
143
- if(codingPolicyPresent.length!==0&&codingPolicyPresent.length!==codingPolicyFields.length) throw new Error('coding policy bootstrap requires path plus SHA-256 as one pair');
144
- if(codingPolicyPresent.length===codingPolicyFields.length){
145
- const sha256=String(codingPolicy.sha256).toLowerCase();
146
- if(!HEX64.test(sha256)) throw new Error('coding policy SHA-256 must be lowercase hex');
147
- out.codingPolicyBootstrap={path:path.resolve(codingPolicy.path),sha256};
148
- }
149
- const desktopPolicyFields=[...desktopPolicyFlags.values()],desktopPolicyPresent=desktopPolicyFields.filter(key=>desktopPolicy[key]!==undefined);
150
- if(desktopPolicyPresent.length!==0&&desktopPolicyPresent.length!==desktopPolicyFields.length) throw new Error('desktop policy bootstrap requires path plus SHA-256 as one pair');
151
- if(desktopPolicyPresent.length===desktopPolicyFields.length){
152
- const sha256=String(desktopPolicy.sha256).toLowerCase();
153
- if(!HEX64.test(sha256)) throw new Error('desktop policy SHA-256 must be lowercase hex');
154
- out.desktopPolicyBootstrap={path:path.resolve(desktopPolicy.path),sha256};
155
- }
156
- const semanticLspFields=[...semanticLspFlags.values()],semanticLspPresent=semanticLspFields.filter(key=>semanticLsp[key]!==undefined);
157
- if(semanticLspPresent.length!==0&&semanticLspPresent.length!==semanticLspFields.length) throw new Error('semantic LSP config bootstrap requires path plus SHA-256 as one pair');
158
- if(semanticLspPresent.length===semanticLspFields.length){
159
- const sha256=String(semanticLsp.sha256).toLowerCase();
160
- if(!HEX64.test(sha256)) throw new Error('semantic LSP config SHA-256 must be lowercase hex');
161
- out.semanticLspBootstrap={path:path.resolve(semanticLsp.path),sha256};
162
- }
163
- const cloudFields=[...cloudFlags.values()],present=cloudFields.filter(key=>cloud[key]!==undefined);
164
- if(present.length!==0&&present.length!==cloudFields.length) throw new Error('named tunnel bootstrap requires cloud path/hash/token/workdir plus persistent public origin');
165
- if(present.length===cloudFields.length){
166
- for(const key of ['cloudflaredSha256','tokenFileSha256']) cloud[key]=String(cloud[key]).toLowerCase();
167
- if(!HEX64.test(cloud.cloudflaredSha256)||!HEX64.test(cloud.tokenFileSha256)) throw new Error('named tunnel dependency SHA-256 must be lowercase hex');
168
- const publicOrigin=normalizePersistentPublicOrigin(cloud.publicOrigin);
169
- out.cloudTransport={
170
- cloudflared:{path:path.resolve(cloud.cloudflaredPath),sha256:cloud.cloudflaredSha256},
171
- tokenFile:{path:path.resolve(cloud.tokenFilePath),sha256:cloud.tokenFileSha256},
172
- childWorkingDirectory:path.resolve(cloud.childWorkingDirectory),
173
- publicOrigin
174
- };
175
- }
176
- return out;
177
- }
178
-
179
- export function parseRollbackArgs(argv){
180
- let activationReceipt=null;
181
- for(let index=0;index<argv.length;index++){
182
- const arg=argv[index];
183
- if(arg!=='--rollback-receipt') throw new Error(`unknown rollback argument '${arg}'`);
184
- if(activationReceipt!==null) throw new Error("duplicate rollback argument '--rollback-receipt'");
185
- const value=argv[++index];
186
- if(!value) throw new Error('--rollback-receipt requires a value');
187
- activationReceipt=path.resolve(value);
188
- }
189
- if(!activationReceipt) throw new Error('--rollback-receipt is required');
190
- return {activationReceipt};
191
- }
192
-
193
- function powershellPath(){
194
- return path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
195
- }
196
- export function windowsPowerShellEnvironment(base=process.env){
197
- const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
198
- const userProfile=base.USERPROFILE||os.homedir();
199
- const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
200
- const environment={...base};
201
- const inherited=Object.entries(base).filter(([key])=>key.toLowerCase()==='psmodulepath').flatMap(([,value])=>String(value??'').split(';')).filter(Boolean);
202
- for(const key of Object.keys(environment)) if(key.toLowerCase()==='psmodulepath') delete environment[key];
203
- const modules=[
204
- path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules'),
205
- path.join(userProfile,'Documents','WindowsPowerShell','Modules'),
206
- path.join(programFiles,'WindowsPowerShell','Modules'),
207
- ...inherited
208
- ];
209
- const seen=new Set();
210
- return {...environment,PSModulePath:modules.filter(modulePath=>{
211
- const key=modulePath.toLowerCase();
212
- if(seen.has(key)) return false;
213
- seen.add(key);return true;
214
- }).join(';')};
215
- }
216
- function commandEnvironment(exe,base=process.env){
217
- return path.resolve(exe).toLowerCase()===path.resolve(powershellPath()).toLowerCase()
218
- ?windowsPowerShellEnvironment(base):base;
219
- }
220
- function runCaptured(exe,args,{cwd,env,timeoutMs=45000}={}){
221
- const baseEnv=env??process.env;
222
- const childEnv=commandEnvironment(exe,baseEnv);
223
- const result=spawnSync(exe,args,{cwd,env:childEnv,encoding:'utf8',windowsHide:true,timeout:timeoutMs,maxBuffer:16*1024*1024});
224
- return {status:result.status,signal:result.signal,error:result.error?{code:result.error.code??null,message:result.error.message}:null,stdout:result.stdout??'',stderr:result.stderr??''};
225
- }
226
- function assertRun(result,label){
227
- if(result.error||result.status!==0) throw new Error(`${label} failed status=${result.status} error=${result.error?.message??'none'}\n${result.stderr||result.stdout}`);
228
- return result.stdout;
229
- }
230
- export async function loadPowerShellDataFile(file){
231
- const env={...process.env,DEADBYTE_R27_CONFIG_READ:path.resolve(file)};
232
- const command="$ErrorActionPreference='Stop'; $v=Import-PowerShellDataFile -LiteralPath $env:DEADBYTE_R27_CONFIG_READ; $v | ConvertTo-Json -Depth 16 -Compress";
233
- const result=runCaptured(powershellPath(),['-NoLogo','-NoProfile','-Command',command],{env,timeoutMs:15000});
234
- const text=assertRun(result,'PowerShell data-file load').trim();
235
- let value; try { value=JSON.parse(text); } catch(error){ throw new Error(`controller config JSON conversion failed: ${error.message}`); }
236
- if(!value||typeof value!=='object'||Array.isArray(value)) throw new Error('controller config must be an object');
237
- return value;
238
- }
239
- async function readJson(file){
240
- const text=(await readFile(file,'utf8')).replace(/^\uFEFF/,'');
241
- let value; try { value=JSON.parse(text); } catch(error){ throw new Error(`JSON invalid ${file}: ${error.message}`); }
242
- return value;
243
- }
244
- async function atomicBytes(file,bytes){
245
- await mkdir(path.dirname(file),{recursive:true});
246
- const temp=`${file}.tmp-${process.pid}-${randomBytes(4).toString('hex')}`;
247
- const handle=await open(temp,'wx');
248
- try { await handle.writeFile(bytes); await handle.sync(); } finally { await handle.close(); }
249
- let renamed=false;
250
- let lastRenameError=null;
251
- for(let attempt=0;attempt<20;attempt+=1){
252
- try { await rename(temp,file); renamed=true; break; }
253
- catch(error){
254
- lastRenameError=error;
255
- if(!["EPERM","EBUSY","EACCES"].includes(error?.code) || attempt===19) break;
256
- await new Promise(resolve=>setTimeout(resolve,Math.min(500,100*(attempt+1))));
257
- }
258
- }
259
- if(!renamed){ await rm(temp,{force:true}).catch(()=>{}); throw lastRenameError; }
260
- if(sha256Bytes(await readFile(file))!==sha256Bytes(bytes)) throw new Error(`atomic write readback mismatch: ${file}`);
261
- }
262
- async function normalizeComponentIdentities(config){
263
- const next=structuredClone(config);
264
- for(const name of ['Bridge','Cloud']){
265
- const spec=next[name];
266
- if(!spec||typeof spec!=='object') throw new Error(`${name} launch specification missing`);
267
- if(typeof spec.FilePath!=='string'||!path.isAbsolute(spec.FilePath)) throw new Error(`${name} FilePath must be absolute`);
268
- const exe=path.resolve(spec.FilePath);
269
- const stat=await lstat(exe);
270
- if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`${name} executable must be a regular file`);
271
- const actual=await sha256File(exe);
272
- if(spec.FileSha256!==undefined&&spec.FileSha256!==''&&spec.FileSha256!==actual) throw new Error(`${name} executable pin drift detected`);
273
- spec.FilePath=exe; spec.FileSha256=actual;
274
- const dependencies=spec.Dependencies??[];
275
- if(!Array.isArray(dependencies)||dependencies.length>16) throw new Error(`${name} Dependencies invalid`);
276
- const normalizedDependencies=[];
277
- for(const dependency of dependencies){
278
- const depName=String(dependency?.Name??'');
279
- const depPath=path.resolve(String(dependency?.Path??''));
280
- const depHash=String(dependency?.Sha256??'').toLowerCase();
281
- if(!/^[A-Za-z0-9._-]{1,32}$/.test(depName)||!path.isAbsolute(String(dependency?.Path??''))||!HEX64.test(depHash)) throw new Error(`${name} dependency contract invalid`);
282
- const depStat=await lstat(depPath); if(!depStat.isFile()||depStat.isSymbolicLink()) throw new Error(`${name} dependency must be a regular file: ${depName}`);
283
- const depActual=await sha256File(depPath); if(depActual!==depHash) throw new Error(`${name} dependency pin drift detected: ${depName}`);
284
- normalizedDependencies.push({Name:depName,Path:depPath,Sha256:depHash});
285
- }
286
- spec.Dependencies=normalizedDependencies;
287
- if(name==='Cloud'&&spec.Transport!==undefined){
288
- const transport=String(spec.Transport);
289
- const valid=(transport==='deadbyte.named-tunnel.v1'&&normalizedDependencies.length===3)||
290
- (transport==='deadbyte.named-tunnel.v2'&&normalizedDependencies.length===2);
291
- if(!valid) throw new Error('Cloud named tunnel transport contract invalid');
292
- }
293
- }
294
- const bridge=next.Bridge;
295
- const raw=String(bridge.Arguments??'');
296
- const normalized=raw.replaceAll('\\','/');
297
- if(!/^[A-Za-z0-9._/-]+\.(?:mjs|js)$/.test(normalized)||path.posix.isAbsolute(normalized)||normalized.split('/').includes('..')) {
298
- throw new Error('R27 Bridge Arguments must be one relative .mjs/.js script path');
299
- }
300
- if(typeof bridge.WorkingDirectory!=='string'||!path.isAbsolute(bridge.WorkingDirectory)) throw new Error('Bridge WorkingDirectory must be absolute');
301
- const script=path.resolve(bridge.WorkingDirectory,...normalized.split('/'));
302
- const scriptStat=await lstat(script);
303
- if(!scriptStat.isFile()||scriptStat.isSymbolicLink()) throw new Error('Bridge script must be a regular file');
304
- const scriptHash=await sha256File(script);
305
- if(bridge.ScriptSha256!==undefined&&bridge.ScriptSha256!==''&&bridge.ScriptSha256!==scriptHash) throw new Error('Bridge script pin drift detected');
306
- bridge.ScriptPath=script; bridge.ScriptSha256=scriptHash;
307
- return next;
308
- }
309
-
310
- function bridgeDependencyName(relativePath,usedNames){
311
- const normalized=String(relativePath).replaceAll('\\','/').replaceAll('/','_');
312
- let name=normalized;
313
- if(!/^[A-Za-z0-9._-]{1,32}$/.test(name)||usedNames.has(name)){
314
- name=`dep-${sha256Bytes(Buffer.from(String(relativePath),'utf8')).slice(0,20)}`;
315
- }
316
- if(usedNames.has(name)) throw new Error(`bridge dependency name collision: ${relativePath}`);
317
- usedNames.add(name);
318
- return name;
319
- }
320
- function bridgeStaticRelativeImports(source,file){
321
- const found=[];
322
- for(const [index,line] of String(source).split(/\r?\n/).entries()){
323
- if(/\bimport\s*\(\s*['"]\.\.?\//.test(line)) throw new Error(`bridge dynamic relative import is forbidden: ${file}:${index+1}`);
324
- const importStart=/^\s*import\b/.test(line);
325
- const exportStart=/^\s*export\b/.test(line)&&/\bfrom\b/.test(line);
326
- if(!importStart&&!exportStart) continue;
327
- const match=importStart
328
- ? /^\s*import\s+(?:(?:[^'"]+?)\s+from\s*)?['"]([^'"]+)['"]\s*;?\s*$/.exec(line)
329
- : /^\s*export\s+[^'"]*?\s+from\s*['"]([^'"]+)['"]\s*;?\s*$/.exec(line);
330
- if(!match) throw new Error(`bridge import syntax unsupported for dependency binding: ${file}:${index+1}`);
331
- if(match[1].startsWith('.')) found.push(match[1]);
332
- }
333
- return found;
334
- }
335
- async function discoverBridgeLocalDependencies(root,entryScript){
336
- const absoluteRoot=path.resolve(root),realRoot=await realpath(absoluteRoot);
337
- const srcRoot=path.join(absoluteRoot,'src');
338
- const queue=[path.resolve(entryScript)],visited=new Set(),files=[];
339
- while(queue.length){
340
- const current=queue.shift();
341
- const currentReal=await realpath(current);
342
- const relativeReal=path.relative(realRoot,currentReal);
343
- if(relativeReal.startsWith('..')||path.isAbsolute(relativeReal)) throw new Error(`bridge dependency escapes bridge root: ${current}`);
344
- if(visited.has(currentReal)) continue;
345
- visited.add(currentReal);
346
- if(visited.size>16) throw new Error('bridge local dependency closure exceeds 16 files');
347
- const stat=await lstat(current);
348
- if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`bridge dependency must be a regular file: ${current}`);
349
- const source=await readFile(current,'utf8');
350
- const specifiers=bridgeStaticRelativeImports(source,current);
351
- if(currentReal!==await realpath(entryScript)) files.push(currentReal);
352
- for(const specifier of specifiers){
353
- if(!specifier.startsWith('./')) throw new Error(`bridge relative import escapes bridge root: ${specifier}`);
354
- if(specifier.includes('?')||specifier.includes('#')) throw new Error(`bridge relative import query/fragment unsupported: ${specifier}`);
355
- const resolved=path.resolve(path.dirname(current),specifier);
356
- const relative=path.relative(absoluteRoot,resolved);
357
- if(relative.startsWith('..')||path.isAbsolute(relative)) throw new Error(`bridge dependency escapes bridge root: ${specifier}`);
358
- if(!/\.(?:mjs|js)$/i.test(resolved)) throw new Error(`bridge local dependency must be .mjs/.js: ${specifier}`);
359
- queue.push(resolved);
360
- }
361
- }
362
- const unique=[...new Set(files)].sort((a,b)=>a.localeCompare(b));
363
- const usedNames=new Set();
364
- const dependencies=[];
365
- for(const file of unique){
366
- const relative=path.relative(srcRoot,file).replaceAll('\\','/');
367
- if(relative.startsWith('..')||path.isAbsolute(relative)) throw new Error(`bridge dependency must remain under src: ${file}`);
368
- dependencies.push({Name:bridgeDependencyName(relative,usedNames),Path:file,Sha256:await sha256File(file)});
369
- }
370
- return dependencies;
371
- }
372
-
373
- export async function prepareBridgeSpec(config,bootstrap){
374
- if(!bootstrap) return structuredClone(config);
375
- if(typeof bootstrap.root!=='string'||!path.isAbsolute(bootstrap.root)||!HEX64.test(String(bootstrap.scriptSha256??''))) {
376
- throw new Error('bridge bootstrap requires absolute root plus lowercase script SHA-256');
377
- }
378
- const root=path.resolve(bootstrap.root),rootStat=await lstat(root);
379
- if(!rootStat.isDirectory()||rootStat.isSymbolicLink()) throw new Error('bridge root must be a real directory');
380
- const packageFile=path.join(root,'package.json'),script=path.join(root,'src','bridge.mjs');
381
- const packageStat=await lstat(packageFile),scriptStat=await lstat(script);
382
- if(!packageStat.isFile()||packageStat.isSymbolicLink()) throw new Error('bridge package.json must be a regular file');
383
- if(!scriptStat.isFile()||scriptStat.isSymbolicLink()) throw new Error('bridge script must be a regular file');
384
- const pkg=JSON.parse(await readFile(packageFile,'utf8'));
385
- if(pkg?.name!=='deadbyte-chatgpt-bridge') throw new Error('bridge package identity mismatch');
386
- const actual=await sha256File(script),expected=String(bootstrap.scriptSha256).toLowerCase();
387
- if(actual!==expected) throw new Error(`bridge script SHA-256 mismatch: expected ${expected}, found ${actual}`);
388
- const next=structuredClone(config);
389
- if(!next.Bridge||typeof next.Bridge!=='object') throw new Error('Bridge launch specification missing');
390
- next.Bridge.WorkingDirectory=root;
391
- next.Bridge.Arguments='src\\bridge.mjs';
392
- next.Bridge.ScriptPath=script;
393
- next.Bridge.ScriptSha256=actual;
394
- next.Bridge.Dependencies=await discoverBridgeLocalDependencies(root,script);
395
- return next;
396
- }
397
-
398
- export async function prepareReleaseBridgeSpec(config,bootstrap,releaseRoot){
399
- if(!bootstrap) return structuredClone(config);
400
- if(typeof releaseRoot!=='string'||!path.isAbsolute(releaseRoot))throw new Error('immutable release root required for Bridge binding');
401
- // Verify the requested development input, then bind runtime paths only to
402
- // the manifest-verified immutable slot produced from that same candidate.
403
- const verifiedSource=await prepareBridgeSpec(config,bootstrap);
404
- return prepareBridgeSpec(verifiedSource,{
405
- root:path.join(path.resolve(releaseRoot),'bridge'),
406
- scriptSha256:verifiedSource.Bridge.ScriptSha256
407
- });
408
- }
409
-
410
- export async function prepareAutonomousSupervisorSpec(config,{releaseRoot,enable=false}={}){
411
- const next=structuredClone(config);
412
- if(!enable) return next;
413
- if(next.AutonomousRuntimeEnabled!==true) throw new Error('autonomous supervisor bootstrap requires AutonomousRuntimeEnabled');
414
- if(typeof releaseRoot!=='string'||!path.isAbsolute(releaseRoot)) throw new Error('autonomous supervisor release root must be absolute');
415
- const release=path.resolve(releaseRoot),script=path.join(release,'src','autonomous-supervisor-host.mjs');
416
- const scriptStat=await lstat(script);
417
- if(!scriptStat.isFile()||scriptStat.isSymbolicLink()) throw new Error('autonomous supervisor script must be a regular release file');
418
- const bridge=next.Bridge;
419
- if(!bridge||typeof bridge.FilePath!=='string'||!path.isAbsolute(bridge.FilePath)||!HEX64.test(String(bridge.FileSha256??''))) {
420
- throw new Error('autonomous supervisor bootstrap requires pinned Bridge executable identity');
421
- }
422
- const executable=path.resolve(bridge.FilePath),exeStat=await lstat(executable);
423
- if(!exeStat.isFile()||exeStat.isSymbolicLink()) throw new Error('autonomous supervisor executable must be a regular file');
424
- const executableSha256=await sha256File(executable);
425
- if(executableSha256!==String(bridge.FileSha256)) throw new Error('autonomous supervisor executable pin drift detected');
426
- next.AutonomousSupervisorEnabled=true;
427
- next.Supervisor={
428
- FilePath:executable,FileSha256:executableSha256,Arguments:'src\\autonomous-supervisor-host.mjs',WorkingDirectory:release,
429
- ScriptPath:script,ScriptSha256:await sha256File(script)
430
- };
431
- return next;
432
- }
433
-
434
- function quoteWindowsArgument(value){
435
- const text=String(value??'');
436
- if(text.length<1||/["\r\n\0]/.test(text)) throw new Error('named tunnel argument contains unsafe characters');
437
- const trailing=(/\\+$/.exec(text)?.[0].length??0);
438
- return `"${text}${'\\'.repeat(trailing)}"`;
439
- }
440
- async function verifyPinnedRegularFile(file,expected,label){
441
- if(typeof file!=='string'||!path.isAbsolute(file)||!HEX64.test(String(expected??''))) throw new Error(`${label} pin contract invalid`);
442
- const absolute=path.resolve(file),stat=await lstat(absolute);
443
- if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`${label} must be a regular file`);
444
- const actual=await sha256File(absolute);
445
- if(actual!==expected) throw new Error(`${label} SHA-256 mismatch: expected ${expected}, found ${actual}`);
446
- return {path:absolute,sha256:actual};
447
- }
448
-
449
- export async function prepareProcessPolicyBootstrap(bootstrap){
450
- if(!bootstrap) return null;
451
- const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'process policy');
452
- const policy=await readJson(pinned.path);
453
- if(policy?.schema!=='deadbyte.process-policy.v1'||policy?.enabled!==true) throw new Error('process policy schema/enabled mismatch');
454
- return {...pinned,policy};
455
- }
456
-
457
- export async function prepareHostPolicyBootstrap(bootstrap){
458
- if(!bootstrap) return null;
459
- const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'host policy');
460
- const policy=await readJson(pinned.path);
461
- if(!['deadbyte.host-policy.v1','deadbyte.host-policy.v2'].includes(policy?.schema)||policy?.enabled!==true||
462
- !policy?.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
463
- throw new Error('host policy schema/enabled/commands mismatch');
464
- }
465
- if(policy.schema==='deadbyte.host-policy.v2'&&(policy.native_exec?.enabled!==true||!Array.isArray(policy.native_exec?.root_ids))) {
466
- throw new Error('host policy v2 native_exec contract invalid');
467
- }
468
- return {...pinned,policy};
469
- }
470
-
471
- export async function prepareCodingPolicyBootstrap(bootstrap){
472
- if(!bootstrap) return null;
473
- const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'coding policy');
474
- const policy=await readJson(pinned.path);
475
- if(policy?.schema!=='deadbyte.coding-policy.v1'||policy?.enabled!==true||
476
- !policy?.roots||typeof policy.roots!=='object'||Array.isArray(policy.roots)||
477
- !policy?.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
478
- throw new Error('coding policy schema/enabled/roots/commands mismatch');
479
- }
480
- return {...pinned,policy};
481
- }
482
-
483
- export async function prepareDesktopPolicyBootstrap(bootstrap){
484
- if(!bootstrap) return null;
485
- const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'desktop policy');
486
- const policy=await readJson(pinned.path);
487
- if(policy?.schema!=='deadbyte.desktop-policy.v1'||policy?.enabled!==true) throw new Error('desktop policy schema/enabled mismatch');
488
- for(const name of ['observer','input','grounder']){
489
- const provider=policy[name];
490
- if(!provider||typeof provider.executable!=='string'||!path.isAbsolute(provider.executable)||!HEX64.test(String(provider.sha256??''))) {
491
- throw new Error(`desktop policy ${name} pin invalid`);
492
- }
493
- }
494
- if(!Array.isArray(policy.allowed_actions)||policy.allowed_actions.length<1) throw new Error('desktop policy allowed_actions invalid');
495
- return {...pinned,policy};
496
- }
497
-
498
- export async function prepareSemanticLspConfigBootstrap(bootstrap){
499
- if(!bootstrap) return null;
500
- const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'semantic LSP config');
501
- const config=await readJson(pinned.path);
502
- if(config?.schema!=='deadbyte.semantic-lsp-config.v1'||!Array.isArray(config.servers)) throw new Error('semantic LSP config schema invalid');
503
- return {...pinned,config};
504
- }
505
-
506
- async function preparePinnedCommandPolicy(policy,{commandPins=[],schema,label}={}){
507
- if(!policy) return {policy:null,repinned:[],matchedCommandIds:[]};
508
- const acceptedSchemas=Array.isArray(schema)?schema:[schema];
509
- if(!acceptedSchemas.includes(policy.schema)||policy.enabled!==true||!policy.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
510
- throw new Error(`${label} policy schema/enabled/commands mismatch`);
511
- }
512
- const requested=new Map();
513
- for(const pin of commandPins){
514
- const commandId=String(pin?.commandId??''),sha256=String(pin?.sha256??'').toLowerCase();
515
- if(!/^[A-Za-z0-9._-]{1,64}$/.test(commandId)||!HEX64.test(sha256)) throw new Error(`${label} command pin contract invalid`);
516
- if(requested.has(commandId)) throw new Error(`duplicate ${label} command pin '${commandId}'`);
517
- requested.set(commandId,sha256);
518
- }
519
- const next=structuredClone(policy),repinned=[],matchedCommandIds=[];
520
- for(const [commandId,command] of Object.entries(next.commands)){
521
- const executable=String(command?.executable??''),expected=String(command?.executable_sha256??'').toLowerCase();
522
- if(!path.isAbsolute(executable)||!HEX64.test(expected)) throw new Error(`${label} command '${commandId}' executable pin contract invalid`);
523
- const absolute=path.resolve(executable),stat=await lstat(absolute);
524
- if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`${label} command '${commandId}' executable must be a regular file`);
525
- const actual=await sha256File(absolute),explicit=requested.get(commandId);
526
- if(explicit) matchedCommandIds.push(commandId);
527
- if(actual!==expected){
528
- if(!explicit) throw new Error(`${label} command '${commandId}' executable pin drift detected; explicit --host-command-pin ${commandId}=${actual} required`);
529
- if(explicit!==actual) throw new Error(`${label} command '${commandId}' repin mismatch: supplied ${explicit}, observed ${actual}`);
530
- command.executable_sha256=actual;
531
- repinned.push({command_id:commandId,executable:absolute,prior_sha256:expected,new_sha256:actual});
532
- } else if(explicit&&explicit!==actual){
533
- throw new Error(`${label} command '${commandId}' repin mismatch: supplied ${explicit}, observed ${actual}`);
534
- }
535
- }
536
- return {policy:next,repinned,matchedCommandIds};
537
- }
538
-
539
- export async function prepareHostPolicy(policy,{commandPins=[]}={}){
540
- if(!policy&&commandPins.length) throw new Error('host command pins supplied but host policy is not configured');
541
- const result=await preparePinnedCommandPolicy(policy,{commandPins,schema:['deadbyte.host-policy.v1','deadbyte.host-policy.v2'],label:'host'});
542
- const matched=new Set(result.matchedCommandIds);
543
- const unknown=commandPins.find(pin=>!matched.has(String(pin?.commandId??'')));
544
- if(unknown) throw new Error(`unknown host command pin '${unknown.commandId}'`);
545
- return {policy:result.policy,repinned:result.repinned};
546
- }
547
-
548
- export async function prepareAuthorityPolicies({hostPolicy,codingPolicy},{commandPins=[]}={}){
549
- const host=await preparePinnedCommandPolicy(hostPolicy,{commandPins,schema:['deadbyte.host-policy.v1','deadbyte.host-policy.v2'],label:'host'});
550
- const coding=await preparePinnedCommandPolicy(codingPolicy,{commandPins,schema:'deadbyte.coding-policy.v1',label:'coding'});
551
- const matched=new Set([...host.matchedCommandIds,...coding.matchedCommandIds]);
552
- const unknown=commandPins.find(pin=>!matched.has(String(pin?.commandId??'')));
553
- if(unknown) throw new Error(`unknown authority command pin '${unknown.commandId}'`);
554
- return {hostPolicy:host.policy,codingPolicy:coding.policy,hostRepinned:host.repinned,codingRepinned:coding.repinned};
555
- }
556
-
557
- export async function prepareNamedTunnelCloudSpec(config,{releaseRoot,bootstrap=null}={}){
558
- const next=structuredClone(config),cloud=next.Cloud??{};
559
- const useNamed=bootstrap!==null||cloud.Transport==='deadbyte.named-tunnel.v1'||cloud.Transport==='deadbyte.named-tunnel.v2';
560
- if(!useNamed) return next;
561
- const release=path.resolve(releaseRoot),hostPath=path.join(release,'bin','deadbyte-tunnel-host.exe');
562
- const hostStat=await lstat(hostPath); if(!hostStat.isFile()||hostStat.isSymbolicLink()) throw new Error('named tunnel host must be a regular release file');
563
- const hostSha256=await sha256File(hostPath);
564
- if(bootstrap!==null){
565
- const cloudflared=await verifyPinnedRegularFile(bootstrap.cloudflared.path,bootstrap.cloudflared.sha256,'named tunnel cloudflared');
566
- const tokenFile=await verifyPinnedRegularFile(bootstrap.tokenFile.path,bootstrap.tokenFile.sha256,'named tunnel token file');
567
- const childWorkingDirectory=path.resolve(bootstrap.childWorkingDirectory),workStat=await lstat(childWorkingDirectory);
568
- if(!workStat.isDirectory()||workStat.isSymbolicLink()) throw new Error('named tunnel child working directory must be a real directory');
569
- next.Cloud={...cloud,FilePath:hostPath,FileSha256:hostSha256,
570
- Arguments:[cloudflared.path,tokenFile.path,childWorkingDirectory].map(quoteWindowsArgument).join(' '),
571
- WorkingDirectory:release,Transport:'deadbyte.named-tunnel.v2',Dependencies:[
572
- {Name:'cloudflared',Path:cloudflared.path,Sha256:cloudflared.sha256},
573
- {Name:'tunnel-token',Path:tokenFile.path,Sha256:tokenFile.sha256}
574
- ]};
575
- } else {
576
- if(cloud.Transport!=='deadbyte.named-tunnel.v2') throw new Error('legacy named tunnel transport requires explicit v2 bootstrap');
577
- if(!Array.isArray(cloud.Dependencies)||cloud.Dependencies.length!==2) throw new Error('existing named tunnel dependency contract missing');
578
- next.Cloud={...cloud,FilePath:hostPath,FileSha256:hostSha256,WorkingDirectory:release};
579
- }
580
- return next;
581
- }
582
- async function ensureStableMcpPath({stateRoot,sourceRoot}){
583
- const bridgeDir=path.join(stateRoot,'bridge');
584
- const tokenFile=path.join(bridgeDir,'path-token.txt');
585
- let token='';
586
- try { token=(await readFile(tokenFile,'utf8')).trim(); } catch(error){
587
- if(error?.code!=='ENOENT') throw error;
588
- if(!sourceRoot) throw new Error('bridge path token missing and no bootstrap source root supplied');
589
- const legacy=(await readFile(path.join(sourceRoot,'controller','runtime','mcp-path.txt'),'utf8')).trim();
590
- const match=/^\/mcp\/([0-9a-f]{64})$/.exec(legacy);
591
- if(!match) throw new Error('legacy MCP path cannot seed stable token');
592
- token=match[1];
593
- await mkdir(bridgeDir,{recursive:true});
594
- await atomicBytes(tokenFile,Buffer.from(`${token}\n`,'ascii'));
595
- }
596
- if(!HEX64.test(token)) throw new Error('bridge path token invalid');
597
- const stableFile=path.join(bridgeDir,'mcp-path.txt');
598
- const desired=Buffer.from(`/mcp/${token}\n`,'ascii');
599
- if(existsSync(stableFile)){
600
- const found=await readFile(stableFile);
601
- if(!found.equals(desired)) throw new Error('stable MCP path file conflicts with token');
602
- } else await atomicBytes(stableFile,desired);
603
- return {path:stableFile,sha256:sha256Bytes(desired)};
604
- }
605
- export async function ensureStableMcpUrl({stateRoot,publicOrigin,mcpPathFile}){
606
- const origin=normalizePersistentPublicOrigin(publicOrigin);
607
- if(typeof mcpPathFile!=='string'||!path.isAbsolute(mcpPathFile)) throw new Error('stable MCP path file must be absolute');
608
- const pathStat=await lstat(mcpPathFile);
609
- if(!pathStat.isFile()||pathStat.isSymbolicLink()) throw new Error('stable MCP path file must be a regular file');
610
- const mcpPath=(await readFile(mcpPathFile,'ascii')).trim();
611
- if(!/^\/mcp\/[0-9a-f]{64}$/.test(mcpPath)) throw new Error('stable MCP path file is invalid');
612
- const bridgeDir=path.join(path.resolve(stateRoot),'bridge'),stableFile=path.join(bridgeDir,'mcp-url.txt');
613
- const desired=Buffer.from(`${origin}${mcpPath}\n`,'ascii');
614
- const before=existsSync(stableFile)?await readFile(stableFile):null;
615
- if(!before||!before.equals(desired)) await atomicBytes(stableFile,desired);
616
- return {path:stableFile,sha256:sha256Bytes(desired),origin,changed:!before||!before.equals(desired)};
617
- }
618
-
619
- async function snapshotFile(file){
620
- try { const bytes=await readFile(file); return {exists:true,bytes,sha256:sha256Bytes(bytes)}; }
621
- catch(error){ if(error?.code==='ENOENT') return {exists:false,bytes:null,sha256:null}; throw error; }
622
- }
623
- async function restoreFile(file,snapshot){
624
- if(snapshot.exists) await atomicBytes(file,snapshot.bytes);
625
- else await rm(file,{force:true});
626
- }
627
-
628
- export function parseControllerStatus(text){
629
- const number=re=>{const match=re.exec(text);return match?Number(match[1]):null;};
630
- return {
631
- online:/DEADBYTE MCP = ONLINE/.test(text),
632
- offline:/DEADBYTE MCP = OFFLINE/.test(text),
633
- bridge_pid:number(/Bridge PID\s*:\s*(\d+)\s*\[verified\]/),
634
- cloud_pid:number(/Cloud PID\s*:\s*(\d+)\s*\[verified\]/),
635
- local_ready:/Local ready\s*:\s*True/.test(text),
636
- process_armed:/Process plane\s*:\s*ARMED \[standing-grant\]/.test(text),
637
- coding_armed:/Coding plane\s*:\s*ARMED \[standing-grant\]/.test(text),
638
- autonomous_armed:/Autonomous\s*:\s*ARMED \[standing-grant\]/.test(text),
639
- desktop_armed:/Desktop plane\s*:\s*ARMED \[standing-grant\]/.test(text),
640
- host_armed:/Host plane\s*:\s*ARMED \[standing-grant\]/.test(text)
641
- };
642
- }
643
- function controlResult(exe,args,{cwd,env,timeoutMs=45000,stdio='pipe'}={}){
644
- if(stdio==='ignore'){
645
- const result=spawnSync(exe,args,{cwd,env:commandEnvironment(exe,env??process.env),windowsHide:true,timeout:timeoutMs,stdio:'ignore'});
646
- return {status:result.status,signal:result.signal,error:result.error?{code:result.error.code??null,message:result.error.message}:null,stdout:'',stderr:''};
647
- }
648
- return runCaptured(exe,args,{cwd,env,timeoutMs});
649
- }
650
- export function legacyContext(sourceRoot){
651
- const root=path.resolve(sourceRoot);
652
- const candidates=['deadbyte-controller-r26.ps1','deadbyte-controller.ps1'];
653
- const controller=candidates.map(name=>path.join(root,'controller',name)).find(existsSync);
654
- if(!controller) throw new Error('legacy controller entry missing');
655
- return {kind:'legacy',source_root:root,core_root:root,controller_path:controller,
656
- cwd:root,runtime_root:path.join(root,'controller','runtime'),state_root:null};
657
- }
658
- function slotContext(stateRoot,pointer){
659
- return {kind:'slot',source_root:null,core_root:path.resolve(pointer.release_root),
660
- controller_path:path.join(pointer.release_root,'controller','deadbyte-controller.ps1'),
661
- cwd:path.resolve(pointer.release_root),runtime_root:path.join(path.resolve(stateRoot),'runtime'),
662
- state_root:path.resolve(stateRoot),pointer};
663
- }
664
- function launcherPath(stateRoot){ return path.join(path.resolve(stateRoot),'bin','DEADBYTE-MCP.ps1'); }
665
- function controllerInvocation(ctx,command){
666
- // Internal control-plane calls use the immutable slot controller directly. The stable launcher is
667
- // user-facing and may lag during a slot switch; coupling supervisor state transitions to it creates
668
- // a bootstrap dependency and can misreport a healthy predecessor as offline.
669
- if(ctx.kind==='slot') return {
670
- exe:powershellPath(),
671
- args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],
672
- cwd:ctx.cwd,
673
- env:{...process.env,DEADBYTE_STATE_ROOT:resolveStateRoot(ctx.state_root),DEADBYTE_CONTROLLER_CONFIG:ctx.pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:ctx.runtime_root}
674
- };
675
- return {exe:powershellPath(),args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],cwd:ctx.cwd};
676
- }
677
- function controllerCaptured(ctx,command,timeoutMs=45000){
678
- const call=controllerInvocation(ctx,command); const result=controlResult(call.exe,call.args,{cwd:call.cwd,env:call.env??process.env,timeoutMs});
679
- return assertRun(result,`controller ${command}`);
680
- }
681
- export function runControllerStartNoCapture(ctx,timeoutMs=45000){
682
- const call=controllerInvocation(ctx,'start');
683
- return controlResult(call.exe,call.args,{cwd:call.cwd,env:call.env??process.env,timeoutMs,stdio:'ignore'});
684
- }
685
- export async function readControllerStartFailure(ctx){
686
- const file=path.join(ctx.runtime_root,'controller-start-failure.json');
687
- let bytes;
688
- try { bytes=await readFile(file); }
689
- catch(error){ if(error?.code==='ENOENT') return null; throw error; }
690
- const text=bytes.toString('utf8').replace(/^\uFEFF/,'');
691
- let value;
692
- try { value=JSON.parse(text); } catch(error){ throw new Error(`controller start failure evidence JSON invalid: ${error.message}`); }
693
- if(value?.schema!=='deadbyte.controller-start-failure.v1'||!['start','resume','restart'].includes(String(value?.command??''))||
694
- typeof value?.message!=='string'||value.message.length<1||typeof value?.failed_at_utc!=='string') {
695
- throw new Error('controller start failure evidence schema invalid');
696
- }
697
- return {path:file,sha256:sha256Bytes(bytes),...value};
698
- }
699
- export async function preserveControllerStartFailure(ctx,evidenceDir){
700
- const evidence=await readControllerStartFailure(ctx);
701
- if(!evidence) return null;
702
- const source=path.join(ctx.runtime_root,'controller-start-failure.json');
703
- const bytes=await readFile(source);
704
- const sourceSha256=sha256Bytes(bytes);
705
- if(sourceSha256!==evidence.sha256) throw new Error('controller start failure evidence changed before preservation');
706
- const destination=path.join(path.resolve(evidenceDir),'controller-start-failure.json');
707
- await atomicBytes(destination,bytes);
708
- const persistedSha256=sha256Bytes(await readFile(destination));
709
- if(persistedSha256!==sourceSha256) throw new Error('preserved controller start failure evidence SHA-256 mismatch');
710
- return {path:destination,sha256:persistedSha256,schema:evidence.schema,command:evidence.command,
711
- failed_at_utc:evidence.failed_at_utc,message:evidence.message};
712
- }
713
- async function readLedger(ctx,name){
714
- const file=path.join(ctx.runtime_root,`${name}.instance.json`);
715
- const value=JSON.parse((await readFile(file,'utf8')).replace(/^\uFEFF/,''));
716
- if(value.schema!=='deadbyte.controller.v1'||value.component!==name||!Number.isInteger(Number(value.pid))||
717
- !HEX64.test(String(value.instance_nonce))||!HEX64.test(String(value.image_sha256))) {
718
- throw new Error(`${name} ledger identity invalid`);
719
- }
720
- return {schema:value.schema,component:value.component,instance_nonce:String(value.instance_nonce),
721
- pid:Number(value.pid),creation_time_utc:String(value.creation_time_utc??''),image_path:String(value.image_path??''),
722
- image_sha256:String(value.image_sha256),script_path:String(value.script_path??''),script_sha256:String(value.script_sha256??'')};
723
- }
724
- function sameGeneration(a,b){
725
- return a.pid===b.pid&&a.instance_nonce===b.instance_nonce&&a.creation_time_utc===b.creation_time_utc&&a.image_sha256===b.image_sha256;
726
- }
727
- function activationGuardianOwnedByLineage(item,selfLineage){
728
- const name=String(item?.Name??'').toLowerCase();
729
- if(name!=='node.exe') return false;
730
- const commandLine=String(item?.CommandLine??'');
731
- if(!commandLine.toLowerCase().includes('remote-session-guardian.mjs')) return false;
732
- const ownerMatch=/(?:^|\s)--owner-pid(?:=|\s+)(\d+)(?=\s|$)/i.exec(commandLine);
733
- const nonceMatch=/(?:^|\s)--session-nonce(?:=|\s+)([0-9a-f]{64})(?=\s|$)/i.exec(commandLine);
734
- if(!ownerMatch||!nonceMatch) return false;
735
- const ownerPid=Number(ownerMatch[1]);
736
- const parentPid=Number(item?.ParentProcessId);
737
- return Number.isInteger(ownerPid)&&ownerPid>0&&parentPid===ownerPid&&selfLineage.has(ownerPid);
738
- }
739
- export function selectOfflineRecoveryBlockers(processes,currentPid=process.pid){
740
- const list=(Array.isArray(processes)?processes:[processes]).filter(Boolean);
741
- const byPid=new Map();
742
- for(const item of list){
743
- const pid=Number(item?.ProcessId);
744
- if(Number.isInteger(pid)&&pid>0) byPid.set(pid,item);
745
- }
746
- const selfLineage=new Set();
747
- let cursor=Number(currentPid);
748
- while(Number.isInteger(cursor)&&cursor>0&&!selfLineage.has(cursor)){
749
- selfLineage.add(cursor);
750
- const item=byPid.get(cursor);
751
- if(!item) break;
752
- const parent=Number(item?.ParentProcessId);
753
- if(!Number.isInteger(parent)||parent<1||parent===cursor) break;
754
- cursor=parent;
755
- }
756
- const runtimeNames=new Set(['node.exe','cloudflared.exe','deadbyte-tunnel-host.exe']);
757
- return list.filter(item=>{
758
- const pid=Number(item?.ProcessId);
759
- if(selfLineage.has(pid)) return false;
760
- if(activationGuardianOwnedByLineage(item,selfLineage)) return false;
761
- const name=String(item?.Name??'').toLowerCase();
762
- if(!runtimeNames.has(name)) return false;
763
- const commandLine=String(item?.CommandLine??'').toLowerCase();
764
- return commandLine.includes('deadbyte-mcp')||commandLine.includes('mcp-server.mjs')||
765
- commandLine.includes('runtime-self-update.mjs')||commandLine.includes('remote-session-guardian.mjs')||
766
- commandLine.includes('deadbyte-tunnel-host.exe');
767
- });
768
- }
769
- function cleanOfflinePredecessor(ctx){
770
- const runtimeRoot=path.resolve(ctx.runtime_root);
771
- const blockers=[
772
- 'bridge.instance.json','cloud.instance.json',
773
- 'process-grant.json','coding-grant.json','autonomous-grant.json',
774
- 'desktop-grant.json','host-grant.json'
775
- ].filter(name=>existsSync(path.join(runtimeRoot,name)));
776
- if(blockers.length) throw new Error('offline predecessor recovery blocked by runtime state: '+blockers.join(','));
777
-
778
- const command="Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,ExecutablePath,CommandLine | ConvertTo-Json -Compress";
779
- const result=runCaptured(powershellPath(),['-NoLogo','-NoProfile','-Command',command],{timeoutMs:15000});
780
- const text=assertRun(result,'runtime process query').trim();
781
- if(text){
782
- let parsed;
783
- try { parsed=JSON.parse(text); } catch(error){ throw new Error('runtime process query JSON invalid: '+error.message); }
784
- const relevant=selectOfflineRecoveryBlockers(Array.isArray(parsed)?parsed:[parsed],process.pid);
785
- if(relevant.length) throw new Error('offline predecessor recovery blocked by DEADBYTE runtime processes: '+relevant.map(item=>String(item?.Name??'unknown')+'#'+String(item?.ProcessId??'?')).join(','));
786
- }
787
- return {
788
- online:false,offline:true,bridge_pid:null,cloud_pid:null,local_ready:false,
789
- process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false
790
- };
791
- }
792
- function nodeProcesses(){
793
- const command=`Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" | Select-Object ProcessId,ParentProcessId,CreationDate,ExecutablePath,CommandLine | ConvertTo-Json -Compress`;
794
- const result=runCaptured(powershellPath(),['-NoLogo','-NoProfile','-Command',command],{timeoutMs:15000});
795
- const text=assertRun(result,'node process query').trim(); if(!text) return [];
796
- const parsed=JSON.parse(text); return Array.isArray(parsed)?parsed:[parsed];
797
- }
798
- export function selectCoreProcess(processes,coreRoot,bridgePid,verifiedBridge=null){
799
- const needle=path.join(coreRoot,'src','mcp-server.mjs').replaceAll('/','\\').toLowerCase();
800
- const children=processes.filter(item=>Number(item.ParentProcessId)===bridgePid);
801
- const visible=children.filter(item=>String(item.CommandLine??'').length>0);
802
- const matches=visible.filter(item=>String(item.CommandLine).replaceAll('/','\\').toLowerCase().includes(needle));
803
- let item=null,identityBasis=null;
804
- if(children.length===1&&visible.length===1&&matches.length===1){
805
- item=matches[0]; identityBasis='command_line';
806
- } else if(children.length===1&&visible.length===0){
807
- if(verifiedBridge?.component!=='bridge'||verifiedBridge.pid!==bridgePid||!path.isAbsolute(String(verifiedBridge.script_path??''))||
808
- !HEX64.test(String(verifiedBridge.script_sha256??''))) {
809
- throw new Error(`opaque downstream core requires verified Bridge script binding for bridge ${bridgePid}`);
810
- }
811
- item=children[0]; identityBasis='verified_bridge_single_opaque_node_child';
812
- } else {
813
- throw new Error(`expected one unambiguous downstream core for bridge ${bridgePid}, found children=${children.length} visible_matches=${matches.length}`);
814
- }
815
- const pid=Number(item.ProcessId),creationDate=String(item.CreationDate??'');
816
- if(!Number.isInteger(pid)||pid<1||creationDate.length<1) throw new Error(`downstream core identity incomplete for bridge ${bridgePid}`);
817
- return {pid,parent_pid:Number(item.ParentProcessId),creation_date:creationDate,
818
- executable_path:String(item.ExecutablePath??''),command_line:String(item.CommandLine??''),identity_basis:identityBasis};
819
- }
820
- function coreProcess(coreRoot,bridge){
821
- return selectCoreProcess(nodeProcesses(),coreRoot,bridge.pid,bridge);
822
- }
823
- export async function waitForCoreProcess(coreRoot,bridge,{
824
- timeoutMs=10000,
825
- pollMs=100,
826
- processProvider=nodeProcesses,
827
- sleepFn=sleep,
828
- nowFn=Date.now
1
+ import { createHash, createPrivateKey, createPublicKey, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
2
+ import { spawn, spawnSync } from 'node:child_process';
3
+ import { existsSync, lstatSync, realpathSync } from 'node:fs';
4
+ import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm, writeFile } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import {
9
+ atomicWriteActivePointer,
10
+ materializeReleaseSlot,
11
+ parseActivePointer,
12
+ parseManifest,
13
+ releaseId,
14
+ renderStableLauncherCmd,
15
+ renderStableLauncherPs1,
16
+ sha256Bytes,
17
+ verifyManifestTree
18
+ } from './release-slot.mjs';
19
+ import { createGenerationBundle, verifyGenerationBundle } from './release-generation.mjs';
20
+ import { resolveStateRoot } from './state-root.mjs';
21
+ import { verifyProjectOwnerApproval } from './r70-owner-approval.mjs';
22
+
23
+ const HEX64=/^[0-9a-f]{64}$/;
24
+ const SEMVER=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
25
+ const RECEIPT_DOMAIN=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0','utf8');
26
+ const UPDATE_SCHEMA='deadbyte.r27-slot-update-receipt.v1';
27
+ const HANDOFF_SCHEMA='deadbyte.r27-slot-update-handoff.v1';
28
+ const FAILURE_SCHEMA='deadbyte.r27-slot-update-failure.v1';
29
+ const ROLLBACK_SCHEMA='deadbyte.r31-slot-rollback-receipt.v1';
30
+ const ROLLBACK_FAILURE_SCHEMA='deadbyte.r31-slot-rollback-failure.v1';
31
+ const DEFAULT_STATE_ROOT=undefined;
32
+ const TOP_LAUNCHER=path.join(os.homedir(),'Downloads','DEADBYTE-MCP.cmd');
33
+ const RUNTIME_STABILITY_MS=12000;
34
+ const RUNTIME_STABILITY_POLL_MS=500;
35
+
36
+ const sha256File=async file=>sha256Bytes(await readFile(file));
37
+ const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
38
+ const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
39
+ ?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
40
+ const canonicalJson=value=>JSON.stringify(canonical(value));
41
+ function normalizePersistentPublicOrigin(value){
42
+ let publicOrigin;
43
+ try { publicOrigin=new URL(String(value)); }
44
+ catch { throw new Error('named tunnel public origin must be one persistent HTTPS origin'); }
45
+ if(publicOrigin.protocol!=='https:'||publicOrigin.username||publicOrigin.password||publicOrigin.search||publicOrigin.hash||publicOrigin.pathname!=='/'||!publicOrigin.hostname) {
46
+ throw new Error('named tunnel public origin must be one persistent HTTPS origin without path, query, fragment, or credentials');
47
+ }
48
+ if(publicOrigin.hostname.toLowerCase().endsWith('.trycloudflare.com')) throw new Error('named tunnel public origin must be persistent; trycloudflare is forbidden');
49
+ return publicOrigin.origin;
50
+ }
51
+ export function parseUpdateArgs(argv){
52
+ const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableDesktop:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,hostPolicyBootstrap:null,codingPolicyBootstrap:null,autonomousPolicyBootstrap:null,ownerApprovalBootstrap:null,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
53
+ const cloud={},bridge={},processPolicy={},hostPolicy={},codingPolicy={},autonomousPolicy={},ownerApproval={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
54
+ const bridgeFlags=new Map([
55
+ ['--bridge-root','root'],['--bridge-script-sha256','scriptSha256']
56
+ ]);
57
+ const processPolicyFlags=new Map([
58
+ ['--process-policy','path'],['--process-policy-sha256','sha256']
59
+ ]);
60
+ const hostPolicyFlags=new Map([
61
+ ['--host-policy','path'],['--host-policy-sha256','sha256']
62
+ ]);
63
+ const codingPolicyFlags=new Map([
64
+ ['--coding-policy','path'],['--coding-policy-sha256','sha256']
65
+ ]);
66
+ const autonomousPolicyFlags=new Map([
67
+ ['--autonomous-policy','path'],['--autonomous-policy-sha256','sha256']
68
+ ]);
69
+ const ownerApprovalFlags=new Map([
70
+ ['--owner-approval','path'],['--owner-approval-sha256','sha256']
71
+ ]);
72
+ const desktopPolicyFlags=new Map([
73
+ ['--desktop-policy','path'],['--desktop-policy-sha256','sha256']
74
+ ]);
75
+ const semanticLspFlags=new Map([
76
+ ['--semantic-lsp-config','path'],['--semantic-lsp-config-sha256','sha256']
77
+ ]);
78
+ const cloudFlags=new Map([
79
+ ['--cloud-cloudflared','cloudflaredPath'],['--cloud-cloudflared-sha256','cloudflaredSha256'],
80
+ ['--cloud-token-file','tokenFilePath'],['--cloud-token-sha256','tokenFileSha256'],
81
+ ['--cloud-child-working-directory','childWorkingDirectory'],['--cloud-public-origin','publicOrigin']
82
+ ]);
83
+ const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...hostPolicyFlags.keys(),...codingPolicyFlags.keys(),...autonomousPolicyFlags.keys(),...ownerApprovalFlags.keys(),...desktopPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
84
+ for(let index=0;index<argv.length;index++){
85
+ const arg=argv[index];
86
+ if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-desktop'||arg==='--enable-autonomous-supervisor'){
87
+ if(seen.has(arg)) throw new Error(`duplicate self-update argument '${arg}'`);
88
+ seen.add(arg);
89
+ if(arg==='--restore-standing-grants') out.restoreStandingGrants=true;
90
+ else if(arg==='--recover-offline-predecessor') out.recoverOfflinePredecessor=true;
91
+ else if(arg==='--enable-machine-fs') out.enableMachineFs=true;
92
+ else if(arg==='--enable-observation') out.enableObservation=true;
93
+ else if(arg==='--enable-desktop') out.enableDesktop=true;
94
+ else out.enableAutonomousSupervisor=true;
95
+ continue;
96
+ }
97
+ if(arg==='--host-command-pin'){
98
+ const value=argv[++index];
99
+ if(!value) throw new Error('--host-command-pin requires command_id=sha256');
100
+ const match=/^([A-Za-z0-9._-]{1,64})=([0-9a-f]{64})$/.exec(value);
101
+ if(!match) throw new Error('host command pin must be command_id=lowercase_sha256');
102
+ const [,commandId,sha256]=match;
103
+ if(hostPinIds.has(commandId)) throw new Error(`duplicate host command pin '${commandId}'`);
104
+ hostPinIds.add(commandId); out.hostCommandPins.push({commandId,sha256}); continue;
105
+ }
106
+ if(!valued.has(arg)) throw new Error(`unknown self-update argument '${arg}'`);
107
+ if(seen.has(arg)) throw new Error(`duplicate self-update argument '${arg}'`);
108
+ seen.add(arg);
109
+ const value=argv[++index];
110
+ if(!value) throw new Error(`${arg} requires a value`);
111
+ if(arg==='--stage') out.stage=path.resolve(value);
112
+ else if(arg==='--development-root') out.developmentRoot=path.resolve(value);
113
+ else if(arg==='--expected-version') out.expectedVersion=value;
114
+ else if(arg==='--expected-manifest') out.expectedManifest=value.toLowerCase();
115
+ else if(arg==='--source-root') out.sourceRoot=path.resolve(value);
116
+ else if(bridgeFlags.has(arg)) bridge[bridgeFlags.get(arg)]=value;
117
+ else if(processPolicyFlags.has(arg)) processPolicy[processPolicyFlags.get(arg)]=value;
118
+ else if(hostPolicyFlags.has(arg)) hostPolicy[hostPolicyFlags.get(arg)]=value;
119
+ else if(codingPolicyFlags.has(arg)) codingPolicy[codingPolicyFlags.get(arg)]=value;
120
+ else if(autonomousPolicyFlags.has(arg)) autonomousPolicy[autonomousPolicyFlags.get(arg)]=value;
121
+ else if(ownerApprovalFlags.has(arg)) ownerApproval[ownerApprovalFlags.get(arg)]=value;
122
+ else if(desktopPolicyFlags.has(arg)) desktopPolicy[desktopPolicyFlags.get(arg)]=value;
123
+ else if(semanticLspFlags.has(arg)) semanticLsp[semanticLspFlags.get(arg)]=value;
124
+ else cloud[cloudFlags.get(arg)]=value;
125
+ }
126
+ if(!out.stage) throw new Error('--stage is required');
127
+ if(!out.developmentRoot) throw new Error('--development-root is required');
128
+ if(!SEMVER.test(String(out.expectedVersion??''))) throw new Error('--expected-version must be SemVer');
129
+ if(!HEX64.test(String(out.expectedManifest??''))) throw new Error('--expected-manifest must be lowercase SHA-256 hex');
130
+ const bridgeFields=[...bridgeFlags.values()],bridgePresent=bridgeFields.filter(key=>bridge[key]!==undefined);
131
+ if(bridgePresent.length!==0&&bridgePresent.length!==bridgeFields.length) throw new Error('bridge bootstrap requires root plus script SHA-256 as one pair');
132
+ if(bridgePresent.length===bridgeFields.length){
133
+ const scriptSha256=String(bridge.scriptSha256).toLowerCase();
134
+ if(!HEX64.test(scriptSha256)) throw new Error('bridge script SHA-256 must be lowercase hex');
135
+ out.bridgeBootstrap={root:path.resolve(bridge.root),scriptSha256};
136
+ }
137
+ const processPolicyFields=[...processPolicyFlags.values()],processPolicyPresent=processPolicyFields.filter(key=>processPolicy[key]!==undefined);
138
+ if(processPolicyPresent.length!==0&&processPolicyPresent.length!==processPolicyFields.length) throw new Error('process policy bootstrap requires path plus SHA-256 as one pair');
139
+ if(processPolicyPresent.length===processPolicyFields.length){
140
+ const sha256=String(processPolicy.sha256).toLowerCase();
141
+ if(!HEX64.test(sha256)) throw new Error('process policy SHA-256 must be lowercase hex');
142
+ out.processPolicyBootstrap={path:path.resolve(processPolicy.path),sha256};
143
+ }
144
+ const hostPolicyFields=[...hostPolicyFlags.values()],hostPolicyPresent=hostPolicyFields.filter(key=>hostPolicy[key]!==undefined);
145
+ if(hostPolicyPresent.length!==0&&hostPolicyPresent.length!==hostPolicyFields.length) throw new Error('host policy bootstrap requires path plus SHA-256 as one pair');
146
+ if(hostPolicyPresent.length===hostPolicyFields.length){
147
+ const sha256=String(hostPolicy.sha256).toLowerCase();
148
+ if(!HEX64.test(sha256)) throw new Error('host policy SHA-256 must be lowercase hex');
149
+ out.hostPolicyBootstrap={path:path.resolve(hostPolicy.path),sha256};
150
+ }
151
+ const codingPolicyFields=[...codingPolicyFlags.values()],codingPolicyPresent=codingPolicyFields.filter(key=>codingPolicy[key]!==undefined);
152
+ if(codingPolicyPresent.length!==0&&codingPolicyPresent.length!==codingPolicyFields.length) throw new Error('coding policy bootstrap requires path plus SHA-256 as one pair');
153
+ if(codingPolicyPresent.length===codingPolicyFields.length){
154
+ const sha256=String(codingPolicy.sha256).toLowerCase();
155
+ if(!HEX64.test(sha256)) throw new Error('coding policy SHA-256 must be lowercase hex');
156
+ out.codingPolicyBootstrap={path:path.resolve(codingPolicy.path),sha256};
157
+ }
158
+ const autonomousPolicyFields=[...autonomousPolicyFlags.values()],autonomousPolicyPresent=autonomousPolicyFields.filter(key=>autonomousPolicy[key]!==undefined);
159
+ if(autonomousPolicyPresent.length!==0&&autonomousPolicyPresent.length!==autonomousPolicyFields.length) throw new Error('autonomous policy bootstrap requires path plus SHA-256 as one pair');
160
+ if(autonomousPolicyPresent.length===autonomousPolicyFields.length){
161
+ const sha256=String(autonomousPolicy.sha256).toLowerCase();
162
+ if(!HEX64.test(sha256)) throw new Error('autonomous policy SHA-256 must be lowercase hex');
163
+ out.autonomousPolicyBootstrap={path:path.resolve(autonomousPolicy.path),sha256};
164
+ }
165
+ const ownerApprovalFields=[...ownerApprovalFlags.values()],ownerApprovalPresent=ownerApprovalFields.filter(key=>ownerApproval[key]!==undefined);
166
+ if(ownerApprovalPresent.length!==0&&ownerApprovalPresent.length!==ownerApprovalFields.length) throw new Error('owner approval bootstrap requires path plus SHA-256 as one pair');
167
+ if(ownerApprovalPresent.length===ownerApprovalFields.length){
168
+ const sha256=String(ownerApproval.sha256).toLowerCase();
169
+ if(!HEX64.test(sha256)) throw new Error('owner approval SHA-256 must be lowercase hex');
170
+ out.ownerApprovalBootstrap={path:path.resolve(ownerApproval.path),sha256};
171
+ }
172
+ if(Boolean(out.autonomousPolicyBootstrap)!==Boolean(out.ownerApprovalBootstrap)) throw new Error('autonomous policy bootstrap requires pinned owner approval, and owner approval requires autonomous policy');
173
+ const desktopPolicyFields=[...desktopPolicyFlags.values()],desktopPolicyPresent=desktopPolicyFields.filter(key=>desktopPolicy[key]!==undefined);
174
+ if(desktopPolicyPresent.length!==0&&desktopPolicyPresent.length!==desktopPolicyFields.length) throw new Error('desktop policy bootstrap requires path plus SHA-256 as one pair');
175
+ if(desktopPolicyPresent.length===desktopPolicyFields.length){
176
+ const sha256=String(desktopPolicy.sha256).toLowerCase();
177
+ if(!HEX64.test(sha256)) throw new Error('desktop policy SHA-256 must be lowercase hex');
178
+ out.desktopPolicyBootstrap={path:path.resolve(desktopPolicy.path),sha256};
179
+ }
180
+ const semanticLspFields=[...semanticLspFlags.values()],semanticLspPresent=semanticLspFields.filter(key=>semanticLsp[key]!==undefined);
181
+ if(semanticLspPresent.length!==0&&semanticLspPresent.length!==semanticLspFields.length) throw new Error('semantic LSP config bootstrap requires path plus SHA-256 as one pair');
182
+ if(semanticLspPresent.length===semanticLspFields.length){
183
+ const sha256=String(semanticLsp.sha256).toLowerCase();
184
+ if(!HEX64.test(sha256)) throw new Error('semantic LSP config SHA-256 must be lowercase hex');
185
+ out.semanticLspBootstrap={path:path.resolve(semanticLsp.path),sha256};
186
+ }
187
+ const cloudFields=[...cloudFlags.values()],present=cloudFields.filter(key=>cloud[key]!==undefined);
188
+ if(present.length!==0&&present.length!==cloudFields.length) throw new Error('named tunnel bootstrap requires cloud path/hash/token/workdir plus persistent public origin');
189
+ if(present.length===cloudFields.length){
190
+ for(const key of ['cloudflaredSha256','tokenFileSha256']) cloud[key]=String(cloud[key]).toLowerCase();
191
+ if(!HEX64.test(cloud.cloudflaredSha256)||!HEX64.test(cloud.tokenFileSha256)) throw new Error('named tunnel dependency SHA-256 must be lowercase hex');
192
+ const publicOrigin=normalizePersistentPublicOrigin(cloud.publicOrigin);
193
+ out.cloudTransport={
194
+ cloudflared:{path:path.resolve(cloud.cloudflaredPath),sha256:cloud.cloudflaredSha256},
195
+ tokenFile:{path:path.resolve(cloud.tokenFilePath),sha256:cloud.tokenFileSha256},
196
+ childWorkingDirectory:path.resolve(cloud.childWorkingDirectory),
197
+ publicOrigin
198
+ };
199
+ }
200
+ return out;
201
+ }
202
+
203
+ export function parseRollbackArgs(argv){
204
+ let activationReceipt=null;
205
+ for(let index=0;index<argv.length;index++){
206
+ const arg=argv[index];
207
+ if(arg!=='--rollback-receipt') throw new Error(`unknown rollback argument '${arg}'`);
208
+ if(activationReceipt!==null) throw new Error("duplicate rollback argument '--rollback-receipt'");
209
+ const value=argv[++index];
210
+ if(!value) throw new Error('--rollback-receipt requires a value');
211
+ activationReceipt=path.resolve(value);
212
+ }
213
+ if(!activationReceipt) throw new Error('--rollback-receipt is required');
214
+ return {activationReceipt};
215
+ }
216
+
217
+ function powershellPath(){
218
+ return path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
219
+ }
220
+ export function windowsPowerShellEnvironment(base=process.env){
221
+ const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
222
+ const userProfile=base.USERPROFILE||os.homedir();
223
+ const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
224
+ const environment={...base};
225
+ const inherited=Object.entries(base).filter(([key])=>key.toLowerCase()==='psmodulepath').flatMap(([,value])=>String(value??'').split(';')).filter(Boolean);
226
+ for(const key of Object.keys(environment)) if(key.toLowerCase()==='psmodulepath') delete environment[key];
227
+ const modules=[
228
+ path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules'),
229
+ path.join(userProfile,'Documents','WindowsPowerShell','Modules'),
230
+ path.join(programFiles,'WindowsPowerShell','Modules'),
231
+ ...inherited
232
+ ];
233
+ const seen=new Set();
234
+ return {...environment,PSModulePath:modules.filter(modulePath=>{
235
+ const key=modulePath.toLowerCase();
236
+ if(seen.has(key)) return false;
237
+ seen.add(key);return true;
238
+ }).join(';')};
239
+ }
240
+ function commandEnvironment(exe,base=process.env){
241
+ return path.resolve(exe).toLowerCase()===path.resolve(powershellPath()).toLowerCase()
242
+ ?windowsPowerShellEnvironment(base):base;
243
+ }
244
+ function runCaptured(exe,args,{cwd,env,timeoutMs=45000}={}){
245
+ const baseEnv=env??process.env;
246
+ const childEnv=commandEnvironment(exe,baseEnv);
247
+ const result=spawnSync(exe,args,{cwd,env:childEnv,encoding:'utf8',windowsHide:true,timeout:timeoutMs,maxBuffer:16*1024*1024});
248
+ return {status:result.status,signal:result.signal,error:result.error?{code:result.error.code??null,message:result.error.message}:null,stdout:result.stdout??'',stderr:result.stderr??''};
249
+ }
250
+ function assertRun(result,label){
251
+ if(result.error||result.status!==0) throw new Error(`${label} failed status=${result.status} error=${result.error?.message??'none'}\n${result.stderr||result.stdout}`);
252
+ return result.stdout;
253
+ }
254
+ export async function loadPowerShellDataFile(file){
255
+ const env={...process.env,DEADBYTE_R27_CONFIG_READ:path.resolve(file)};
256
+ const command="$ErrorActionPreference='Stop'; $v=Import-PowerShellDataFile -LiteralPath $env:DEADBYTE_R27_CONFIG_READ; $v | ConvertTo-Json -Depth 16 -Compress";
257
+ const result=runCaptured(powershellPath(),['-NoLogo','-NoProfile','-Command',command],{env,timeoutMs:15000});
258
+ const text=assertRun(result,'PowerShell data-file load').trim();
259
+ let value; try { value=JSON.parse(text); } catch(error){ throw new Error(`controller config JSON conversion failed: ${error.message}`); }
260
+ if(!value||typeof value!=='object'||Array.isArray(value)) throw new Error('controller config must be an object');
261
+ return value;
262
+ }
263
+ async function readJson(file){
264
+ const text=(await readFile(file,'utf8')).replace(/^\uFEFF/,'');
265
+ let value; try { value=JSON.parse(text); } catch(error){ throw new Error(`JSON invalid ${file}: ${error.message}`); }
266
+ return value;
267
+ }
268
+ async function atomicBytes(file,bytes){
269
+ await mkdir(path.dirname(file),{recursive:true});
270
+ const temp=`${file}.tmp-${process.pid}-${randomBytes(4).toString('hex')}`;
271
+ const handle=await open(temp,'wx');
272
+ try { await handle.writeFile(bytes); await handle.sync(); } finally { await handle.close(); }
273
+ let renamed=false;
274
+ let lastRenameError=null;
275
+ for(let attempt=0;attempt<20;attempt+=1){
276
+ try { await rename(temp,file); renamed=true; break; }
277
+ catch(error){
278
+ lastRenameError=error;
279
+ if(!["EPERM","EBUSY","EACCES"].includes(error?.code) || attempt===19) break;
280
+ await new Promise(resolve=>setTimeout(resolve,Math.min(500,100*(attempt+1))));
281
+ }
282
+ }
283
+ if(!renamed){ await rm(temp,{force:true}).catch(()=>{}); throw lastRenameError; }
284
+ if(sha256Bytes(await readFile(file))!==sha256Bytes(bytes)) throw new Error(`atomic write readback mismatch: ${file}`);
285
+ }
286
+ async function normalizeComponentIdentities(config){
287
+ const next=structuredClone(config);
288
+ for(const name of ['Bridge','Cloud']){
289
+ const spec=next[name];
290
+ if(!spec||typeof spec!=='object') throw new Error(`${name} launch specification missing`);
291
+ if(typeof spec.FilePath!=='string'||!path.isAbsolute(spec.FilePath)) throw new Error(`${name} FilePath must be absolute`);
292
+ const exe=path.resolve(spec.FilePath);
293
+ const stat=await lstat(exe);
294
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`${name} executable must be a regular file`);
295
+ const actual=await sha256File(exe);
296
+ if(spec.FileSha256!==undefined&&spec.FileSha256!==''&&spec.FileSha256!==actual) throw new Error(`${name} executable pin drift detected`);
297
+ spec.FilePath=exe; spec.FileSha256=actual;
298
+ const dependencies=spec.Dependencies??[];
299
+ if(!Array.isArray(dependencies)||dependencies.length>16) throw new Error(`${name} Dependencies invalid`);
300
+ const normalizedDependencies=[];
301
+ for(const dependency of dependencies){
302
+ const depName=String(dependency?.Name??'');
303
+ const depPath=path.resolve(String(dependency?.Path??''));
304
+ const depHash=String(dependency?.Sha256??'').toLowerCase();
305
+ if(!/^[A-Za-z0-9._-]{1,32}$/.test(depName)||!path.isAbsolute(String(dependency?.Path??''))||!HEX64.test(depHash)) throw new Error(`${name} dependency contract invalid`);
306
+ const depStat=await lstat(depPath); if(!depStat.isFile()||depStat.isSymbolicLink()) throw new Error(`${name} dependency must be a regular file: ${depName}`);
307
+ const depActual=await sha256File(depPath); if(depActual!==depHash) throw new Error(`${name} dependency pin drift detected: ${depName}`);
308
+ normalizedDependencies.push({Name:depName,Path:depPath,Sha256:depHash});
309
+ }
310
+ spec.Dependencies=normalizedDependencies;
311
+ if(name==='Cloud'&&spec.Transport!==undefined){
312
+ const transport=String(spec.Transport);
313
+ const valid=(transport==='deadbyte.named-tunnel.v1'&&normalizedDependencies.length===3)||
314
+ (transport==='deadbyte.named-tunnel.v2'&&normalizedDependencies.length===2);
315
+ if(!valid) throw new Error('Cloud named tunnel transport contract invalid');
316
+ }
317
+ }
318
+ const bridge=next.Bridge;
319
+ const raw=String(bridge.Arguments??'');
320
+ const normalized=raw.replaceAll('\\','/');
321
+ if(!/^[A-Za-z0-9._/-]+\.(?:mjs|js)$/.test(normalized)||path.posix.isAbsolute(normalized)||normalized.split('/').includes('..')) {
322
+ throw new Error('R27 Bridge Arguments must be one relative .mjs/.js script path');
323
+ }
324
+ if(typeof bridge.WorkingDirectory!=='string'||!path.isAbsolute(bridge.WorkingDirectory)) throw new Error('Bridge WorkingDirectory must be absolute');
325
+ const script=path.resolve(bridge.WorkingDirectory,...normalized.split('/'));
326
+ const scriptStat=await lstat(script);
327
+ if(!scriptStat.isFile()||scriptStat.isSymbolicLink()) throw new Error('Bridge script must be a regular file');
328
+ const scriptHash=await sha256File(script);
329
+ if(bridge.ScriptSha256!==undefined&&bridge.ScriptSha256!==''&&bridge.ScriptSha256!==scriptHash) throw new Error('Bridge script pin drift detected');
330
+ bridge.ScriptPath=script; bridge.ScriptSha256=scriptHash;
331
+ return next;
332
+ }
333
+
334
+ function bridgeDependencyName(relativePath,usedNames){
335
+ const normalized=String(relativePath).replaceAll('\\','/').replaceAll('/','_');
336
+ let name=normalized;
337
+ if(!/^[A-Za-z0-9._-]{1,32}$/.test(name)||usedNames.has(name)){
338
+ name=`dep-${sha256Bytes(Buffer.from(String(relativePath),'utf8')).slice(0,20)}`;
339
+ }
340
+ if(usedNames.has(name)) throw new Error(`bridge dependency name collision: ${relativePath}`);
341
+ usedNames.add(name);
342
+ return name;
343
+ }
344
+ function bridgeStaticRelativeImports(source,file){
345
+ const found=[];
346
+ for(const [index,line] of String(source).split(/\r?\n/).entries()){
347
+ if(/\bimport\s*\(\s*['"]\.\.?\//.test(line)) throw new Error(`bridge dynamic relative import is forbidden: ${file}:${index+1}`);
348
+ const importStart=/^\s*import\b/.test(line);
349
+ const exportStart=/^\s*export\b/.test(line)&&/\bfrom\b/.test(line);
350
+ if(!importStart&&!exportStart) continue;
351
+ const match=importStart
352
+ ? /^\s*import\s+(?:(?:[^'"]+?)\s+from\s*)?['"]([^'"]+)['"]\s*;?\s*$/.exec(line)
353
+ : /^\s*export\s+[^'"]*?\s+from\s*['"]([^'"]+)['"]\s*;?\s*$/.exec(line);
354
+ if(!match) throw new Error(`bridge import syntax unsupported for dependency binding: ${file}:${index+1}`);
355
+ if(match[1].startsWith('.')) found.push(match[1]);
356
+ }
357
+ return found;
358
+ }
359
+ async function discoverBridgeLocalDependencies(root,entryScript){
360
+ const absoluteRoot=path.resolve(root),realRoot=await realpath(absoluteRoot);
361
+ const srcRoot=path.join(absoluteRoot,'src');
362
+ const queue=[path.resolve(entryScript)],visited=new Set(),files=[];
363
+ while(queue.length){
364
+ const current=queue.shift();
365
+ const currentReal=await realpath(current);
366
+ const relativeReal=path.relative(realRoot,currentReal);
367
+ if(relativeReal.startsWith('..')||path.isAbsolute(relativeReal)) throw new Error(`bridge dependency escapes bridge root: ${current}`);
368
+ if(visited.has(currentReal)) continue;
369
+ visited.add(currentReal);
370
+ if(visited.size>16) throw new Error('bridge local dependency closure exceeds 16 files');
371
+ const stat=await lstat(current);
372
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`bridge dependency must be a regular file: ${current}`);
373
+ const source=await readFile(current,'utf8');
374
+ const specifiers=bridgeStaticRelativeImports(source,current);
375
+ if(currentReal!==await realpath(entryScript)) files.push(currentReal);
376
+ for(const specifier of specifiers){
377
+ if(!specifier.startsWith('./')) throw new Error(`bridge relative import escapes bridge root: ${specifier}`);
378
+ if(specifier.includes('?')||specifier.includes('#')) throw new Error(`bridge relative import query/fragment unsupported: ${specifier}`);
379
+ const resolved=path.resolve(path.dirname(current),specifier);
380
+ const relative=path.relative(absoluteRoot,resolved);
381
+ if(relative.startsWith('..')||path.isAbsolute(relative)) throw new Error(`bridge dependency escapes bridge root: ${specifier}`);
382
+ if(!/\.(?:mjs|js)$/i.test(resolved)) throw new Error(`bridge local dependency must be .mjs/.js: ${specifier}`);
383
+ queue.push(resolved);
384
+ }
385
+ }
386
+ const unique=[...new Set(files)].sort((a,b)=>a.localeCompare(b));
387
+ const usedNames=new Set();
388
+ const dependencies=[];
389
+ for(const file of unique){
390
+ const relative=path.relative(srcRoot,file).replaceAll('\\','/');
391
+ if(relative.startsWith('..')||path.isAbsolute(relative)) throw new Error(`bridge dependency must remain under src: ${file}`);
392
+ dependencies.push({Name:bridgeDependencyName(relative,usedNames),Path:file,Sha256:await sha256File(file)});
393
+ }
394
+ return dependencies;
395
+ }
396
+
397
+ export async function prepareBridgeSpec(config,bootstrap){
398
+ if(!bootstrap) return structuredClone(config);
399
+ if(typeof bootstrap.root!=='string'||!path.isAbsolute(bootstrap.root)||!HEX64.test(String(bootstrap.scriptSha256??''))) {
400
+ throw new Error('bridge bootstrap requires absolute root plus lowercase script SHA-256');
401
+ }
402
+ const root=path.resolve(bootstrap.root),rootStat=await lstat(root);
403
+ if(!rootStat.isDirectory()||rootStat.isSymbolicLink()) throw new Error('bridge root must be a real directory');
404
+ const packageFile=path.join(root,'package.json'),script=path.join(root,'src','bridge.mjs');
405
+ const packageStat=await lstat(packageFile),scriptStat=await lstat(script);
406
+ if(!packageStat.isFile()||packageStat.isSymbolicLink()) throw new Error('bridge package.json must be a regular file');
407
+ if(!scriptStat.isFile()||scriptStat.isSymbolicLink()) throw new Error('bridge script must be a regular file');
408
+ const pkg=JSON.parse(await readFile(packageFile,'utf8'));
409
+ if(pkg?.name!=='deadbyte-chatgpt-bridge') throw new Error('bridge package identity mismatch');
410
+ const actual=await sha256File(script),expected=String(bootstrap.scriptSha256).toLowerCase();
411
+ if(actual!==expected) throw new Error(`bridge script SHA-256 mismatch: expected ${expected}, found ${actual}`);
412
+ const next=structuredClone(config);
413
+ if(!next.Bridge||typeof next.Bridge!=='object') throw new Error('Bridge launch specification missing');
414
+ next.Bridge.WorkingDirectory=root;
415
+ next.Bridge.Arguments='src\\bridge.mjs';
416
+ next.Bridge.ScriptPath=script;
417
+ next.Bridge.ScriptSha256=actual;
418
+ next.Bridge.Dependencies=await discoverBridgeLocalDependencies(root,script);
419
+ return next;
420
+ }
421
+
422
+ export async function prepareReleaseBridgeSpec(config,bootstrap,releaseRoot){
423
+ if(!bootstrap) return structuredClone(config);
424
+ if(typeof releaseRoot!=='string'||!path.isAbsolute(releaseRoot))throw new Error('immutable release root required for Bridge binding');
425
+ // Verify the requested development input, then bind runtime paths only to
426
+ // the manifest-verified immutable slot produced from that same candidate.
427
+ const verifiedSource=await prepareBridgeSpec(config,bootstrap);
428
+ return prepareBridgeSpec(verifiedSource,{
429
+ root:path.join(path.resolve(releaseRoot),'bridge'),
430
+ scriptSha256:verifiedSource.Bridge.ScriptSha256
431
+ });
432
+ }
433
+
434
+ export async function prepareAutonomousSupervisorSpec(config,{releaseRoot,enable=false}={}){
435
+ const next=structuredClone(config);
436
+ if(!enable) return next;
437
+ if(next.AutonomousRuntimeEnabled!==true) throw new Error('autonomous supervisor bootstrap requires AutonomousRuntimeEnabled');
438
+ if(typeof releaseRoot!=='string'||!path.isAbsolute(releaseRoot)) throw new Error('autonomous supervisor release root must be absolute');
439
+ const release=path.resolve(releaseRoot),script=path.join(release,'src','autonomous-supervisor-host.mjs');
440
+ const scriptStat=await lstat(script);
441
+ if(!scriptStat.isFile()||scriptStat.isSymbolicLink()) throw new Error('autonomous supervisor script must be a regular release file');
442
+ const bridge=next.Bridge;
443
+ if(!bridge||typeof bridge.FilePath!=='string'||!path.isAbsolute(bridge.FilePath)||!HEX64.test(String(bridge.FileSha256??''))) {
444
+ throw new Error('autonomous supervisor bootstrap requires pinned Bridge executable identity');
445
+ }
446
+ const executable=path.resolve(bridge.FilePath),exeStat=await lstat(executable);
447
+ if(!exeStat.isFile()||exeStat.isSymbolicLink()) throw new Error('autonomous supervisor executable must be a regular file');
448
+ const executableSha256=await sha256File(executable);
449
+ if(executableSha256!==String(bridge.FileSha256)) throw new Error('autonomous supervisor executable pin drift detected');
450
+ next.AutonomousSupervisorEnabled=true;
451
+ next.Supervisor={
452
+ FilePath:executable,FileSha256:executableSha256,Arguments:'src\\autonomous-supervisor-host.mjs',WorkingDirectory:release,
453
+ ScriptPath:script,ScriptSha256:await sha256File(script)
454
+ };
455
+ return next;
456
+ }
457
+
458
+ function quoteWindowsArgument(value){
459
+ const text=String(value??'');
460
+ if(text.length<1||/["\r\n\0]/.test(text)) throw new Error('named tunnel argument contains unsafe characters');
461
+ const trailing=(/\\+$/.exec(text)?.[0].length??0);
462
+ return `"${text}${'\\'.repeat(trailing)}"`;
463
+ }
464
+ async function verifyPinnedRegularFile(file,expected,label){
465
+ if(typeof file!=='string'||!path.isAbsolute(file)||!HEX64.test(String(expected??''))) throw new Error(`${label} pin contract invalid`);
466
+ const absolute=path.resolve(file),stat=await lstat(absolute);
467
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`${label} must be a regular file`);
468
+ const actual=await sha256File(absolute);
469
+ if(actual!==expected) throw new Error(`${label} SHA-256 mismatch: expected ${expected}, found ${actual}`);
470
+ return {path:absolute,sha256:actual};
471
+ }
472
+
473
+ export async function readPinnedJsonPolicy(bootstrap,label,readBytes=readFile){
474
+ const file=bootstrap?.path,expected=bootstrap?.sha256;
475
+ if(typeof file!=='string'||!path.isAbsolute(file)||!HEX64.test(String(expected??''))) throw new Error(`${label} pin contract invalid`);
476
+ const absolute=path.resolve(file),stat=await lstat(absolute);
477
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`${label} must be a regular file`);
478
+ const bytes=await readBytes(absolute);
479
+ if(!Buffer.isBuffer(bytes)) throw new Error(`${label} read must return bytes`);
480
+ const actual=sha256Bytes(bytes);
481
+ if(actual!==expected) throw new Error(`${label} SHA-256 mismatch: expected ${expected}, found ${actual}`);
482
+ let policy;
483
+ try{policy=JSON.parse(bytes.toString('utf8'));}
484
+ catch{throw new Error(`${label} JSON invalid`);}
485
+ return {path:absolute,sha256:actual,policy};
486
+ }
487
+
488
+ export async function prepareProcessPolicyBootstrap(bootstrap){
489
+ if(!bootstrap) return null;
490
+ const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'process policy');
491
+ const policy=await readJson(pinned.path);
492
+ if(policy?.schema!=='deadbyte.process-policy.v1'||policy?.enabled!==true) throw new Error('process policy schema/enabled mismatch');
493
+ return {...pinned,policy};
494
+ }
495
+
496
+ export async function prepareHostPolicyBootstrap(bootstrap){
497
+ if(!bootstrap) return null;
498
+ const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'host policy');
499
+ const policy=await readJson(pinned.path);
500
+ if(!['deadbyte.host-policy.v1','deadbyte.host-policy.v2'].includes(policy?.schema)||policy?.enabled!==true||
501
+ !policy?.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
502
+ throw new Error('host policy schema/enabled/commands mismatch');
503
+ }
504
+ if(policy.schema==='deadbyte.host-policy.v2'&&(policy.native_exec?.enabled!==true||!Array.isArray(policy.native_exec?.root_ids))) {
505
+ throw new Error('host policy v2 native_exec contract invalid');
506
+ }
507
+ return {...pinned,policy};
508
+ }
509
+
510
+ export async function prepareCodingPolicyBootstrap(bootstrap){
511
+ if(!bootstrap) return null;
512
+ const {policy,...pinned}=await readPinnedJsonPolicy(bootstrap,'coding policy');
513
+ if(policy?.schema!=='deadbyte.coding-policy.v1'||policy?.enabled!==true||
514
+ !policy?.roots||typeof policy.roots!=='object'||Array.isArray(policy.roots)||
515
+ !policy?.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
516
+ throw new Error('coding policy schema/enabled/roots/commands mismatch');
517
+ }
518
+ return {...pinned,policy};
519
+ }
520
+
521
+ export async function prepareAutonomousPolicyBootstrap(bootstrap){
522
+ if(!bootstrap) return null;
523
+ const {policy,...pinned}=await readPinnedJsonPolicy(bootstrap,'autonomous policy');
524
+ if(policy?.schema!=='deadbyte.autonomous-policy.v1'||policy?.enabled!==true||
525
+ !HEX64.test(String(policy.coding_policy_sha256??''))||
526
+ !Array.isArray(policy.root_ids)||policy.root_ids.length<1||
527
+ !policy.profiles||typeof policy.profiles!=='object'||Array.isArray(policy.profiles)) {
528
+ throw new Error('autonomous policy schema/enabled/coding digest/roots/profiles mismatch');
529
+ }
530
+ return {...pinned,policy};
531
+ }
532
+
533
+ export function assertAutonomousBootstrapRoots(autonomousPolicy,codingPolicy,selectedCodingSha256=null){
534
+ const roots=autonomousPolicy?.root_ids;
535
+ for(const codingRoot of Object.values(codingPolicy?.roots??{})){
536
+ const candidate=codingRoot?.path;
537
+ if((codingRoot?.read===true||codingRoot?.write===true||codingRoot?.exec===true)&&
538
+ typeof candidate==='string'&&path.isAbsolute(candidate)){
539
+ const resolved=path.resolve(candidate),filesystemRoot=path.parse(resolved).root;
540
+ const normalize=value=>process.platform==='win32'?value.toLowerCase():value;
541
+ if(normalize(resolved)===normalize(filesystemRoot)) throw new Error('coding policy contains a broad filesystem root');
542
+ }
543
+ }
544
+ if(!Array.isArray(roots)||roots.length<1||roots.some(id=>typeof id!=='string'||
545
+ !/^[A-Za-z0-9._-]{1,64}$/.test(id)||!Object.hasOwn(codingPolicy?.roots??{},id))) {
546
+ throw new Error('autonomous bootstrap root is outside selected coding policy');
547
+ }
548
+ const profiles=autonomousPolicy?.profiles??{};
549
+ if(!profiles||typeof profiles!=='object'||Array.isArray(profiles)) throw new Error('autonomous bootstrap profiles invalid');
550
+ for(const [id,profile] of Object.entries(profiles)){
551
+ if(typeof profile?.root_id!=='string'||!roots.includes(profile.root_id)){
552
+ throw new Error(`autonomous bootstrap profile '${id}' root outside autonomous roots`);
553
+ }
554
+ }
555
+ if(selectedCodingSha256!==null&&autonomousPolicy.coding_policy_sha256!==selectedCodingSha256) {
556
+ throw new Error('autonomous bootstrap coding policy SHA-256 mismatch');
557
+ }
558
+ for(const id of roots){
559
+ const rootPath=codingPolicy.roots[id]?.path;
560
+ if(typeof rootPath==='string'&&path.isAbsolute(rootPath)){
561
+ const resolved=path.resolve(rootPath),filesystemRoot=path.parse(resolved).root;
562
+ const normalize=value=>process.platform==='win32'?value.toLowerCase():value;
563
+ if(normalize(resolved)===normalize(filesystemRoot)) throw new Error('autonomous bootstrap requires a project-scoped root');
564
+ }
565
+ try{
566
+ if(typeof rootPath!=='string'||!path.isAbsolute(rootPath)) throw new Error('path invalid');
567
+ const stat=lstatSync(rootPath);
568
+ if(!stat.isDirectory()||stat.isSymbolicLink()) throw new Error('not a regular directory');
569
+ const resolved=path.resolve(rootPath),real=realpathSync(rootPath);
570
+ const normalize=value=>process.platform==='win32'?value.toLowerCase():value;
571
+ if(normalize(resolved)!==normalize(path.resolve(real))) throw new Error('path indirection');
572
+ }catch{ throw new Error(`autonomous bootstrap root '${id}' must be an existing real directory`); }
573
+ }
574
+ return roots;
575
+ }
576
+
577
+ // R70: Admission check for successor POLICY BOOTSTRAP only. This does not
578
+ // grant authority or establish the owner trust anchor by itself.
579
+ export async function verifyAutonomousOwnerBootstrap({
580
+ stateRoot,bootstrap,autonomousPolicy,codingPolicy,
581
+ codingPolicySha256,autonomousPolicySha256,expectedManifest,now=new Date()
829
582
  }={}){
830
- const bridgePid=Number(bridge?.pid);
831
- if(!Number.isInteger(bridgePid)||bridgePid<1) throw new Error('downstream core wait requires verified bridge PID');
832
- if(!Number.isFinite(timeoutMs)||timeoutMs<0) throw new Error('downstream core wait timeout invalid');
833
- if(!Number.isFinite(pollMs)||pollMs<1) throw new Error('downstream core wait poll invalid');
834
- if(typeof processProvider!=='function'||typeof sleepFn!=='function'||typeof nowFn!=='function') throw new Error('downstream core wait hooks invalid');
835
- const started=Number(nowFn());
836
- let lastError=null;
837
- while(true){
838
- try {
839
- const processes=await processProvider();
840
- return selectCoreProcess(processes,coreRoot,bridgePid,bridge);
841
- } catch(error){
842
- const message=String(error?.message??error);
843
- const transient=`expected one unambiguous downstream core for bridge ${bridgePid}, found children=0 visible_matches=0`;
844
- if(message!==transient) throw error;
845
- lastError=error;
846
- }
847
- const elapsed=Math.max(0,Number(nowFn())-started);
848
- if(elapsed>=timeoutMs){
849
- throw new Error(`downstream core did not appear for bridge ${bridgePid} within ${timeoutMs}ms: ${String(lastError?.message??lastError)}`);
850
- }
851
- await sleepFn(Math.min(pollMs,Math.max(1,timeoutMs-elapsed)));
852
- }
853
- }
854
- async function captureRuntime(ctx){
855
- const statusText=controllerCaptured(ctx,'status',15000); const status=parseControllerStatus(statusText);
856
- if(!status.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
857
- if(status.host_armed) throw new Error('slot update requires host plane DISARMED');
858
- if(status.autonomous_armed&&!status.coding_armed) throw new Error('invalid runtime authority: autonomous armed without coding');
859
- const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
860
- return {status,status_text:statusText.trim(),bridge,cloud,core};
861
- }
862
- export async function capturePredecessor(ctx,recoverOfflinePredecessor,{allowHostDisarm=true}={}){
863
- let statusText,status;
864
- try{
865
- statusText=controllerCaptured(ctx,'status',15000);
866
- status=parseControllerStatus(statusText);
867
- }catch(error){
868
- if(!recoverOfflinePredecessor) throw error;
869
- try{
870
- const runtimeDir=ctx.runtime_root;
871
- const hasBridge=existsSync(path.join(runtimeDir,'bridge.instance.json'));
872
- const hasCloud=existsSync(path.join(runtimeDir,'cloud.instance.json'));
873
- if(hasBridge&&hasCloud){
874
- const armed=['process-grant.json','coding-grant.json','autonomous-grant.json','desktop-grant.json','host-grant.json']
875
- .some(name=>existsSync(path.join(runtimeDir,name)));
876
- if(armed) throw new Error('controller status failed while authority grant state is present');
877
- const bridge=await readLedger(ctx,'bridge');
878
- const cloud=await readLedger(ctx,'cloud');
879
- const core=coreProcess(ctx.core_root,bridge);
880
- const status={online:true,offline:false,bridge_pid:bridge.pid,cloud_pid:cloud.pid,local_ready:true,
881
- process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false};
882
- return {mode:'online-ledger-recovery',status,status_text:`controller status unavailable; verified runtime ledgers established: ${String(error?.message??error)}`,runtime:{status,status_text:'verified by controller ledgers',bridge,cloud,core}};
883
- }
884
- const offline=cleanOfflinePredecessor(ctx);
885
- return {mode:'offline-recovery',status:offline,status_text:`controller status unavailable; clean offline evidence established: ${String(error?.message??error)}`,runtime:null};
886
- }catch(recoveryError){
887
- throw new Error(`controller status failed and predecessor recovery was not applicable: ${String(error?.message??error)}; ${String(recoveryError?.message??recoveryError)}`);
888
- }
889
- }
890
- if(status.online&&status.bridge_pid&&status.cloud_pid&&status.local_ready){
891
- const runtime=await captureRuntime(ctx);
892
- return {mode:'online',status:runtime.status,status_text:runtime.status_text,runtime};
893
- }
894
- if(!recoverOfflinePredecessor) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
895
- if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery requires clean OFFLINE controller state: ${statusText}`);
896
- if(status.host_armed){
897
- if(!allowHostDisarm) throw new Error('offline predecessor recovery requires host plane DISARMED');
898
- controllerCaptured(ctx,'disarmhost',15000);
899
- statusText=controllerCaptured(ctx,'status',15000);
900
- status=parseControllerStatus(statusText);
901
- if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery changed controller state while revoking Host authority: ${statusText}`);
902
- if(status.host_armed) throw new Error('offline predecessor recovery requires host plane DISARMED');
903
- }
904
- if(status.autonomous_armed&&!status.coding_armed) throw new Error('invalid runtime authority: autonomous armed without coding');
905
- return {mode:'offline-recovery',status,status_text:statusText.trim(),runtime:null};
906
- }
907
- async function waitCoreGone(runtime,timeoutMs=10000){
908
- const started=Date.now();
909
- while(Date.now()-started<timeoutMs){
910
- const found=nodeProcesses().find(item=>Number(item.ProcessId)===runtime.core.pid&&String(item.CreationDate??'')===runtime.core.creation_date);
911
- if(!found) return true; await sleep(250);
912
- }
913
- return false;
914
- }
915
- async function startAndVerify(ctx,oldRuntime){
916
- let start=null;
917
- await rm(path.join(ctx.runtime_root,'controller-start-failure.json'),{force:true});
918
- for(let attempt=1;attempt<=3;attempt+=1){
919
- start=runControllerStartNoCapture(ctx,45000);
920
- if(!start.error&&start.status===0) break;
921
- if(attempt<3) await sleep(1500);
922
- }
923
- if(start.error||start.status!==0){
924
- let detail='';
925
- try {
926
- const evidence=await readControllerStartFailure(ctx);
927
- if(evidence) detail=` controller=${evidence.message} evidence_sha256=${evidence.sha256}`;
928
- } catch(error) { detail=` controller_evidence_error=${error.message}`; }
929
- throw new Error(`controller start transport failed status=${start.status} error=${start.error?.message??'none'}${detail}`);
930
- }
931
- let statusText=''; let status=null;
932
- const started=Date.now();
933
- while(Date.now()-started<45000){
934
- try { statusText=controllerCaptured(ctx,'status',15000); status=parseControllerStatus(statusText); if(status.online&&status.bridge_pid&&status.cloud_pid&&status.local_ready) break; }
935
- catch {} await sleep(500);
936
- }
937
- if(!status?.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) throw new Error(`new runtime failed ONLINE gate: ${statusText}`);
938
- const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=await waitForCoreProcess(ctx.core_root,bridge);
939
- if(oldRuntime?.bridge&&sameGeneration(oldRuntime.bridge,bridge)) throw new Error('fresh bridge generation gate failed');
940
- if(oldRuntime?.cloud&&sameGeneration(oldRuntime.cloud,cloud)) throw new Error('fresh cloud generation gate failed');
941
- if(oldRuntime?.core&&oldRuntime.core.pid===core.pid&&oldRuntime.core.creation_date===core.creation_date) throw new Error('fresh downstream core generation gate failed');
942
- return {status,status_text:statusText.trim(),bridge,cloud,core};
943
- }
944
- async function stabilizeRuntime(ctx,runtime){
945
- const started=Date.now(); let probes=0;
946
- while(Date.now()-started<RUNTIME_STABILITY_MS){
947
- await sleep(RUNTIME_STABILITY_POLL_MS);
948
- const statusText=controllerCaptured(ctx,'status',15000),status=parseControllerStatus(statusText);
949
- if(!status.online||!status.local_ready||status.bridge_pid!==runtime.bridge.pid||status.cloud_pid!==runtime.cloud.pid) {
950
- throw new Error(`new runtime lost stability gate: ${statusText}`);
951
- }
952
- const bridge=await readLedger(ctx,'bridge'),cloud=await readLedger(ctx,'cloud');
953
- if(!sameGeneration(runtime.bridge,bridge)||!sameGeneration(runtime.cloud,cloud)) throw new Error('new runtime generation changed during stability gate');
954
- probes++;
955
- }
956
- controllerCaptured(ctx,'test',60000);
957
- const finalText=controllerCaptured(ctx,'status',15000),final=parseControllerStatus(finalText);
958
- if(!final.online||!final.local_ready||final.bridge_pid!==runtime.bridge.pid||final.cloud_pid!==runtime.cloud.pid) {
959
- throw new Error(`new runtime failed post-public stability gate: ${finalText}`);
960
- }
961
- return {duration_ms:Date.now()-started,probes,public_mcp_gate:'passed'};
962
- }
963
- async function verifyPackageRoot(root,expectedVersion,expectedManifest,label){
964
- const absolute=path.resolve(root); const info=await lstat(absolute);
965
- if(!info.isDirectory()||info.isSymbolicLink()) throw new Error(`${label} root must be a real directory`);
966
- const manifestBytes=await readFile(path.join(absolute,'MANIFEST.SHA256'));
967
- const digest=sha256Bytes(manifestBytes); if(digest!==expectedManifest) throw new Error(`${label} manifest digest mismatch: ${digest}`);
968
- const entries=parseManifest(manifestBytes.toString('utf8')); await verifyManifestTree(absolute,entries);
969
- const pkg=await readJson(path.join(absolute,'package.json'));
970
- if(pkg?.name!=='deadbyte-mcp'||pkg?.version!==expectedVersion) throw new Error(`${label} package identity mismatch`);
971
- return {root:absolute,manifest_sha256:digest,entries:entries.size};
972
- }
973
- async function verifyStoredInstallation(stateRoot,pointer){
974
- const state=path.resolve(stateRoot); const expectedRelease=path.join(state,'releases',releaseId(pointer.version,pointer.manifest_sha256));
975
- if(path.resolve(pointer.release_root)!==path.resolve(expectedRelease)) throw new Error('active pointer release root is not content-addressed store path');
976
- const release=await verifyPackageRoot(pointer.release_root,pointer.version,pointer.manifest_sha256,'active release');
977
- const generation=await verifyGenerationBundle(pointer.generation_root,pointer.generation_manifest_sha256);
978
- const expectedConfig=path.join(path.resolve(pointer.generation_root),'controller-config.psd1');
979
- if(path.resolve(pointer.config_path)!==path.resolve(expectedConfig)) throw new Error('active pointer config path mismatch');
980
- return {release,generation};
981
- }
982
- async function verifyActiveInstallation(stateRoot,pointer){
983
- const state=path.resolve(stateRoot);
984
- const verified=await verifyStoredInstallation(state,pointer);
985
- const currentLauncher=await sha256File(launcherPath(state));
986
- if(currentLauncher!==pointer.launcher_sha256) throw new Error('active pointer launcher digest mismatch');
987
- return verified;
988
- }
989
- async function loadPointerIfPresent(stateRoot){
990
- const file=path.join(path.resolve(stateRoot),'active-release.json');
991
- if(!existsSync(file)) return {file,pointer:null,snapshot:await snapshotFile(file)};
992
- const snapshot=await snapshotFile(file); const pointer=parseActivePointer(snapshot.bytes.toString('utf8'));
993
- await verifyActiveInstallation(stateRoot,pointer); return {file,pointer,snapshot};
994
- }
995
- async function loadControlInputs({stateRoot,sourceRoot,pointerInfo,bridgeBootstrap=null}){
996
- let configPath,ctx;
997
- if(pointerInfo.pointer){
998
- configPath=pointerInfo.pointer.config_path; ctx=slotContext(stateRoot,pointerInfo.pointer);
999
- } else {
1000
- if(!sourceRoot) throw new Error('--source-root is required for initial slot migration');
1001
- ctx=legacyContext(sourceRoot); configPath=path.join(ctx.source_root,'controller','deadbyte-controller.config.psd1');
1002
- }
1003
- let config=await loadPowerShellDataFile(configPath);
1004
- if(bridgeBootstrap){
1005
- if(!bridgeBootstrap.root||!path.isAbsolute(bridgeBootstrap.root)||!HEX64.test(String(bridgeBootstrap.scriptSha256??''))) throw new Error('bridge bootstrap requires absolute root plus lowercase script SHA-256');
1006
- const bootstrapRoot=path.resolve(bridgeBootstrap.root);
1007
- const bootstrapScript=path.join(bootstrapRoot,'src','bridge.mjs');
1008
- const bootstrapActual=await sha256File(bootstrapScript);
1009
- if(bootstrapActual!==String(bridgeBootstrap.scriptSha256).toLowerCase()) throw new Error(`bridge script SHA-256 mismatch: expected ${String(bridgeBootstrap.scriptSha256).toLowerCase()}, found ${bootstrapActual}`);
1010
- if(!config.Bridge||typeof config.Bridge!=='object') throw new Error('Bridge launch specification missing');
1011
- config.Bridge.WorkingDirectory=bootstrapRoot;
1012
- config.Bridge.Arguments='src\\bridge.mjs';
1013
- config.Bridge.ScriptPath=bootstrapScript;
1014
- config.Bridge.ScriptSha256=bootstrapActual;
1015
- }
1016
- config=await normalizeComponentIdentities(config);
1017
- const codingPath=String(config.CodingPolicyFile??''); const autonomousPath=String(config.AutonomousPolicyFile??''); const hostPath=String(config.HostPolicyFile??''); const processPath=String(config.ProcessPolicyFile??''); const desktopPath=String(config.DesktopPolicyFile??'');
1018
- if(!path.isAbsolute(codingPath)||!path.isAbsolute(autonomousPath)) throw new Error('coding/autonomous policy paths must be absolute');
1019
- if(processPath&&!path.isAbsolute(processPath)) throw new Error('process policy path must be absolute');
1020
- if(desktopPath&&!path.isAbsolute(desktopPath)) throw new Error('desktop policy path must be absolute');
1021
- const codingPolicy=await readJson(codingPath); const autonomousPolicy=await readJson(autonomousPath); const hostPolicy=hostPath?await readJson(hostPath):null; const processPolicy=processPath?await readJson(processPath):null; const desktopPolicy=desktopPath?await readJson(desktopPath):null;
1022
- const legacyCore=typeof config.CoreRoot==='string'&&config.CoreRoot?path.resolve(config.CoreRoot):ctx.core_root;
1023
- const workspaceRoot=typeof config.WorkspaceRoot==='string'&&config.WorkspaceRoot?path.resolve(config.WorkspaceRoot):path.join(legacyCore,'workspaces');
1024
- return {ctx,configPath,config,codingPolicy,autonomousPolicy,hostPolicy,processPolicy,desktopPolicy,workspaceRoot};
1025
- }
1026
- export function assertDevelopmentRootSafe(developmentRoot,stateRoot){
1027
- const dev=path.resolve(developmentRoot),state=path.resolve(stateRoot);
1028
- const forbidden=[path.join(state,'releases'),path.join(state,'generations'),path.join(state,'runtime')].map(item=>path.resolve(item).toLowerCase());
1029
- const lower=dev.toLowerCase();
1030
- if(forbidden.some(prefix=>lower===prefix||lower.startsWith(`${prefix}${path.sep}`))) throw new Error('development root cannot live inside immutable/runtime state stores');
1031
- return dev;
1032
- }
1033
- async function workspacePlan(currentWorkspace,stateRoot){
1034
- const source=path.resolve(currentWorkspace),target=path.join(path.resolve(stateRoot),'workspaces');
1035
- if(source.toLowerCase()===path.resolve(target).toLowerCase()) return {source,target,mode:'existing',moved:false};
1036
- const sourceExists=existsSync(source),targetExists=existsSync(target);
1037
- if(targetExists){
1038
- const info=await lstat(target); if(!info.isDirectory()||info.isSymbolicLink()) throw new Error('stable workspace root must be a real directory');
1039
- if(sourceExists) throw new Error(`workspace migration conflict: both source and stable target exist (${source}, ${target})`);
1040
- return {source,target,mode:'target-existing',moved:false};
1041
- }
1042
- if(!sourceExists) return {source,target,mode:'create',moved:false};
1043
- const info=await lstat(source); if(!info.isDirectory()||info.isSymbolicLink()) throw new Error('legacy workspace root must be a real directory');
1044
- if(path.parse(source).root.toLowerCase()!==path.parse(target).root.toLowerCase()) throw new Error('workspace migration requires same-volume rename');
1045
- return {source,target,mode:'rename',moved:false};
1046
- }
1047
- async function applyWorkspacePlan(plan){
1048
- if(plan.mode==='existing'||plan.mode==='target-existing') return plan;
1049
- const sourceExists=existsSync(plan.source),targetExists=existsSync(plan.target);
1050
- if(plan.mode==='create'){
1051
- if(targetExists){ plan.created=true; return plan; }
1052
- await mkdir(path.dirname(plan.target),{recursive:true});
1053
- await mkdir(plan.target,{recursive:false}); plan.created=true; return plan;
1054
- }
1055
- if(targetExists&&!sourceExists){ plan.moved=true; return plan; }
1056
- if(sourceExists&&targetExists) throw new Error('workspace migration conflict: both source and stable target exist ('+plan.source+', '+plan.target+')');
1057
- await mkdir(path.dirname(plan.target),{recursive:true});
1058
- await rename(plan.source,plan.target); plan.moved=true; return plan;
1059
- }
1060
- async function rollbackWorkspacePlan(plan){
1061
- if(plan.mode==='rename'&&existsSync(plan.target)&&!existsSync(plan.source)){ await rename(plan.target,plan.source); plan.moved=false; return true; }
1062
- if(plan.mode==='create'&&existsSync(plan.target)){ await rm(plan.target,{recursive:true,force:true}); plan.created=false; return true; }
1063
- return false;
1064
- }
1065
- async function launcherPlan(stateRoot){
1066
- const bin=path.join(path.resolve(stateRoot),'bin');
1067
- const ps1=path.join(bin,'DEADBYTE-MCP.ps1'),cmd=path.join(bin,'DEADBYTE-MCP.cmd'),top=TOP_LAUNCHER;
1068
- const ps1Bytes=Buffer.from(renderStableLauncherPs1(),'utf8'),cmdBytes=Buffer.from(renderStableLauncherCmd(),'utf8');
1069
- return {ps1,cmd,top,ps1Bytes,cmdBytes,launcher_sha256:sha256Bytes(ps1Bytes),
1070
- snapshots:{ps1:await snapshotFile(ps1),cmd:await snapshotFile(cmd),top:await snapshotFile(top)},installed:false};
1071
- }
1072
- async function installLaunchers(plan){
1073
- await atomicBytes(plan.ps1,plan.ps1Bytes); await atomicBytes(plan.cmd,plan.cmdBytes); await atomicBytes(plan.top,plan.cmdBytes);
1074
- plan.installed=true;
1075
- if(await sha256File(plan.ps1)!==plan.launcher_sha256) throw new Error('stable launcher post-install hash mismatch');
1076
- return plan;
1077
- }
1078
- async function rollbackLaunchers(plan){
1079
- if(!plan) return false;
1080
- const touched=plan.installed===true || (existsSync(plan.ps1)&&sha256Bytes(await readFile(plan.ps1))===plan.launcher_sha256);
1081
- if(!touched) return false;
1082
- await restoreFile(plan.ps1,plan.snapshots.ps1); await restoreFile(plan.cmd,plan.snapshots.cmd); await restoreFile(plan.top,plan.snapshots.top);
1083
- plan.installed=false; return true;
1084
- }
1085
- async function revokeAuthority(ctx,prior){
1086
- let revoked=false;
1087
- if(prior.desktop_armed){ controllerCaptured(ctx,'disarmdesktop',15000); revoked=true; }
1088
- if(prior.autonomous_armed){ controllerCaptured(ctx,'disarmautonomous',15000); revoked=true; }
1089
- if(prior.coding_armed){ controllerCaptured(ctx,'disarmcoding',15000); revoked=true; }
1090
- if(prior.process_armed){ controllerCaptured(ctx,'disarmprocess',15000); revoked=true; }
1091
- const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
1092
- if(status.process_armed||status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority revoke gate failed');
1093
- if(status.host_armed) throw new Error('host plane became armed during revoke');
1094
- return {revoked,status};
1095
- }
1096
- export function standingAuthorityRestorePlan(prior,enabled){
1097
- const before={
1098
- process_armed:prior?.process_armed===true,
1099
- coding_armed:prior?.coding_armed===true,
1100
- autonomous_armed:prior?.autonomous_armed===true,
1101
- desktop_armed:prior?.desktop_armed===true,
1102
- host_armed:prior?.host_armed===true
1103
- };
1104
- if(before.autonomous_armed&&!before.coding_armed) throw new Error('invalid prior authority: autonomous armed without coding');
1105
- const expected={process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false};
1106
- const commands=[];
1107
- if(enabled){
1108
- expected.process_armed=before.process_armed;
1109
- expected.coding_armed=before.coding_armed;
1110
- expected.autonomous_armed=before.autonomous_armed;
1111
- if(before.coding_armed) commands.push('armcoding');
1112
- if(before.autonomous_armed) commands.push('armautonomous');
1113
- if(before.process_armed) commands.push('armprocess');
1114
- }
1115
- return {commands,expected};
1116
- }
1117
- async function restoreStandingAuthority(ctx,prior,enabled){
1118
- const plan=standingAuthorityRestorePlan(prior,enabled);
1119
- for(const command of plan.commands) controllerCaptured(ctx,command,15000);
1120
- const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
1121
- for(const key of ['process_armed','coding_armed','autonomous_armed']){
1122
- if(status[key]!==plan.expected[key]) throw new Error(`${key.replace('_armed','')} standing-grant restore gate failed`);
1123
- }
1124
- if(status.desktop_armed!==plan.expected.desktop_armed) throw new Error('desktop plane must remain disarmed after slot switch');
1125
- if(status.host_armed!==plan.expected.host_armed) throw new Error('host plane became armed during slot switch');
1126
- return status;
1127
- }
1128
- async function stopAndVerify(ctx,runtime){
1129
- try{ controllerCaptured(ctx,'supervisorstop',15000); }catch(error){
1130
- if(!/not running|no supervisor|offline|missing/i.test(String(error?.message??error))) throw error;
1131
- }
1132
- const output=controllerCaptured(ctx,'stop',30000);
1133
- if(!(await waitCoreGone(runtime,10000))) throw new Error(`old downstream core ${runtime.core.pid} survived verified stop`);
1134
- const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
1135
- if(!status.offline) throw new Error(`controller stop did not reach OFFLINE: ${output}`);
1136
- return {output:output.trim(),status};
1137
- }
1138
- async function loadUpdateKey(stateRoot){
1139
- const trust=path.join(path.resolve(stateRoot),'trust');
1140
- const privateKey=createPrivateKey(await readFile(path.join(trust,'ed25519-private.pem')));
1141
- const publicKey=createPublicKey(await readFile(path.join(trust,'ed25519-public.pem'))); const derived=createPublicKey(privateKey);
1142
- if(privateKey.asymmetricKeyType!=='ed25519'||publicKey.asymmetricKeyType!=='ed25519') throw new Error('slot-update trust key must be Ed25519');
1143
- const der=key=>Buffer.from(key.export({type:'spki',format:'der'})); const keyId=sha256Bytes(der(publicKey));
1144
- if(sha256Bytes(der(derived))!==keyId) throw new Error('slot-update private/public key mismatch');
1145
- return {privateKey,publicKey,keyId};
1146
- }
1147
- function signEnvelope(body,key){
1148
- const bodyBytes=Buffer.from(canonicalJson(body),'utf8'); const signature=cryptoSign(null,Buffer.concat([RECEIPT_DOMAIN,bodyBytes]),key.privateKey);
1149
- const envelope={body,body_sha256:sha256Bytes(bodyBytes),attestation:{algorithm:'ed25519',domain:'DEADBYTE-R27-SLOT-UPDATE-V1',key_id:key.keyId,signature_base64:signature.toString('base64')}};
1150
- envelope.receipt_sha256=sha256Bytes(Buffer.from(canonicalJson(envelope),'utf8'));
1151
- if(!cryptoVerify(null,Buffer.concat([RECEIPT_DOMAIN,bodyBytes]),key.publicKey,signature)) throw new Error('slot-update signature self-check failed');
1152
- return envelope;
1153
- }
1154
- export function verifySlotUpdateEnvelope(envelope,publicKey){
1155
- if(!envelope?.body||!envelope?.attestation) return false; const bodyBytes=Buffer.from(canonicalJson(envelope.body),'utf8');
1156
- if(sha256Bytes(bodyBytes)!==envelope.body_sha256) return false; const {receipt_sha256,...unsigned}=envelope;
1157
- if(sha256Bytes(Buffer.from(canonicalJson(unsigned),'utf8'))!==receipt_sha256) return false;
1158
- if(envelope.attestation.algorithm!=='ed25519'||envelope.attestation.domain!=='DEADBYTE-R27-SLOT-UPDATE-V1') return false;
1159
- const keyId=sha256Bytes(Buffer.from(publicKey.export({type:'spki',format:'der'}))); if(keyId!==envelope.attestation.key_id) return false;
1160
- return cryptoVerify(null,Buffer.concat([RECEIPT_DOMAIN,bodyBytes]),publicKey,Buffer.from(envelope.attestation.signature_base64,'base64'));
1161
- }
1162
- async function writeEnvelope(file,body,key){ const envelope=signEnvelope(body,key); await atomicBytes(file,Buffer.from(`${JSON.stringify(envelope,null,2)}\n`,'utf8')); return envelope; }
1163
- async function writeState(file,state,phase,extra={}){
1164
- let observed=null;
1165
- try { observed=await readJson(file); } catch(error) { if(error?.code!=='ENOENT') throw error; }
1166
- const observedRank=phaseRank(observed?.phase);
1167
- const requestedRank=phaseRank(phase);
1168
- const effectivePhase=observedRank>requestedRank?String(observed.phase):phase;
1169
- if(observed) Object.assign(state,observed);
1170
- Object.assign(state,extra,{phase:effectivePhase,updated_at_utc:new Date().toISOString()});
1171
- await atomicBytes(file,Buffer.from(`${JSON.stringify(state,null,2)}\n`,'utf8'));
1172
- }
1173
- function generationTuple(runtime){
1174
- if(!runtime) return null;
1175
- return {bridge:{pid:runtime.bridge.pid,instance_nonce:runtime.bridge.instance_nonce,creation_time_utc:runtime.bridge.creation_time_utc,image_sha256:runtime.bridge.image_sha256},
1176
- cloud:{pid:runtime.cloud.pid,instance_nonce:runtime.cloud.instance_nonce,creation_time_utc:runtime.cloud.creation_time_utc,image_sha256:runtime.cloud.image_sha256},
1177
- core:{pid:runtime.core.pid,parent_pid:runtime.core.parent_pid,creation_date:runtime.core.creation_date,
1178
- command_line:runtime.core.command_line,identity_basis:runtime.core.identity_basis}};
1179
- }
1180
- function publicPointer(pointer){
1181
- if(!pointer) return null;
1182
- return {schema:pointer.schema,release_id:pointer.release_id,version:pointer.version,manifest_sha256:pointer.manifest_sha256,
1183
- release_root:pointer.release_root,generation_root:pointer.generation_root,generation_manifest_sha256:pointer.generation_manifest_sha256,
1184
- launcher_sha256:pointer.launcher_sha256,config_path:pointer.config_path};
1185
- }
1186
-
1187
- function encodeSnapshot(snapshot){
1188
- if(!snapshot||typeof snapshot!=='object') throw new Error('snapshot object required');
1189
- return {exists:snapshot.exists===true,sha256:snapshot.sha256??null,bytes_base64:snapshot.exists&&snapshot.bytes?Buffer.from(snapshot.bytes).toString('base64'):null};
1190
- }
1191
- function decodeSnapshot(snapshot,label){
1192
- if(!snapshot||typeof snapshot!=='object'||typeof snapshot.exists!=='boolean') throw new Error(label+' snapshot invalid');
1193
- if(snapshot.exists){
1194
- if(typeof snapshot.bytes_base64!=='string') throw new Error(label+' snapshot bytes missing');
1195
- const bytes=Buffer.from(snapshot.bytes_base64,'base64'),actual=sha256Bytes(bytes);
1196
- if(snapshot.sha256!==actual) throw new Error(label+' snapshot SHA-256 mismatch');
1197
- return {exists:true,bytes,sha256:actual};
1198
- }
1199
- return {exists:false,bytes:null,sha256:null};
1200
- }
1201
- export function rollbackLauncherPlanFromHandoff(body,priorPointer,activePointer,stateRoot=DEFAULT_STATE_ROOT){
1202
- if(!body||typeof body!=='object') throw new Error('rollback handoff body required');
1203
- const handoffPrior=body.prior_pointer?parseActivePointer(JSON.stringify(body.prior_pointer)):null;
1204
- const handoffActive=body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
1205
- if(!samePublicPointer(handoffPrior,priorPointer)||!samePublicPointer(handoffActive,activePointer)) throw new Error('rollback handoff pointer binding mismatch');
1206
- const state=path.resolve(stateRoot);
1207
- const launchers={
1208
- ps1:path.resolve(String(body.launchers?.ps1??'')),
1209
- cmd:path.resolve(String(body.launchers?.cmd??'')),
1210
- top:path.resolve(String(body.launchers?.top??TOP_LAUNCHER)),
1211
- launcher_sha256:String(body.launchers?.launcher_sha256??''),
1212
- ps1Bytes:Buffer.from(String(body.launchers?.ps1_bytes??''),'base64'),
1213
- cmdBytes:Buffer.from(String(body.launchers?.cmd_bytes??''),'base64'),
1214
- snapshots:{
1215
- ps1:decodeSnapshot(body.launchers?.snapshots?.ps1,'rollback.launcher.ps1'),
1216
- cmd:decodeSnapshot(body.launchers?.snapshots?.cmd,'rollback.launcher.cmd'),
1217
- top:decodeSnapshot(body.launchers?.snapshots?.top,'rollback.launcher.top')
1218
- },
1219
- installed:true
583
+ if(!bootstrap) throw new Error('owner approval bootstrap is required');
584
+ if(typeof stateRoot!=='string'||!path.isAbsolute(stateRoot)) throw new Error('owner approval state root invalid');
585
+ if(!HEX64.test(String(codingPolicySha256??''))||
586
+ !HEX64.test(String(autonomousPolicySha256??''))||
587
+ !HEX64.test(String(expectedManifest??''))) throw new Error('owner approval digest inputs invalid');
588
+ const ids=assertAutonomousBootstrapRoots(autonomousPolicy,codingPolicy,codingPolicySha256);
589
+ if(ids.length!==1) throw new Error('owner approval requires exactly one project-scoped autonomous root');
590
+ const rootId=ids[0];
591
+ const projectRoot=codingPolicy.roots[rootId].path;
592
+ const trustRoot=path.resolve(stateRoot,'trust');
593
+ const ownerKeyPath=path.join(trustRoot,'r70-owner-public.pem');
594
+ const normalize=p=>process.platform==='win32'?path.resolve(p).toLowerCase():path.resolve(p);
595
+ const under=(file,root)=>{
596
+ const relative=path.relative(normalize(root),normalize(file));
597
+ return relative===''||(!relative.startsWith('..'+path.sep)&&relative!=='..'&&!path.isAbsolute(relative));
1220
598
  };
1221
- if(launchers.ps1!==path.resolve(launcherPath(state))||
1222
- launchers.cmd!==path.resolve(path.join(state,'bin','DEADBYTE-MCP.cmd'))||
1223
- launchers.top!==path.resolve(TOP_LAUNCHER)) throw new Error('rollback launcher paths are not canonical');
1224
- if(!HEX64.test(launchers.launcher_sha256)||sha256Bytes(launchers.ps1Bytes)!==launchers.launcher_sha256) throw new Error('rollback successor launcher bytes/hash mismatch');
1225
- if(launchers.launcher_sha256!==activePointer.launcher_sha256) throw new Error('rollback successor launcher hash does not match active pointer');
1226
- if(!launchers.snapshots.ps1.exists||launchers.snapshots.ps1.sha256!==priorPointer.launcher_sha256) throw new Error('rollback predecessor launcher snapshot does not match signed prior pointer');
1227
- return launchers;
1228
- }
1229
- async function restoreRollbackLaunchers(plan){
1230
- await restoreFile(plan.ps1,plan.snapshots.ps1);
1231
- await restoreFile(plan.cmd,plan.snapshots.cmd);
1232
- await restoreFile(plan.top,plan.snapshots.top);
1233
- if(await sha256File(plan.ps1)!==plan.snapshots.ps1.sha256) throw new Error('rollback predecessor launcher restore hash mismatch');
1234
- return plan;
1235
- }
1236
- const UPDATE_PHASE_RANK=Object.freeze({
1237
- initial:0,candidate_prepared:10,old_runtime_verified:20,shutdown_handoff_armed:25,supervisor_spawned:30,
1238
- authority_revoked:40,old_runtime_stopped:50,workspace_ready:60,launcher_installed:70,pointer_committed:80,
1239
- fresh_runtime_verified:90,authority_verified:100,completed:110,superseded:120,failed:1000,failed_receipted:1001,failed_unreceipted:1002,precondition_rejected:1003
1240
- });
1241
- function phaseRank(phase){ return UPDATE_PHASE_RANK[String(phase)]??-1; }
1242
- function activeTransitionPhase(phase){
1243
- const rank=phaseRank(phase);
1244
- return rank>=phaseRank('shutdown_handoff_armed')&&rank<phaseRank('completed')&&rank<phaseRank('failed');
1245
- }
1246
- function samePublicPointer(a,b){
1247
- if(!a&&!b) return true;
1248
- if(!a||!b) return false;
1249
- return canonicalJson(publicPointer(a))===canonicalJson(publicPointer(b));
1250
- }
1251
- function statePointer(state,key){
1252
- if(!state?.[key]) return null;
1253
- return parseActivePointer(JSON.stringify(state[key]));
1254
- }
1255
- async function spawnDetachedSupervisor({script,handoffPath,cwd}){
1256
- const absoluteScript=path.resolve(script),absoluteHandoff=path.resolve(handoffPath);
1257
- const stat=await lstat(absoluteScript);
1258
- if(!stat.isFile()||stat.isSymbolicLink()) throw new Error('detached updater supervisor script must be a regular file');
1259
- const child=await new Promise((resolve,reject)=>{
1260
- const proc=spawn(process.execPath,[absoluteScript,'--resume-handoff',absoluteHandoff],{
1261
- cwd:path.resolve(cwd),detached:true,windowsHide:true,stdio:'ignore'
1262
- });
1263
- const onError=error=>reject(error);
1264
- proc.once('error',onError);
1265
- proc.once('spawn',()=>{
1266
- proc.removeListener('error',onError);
1267
- proc.unref();
1268
- resolve(proc);
1269
- });
1270
- });
1271
- if(!child.pid||child.pid<1) throw new Error('detached updater supervisor did not return a valid PID');
1272
- return {pid:child.pid,script_sha256:await sha256File(absoluteScript)};
1273
- }
1274
- async function acquireSupervisorLease(handoffPath){
1275
- const lockDir=path.resolve(handoffPath)+'.lock',lockFile=path.join(lockDir,'owner.json');
1276
- for(let attempt=0;attempt<2;attempt+=1){
1277
- try{
1278
- await mkdir(lockDir,{recursive:false});
1279
- await atomicBytes(lockFile,Buffer.from(JSON.stringify({pid:process.pid,handoff:path.resolve(handoffPath),started_at_utc:new Date().toISOString()})+'\n','utf8'));
1280
- return async()=>{ await rm(lockDir,{recursive:true,force:true}); };
1281
- }catch(error){
1282
- if(error?.code!=='EEXIST') throw error;
1283
- let owner=null; try{ owner=await readJson(lockFile); }catch{}
1284
- const ownerPid=Number(owner?.pid),needle=path.resolve(handoffPath).replaceAll('/','\\\\').toLowerCase();
1285
- const alive=Number.isInteger(ownerPid)&&ownerPid>0&&nodeProcesses().some(item=>
1286
- Number(item.ProcessId)===ownerPid&&String(item.CommandLine??'').replaceAll('/','\\\\').toLowerCase().includes(needle)
1287
- );
1288
- if(alive) throw new Error('slot update supervisor already active pid='+ownerPid);
1289
- await rm(lockDir,{recursive:true,force:true});
1290
- }
1291
- }
1292
- throw new Error('unable to acquire slot update supervisor lease');
1293
- }
1294
- export function parseResumeHandoffArgs(argv){
1295
- let handoff=null;
1296
- for(let index=0;index<argv.length;index+=1){
1297
- const arg=argv[index];
1298
- if(arg!=='--resume-handoff') throw new Error("unknown supervisor argument '"+arg+"'");
1299
- if(handoff!==null) throw new Error("duplicate supervisor argument '--resume-handoff'");
1300
- const value=argv[++index];
1301
- if(!value) throw new Error('--resume-handoff requires a value');
1302
- handoff=path.resolve(value);
1303
- }
1304
- if(!handoff) throw new Error('--resume-handoff is required');
1305
- return {handoff};
1306
- }
1307
- async function readHandoffEnvelope(handoffPath,stateRoot){
1308
- const envelope=await readJson(handoffPath),key=await loadUpdateKey(stateRoot);
1309
- if(!verifySlotUpdateEnvelope(envelope,key.publicKey)) throw new Error('slot-update handoff signature invalid');
1310
- if(envelope.body?.schema!==HANDOFF_SCHEMA) throw new Error('slot-update handoff schema invalid');
1311
- const body=envelope.body;
1312
- if(typeof body.update_id!=='string'||!HEX64.test(String(body.supervisor_script_sha256??''))||
1313
- !path.isAbsolute(String(body.supervisor_script??''))||!path.isAbsolute(String(body.state_path??''))) {
1314
- throw new Error('slot-update handoff identity invalid');
1315
- }
1316
- const actualSupervisorHash=await sha256File(path.resolve(body.supervisor_script));
1317
- if(actualSupervisorHash!==body.supervisor_script_sha256) throw new Error('slot-update handoff supervisor hash drift');
1318
- return {envelope,body,key};
1319
- }
1320
- async function writeHandoff({stateRoot,state,journal,statePath,evidenceDir,key,pointerInfo,newPointer,predecessor,priorStatus,workspace,launchers,stableMcpUrl,mcpUrlSnapshot,controlCtx}){
1321
- const supervisorScript=path.join(path.resolve(newPointer.release_root),'scripts','runtime-self-update.mjs');
1322
- const supervisorScriptSha256=await sha256File(supervisorScript);
1323
- const handoffPath=path.join(path.resolve(evidenceDir),'handoff.json');
1324
- const body={
1325
- schema:HANDOFF_SCHEMA,update_id:state.update_id,created_at_utc:new Date().toISOString(),
1326
- state_root:path.resolve(stateRoot),state_path:path.resolve(statePath),handoff_path:path.resolve(handoffPath),
1327
- supervisor_script:path.resolve(supervisorScript),supervisor_script_sha256:supervisorScriptSha256,
1328
- prior_pointer:publicPointer(pointerInfo.pointer),new_pointer:publicPointer(newPointer),predecessor_mode:predecessor.mode,
1329
- old_runtime:generationTuple(predecessor.runtime),
1330
- prior_authority:{
1331
- process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,
1332
- desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed
1333
- },
1334
- pointer_snapshot:encodeSnapshot(pointerInfo.snapshot),
1335
- workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode,moved:workspace.moved===true,created:workspace.created===true},
1336
- launchers:{
1337
- ps1:launchers.ps1,cmd:launchers.cmd,top:launchers.top,launcher_sha256:launchers.launcher_sha256,
1338
- ps1_bytes:Buffer.from(launchers.ps1Bytes).toString('base64'),cmd_bytes:Buffer.from(launchers.cmdBytes).toString('base64'),
1339
- snapshots:{
1340
- ps1:encodeSnapshot(launchers.snapshots.ps1),cmd:encodeSnapshot(launchers.snapshots.cmd),top:encodeSnapshot(launchers.snapshots.top)
1341
- }
1342
- },
1343
- stable_mcp_url:stableMcpUrl?{path:stableMcpUrl.path,sha256:stableMcpUrl.sha256,origin:stableMcpUrl.origin,changed:stableMcpUrl.changed===true}:null,
1344
- mcp_url_snapshot:stableMcpUrl?.changed?encodeSnapshot(mcpUrlSnapshot):null,
1345
- restore_standing_grants:state.restore_standing_grants===true
1346
- };
1347
- const receipt=await writeEnvelope(handoffPath,body,key);
1348
- await writeState(statePath,journal,'shutdown_handoff_armed',{
1349
- handoff_path:handoffPath,handoff_receipt_sha256:receipt.receipt_sha256,
1350
- supervisor_script:supervisorScript,supervisor_script_sha256:supervisorScriptSha256
599
+ // Coding commands execute as the owner OS principal, not inside a
600
+ // filesystem sandbox. Project-scoped write checks cannot protect a trust
601
+ // anchor while any selected Coding root can execute arbitrary scripts.
602
+ for(const root of Object.values(codingPolicy.roots)){
603
+ if(root?.exec===true) throw new Error('owner approval cannot trust an anchor with unsandboxed Coding execution');
604
+ if(root?.write===true&&typeof root.path==='string'&&path.isAbsolute(root.path)&&
605
+ under(ownerKeyPath,root.path)) throw new Error('owner trust key is writable by Coding policy');
606
+ }
607
+ const trustStat=await lstat(trustRoot);
608
+ const keyStat=await lstat(ownerKeyPath);
609
+ if(!trustStat.isDirectory()||trustStat.isSymbolicLink()||
610
+ !keyStat.isFile()||keyStat.isSymbolicLink()||
611
+ normalize(await realpath(trustRoot))!==normalize(trustRoot)||
612
+ normalize(await realpath(ownerKeyPath))!==normalize(ownerKeyPath)){
613
+ throw new Error('owner trust key must be a regular file below a real trust directory');
614
+ }
615
+ const ownerPublicKey=await readFile(ownerKeyPath);
616
+ const pinned=await readPinnedJsonPolicy(bootstrap,'owner approval');
617
+ return verifyProjectOwnerApproval({
618
+ approval:pinned.policy,ownerPublicKey,
619
+ expected:{rootId,projectRoot,codingPolicySha256,autonomousPolicySha256,
620
+ reviewedArtifactSha256:expectedManifest},
621
+ now
1351
622
  });
1352
- return {path:handoffPath,body,receipt};
1353
- }
1354
- async function parseCurrentPointer(file){
1355
- if(!existsSync(file)) return null;
1356
- return parseActivePointer(await readFile(file,'utf8'));
1357
- }
1358
- async function ensurePointerCommitted(pointerFile,priorPointer,newPointer){
1359
- const current=await parseCurrentPointer(pointerFile);
1360
- if(samePublicPointer(current,newPointer)) return {committed:true,already:true};
1361
- if(!samePublicPointer(current,priorPointer)) throw new Error('active pointer changed unexpectedly before slot commit');
1362
- await atomicWriteActivePointer(pointerFile,newPointer);
1363
- const verify=await parseCurrentPointer(pointerFile);
1364
- if(!samePublicPointer(verify,newPointer)) throw new Error('active pointer commit readback mismatch');
1365
- return {committed:true,already:false};
1366
- }
1367
- async function coreGone(runtime){
1368
- return !runtime||!(await nodeProcesses()).some(item=>
1369
- Number(item.ProcessId)===Number(runtime.core.pid)&&String(item.CreationDate??'')===String(runtime.core.creation_date)
1370
- );
1371
- }
1372
- async function runtimeAlreadyOnline(ctx,oldRuntime){
1373
- const statusText=controllerCaptured(ctx,'status',15000),status=parseControllerStatus(statusText);
1374
- if(!status.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) return null;
1375
- const bridge=await readLedger(ctx,'bridge'),cloud=await readLedger(ctx,'cloud'),core=coreProcess(ctx.core_root,bridge);
1376
- if(oldRuntime?.bridge&&sameGeneration(oldRuntime.bridge,bridge)) throw new Error('fresh bridge generation gate failed');
1377
- if(oldRuntime?.cloud&&sameGeneration(oldRuntime.cloud,cloud)) throw new Error('fresh cloud generation gate failed');
1378
- if(oldRuntime?.core&&oldRuntime.core.pid===core.pid&&oldRuntime.core.creation_date===core.creation_date) {
1379
- throw new Error('fresh downstream core generation gate failed');
1380
- }
1381
- return {status,status_text:statusText.trim(),bridge,cloud,core};
1382
- }
1383
- async function ensureFreshRuntime(ctx,oldRuntime){
1384
- let existing=null;
1385
- try{ existing=await runtimeAlreadyOnline(ctx,oldRuntime); }
1386
- catch(error){ if(/generation gate/.test(String(error?.message??''))) throw error; }
1387
- return existing??await startAndVerify(ctx,oldRuntime);
1388
- }
1389
- async function ensureAuthorityRevoked(ctx,prior){
1390
- if(!prior.process_armed&&!prior.coding_armed&&!prior.autonomous_armed&&!prior.desktop_armed&&!prior.host_armed){
1391
- if(prior.online&&prior.bridge_pid&&prior.cloud_pid) return {revoked:false,status:prior};
1392
- if(existsSync(path.join(ctx.runtime_root,'bridge.instance.json'))&&existsSync(path.join(ctx.runtime_root,'cloud.instance.json'))){
1393
- try{
1394
- const bridge=await readLedger(ctx,'bridge'),cloud=await readLedger(ctx,'cloud');
1395
- return {revoked:false,status:{online:true,offline:false,bridge_pid:bridge.pid,cloud_pid:cloud.pid,local_ready:true,
1396
- process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false}};
1397
- }catch{}
1398
- }
1399
- const clean=cleanOfflinePredecessor(ctx);
1400
- return {revoked:false,status:clean};
1401
- }
1402
- const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
1403
- if(status.host_armed) throw new Error('host plane armed during authority revoke recovery');
1404
- if(!status.process_armed&&!status.coding_armed&&!status.autonomous_armed&&!status.desktop_armed) {
1405
- return {revoked:prior.process_armed||prior.coding_armed||prior.autonomous_armed||prior.desktop_armed,status};
1406
- }
1407
- return revokeAuthority(ctx,status);
1408
- }
1409
- function handoffPriorPointer(body){
1410
- return body.prior_pointer?parseActivePointer(JSON.stringify(body.prior_pointer)):null;
1411
- }
1412
- function handoffNewPointer(body){
1413
- return body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
1414
- }
1415
- async function verifyCompletedUpdateReceipt(body,state,statePath,key){
1416
- const candidate=handoffNewPointer(body),prior=handoffPriorPointer(body);
1417
- if(!candidate||!samePublicPointer(candidate,statePointer(state,'new_pointer'))||!samePublicPointer(prior,statePointer(state,'prior_pointer'))) throw new Error('completed update handoff pointer binding mismatch');
1418
- if(state.expected_version!==candidate.version||state.expected_manifest_sha256!==candidate.manifest_sha256) throw new Error('completed update journal subject mismatch');
1419
- const expectedPath=path.join(path.dirname(statePath),'receipt.json');
1420
- if(typeof state.receipt_path!=='string'||!path.isAbsolute(state.receipt_path)||path.resolve(state.receipt_path)!==expectedPath||!HEX64.test(String(state.receipt_sha256??''))) throw new Error('completed update receipt reference invalid');
1421
- const info=await lstat(expectedPath);
1422
- if(!info.isFile()||info.isSymbolicLink()) throw new Error('completed update receipt must be a regular file');
1423
- const receipt=await readJson(expectedPath);
1424
- if(!verifySlotUpdateEnvelope(receipt,key.publicKey)||receipt.receipt_sha256!==state.receipt_sha256) throw new Error('completed update receipt signature or digest mismatch');
1425
- const subject=receipt.body;
1426
- if(subject.schema!==UPDATE_SCHEMA||subject.rollback_performed!==false||subject.update_id!==body.update_id||subject.release_version!==candidate.version||subject.candidate_manifest_sha256!==candidate.manifest_sha256||
1427
- !samePublicPointer(statePointer(subject,'active_pointer'),candidate)||!samePublicPointer(statePointer(subject,'prior_pointer'),prior)||
1428
- subject.release_slot?.release_id!==candidate.release_id||subject.release_slot?.release_root!==candidate.release_root||subject.release_slot?.manifest_sha256!==candidate.manifest_sha256||
1429
- subject.generation?.generation_root!==candidate.generation_root||subject.generation?.generation_manifest_sha256!==candidate.generation_manifest_sha256||subject.launcher?.sha256!==candidate.launcher_sha256) throw new Error('completed update receipt subject binding mismatch');
1430
- return {receipt_path:expectedPath,receipt_sha256:receipt.receipt_sha256};
1431
- }
1432
- async function resumeSlotUpdate({handoffPath}){
1433
- const absoluteHandoff=path.resolve(handoffPath),probe=await readJson(absoluteHandoff);
1434
- const stateRoot=path.resolve(String(probe?.body?.state_root??path.dirname(path.dirname(path.dirname(path.dirname(absoluteHandoff))))));
1435
- const {body,key}=await readHandoffEnvelope(absoluteHandoff,stateRoot);
1436
- const statePath=path.resolve(body.state_path),releaseState=await readJson(statePath);
1437
- if(releaseState.schema!=='deadbyte.r27-slot-update-state.v1'||releaseState.update_id!==body.update_id) throw new Error('handoff state binding mismatch');
1438
- let phase=String(releaseState.phase??'');
1439
- if(phase==='completed'){
1440
- const verifiedReceipt=await verifyCompletedUpdateReceipt(body,releaseState,statePath,key);
1441
- return {ok:true,resumed:false,completed:true,update_id:body.update_id,state_path:statePath,
1442
- ...verifiedReceipt,historical_receipt_verified:true,current_runtime_verified:false};
1443
- }
1444
- if(phase.startsWith('failed')) throw new Error('slot update already failed at phase='+phase);
1445
- const priorPointer=handoffPriorPointer(body),candidatePointer=handoffNewPointer(body);
1446
- const statePrior=statePointer(releaseState,'prior_pointer'),stateCandidate=statePointer(releaseState,'new_pointer');
1447
- if(!samePublicPointer(priorPointer,statePrior)||!samePublicPointer(candidatePointer,stateCandidate)) throw new Error('handoff pointer binding mismatch');
1448
- if(!candidatePointer) throw new Error('handoff candidate pointer missing');
1449
- const pointerFile=path.join(stateRoot,'active-release.json'),currentPointer=await parseCurrentPointer(pointerFile);
1450
- if(!samePublicPointer(currentPointer,priorPointer)&&!samePublicPointer(currentPointer,candidatePointer)) throw new Error('active pointer is neither handoff predecessor nor candidate');
1451
- const pointerInfo={file:pointerFile,pointer:currentPointer,snapshot:decodeSnapshot(body.pointer_snapshot,'active pointer')};
1452
- const ctx=slotContext(stateRoot,priorPointer),newCtx=slotContext(stateRoot,candidatePointer);
1453
- const priorStatus={
1454
- process_armed:body.prior_authority?.process_armed===true,coding_armed:body.prior_authority?.coding_armed===true,
1455
- autonomous_armed:body.prior_authority?.autonomous_armed===true,desktop_armed:body.prior_authority?.desktop_armed===true,
1456
- host_armed:body.prior_authority?.host_armed===true
1457
- };
1458
- if(priorStatus.autonomous_armed&&!priorStatus.coding_armed) throw new Error('invalid prior authority in handoff');
1459
- const oldRuntime=body.old_runtime?{bridge:body.old_runtime.bridge??null,cloud:body.old_runtime.cloud??null,core:body.old_runtime.core??null}:null;
1460
- if(oldRuntime?.core&&(!Number.isInteger(Number(oldRuntime.core.pid))||String(oldRuntime.core.creation_date??'').length<1)) throw new Error('handoff old runtime core identity invalid');
1461
- const launchers={
1462
- ps1:path.resolve(String(body.launchers?.ps1??'')),cmd:path.resolve(String(body.launchers?.cmd??'')),top:path.resolve(String(body.launchers?.top??TOP_LAUNCHER)),
1463
- launcher_sha256:String(body.launchers?.launcher_sha256??''),
1464
- ps1Bytes:Buffer.from(String(body.launchers?.ps1_bytes??''),'base64'),cmdBytes:Buffer.from(String(body.launchers?.cmd_bytes??''),'base64'),
1465
- snapshots:{
1466
- ps1:decodeSnapshot(body.launchers?.snapshots?.ps1,'launcher.ps1'),cmd:decodeSnapshot(body.launchers?.snapshots?.cmd,'launcher.cmd'),top:decodeSnapshot(body.launchers?.snapshots?.top,'launcher.top')
1467
- },
1468
- installed:phaseRank(phase)>=phaseRank('launcher_installed')
1469
- };
1470
- if(!HEX64.test(launchers.launcher_sha256)||sha256Bytes(launchers.ps1Bytes)!==launchers.launcher_sha256) throw new Error('handoff launcher bytes/hash mismatch');
1471
- const workspace={
1472
- source:path.resolve(String(body.workspace?.source??releaseState.workspace?.source??'')),
1473
- target:path.resolve(String(body.workspace?.target??releaseState.workspace?.target??'')),
1474
- mode:String(body.workspace?.mode??releaseState.workspace?.mode??''),
1475
- moved:releaseState.workspace?.moved===true,created:releaseState.workspace?.created===true
1476
- };
1477
- if(!['existing','target-existing','create','rename'].includes(workspace.mode)) throw new Error('handoff workspace mode invalid');
1478
- const stableMcpUrl=body.stable_mcp_url?{...body.stable_mcp_url}:releaseState.stable_mcp_url?{...releaseState.stable_mcp_url}:null;
1479
- const mcpUrlSnapshot=body.mcp_url_snapshot?decodeSnapshot(body.mcp_url_snapshot,'MCP URL'):{exists:false,bytes:null,sha256:null};
1480
- const cleanup=await acquireSupervisorLease(absoluteHandoff);
1481
- const journal=releaseState;
1482
- let pointerCommitted=phaseRank(phase)>=phaseRank('pointer_committed')||samePublicPointer(currentPointer,candidatePointer);
1483
- let stopped=phaseRank(phase)>=phaseRank('old_runtime_stopped');
1484
- try{
1485
- if(phaseRank(phase)<phaseRank('supervisor_spawned')){
1486
- await writeState(statePath,journal,'supervisor_spawned',{supervisor_pid:process.pid,supervisor_script:body.supervisor_script,supervisor_script_sha256:body.supervisor_script_sha256});
1487
- phase='supervisor_spawned';
1488
- }
1489
- let revoked=phaseRank(phase)>=phaseRank('authority_revoked')||journal.revoked_before_switch===true;
1490
- let newRuntime=null,stabilization=null;
1491
-
1492
- if(phaseRank(phase)<phaseRank('authority_revoked')){
1493
- const revoke=await ensureAuthorityRevoked(ctx,priorStatus);
1494
- revoked=revoke.revoked;
1495
- await writeState(statePath,journal,'authority_revoked',{revoked_before_switch:revoked});
1496
- phase='authority_revoked';
1497
- }
1498
- if(!stopped){
1499
- if(!oldRuntime){
1500
- cleanOfflinePredecessor(ctx);
1501
- await writeState(statePath,journal,'old_runtime_stopped',{stop_output:'recovery observed predecessor has no captured runtime and clean offline evidence'});
1502
- }else{
1503
- const status=parseControllerStatus(controllerCaptured(ctx,'status',15000)),gone=await coreGone(oldRuntime);
1504
- if(status.offline&&gone){
1505
- await writeState(statePath,journal,'old_runtime_stopped',{stop_output:'recovery observed predecessor already OFFLINE'});
1506
- }else{
1507
- const stoppedResult=await stopAndVerify(ctx,oldRuntime);
1508
- await writeState(statePath,journal,'old_runtime_stopped',{stop_output:stoppedResult.output});
1509
- }
1510
- }
1511
- stopped=true; phase='old_runtime_stopped';
1512
- }
1513
- if(phaseRank(phase)<phaseRank('workspace_ready')){
1514
- await applyWorkspacePlan(workspace);
1515
- await writeState(statePath,journal,'workspace_ready',{workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode,moved:workspace.moved===true,created:workspace.created===true}});
1516
- phase='workspace_ready';
1517
- }else{
1518
- workspace.moved=journal.workspace?.moved===true; workspace.created=journal.workspace?.created===true;
1519
- }
1520
- if(phaseRank(phase)<phaseRank('launcher_installed')){
1521
- await installLaunchers(launchers);
1522
- await writeState(statePath,journal,'launcher_installed',{launcher_sha256:launchers.launcher_sha256,launcher_path:launchers.ps1,top_launcher:launchers.top});
1523
- phase='launcher_installed';
1524
- }
1525
- if(!pointerCommitted){
1526
- await ensurePointerCommitted(pointerFile,priorPointer,candidatePointer);
1527
- pointerCommitted=true;
1528
- await writeState(statePath,journal,'pointer_committed',{active_pointer:publicPointer(candidatePointer)});
1529
- phase='pointer_committed';
1530
- }
1531
- await verifyActiveInstallation(stateRoot,candidatePointer);
1532
- newRuntime=await ensureFreshRuntime(newCtx,oldRuntime);
1533
- stabilization=await stabilizeRuntime(newCtx,newRuntime);
1534
- await writeState(statePath,journal,'fresh_runtime_verified',{new_runtime:generationTuple(newRuntime),stabilization});
1535
- phase='fresh_runtime_verified';
1536
-
1537
- const finalAuthority=await restoreStandingAuthority(newCtx,priorStatus,journal.restore_standing_grants===true);
1538
- await writeState(statePath,journal,'authority_verified',{final_authority:{
1539
- process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,
1540
- desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed
1541
- }});
1542
- phase='authority_verified';
1543
-
1544
- const bodyReceipt={
1545
- schema:UPDATE_SCHEMA,update_id:body.update_id,release_version:journal.expected_version,candidate_manifest_sha256:journal.expected_manifest_sha256,
1546
- completed_at_utc:new Date().toISOString(),activation_boundary:'active-release-pointer',
1547
- mechanism:'immutable-release-slot-plus-atomic-pointer-rename-plus-detached-supervisor-handoff',
1548
- predecessor_mode:journal.predecessor_mode??body.predecessor_mode,prior_pointer:publicPointer(priorPointer),active_pointer:publicPointer(candidatePointer),
1549
- release_slot:journal.release_slot,generation:journal.generation,
1550
- launcher:{path:launchers.ps1,sha256:launchers.launcher_sha256,top_path:launchers.top},
1551
- workspace:{root:workspace.target,migrated:workspace.moved===true},development_root:journal.development_root,
1552
- host_repin:journal.host_repin??{requested:false,repinned:false},coding_repin:journal.coding_repin??{requested:false,repinned:false},
1553
- old_runtime:journal.old_runtime??body.old_runtime,new_runtime:generationTuple(newRuntime),stabilization,
1554
- authority:{
1555
- prior:priorStatus,revoked_before_switch:revoked,restore_requested:journal.restore_standing_grants===true,desktop_restore_policy:'explicit-rearm-required',
1556
- final:{
1557
- process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,
1558
- desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed
1559
- }
1560
- },
1561
- rollback_performed:false
1562
- };
1563
- const receiptPath=path.join(path.dirname(statePath),'receipt.json'),receipt=await writeEnvelope(receiptPath,bodyReceipt,key);
1564
- await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
1565
- return {ok:true,resumed:true,completed:true,update_id:body.update_id,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,
1566
- pointer:candidatePointer,new_runtime:newRuntime,final_authority:finalAuthority,workspace_root:workspace.target};
1567
- }catch(error){
1568
- let controllerStartFailure=null,controllerStartFailurePreservationError=null;
1569
- if(pointerCommitted){
1570
- try{
1571
- controllerStartFailure=await preserveControllerStartFailure(newCtx,path.dirname(statePath));
1572
- const claimed=/evidence_sha256=([0-9a-f]{64})/.exec(String(error?.message??error));
1573
- if(claimed&&(!controllerStartFailure||controllerStartFailure.sha256!==claimed[1])){
1574
- controllerStartFailurePreservationError='controller start failure evidence claim mismatch expected='+claimed[1]+' preserved='+(controllerStartFailure?.sha256??'missing');
1575
- }
1576
- }catch(preserveError){ controllerStartFailurePreservationError=String(preserveError?.message??preserveError); }
1577
- }
1578
- const rollback={attempted:false,new_runtime_stopped:false,pointer_restored:false,launchers_restored:false,workspace_restored:false,mcp_url_restored:false,old_runtime_restarted:false,authority_restored:false,errors:[]};
1579
- try{
1580
- if(pointerCommitted){
1581
- rollback.attempted=true;
1582
- try{ controllerCaptured(newCtx,'stop',30000); rollback.new_runtime_stopped=true; }catch(stopError){ rollback.errors.push('stop-new:'+stopError.message); }
1583
- }
1584
- const currentAfter=await parseCurrentPointer(pointerFile);
1585
- if(samePublicPointer(currentAfter,candidatePointer)){
1586
- try{ await restoreFile(pointerFile,pointerInfo.snapshot); rollback.pointer_restored=true; }catch(pointerError){ rollback.errors.push('pointer:'+pointerError.message); }
1587
- }
1588
- if(stableMcpUrl?.changed&&mcpUrlSnapshot){
1589
- try{ await restoreFile(stableMcpUrl.path,mcpUrlSnapshot); rollback.mcp_url_restored=true; }catch(mcpUrlError){ rollback.errors.push('mcp-url:'+mcpUrlError.message); }
1590
- }
1591
- if(phaseRank(journal.phase)>=phaseRank('launcher_installed')||launchers.installed){
1592
- try{ await rollbackLaunchers({...launchers,installed:true}); rollback.launchers_restored=true; }catch(launcherError){ rollback.errors.push('launcher:'+launcherError.message); }
1593
- }
1594
- if(phaseRank(journal.phase)>=phaseRank('workspace_ready')||workspace.moved||workspace.created){
1595
- try{ rollback.workspace_restored=await rollbackWorkspacePlan(workspace); }catch(workspaceError){ rollback.errors.push('workspace:'+workspaceError.message); }
1596
- }
1597
- if(stopped&&oldRuntime&&rollback.pointer_restored){
1598
- try{ await startAndVerify(ctx,oldRuntime); rollback.old_runtime_restarted=true; }catch(startError){ rollback.errors.push('restart-old:'+startError.message); }
1599
- }
1600
- if((!stopped||rollback.old_runtime_restarted)&&journal.restore_standing_grants===true){
1601
- try{ await restoreStandingAuthority(ctx,priorStatus,true); rollback.authority_restored=true; }catch(authorityError){ rollback.errors.push('restore-authority:'+authorityError.message); }
1602
- }
1603
- }catch(rollbackError){ rollback.errors.push('rollback:'+rollbackError.message); }
1604
- const failure={
1605
- schema:FAILURE_SCHEMA,update_id:body.update_id,release_version:journal.expected_version,candidate_manifest_sha256:journal.expected_manifest_sha256,
1606
- failed_at_utc:new Date().toISOString(),phase:journal.phase??phase,error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
1607
- controller_start_failure:controllerStartFailure,controller_start_failure_preservation_error:controllerStartFailurePreservationError,
1608
- predecessor_mode:journal.predecessor_mode??body.predecessor_mode,prior_pointer:publicPointer(priorPointer),attempted_pointer:publicPointer(candidatePointer),
1609
- old_runtime:journal.old_runtime??body.old_runtime,rollback
1610
- };
1611
- await writeState(statePath,journal,'failed',{failure,rollback}).catch(()=>{});
1612
- let failureReceipt=null;
1613
- try{
1614
- const failurePath=path.join(path.dirname(statePath),'failure-receipt.json');
1615
- failureReceipt=await writeEnvelope(failurePath,failure,key);
1616
- await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});
1617
- }catch(receiptError){
1618
- rollback.errors.push('failure-receipt:'+receiptError.message);
1619
- await writeState(statePath,journal,'failed_unreceipted',{rollback}).catch(()=>{});
1620
- }
1621
- const wrapped=new Error('R27 slot update failed in detached supervisor: '+(error?.message??error));
1622
- wrapped.cause=error; wrapped.statePath=statePath; wrapped.failureReceiptPath=failureReceipt?path.join(path.dirname(statePath),'failure-receipt.json'):null; throw wrapped;
1623
- }finally{
1624
- await cleanup();
1625
- }
1626
- }
1627
- export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROOT}={}){
1628
- stateRoot=resolveStateRoot(stateRoot);
1629
- const evidenceRoot=path.join(path.resolve(stateRoot),'evidence','updates');
1630
- if(!existsSync(evidenceRoot)) return {ok:true,launched:0,updates:[]};
1631
- const entries=await readdir(evidenceRoot,{withFileTypes:true});
1632
- const pointerPath=path.join(path.resolve(stateRoot),'active-release.json');
1633
- const activePointer=existsSync(pointerPath)?await parseCurrentPointer(pointerPath):null;
1634
- let launched=0;const updates=[];
1635
- for(const entry of entries){
1636
- if(!entry.isDirectory()) continue;
1637
- const statePath=path.join(evidenceRoot,entry.name,'state.json');
1638
- if(!existsSync(statePath)) continue;
1639
- let state;try{ state=await readJson(statePath); }catch{ continue; }
1640
- if(!activeTransitionPhase(state?.phase)) continue;
1641
- if(state?.failure || existsSync(path.join(path.dirname(statePath),'failure-receipt.json'))) continue;
1642
- const pendingPointer=statePointer(state,'new_pointer');
1643
- if(activePointer&&pendingPointer&&samePublicPointer(activePointer,pendingPointer)){
1644
- await writeState(statePath,state,'superseded',{superseded_at_utc:new Date().toISOString(),superseded_by_pointer:publicPointer(activePointer)});
1645
- continue;
1646
- }
1647
- const handoffPath=String(state?.handoff_path??'');
1648
- if(!handoffPath||!path.isAbsolute(handoffPath)||!existsSync(handoffPath)) continue;
1649
- const handoff=await readJson(handoffPath);
1650
- if(handoff?.body?.schema!==HANDOFF_SCHEMA) continue;
1651
- const script=path.resolve(String(handoff.body.supervisor_script??''));
1652
- if(await sha256File(script)!==handoff.body.supervisor_script_sha256) throw new Error('interrupted update supervisor hash drift: '+state.update_id);
1653
- const result=await spawnDetachedSupervisor({script,handoffPath,cwd:path.dirname(script)});
1654
- launched+=1;updates.push({update_id:state.update_id,state_path:statePath,handoff_path:handoffPath,supervisor_pid:result.pid});
1655
- await atomicBytes(path.join(path.dirname(statePath),'recovery-launch.json'),Buffer.from(JSON.stringify({update_id:state.update_id,launched_at_utc:new Date().toISOString(),supervisor_pid:result.pid})+'\n','utf8'));
1656
- }
1657
- return {ok:true,launched,updates};
1658
- }
1659
-
1660
- export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,hostPolicyBootstrap=null,codingPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
1661
- // R64 grants have no signed revocation epoch. The historical boolean restore
1662
- // cannot prove continuity, and must never materialize a successor grant.
1663
- if(restoreStandingGrants)throw new Error('R65 standing grant auto-restore disabled: explicit reauthorization required');
1664
- stateRoot=resolveStateRoot(stateRoot);
1665
- if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
1666
- const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
1667
- const key=await loadUpdateKey(state),updateId='r27-'+expectedManifest.slice(0,16)+'-'+Date.now().toString(36)+'-'+randomBytes(4).toString('hex');
1668
- const evidenceDir=path.join(state,'evidence','updates',updateId),statePath=path.join(evidenceDir,'state.json'),failurePath=path.join(evidenceDir,'failure-receipt.json');
1669
- await mkdir(evidenceDir,{recursive:true});
1670
- const journal={schema:'deadbyte.r27-slot-update-state.v1',update_id:updateId,stage_root:stageRoot,development_root:development,
1671
- source_root:sourceRoot?path.resolve(sourceRoot):null,expected_version:expectedVersion,expected_manifest_sha256:expectedManifest,
1672
- restore_standing_grants:restoreStandingGrants,recover_offline_predecessor:recoverOfflinePredecessor,created_at_utc:new Date().toISOString()};
1673
- await writeState(statePath,journal,'initial');
1674
- let pointerInfo=null,control=null,predecessor=null,oldRuntime=null,priorStatus=null,authorityPreparation=null,workspace=null,launchers=null,newPointer=null;
1675
- let stableMcpUrl=null,mcpUrlSnapshot=null;
1676
- let mutationBegan=false;
1677
- try{
1678
- pointerInfo=await loadPointerIfPresent(state);
1679
- const stageVerified=await verifyPackageRoot(stageRoot,expectedVersion,expectedManifest,'stage');
1680
- const developmentVerified=await verifyPackageRoot(development,expectedVersion,expectedManifest,'development');
1681
- control=await loadControlInputs({stateRoot:state,sourceRoot,pointerInfo,bridgeBootstrap});
1682
- // Read-only predecessor authority and process identity must be safe before
1683
- // materializing a slot or changing any stable runtime file.
1684
- await capturePredecessor(control.ctx,recoverOfflinePredecessor,{allowHostDisarm:false});
1685
- mutationBegan=true;
1686
- const slot=await materializeReleaseSlot({source:stageRoot,stateRoot:state,version:expectedVersion,manifestSha256:expectedManifest});
1687
- const hostBootstrap=await prepareHostPolicyBootstrap(hostPolicyBootstrap);if(hostBootstrap) control.hostPolicy=hostBootstrap.policy;
1688
- const codingBootstrap=await prepareCodingPolicyBootstrap(codingPolicyBootstrap);if(codingBootstrap) control.codingPolicy=codingBootstrap.policy;
1689
- const processBootstrap=await prepareProcessPolicyBootstrap(processPolicyBootstrap);
1690
- if(processBootstrap){ control.processPolicy=processBootstrap.policy;control.config.ProcessRuntimeEnabled=true; }
1691
- const desktopBootstrap=await prepareDesktopPolicyBootstrap(desktopPolicyBootstrap);if(desktopBootstrap) control.desktopPolicy=desktopBootstrap.policy;
1692
- const semanticBootstrap=await prepareSemanticLspConfigBootstrap(semanticLspBootstrap);
1693
- if(semanticBootstrap){ control.config.SemanticLspConfigFile=semanticBootstrap.path;control.config.SemanticLspConfigSha256=semanticBootstrap.sha256; }
1694
- if(enableMachineFs){
1695
- if(control.config.CodingRuntimeEnabled!==true) throw new Error('MachineFsRuntimeEnabled requires CodingRuntimeEnabled');
1696
- control.config.MachineFsRuntimeEnabled=true;
1697
- }
1698
- if(enableObservation){
1699
- if(control.config.MachineRuntimeEnabled===false) throw new Error('ObservationRuntimeEnabled requires MachineRuntimeEnabled');
1700
- if(control.config.CodingRuntimeEnabled!==true) throw new Error('ObservationRuntimeEnabled requires CodingRuntimeEnabled');
1701
- control.config.ObservationRuntimeEnabled=true;
1702
- }
1703
- if(enableDesktop){
1704
- if(!control.desktopPolicy) throw new Error('DesktopRuntimeEnabled requires pinned desktop policy bootstrap or existing policy');
1705
- control.config.DesktopRuntimeEnabled=true;
1706
- }
1707
- control.config=await prepareReleaseBridgeSpec(control.config,bridgeBootstrap,slot.release_root);
1708
- control.config=await prepareNamedTunnelCloudSpec(control.config,{releaseRoot:slot.release_root,bootstrap:cloudTransport});
1709
- control.config=await prepareAutonomousSupervisorSpec(control.config,{releaseRoot:slot.release_root,enable:enableAutonomousSupervisor});
1710
- authorityPreparation=await prepareAuthorityPolicies({hostPolicy:control.hostPolicy,codingPolicy:control.codingPolicy},{commandPins:hostCommandPins});
1711
- control.hostPolicy=authorityPreparation.hostPolicy;control.codingPolicy=authorityPreparation.codingPolicy;
1712
- const stableMcp=await ensureStableMcpPath({stateRoot:state,sourceRoot:pointerInfo.pointer?null:sourceRoot});
1713
- control.config.McpPathFile=stableMcp.path;
1714
- if(cloudTransport?.publicOrigin){
1715
- const mcpUrlFile=path.join(state,'bridge','mcp-url.txt');mcpUrlSnapshot=await snapshotFile(mcpUrlFile);
1716
- stableMcpUrl=await ensureStableMcpUrl({stateRoot:state,publicOrigin:cloudTransport.publicOrigin,mcpPathFile:stableMcp.path});
1717
- control.config.McpUrlFile=stableMcpUrl.path;control.config.McpUrlFileSha256=stableMcpUrl.sha256;
1718
- }else if(control.config.Cloud?.Transport==='deadbyte.named-tunnel.v2'){
1719
- if(typeof control.config.McpUrlFile!=='string'||!path.isAbsolute(control.config.McpUrlFile)||!HEX64.test(String(control.config.McpUrlFileSha256??''))) throw new Error('existing named tunnel v2 requires pinned McpUrlFile before update');
1720
- const pinned=await verifyPinnedRegularFile(control.config.McpUrlFile,control.config.McpUrlFileSha256,'existing named tunnel MCP URL file');
1721
- control.config.McpUrlFile=pinned.path;control.config.McpUrlFileSha256=pinned.sha256;
1722
- }
1723
- workspace=await workspacePlan(control.workspaceRoot,state);launchers=await launcherPlan(state);
1724
- const generation=await createGenerationBundle({stateRoot:state,releaseRoot:slot.release_root,developmentRoot:development,workspaceRoot:workspace.target,releaseId:slot.release_id,currentConfig:control.config,codingPolicy:control.codingPolicy,autonomousPolicy:control.autonomousPolicy,hostPolicy:control.hostPolicy,processPolicy:control.processPolicy,desktopPolicy:control.desktopPolicy});
1725
- newPointer={schema:'deadbyte.active-release.v1',release_id:slot.release_id,version:expectedVersion,manifest_sha256:expectedManifest,release_root:slot.release_root,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,launcher_sha256:launchers.launcher_sha256,config_path:generation.config_path};
1726
- parseActivePointer(JSON.stringify(newPointer));
1727
- await writeState(statePath,journal,'candidate_prepared',{prior_pointer:publicPointer(pointerInfo.pointer),stage:stageVerified,development:developmentVerified,
1728
- release_slot:slot,generation:{generation_id:generation.generation_id,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,files:generation.files},
1729
- workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode},stable_mcp_path:{path:stableMcp.path,sha256:stableMcp.sha256},
1730
- stable_mcp_url:stableMcpUrl?{path:stableMcpUrl.path,sha256:stableMcpUrl.sha256,origin:stableMcpUrl.origin,changed:stableMcpUrl.changed}:null,
1731
- bridge:{working_directory:control.config.Bridge.WorkingDirectory,script_path:control.config.Bridge.ScriptPath,script_sha256:control.config.Bridge.ScriptSha256},
1732
- host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},
1733
- new_pointer:publicPointer(newPointer)});
1734
- predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor,{allowHostDisarm:false});oldRuntime=predecessor.runtime;priorStatus=predecessor.status;
1735
- await writeState(statePath,journal,'old_runtime_verified',{predecessor_mode:predecessor.mode,old_runtime:generationTuple(oldRuntime),
1736
- prior_authority:{process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed}});
1737
- const handoff=await writeHandoff({stateRoot:state,state:journal,journal,statePath,evidenceDir,key,pointerInfo,newPointer,predecessor,priorStatus,workspace,launchers,stableMcpUrl,mcpUrlSnapshot,controlCtx:control.ctx});
1738
- const supervisor=await spawnDetachedSupervisor({script:handoff.body.supervisor_script,handoffPath:handoff.path,cwd:control.ctx.core_root});
1739
- return {ok:true,handed_off:true,completed:false,update_id:updateId,state_path:statePath,handoff_path:handoff.path,handoff_receipt_sha256:handoff.receipt.receipt_sha256,supervisor_pid:supervisor.pid,pointer:newPointer};
1740
- }catch(error){
1741
- const failure={schema:FAILURE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,failed_at_utc:new Date().toISOString(),
1742
- result:mutationBegan?'MUTATION_OUTCOME_REQUIRES_RECONCILIATION':'PRECONDITION_REJECTED',
1743
- phase:journal.phase??'unknown',error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
1744
- predecessor_mode:predecessor?.mode??null,prior_pointer:publicPointer(pointerInfo?.pointer??null),attempted_pointer:publicPointer(newPointer),old_runtime:generationTuple(oldRuntime),
1745
- rollback:{attempted:false,new_runtime_stopped:false,pointer_restored:false,launchers_restored:false,workspace_restored:false,mcp_url_restored:false,old_runtime_restarted:false,authority_restored:false,errors:[]}};
1746
- await writeState(statePath,journal,'failed',{failure}).catch(()=>{});
1747
- let failureReceipt=null;
1748
- try{ failureReceipt=await writeEnvelope(failurePath,failure,key);await writeState(statePath,journal,mutationBegan?'failed_receipted':'precondition_rejected',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});}
1749
- catch(receiptError){ await writeState(statePath,journal,'failed_unreceipted',{failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{}); }
1750
- const wrapped=new Error('R27 slot update handoff failed: '+(error?.message??error));wrapped.cause=error;wrapped.statePath=statePath;wrapped.failureReceiptPath=failureReceipt?failurePath:null;throw wrapped;
1751
- }
1752
623
  }
1753
624
 
1754
- export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_STATE_ROOT}){
1755
- stateRoot=resolveStateRoot(stateRoot);
1756
- if(process.platform!=='win32') throw new Error('R31 slot rollback is Windows-only');
1757
- const state=path.resolve(stateRoot),receiptFile=path.resolve(activationReceipt);
1758
- const key=await loadUpdateKey(state);
1759
- const activationEnvelope=await readJson(receiptFile);
1760
- if(!verifySlotUpdateEnvelope(activationEnvelope,key.publicKey)) throw new Error('activation receipt signature invalid');
1761
- if(activationEnvelope.body?.schema!==UPDATE_SCHEMA||activationEnvelope.body?.rollback_performed!==false) throw new Error('activation receipt is not one successful slot activation');
1762
- const priorPointer=parseActivePointer(JSON.stringify(activationEnvelope.body.prior_pointer));
1763
- const activePointer=parseActivePointer(JSON.stringify(activationEnvelope.body.active_pointer));
1764
- const pointerInfo=await loadPointerIfPresent(state);
1765
- if(!pointerInfo.pointer||canonicalJson(publicPointer(pointerInfo.pointer))!==canonicalJson(publicPointer(activePointer))) throw new Error('rollback current pointer does not match signed activation pointer');
1766
-
1767
- const handoffPath=path.join(path.dirname(receiptFile),'handoff.json');
1768
- const {body:handoffBody}=await readHandoffEnvelope(handoffPath,state);
1769
- if(handoffBody.update_id!==activationEnvelope.body.update_id) throw new Error('rollback handoff update identity mismatch');
1770
- const rollbackLaunchers=rollbackLauncherPlanFromHandoff(handoffBody,priorPointer,activePointer,state);
1771
- await verifyStoredInstallation(state,priorPointer);
1772
-
1773
- const currentCtx=slotContext(state,pointerInfo.pointer),currentRuntime=await captureRuntime(currentCtx);
1774
- const revoked=await revokeAuthority(currentCtx,currentRuntime.status);
1775
- const rollbackId=`r31-rollback-${activationEnvelope.receipt_sha256.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
1776
- const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json'),failurePath=path.join(evidenceDir,'failure-receipt.json');
1777
- await mkdir(evidenceDir,{recursive:true});
1778
- const journal={schema:'deadbyte.r31-slot-rollback-state.v1',rollback_id:rollbackId,activation_receipt:path.resolve(receiptFile),
1779
- activation_receipt_sha256:activationEnvelope.receipt_sha256,handoff_path:handoffPath,
1780
- from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),created_at_utc:new Date().toISOString()};
1781
- await writeState(statePath,journal,'verified');
1782
- let stopped=false,launchersRestored=false,pointerCommitted=false,priorCtx=null,priorRuntime=null;
1783
- try {
1784
- await stopAndVerify(currentCtx,currentRuntime); stopped=true;
1785
- await writeState(statePath,journal,'successor_stopped');
1786
- await restoreRollbackLaunchers(rollbackLaunchers); launchersRestored=true;
1787
- await writeState(statePath,journal,'predecessor_launcher_restored',{launcher_sha256:priorPointer.launcher_sha256});
1788
- await atomicWriteActivePointer(pointerInfo.file,priorPointer); pointerCommitted=true;
1789
- await verifyActiveInstallation(state,priorPointer);
1790
- await writeState(statePath,journal,'prior_pointer_restored',{active_pointer:publicPointer(priorPointer)});
1791
- priorCtx=slotContext(state,priorPointer);
1792
- priorRuntime=await startAndVerify(priorCtx,currentRuntime);
1793
- const stabilization=await stabilizeRuntime(priorCtx,priorRuntime);
1794
- const finalStatus=parseControllerStatus(controllerCaptured(priorCtx,'status',15000));
1795
- if(finalStatus.process_armed||finalStatus.coding_armed||finalStatus.autonomous_armed||finalStatus.desktop_armed||finalStatus.host_armed) throw new Error('rollback predecessor authority must remain disarmed');
1796
- const body={schema:ROLLBACK_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
1797
- completed_at_utc:new Date().toISOString(),from_pointer:publicPointer(activePointer),active_pointer:publicPointer(priorPointer),
1798
- launcher_restored:true,old_runtime:generationTuple(currentRuntime),new_runtime:generationTuple(priorRuntime),stabilization,
1799
- authority_revoked:revoked.revoked,rollback_performed:true};
1800
- const receipt=await writeEnvelope(receiptPath,body,key);
1801
- await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
1802
- return {ok:true,rollback_id:rollbackId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,pointer:priorPointer,new_runtime:priorRuntime};
1803
- } catch(error){
1804
- const recovery={attempted:false,prior_runtime_stopped:false,successor_launcher_restored:false,successor_pointer_restored:false,successor_runtime_restarted:false,errors:[]};
1805
- if(stopped){
1806
- recovery.attempted=true;
1807
- if(pointerCommitted&&priorCtx){ try { controllerCaptured(priorCtx,'stop',30000); recovery.prior_runtime_stopped=true; } catch(recoveryError){ recovery.errors.push(`stop-prior:${recoveryError.message}`); } }
1808
- if(launchersRestored){
1809
- try { await installLaunchers(rollbackLaunchers); recovery.successor_launcher_restored=true; }
1810
- catch(recoveryError){ recovery.errors.push(`restore-successor-launcher:${recoveryError.message}`); }
1811
- }
1812
- if(pointerCommitted){
1813
- try { await atomicWriteActivePointer(pointerInfo.file,activePointer); await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; }
1814
- catch(recoveryError){ recovery.errors.push(`restore-pointer:${recoveryError.message}`); }
1815
- }else if(recovery.successor_launcher_restored){
1816
- try { await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; }
1817
- catch(recoveryError){ recovery.errors.push(`verify-successor-pointer:${recoveryError.message}`); }
1818
- }
1819
- try { await startAndVerify(currentCtx,currentRuntime); recovery.successor_runtime_restarted=true; } catch(recoveryError){ recovery.errors.push(`restart-successor:${recoveryError.message}`); }
1820
- }
1821
- const failure={schema:ROLLBACK_FAILURE_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
1822
- failed_at_utc:new Date().toISOString(),phase:journal.phase??'unknown',from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),
1823
- error:{name:error?.name??'Error',message:String(error?.message??error)},recovery};
1824
- await writeState(statePath,journal,'failed',{error:failure.error,recovery}).catch(()=>{});
1825
- let failureReceipt=null;
1826
- try{
1827
- failureReceipt=await writeEnvelope(failurePath,failure,key);
1828
- await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});
1829
- }catch(receiptError){
1830
- recovery.errors.push('failure-receipt:'+receiptError.message);
1831
- await writeState(statePath,journal,'failed_unreceipted',{recovery,failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{});
1832
- }
1833
- const wrapped=new Error(`R31 slot rollback failed: ${error?.message??error}`); wrapped.cause=error; wrapped.statePath=statePath; wrapped.failureReceiptPath=failureReceipt?failurePath:null; throw wrapped;
1834
- }
1835
- }
1836
-
1837
- export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEFAULT_STATE_ROOT}={}){
1838
- stateRoot=resolveStateRoot(stateRoot);
1839
- if(argv.includes('--rollback-receipt')){
1840
- const args=parseRollbackArgs(argv);
1841
- const result=await performSlotRollback({activationReceipt:args.activationReceipt,stateRoot});
1842
- console.log('R31_SLOT_ROLLBACK_PASS');
1843
- console.log('ROLLBACK_ID '+result.rollback_id); console.log('RECEIPT_SHA256 '+result.receipt_sha256);
1844
- console.log('STATE_PATH '+result.state_path); console.log('ACTIVE_RELEASE '+result.pointer.release_id);
1845
- return result;
1846
- }
1847
- if(argv.includes('--resume-handoff')){
1848
- const args=parseResumeHandoffArgs(argv);
1849
- const result=await resumeSlotUpdate({handoffPath:args.handoff});
1850
- console.log(result.completed?'R27_SLOT_UPDATE_RESUME_PASS':'R27_SLOT_UPDATE_RESUME_DEFERRED');
1851
- console.log('UPDATE_ID '+result.update_id);
1852
- console.log('STATE_PATH '+result.state_path);
1853
- if(result.receipt_sha256) console.log('RECEIPT_SHA256 '+result.receipt_sha256);
1854
- if(result.historical_receipt_verified===true) console.log('VERIFICATION_SCOPE historical-signed-receipt; current-runtime-not-observed');
1855
- return result;
1856
- }
1857
- if(argv.includes('--recover-interrupted')){
1858
- const result=await recoverInterruptedSlotUpdates({stateRoot});
1859
- console.log('R27_SLOT_UPDATE_RECOVERY launched='+result.launched);
1860
- for(const update of result.updates) console.log('UPDATE_ID '+update.update_id+' SUPERVISOR_PID '+update.supervisor_pid);
1861
- return result;
1862
- }
1863
- const args=parseUpdateArgs(argv);
1864
- const result=await performSlotUpdate({
1865
- stage:args.stage,developmentRoot:args.developmentRoot,expectedVersion:args.expectedVersion,expectedManifest:args.expectedManifest,
1866
- sourceRoot:args.sourceRoot,stateRoot,restoreStandingGrants:args.restoreStandingGrants,recoverOfflinePredecessor:args.recoverOfflinePredecessor,
1867
- cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,
1868
- hostPolicyBootstrap:args.hostPolicyBootstrap,codingPolicyBootstrap:args.codingPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,
1869
- semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,enableObservation:args.enableObservation,
1870
- enableDesktop:args.enableDesktop,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins
1871
- });
1872
- if(result.handed_off){
1873
- console.log('R27_SLOT_UPDATE_HANDOFF version='+args.expectedVersion);
1874
- console.log('UPDATE_ID '+result.update_id);
1875
- console.log('HANDOFF_PATH '+result.handoff_path);
1876
- console.log('HANDOFF_RECEIPT_SHA256 '+result.handoff_receipt_sha256);
1877
- console.log('SUPERVISOR_PID '+result.supervisor_pid);
1878
- }else{
1879
- console.log('R27_SLOT_UPDATE_PASS version='+args.expectedVersion);
1880
- console.log('UPDATE_ID '+result.update_id);
1881
- console.log('RECEIPT_SHA256 '+result.receipt_sha256);
1882
- console.log('STATE_PATH '+result.state_path);
1883
- console.log('ACTIVE_RELEASE '+result.pointer.release_id);
1884
- }
1885
- return result;
1886
- }
1887
- const invoked=process.argv[1]&&path.resolve(process.argv[1])===path.resolve(fileURLToPath(import.meta.url));
1888
- if(invoked){
1889
- try { await runSlotUpdateCli(); }
1890
- catch(error){
1891
- console.error(`R27_SLOT_UPDATE_FAIL ${error?.message??error}`);
1892
- if(error?.statePath) console.error(`STATE_PATH ${error.statePath}`);
1893
- if(error?.failureReceiptPath) console.error(`FAILURE_RECEIPT_PATH ${error.failureReceiptPath}`);
1894
- process.exitCode=1;
1895
- }
1896
- }
625
+ export async function prepareDesktopPolicyBootstrap(bootstrap){
626
+ if(!bootstrap) return null;
627
+ const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'desktop policy');
628
+ const policy=await readJson(pinned.path);
629
+ if(policy?.schema!=='deadbyte.desktop-policy.v1'||policy?.enabled!==true) throw new Error('desktop policy schema/enabled mismatch');
630
+ for(const name of ['observer','input','grounder']){
631
+ const provider=policy[name];
632
+ if(!provider||typeof provider.executable!=='string'||!path.isAbsolute(provider.executable)||!HEX64.test(String(provider.sha256??''))) {
633
+ throw new Error(`desktop policy ${name} pin invalid`);
634
+ }
635
+ }
636
+ if(!Array.isArray(policy.allowed_actions)||policy.allowed_actions.length<1) throw new Error('desktop policy allowed_actions invalid');
637
+ return {...pinned,policy};
638
+ }
639
+
640
+ export async function prepareSemanticLspConfigBootstrap(bootstrap){
641
+ if(!bootstrap) return null;
642
+ const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'semantic LSP config');
643
+ const config=await readJson(pinned.path);
644
+ if(config?.schema!=='deadbyte.semantic-lsp-config.v1'||!Array.isArray(config.servers)) throw new Error('semantic LSP config schema invalid');
645
+ return {...pinned,config};
646
+ }
647
+
648
+ async function preparePinnedCommandPolicy(policy,{commandPins=[],schema,label}={}){
649
+ if(!policy) return {policy:null,repinned:[],matchedCommandIds:[]};
650
+ const acceptedSchemas=Array.isArray(schema)?schema:[schema];
651
+ if(!acceptedSchemas.includes(policy.schema)||policy.enabled!==true||!policy.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
652
+ throw new Error(`${label} policy schema/enabled/commands mismatch`);
653
+ }
654
+ const requested=new Map();
655
+ for(const pin of commandPins){
656
+ const commandId=String(pin?.commandId??''),sha256=String(pin?.sha256??'').toLowerCase();
657
+ if(!/^[A-Za-z0-9._-]{1,64}$/.test(commandId)||!HEX64.test(sha256)) throw new Error(`${label} command pin contract invalid`);
658
+ if(requested.has(commandId)) throw new Error(`duplicate ${label} command pin '${commandId}'`);
659
+ requested.set(commandId,sha256);
660
+ }
661
+ const next=structuredClone(policy),repinned=[],matchedCommandIds=[];
662
+ for(const [commandId,command] of Object.entries(next.commands)){
663
+ const executable=String(command?.executable??''),expected=String(command?.executable_sha256??'').toLowerCase();
664
+ if(!path.isAbsolute(executable)||!HEX64.test(expected)) throw new Error(`${label} command '${commandId}' executable pin contract invalid`);
665
+ const absolute=path.resolve(executable),stat=await lstat(absolute);
666
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`${label} command '${commandId}' executable must be a regular file`);
667
+ const actual=await sha256File(absolute),explicit=requested.get(commandId);
668
+ if(explicit) matchedCommandIds.push(commandId);
669
+ if(actual!==expected){
670
+ if(!explicit) throw new Error(`${label} command '${commandId}' executable pin drift detected; explicit --host-command-pin ${commandId}=${actual} required`);
671
+ if(explicit!==actual) throw new Error(`${label} command '${commandId}' repin mismatch: supplied ${explicit}, observed ${actual}`);
672
+ command.executable_sha256=actual;
673
+ repinned.push({command_id:commandId,executable:absolute,prior_sha256:expected,new_sha256:actual});
674
+ } else if(explicit&&explicit!==actual){
675
+ throw new Error(`${label} command '${commandId}' repin mismatch: supplied ${explicit}, observed ${actual}`);
676
+ }
677
+ }
678
+ return {policy:next,repinned,matchedCommandIds};
679
+ }
680
+
681
+ export async function prepareHostPolicy(policy,{commandPins=[]}={}){
682
+ if(!policy&&commandPins.length) throw new Error('host command pins supplied but host policy is not configured');
683
+ const result=await preparePinnedCommandPolicy(policy,{commandPins,schema:['deadbyte.host-policy.v1','deadbyte.host-policy.v2'],label:'host'});
684
+ const matched=new Set(result.matchedCommandIds);
685
+ const unknown=commandPins.find(pin=>!matched.has(String(pin?.commandId??'')));
686
+ if(unknown) throw new Error(`unknown host command pin '${unknown.commandId}'`);
687
+ return {policy:result.policy,repinned:result.repinned};
688
+ }
689
+
690
+ export async function prepareAuthorityPolicies({hostPolicy,codingPolicy},{commandPins=[]}={}){
691
+ const host=await preparePinnedCommandPolicy(hostPolicy,{commandPins,schema:['deadbyte.host-policy.v1','deadbyte.host-policy.v2'],label:'host'});
692
+ const coding=await preparePinnedCommandPolicy(codingPolicy,{commandPins,schema:'deadbyte.coding-policy.v1',label:'coding'});
693
+ const matched=new Set([...host.matchedCommandIds,...coding.matchedCommandIds]);
694
+ const unknown=commandPins.find(pin=>!matched.has(String(pin?.commandId??'')));
695
+ if(unknown) throw new Error(`unknown authority command pin '${unknown.commandId}'`);
696
+ return {hostPolicy:host.policy,codingPolicy:coding.policy,hostRepinned:host.repinned,codingRepinned:coding.repinned};
697
+ }
698
+
699
+ export async function prepareNamedTunnelCloudSpec(config,{releaseRoot,bootstrap=null}={}){
700
+ const next=structuredClone(config),cloud=next.Cloud??{};
701
+ const useNamed=bootstrap!==null||cloud.Transport==='deadbyte.named-tunnel.v1'||cloud.Transport==='deadbyte.named-tunnel.v2';
702
+ if(!useNamed) return next;
703
+ const release=path.resolve(releaseRoot),hostPath=path.join(release,'bin','deadbyte-tunnel-host.exe');
704
+ const hostStat=await lstat(hostPath); if(!hostStat.isFile()||hostStat.isSymbolicLink()) throw new Error('named tunnel host must be a regular release file');
705
+ const hostSha256=await sha256File(hostPath);
706
+ if(bootstrap!==null){
707
+ const cloudflared=await verifyPinnedRegularFile(bootstrap.cloudflared.path,bootstrap.cloudflared.sha256,'named tunnel cloudflared');
708
+ const tokenFile=await verifyPinnedRegularFile(bootstrap.tokenFile.path,bootstrap.tokenFile.sha256,'named tunnel token file');
709
+ const childWorkingDirectory=path.resolve(bootstrap.childWorkingDirectory),workStat=await lstat(childWorkingDirectory);
710
+ if(!workStat.isDirectory()||workStat.isSymbolicLink()) throw new Error('named tunnel child working directory must be a real directory');
711
+ next.Cloud={...cloud,FilePath:hostPath,FileSha256:hostSha256,
712
+ Arguments:[cloudflared.path,tokenFile.path,childWorkingDirectory].map(quoteWindowsArgument).join(' '),
713
+ WorkingDirectory:release,Transport:'deadbyte.named-tunnel.v2',Dependencies:[
714
+ {Name:'cloudflared',Path:cloudflared.path,Sha256:cloudflared.sha256},
715
+ {Name:'tunnel-token',Path:tokenFile.path,Sha256:tokenFile.sha256}
716
+ ]};
717
+ } else {
718
+ if(cloud.Transport!=='deadbyte.named-tunnel.v2') throw new Error('legacy named tunnel transport requires explicit v2 bootstrap');
719
+ if(!Array.isArray(cloud.Dependencies)||cloud.Dependencies.length!==2) throw new Error('existing named tunnel dependency contract missing');
720
+ next.Cloud={...cloud,FilePath:hostPath,FileSha256:hostSha256,WorkingDirectory:release};
721
+ }
722
+ return next;
723
+ }
724
+ async function ensureStableMcpPath({stateRoot,sourceRoot}){
725
+ const bridgeDir=path.join(stateRoot,'bridge');
726
+ const tokenFile=path.join(bridgeDir,'path-token.txt');
727
+ let token='';
728
+ try { token=(await readFile(tokenFile,'utf8')).trim(); } catch(error){
729
+ if(error?.code!=='ENOENT') throw error;
730
+ if(!sourceRoot) throw new Error('bridge path token missing and no bootstrap source root supplied');
731
+ const legacy=(await readFile(path.join(sourceRoot,'controller','runtime','mcp-path.txt'),'utf8')).trim();
732
+ const match=/^\/mcp\/([0-9a-f]{64})$/.exec(legacy);
733
+ if(!match) throw new Error('legacy MCP path cannot seed stable token');
734
+ token=match[1];
735
+ await mkdir(bridgeDir,{recursive:true});
736
+ await atomicBytes(tokenFile,Buffer.from(`${token}\n`,'ascii'));
737
+ }
738
+ if(!HEX64.test(token)) throw new Error('bridge path token invalid');
739
+ const stableFile=path.join(bridgeDir,'mcp-path.txt');
740
+ const desired=Buffer.from(`/mcp/${token}\n`,'ascii');
741
+ if(existsSync(stableFile)){
742
+ const found=await readFile(stableFile);
743
+ if(!found.equals(desired)) throw new Error('stable MCP path file conflicts with token');
744
+ } else await atomicBytes(stableFile,desired);
745
+ return {path:stableFile,sha256:sha256Bytes(desired)};
746
+ }
747
+ export async function ensureStableMcpUrl({stateRoot,publicOrigin,mcpPathFile}){
748
+ const origin=normalizePersistentPublicOrigin(publicOrigin);
749
+ if(typeof mcpPathFile!=='string'||!path.isAbsolute(mcpPathFile)) throw new Error('stable MCP path file must be absolute');
750
+ const pathStat=await lstat(mcpPathFile);
751
+ if(!pathStat.isFile()||pathStat.isSymbolicLink()) throw new Error('stable MCP path file must be a regular file');
752
+ const mcpPath=(await readFile(mcpPathFile,'ascii')).trim();
753
+ if(!/^\/mcp\/[0-9a-f]{64}$/.test(mcpPath)) throw new Error('stable MCP path file is invalid');
754
+ const bridgeDir=path.join(path.resolve(stateRoot),'bridge'),stableFile=path.join(bridgeDir,'mcp-url.txt');
755
+ const desired=Buffer.from(`${origin}${mcpPath}\n`,'ascii');
756
+ const before=existsSync(stableFile)?await readFile(stableFile):null;
757
+ if(!before||!before.equals(desired)) await atomicBytes(stableFile,desired);
758
+ return {path:stableFile,sha256:sha256Bytes(desired),origin,changed:!before||!before.equals(desired)};
759
+ }
760
+
761
+ async function snapshotFile(file){
762
+ try { const bytes=await readFile(file); return {exists:true,bytes,sha256:sha256Bytes(bytes)}; }
763
+ catch(error){ if(error?.code==='ENOENT') return {exists:false,bytes:null,sha256:null}; throw error; }
764
+ }
765
+ async function restoreFile(file,snapshot){
766
+ if(snapshot.exists) await atomicBytes(file,snapshot.bytes);
767
+ else await rm(file,{force:true});
768
+ }
769
+
770
+ export function parseControllerStatus(text){
771
+ const number=re=>{const match=re.exec(text);return match?Number(match[1]):null;};
772
+ return {
773
+ online:/DEADBYTE MCP = ONLINE/.test(text),
774
+ offline:/DEADBYTE MCP = OFFLINE/.test(text),
775
+ bridge_pid:number(/Bridge PID\s*:\s*(\d+)\s*\[verified\]/),
776
+ cloud_pid:number(/Cloud PID\s*:\s*(\d+)\s*\[verified\]/),
777
+ local_ready:/Local ready\s*:\s*True/.test(text),
778
+ process_armed:/Process plane\s*:\s*ARMED \[standing-grant\]/.test(text),
779
+ coding_armed:/Coding plane\s*:\s*ARMED \[standing-grant\]/.test(text),
780
+ autonomous_armed:/Autonomous\s*:\s*ARMED \[standing-grant\]/.test(text),
781
+ desktop_armed:/Desktop plane\s*:\s*ARMED \[standing-grant\]/.test(text),
782
+ host_armed:/Host plane\s*:\s*ARMED \[standing-grant\]/.test(text)
783
+ };
784
+ }
785
+ function controlResult(exe,args,{cwd,env,timeoutMs=45000,stdio='pipe'}={}){
786
+ if(stdio==='ignore'){
787
+ const result=spawnSync(exe,args,{cwd,env:commandEnvironment(exe,env??process.env),windowsHide:true,timeout:timeoutMs,stdio:'ignore'});
788
+ return {status:result.status,signal:result.signal,error:result.error?{code:result.error.code??null,message:result.error.message}:null,stdout:'',stderr:''};
789
+ }
790
+ return runCaptured(exe,args,{cwd,env,timeoutMs});
791
+ }
792
+ export function legacyContext(sourceRoot){
793
+ const root=path.resolve(sourceRoot);
794
+ const candidates=['deadbyte-controller-r26.ps1','deadbyte-controller.ps1'];
795
+ const controller=candidates.map(name=>path.join(root,'controller',name)).find(existsSync);
796
+ if(!controller) throw new Error('legacy controller entry missing');
797
+ return {kind:'legacy',source_root:root,core_root:root,controller_path:controller,
798
+ cwd:root,runtime_root:path.join(root,'controller','runtime'),state_root:null};
799
+ }
800
+ function slotContext(stateRoot,pointer){
801
+ return {kind:'slot',source_root:null,core_root:path.resolve(pointer.release_root),
802
+ controller_path:path.join(pointer.release_root,'controller','deadbyte-controller.ps1'),
803
+ cwd:path.resolve(pointer.release_root),runtime_root:path.join(path.resolve(stateRoot),'runtime'),
804
+ state_root:path.resolve(stateRoot),pointer};
805
+ }
806
+ function launcherPath(stateRoot){ return path.join(path.resolve(stateRoot),'bin','DEADBYTE-MCP.ps1'); }
807
+ function controllerInvocation(ctx,command){
808
+ // Internal control-plane calls use the immutable slot controller directly. The stable launcher is
809
+ // user-facing and may lag during a slot switch; coupling supervisor state transitions to it creates
810
+ // a bootstrap dependency and can misreport a healthy predecessor as offline.
811
+ if(ctx.kind==='slot') return {
812
+ exe:powershellPath(),
813
+ args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],
814
+ cwd:ctx.cwd,
815
+ env:{...process.env,DEADBYTE_STATE_ROOT:resolveStateRoot(ctx.state_root),DEADBYTE_CONTROLLER_CONFIG:ctx.pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:ctx.runtime_root}
816
+ };
817
+ return {exe:powershellPath(),args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],cwd:ctx.cwd};
818
+ }
819
+ function controllerCaptured(ctx,command,timeoutMs=45000){
820
+ const call=controllerInvocation(ctx,command); const result=controlResult(call.exe,call.args,{cwd:call.cwd,env:call.env??process.env,timeoutMs});
821
+ return assertRun(result,`controller ${command}`);
822
+ }
823
+ export function runControllerStartNoCapture(ctx,timeoutMs=45000){
824
+ const call=controllerInvocation(ctx,'start');
825
+ return controlResult(call.exe,call.args,{cwd:call.cwd,env:call.env??process.env,timeoutMs,stdio:'ignore'});
826
+ }
827
+ export async function readControllerStartFailure(ctx){
828
+ const file=path.join(ctx.runtime_root,'controller-start-failure.json');
829
+ let bytes;
830
+ try { bytes=await readFile(file); }
831
+ catch(error){ if(error?.code==='ENOENT') return null; throw error; }
832
+ const text=bytes.toString('utf8').replace(/^\uFEFF/,'');
833
+ let value;
834
+ try { value=JSON.parse(text); } catch(error){ throw new Error(`controller start failure evidence JSON invalid: ${error.message}`); }
835
+ if(value?.schema!=='deadbyte.controller-start-failure.v1'||!['start','resume','restart'].includes(String(value?.command??''))||
836
+ typeof value?.message!=='string'||value.message.length<1||typeof value?.failed_at_utc!=='string') {
837
+ throw new Error('controller start failure evidence schema invalid');
838
+ }
839
+ return {path:file,sha256:sha256Bytes(bytes),...value};
840
+ }
841
+ export async function preserveControllerStartFailure(ctx,evidenceDir){
842
+ const evidence=await readControllerStartFailure(ctx);
843
+ if(!evidence) return null;
844
+ const source=path.join(ctx.runtime_root,'controller-start-failure.json');
845
+ const bytes=await readFile(source);
846
+ const sourceSha256=sha256Bytes(bytes);
847
+ if(sourceSha256!==evidence.sha256) throw new Error('controller start failure evidence changed before preservation');
848
+ const destination=path.join(path.resolve(evidenceDir),'controller-start-failure.json');
849
+ await atomicBytes(destination,bytes);
850
+ const persistedSha256=sha256Bytes(await readFile(destination));
851
+ if(persistedSha256!==sourceSha256) throw new Error('preserved controller start failure evidence SHA-256 mismatch');
852
+ return {path:destination,sha256:persistedSha256,schema:evidence.schema,command:evidence.command,
853
+ failed_at_utc:evidence.failed_at_utc,message:evidence.message};
854
+ }
855
+ async function readLedger(ctx,name){
856
+ const file=path.join(ctx.runtime_root,`${name}.instance.json`);
857
+ const value=JSON.parse((await readFile(file,'utf8')).replace(/^\uFEFF/,''));
858
+ if(value.schema!=='deadbyte.controller.v1'||value.component!==name||!Number.isInteger(Number(value.pid))||
859
+ !HEX64.test(String(value.instance_nonce))||!HEX64.test(String(value.image_sha256))) {
860
+ throw new Error(`${name} ledger identity invalid`);
861
+ }
862
+ return {schema:value.schema,component:value.component,instance_nonce:String(value.instance_nonce),
863
+ pid:Number(value.pid),creation_time_utc:String(value.creation_time_utc??''),image_path:String(value.image_path??''),
864
+ image_sha256:String(value.image_sha256),script_path:String(value.script_path??''),script_sha256:String(value.script_sha256??'')};
865
+ }
866
+ function sameGeneration(a,b){
867
+ return a.pid===b.pid&&a.instance_nonce===b.instance_nonce&&a.creation_time_utc===b.creation_time_utc&&a.image_sha256===b.image_sha256;
868
+ }
869
+ function activationGuardianOwnedByLineage(item,selfLineage){
870
+ const name=String(item?.Name??'').toLowerCase();
871
+ if(name!=='node.exe') return false;
872
+ const commandLine=String(item?.CommandLine??'');
873
+ if(!commandLine.toLowerCase().includes('remote-session-guardian.mjs')) return false;
874
+ const ownerMatch=/(?:^|\s)--owner-pid(?:=|\s+)(\d+)(?=\s|$)/i.exec(commandLine);
875
+ const nonceMatch=/(?:^|\s)--session-nonce(?:=|\s+)([0-9a-f]{64})(?=\s|$)/i.exec(commandLine);
876
+ if(!ownerMatch||!nonceMatch) return false;
877
+ const ownerPid=Number(ownerMatch[1]);
878
+ const parentPid=Number(item?.ParentProcessId);
879
+ return Number.isInteger(ownerPid)&&ownerPid>0&&parentPid===ownerPid&&selfLineage.has(ownerPid);
880
+ }
881
+ export function selectOfflineRecoveryBlockers(processes,currentPid=process.pid){
882
+ const list=(Array.isArray(processes)?processes:[processes]).filter(Boolean);
883
+ const byPid=new Map();
884
+ for(const item of list){
885
+ const pid=Number(item?.ProcessId);
886
+ if(Number.isInteger(pid)&&pid>0) byPid.set(pid,item);
887
+ }
888
+ const selfLineage=new Set();
889
+ let cursor=Number(currentPid);
890
+ while(Number.isInteger(cursor)&&cursor>0&&!selfLineage.has(cursor)){
891
+ selfLineage.add(cursor);
892
+ const item=byPid.get(cursor);
893
+ if(!item) break;
894
+ const parent=Number(item?.ParentProcessId);
895
+ if(!Number.isInteger(parent)||parent<1||parent===cursor) break;
896
+ cursor=parent;
897
+ }
898
+ const runtimeNames=new Set(['node.exe','cloudflared.exe','deadbyte-tunnel-host.exe']);
899
+ return list.filter(item=>{
900
+ const pid=Number(item?.ProcessId);
901
+ if(selfLineage.has(pid)) return false;
902
+ if(activationGuardianOwnedByLineage(item,selfLineage)) return false;
903
+ const name=String(item?.Name??'').toLowerCase();
904
+ if(!runtimeNames.has(name)) return false;
905
+ const commandLine=String(item?.CommandLine??'').toLowerCase();
906
+ return commandLine.includes('deadbyte-mcp')||commandLine.includes('mcp-server.mjs')||
907
+ commandLine.includes('runtime-self-update.mjs')||commandLine.includes('remote-session-guardian.mjs')||
908
+ commandLine.includes('deadbyte-tunnel-host.exe');
909
+ });
910
+ }
911
+ function cleanOfflinePredecessor(ctx){
912
+ const runtimeRoot=path.resolve(ctx.runtime_root);
913
+ const blockers=[
914
+ 'bridge.instance.json','cloud.instance.json',
915
+ 'process-grant.json','coding-grant.json','autonomous-grant.json',
916
+ 'desktop-grant.json','host-grant.json'
917
+ ].filter(name=>existsSync(path.join(runtimeRoot,name)));
918
+ if(blockers.length) throw new Error('offline predecessor recovery blocked by runtime state: '+blockers.join(','));
919
+
920
+ const command="Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,ExecutablePath,CommandLine | ConvertTo-Json -Compress";
921
+ const result=runCaptured(powershellPath(),['-NoLogo','-NoProfile','-Command',command],{timeoutMs:15000});
922
+ const text=assertRun(result,'runtime process query').trim();
923
+ if(text){
924
+ let parsed;
925
+ try { parsed=JSON.parse(text); } catch(error){ throw new Error('runtime process query JSON invalid: '+error.message); }
926
+ const relevant=selectOfflineRecoveryBlockers(Array.isArray(parsed)?parsed:[parsed],process.pid);
927
+ if(relevant.length) throw new Error('offline predecessor recovery blocked by DEADBYTE runtime processes: '+relevant.map(item=>String(item?.Name??'unknown')+'#'+String(item?.ProcessId??'?')).join(','));
928
+ }
929
+ return {
930
+ online:false,offline:true,bridge_pid:null,cloud_pid:null,local_ready:false,
931
+ process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false
932
+ };
933
+ }
934
+ function nodeProcesses(){
935
+ const command=`Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" | Select-Object ProcessId,ParentProcessId,CreationDate,ExecutablePath,CommandLine | ConvertTo-Json -Compress`;
936
+ const result=runCaptured(powershellPath(),['-NoLogo','-NoProfile','-Command',command],{timeoutMs:15000});
937
+ const text=assertRun(result,'node process query').trim(); if(!text) return [];
938
+ const parsed=JSON.parse(text); return Array.isArray(parsed)?parsed:[parsed];
939
+ }
940
+ export function selectCoreProcess(processes,coreRoot,bridgePid,verifiedBridge=null){
941
+ const needle=path.join(coreRoot,'src','mcp-server.mjs').replaceAll('/','\\').toLowerCase();
942
+ const children=processes.filter(item=>Number(item.ParentProcessId)===bridgePid);
943
+ const visible=children.filter(item=>String(item.CommandLine??'').length>0);
944
+ const matches=visible.filter(item=>String(item.CommandLine).replaceAll('/','\\').toLowerCase().includes(needle));
945
+ let item=null,identityBasis=null;
946
+ if(children.length===1&&visible.length===1&&matches.length===1){
947
+ item=matches[0]; identityBasis='command_line';
948
+ } else if(children.length===1&&visible.length===0){
949
+ if(verifiedBridge?.component!=='bridge'||verifiedBridge.pid!==bridgePid||!path.isAbsolute(String(verifiedBridge.script_path??''))||
950
+ !HEX64.test(String(verifiedBridge.script_sha256??''))) {
951
+ throw new Error(`opaque downstream core requires verified Bridge script binding for bridge ${bridgePid}`);
952
+ }
953
+ item=children[0]; identityBasis='verified_bridge_single_opaque_node_child';
954
+ } else {
955
+ throw new Error(`expected one unambiguous downstream core for bridge ${bridgePid}, found children=${children.length} visible_matches=${matches.length}`);
956
+ }
957
+ const pid=Number(item.ProcessId),creationDate=String(item.CreationDate??'');
958
+ if(!Number.isInteger(pid)||pid<1||creationDate.length<1) throw new Error(`downstream core identity incomplete for bridge ${bridgePid}`);
959
+ return {pid,parent_pid:Number(item.ParentProcessId),creation_date:creationDate,
960
+ executable_path:String(item.ExecutablePath??''),command_line:String(item.CommandLine??''),identity_basis:identityBasis};
961
+ }
962
+ function coreProcess(coreRoot,bridge){
963
+ return selectCoreProcess(nodeProcesses(),coreRoot,bridge.pid,bridge);
964
+ }
965
+ export async function waitForCoreProcess(coreRoot,bridge,{
966
+ timeoutMs=10000,
967
+ pollMs=100,
968
+ processProvider=nodeProcesses,
969
+ sleepFn=sleep,
970
+ nowFn=Date.now
971
+ }={}){
972
+ const bridgePid=Number(bridge?.pid);
973
+ if(!Number.isInteger(bridgePid)||bridgePid<1) throw new Error('downstream core wait requires verified bridge PID');
974
+ if(!Number.isFinite(timeoutMs)||timeoutMs<0) throw new Error('downstream core wait timeout invalid');
975
+ if(!Number.isFinite(pollMs)||pollMs<1) throw new Error('downstream core wait poll invalid');
976
+ if(typeof processProvider!=='function'||typeof sleepFn!=='function'||typeof nowFn!=='function') throw new Error('downstream core wait hooks invalid');
977
+ const started=Number(nowFn());
978
+ let lastError=null;
979
+ while(true){
980
+ try {
981
+ const processes=await processProvider();
982
+ return selectCoreProcess(processes,coreRoot,bridgePid,bridge);
983
+ } catch(error){
984
+ const message=String(error?.message??error);
985
+ const transient=`expected one unambiguous downstream core for bridge ${bridgePid}, found children=0 visible_matches=0`;
986
+ if(message!==transient) throw error;
987
+ lastError=error;
988
+ }
989
+ const elapsed=Math.max(0,Number(nowFn())-started);
990
+ if(elapsed>=timeoutMs){
991
+ throw new Error(`downstream core did not appear for bridge ${bridgePid} within ${timeoutMs}ms: ${String(lastError?.message??lastError)}`);
992
+ }
993
+ await sleepFn(Math.min(pollMs,Math.max(1,timeoutMs-elapsed)));
994
+ }
995
+ }
996
+ async function captureRuntime(ctx){
997
+ const statusText=controllerCaptured(ctx,'status',15000); const status=parseControllerStatus(statusText);
998
+ if(!status.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
999
+ if(status.host_armed) throw new Error('slot update requires host plane DISARMED');
1000
+ if(status.autonomous_armed&&!status.coding_armed) throw new Error('invalid runtime authority: autonomous armed without coding');
1001
+ const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
1002
+ return {status,status_text:statusText.trim(),bridge,cloud,core};
1003
+ }
1004
+ export async function capturePredecessor(ctx,recoverOfflinePredecessor,{allowHostDisarm=true}={}){
1005
+ let statusText,status;
1006
+ try{
1007
+ statusText=controllerCaptured(ctx,'status',15000);
1008
+ status=parseControllerStatus(statusText);
1009
+ }catch(error){
1010
+ if(!recoverOfflinePredecessor) throw error;
1011
+ try{
1012
+ const runtimeDir=ctx.runtime_root;
1013
+ const hasBridge=existsSync(path.join(runtimeDir,'bridge.instance.json'));
1014
+ const hasCloud=existsSync(path.join(runtimeDir,'cloud.instance.json'));
1015
+ if(hasBridge&&hasCloud){
1016
+ const armed=['process-grant.json','coding-grant.json','autonomous-grant.json','desktop-grant.json','host-grant.json']
1017
+ .some(name=>existsSync(path.join(runtimeDir,name)));
1018
+ if(armed) throw new Error('controller status failed while authority grant state is present');
1019
+ const bridge=await readLedger(ctx,'bridge');
1020
+ const cloud=await readLedger(ctx,'cloud');
1021
+ const core=coreProcess(ctx.core_root,bridge);
1022
+ const status={online:true,offline:false,bridge_pid:bridge.pid,cloud_pid:cloud.pid,local_ready:true,
1023
+ process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false};
1024
+ return {mode:'online-ledger-recovery',status,status_text:`controller status unavailable; verified runtime ledgers established: ${String(error?.message??error)}`,runtime:{status,status_text:'verified by controller ledgers',bridge,cloud,core}};
1025
+ }
1026
+ const offline=cleanOfflinePredecessor(ctx);
1027
+ return {mode:'offline-recovery',status:offline,status_text:`controller status unavailable; clean offline evidence established: ${String(error?.message??error)}`,runtime:null};
1028
+ }catch(recoveryError){
1029
+ throw new Error(`controller status failed and predecessor recovery was not applicable: ${String(error?.message??error)}; ${String(recoveryError?.message??recoveryError)}`);
1030
+ }
1031
+ }
1032
+ if(status.online&&status.bridge_pid&&status.cloud_pid&&status.local_ready){
1033
+ const runtime=await captureRuntime(ctx);
1034
+ return {mode:'online',status:runtime.status,status_text:runtime.status_text,runtime};
1035
+ }
1036
+ if(!recoverOfflinePredecessor) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
1037
+ if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery requires clean OFFLINE controller state: ${statusText}`);
1038
+ if(status.host_armed){
1039
+ if(!allowHostDisarm) throw new Error('offline predecessor recovery requires host plane DISARMED');
1040
+ controllerCaptured(ctx,'disarmhost',15000);
1041
+ statusText=controllerCaptured(ctx,'status',15000);
1042
+ status=parseControllerStatus(statusText);
1043
+ if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery changed controller state while revoking Host authority: ${statusText}`);
1044
+ if(status.host_armed) throw new Error('offline predecessor recovery requires host plane DISARMED');
1045
+ }
1046
+ if(status.autonomous_armed&&!status.coding_armed) throw new Error('invalid runtime authority: autonomous armed without coding');
1047
+ return {mode:'offline-recovery',status,status_text:statusText.trim(),runtime:null};
1048
+ }
1049
+ async function waitCoreGone(runtime,timeoutMs=10000){
1050
+ const started=Date.now();
1051
+ while(Date.now()-started<timeoutMs){
1052
+ const found=nodeProcesses().find(item=>Number(item.ProcessId)===runtime.core.pid&&String(item.CreationDate??'')===runtime.core.creation_date);
1053
+ if(!found) return true; await sleep(250);
1054
+ }
1055
+ return false;
1056
+ }
1057
+ async function startAndVerify(ctx,oldRuntime){
1058
+ let start=null;
1059
+ await rm(path.join(ctx.runtime_root,'controller-start-failure.json'),{force:true});
1060
+ for(let attempt=1;attempt<=3;attempt+=1){
1061
+ start=runControllerStartNoCapture(ctx,45000);
1062
+ if(!start.error&&start.status===0) break;
1063
+ if(attempt<3) await sleep(1500);
1064
+ }
1065
+ if(start.error||start.status!==0){
1066
+ let detail='';
1067
+ try {
1068
+ const evidence=await readControllerStartFailure(ctx);
1069
+ if(evidence) detail=` controller=${evidence.message} evidence_sha256=${evidence.sha256}`;
1070
+ } catch(error) { detail=` controller_evidence_error=${error.message}`; }
1071
+ throw new Error(`controller start transport failed status=${start.status} error=${start.error?.message??'none'}${detail}`);
1072
+ }
1073
+ let statusText=''; let status=null;
1074
+ const started=Date.now();
1075
+ while(Date.now()-started<45000){
1076
+ try { statusText=controllerCaptured(ctx,'status',15000); status=parseControllerStatus(statusText); if(status.online&&status.bridge_pid&&status.cloud_pid&&status.local_ready) break; }
1077
+ catch {} await sleep(500);
1078
+ }
1079
+ if(!status?.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) throw new Error(`new runtime failed ONLINE gate: ${statusText}`);
1080
+ const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=await waitForCoreProcess(ctx.core_root,bridge);
1081
+ if(oldRuntime?.bridge&&sameGeneration(oldRuntime.bridge,bridge)) throw new Error('fresh bridge generation gate failed');
1082
+ if(oldRuntime?.cloud&&sameGeneration(oldRuntime.cloud,cloud)) throw new Error('fresh cloud generation gate failed');
1083
+ if(oldRuntime?.core&&oldRuntime.core.pid===core.pid&&oldRuntime.core.creation_date===core.creation_date) throw new Error('fresh downstream core generation gate failed');
1084
+ return {status,status_text:statusText.trim(),bridge,cloud,core};
1085
+ }
1086
+ async function stabilizeRuntime(ctx,runtime){
1087
+ const started=Date.now(); let probes=0;
1088
+ while(Date.now()-started<RUNTIME_STABILITY_MS){
1089
+ await sleep(RUNTIME_STABILITY_POLL_MS);
1090
+ const statusText=controllerCaptured(ctx,'status',15000),status=parseControllerStatus(statusText);
1091
+ if(!status.online||!status.local_ready||status.bridge_pid!==runtime.bridge.pid||status.cloud_pid!==runtime.cloud.pid) {
1092
+ throw new Error(`new runtime lost stability gate: ${statusText}`);
1093
+ }
1094
+ const bridge=await readLedger(ctx,'bridge'),cloud=await readLedger(ctx,'cloud');
1095
+ if(!sameGeneration(runtime.bridge,bridge)||!sameGeneration(runtime.cloud,cloud)) throw new Error('new runtime generation changed during stability gate');
1096
+ probes++;
1097
+ }
1098
+ controllerCaptured(ctx,'test',60000);
1099
+ const finalText=controllerCaptured(ctx,'status',15000),final=parseControllerStatus(finalText);
1100
+ if(!final.online||!final.local_ready||final.bridge_pid!==runtime.bridge.pid||final.cloud_pid!==runtime.cloud.pid) {
1101
+ throw new Error(`new runtime failed post-public stability gate: ${finalText}`);
1102
+ }
1103
+ return {duration_ms:Date.now()-started,probes,public_mcp_gate:'passed'};
1104
+ }
1105
+ async function verifyPackageRoot(root,expectedVersion,expectedManifest,label){
1106
+ const absolute=path.resolve(root); const info=await lstat(absolute);
1107
+ if(!info.isDirectory()||info.isSymbolicLink()) throw new Error(`${label} root must be a real directory`);
1108
+ const manifestBytes=await readFile(path.join(absolute,'MANIFEST.SHA256'));
1109
+ const digest=sha256Bytes(manifestBytes); if(digest!==expectedManifest) throw new Error(`${label} manifest digest mismatch: ${digest}`);
1110
+ const entries=parseManifest(manifestBytes.toString('utf8')); await verifyManifestTree(absolute,entries);
1111
+ const pkg=await readJson(path.join(absolute,'package.json'));
1112
+ if(pkg?.name!=='deadbyte-mcp'||pkg?.version!==expectedVersion) throw new Error(`${label} package identity mismatch`);
1113
+ return {root:absolute,manifest_sha256:digest,entries:entries.size};
1114
+ }
1115
+ async function verifyStoredInstallation(stateRoot,pointer){
1116
+ const state=path.resolve(stateRoot); const expectedRelease=path.join(state,'releases',releaseId(pointer.version,pointer.manifest_sha256));
1117
+ if(path.resolve(pointer.release_root)!==path.resolve(expectedRelease)) throw new Error('active pointer release root is not content-addressed store path');
1118
+ const release=await verifyPackageRoot(pointer.release_root,pointer.version,pointer.manifest_sha256,'active release');
1119
+ const generation=await verifyGenerationBundle(pointer.generation_root,pointer.generation_manifest_sha256);
1120
+ const expectedConfig=path.join(path.resolve(pointer.generation_root),'controller-config.psd1');
1121
+ if(path.resolve(pointer.config_path)!==path.resolve(expectedConfig)) throw new Error('active pointer config path mismatch');
1122
+ return {release,generation};
1123
+ }
1124
+ async function verifyActiveInstallation(stateRoot,pointer){
1125
+ const state=path.resolve(stateRoot);
1126
+ const verified=await verifyStoredInstallation(state,pointer);
1127
+ const currentLauncher=await sha256File(launcherPath(state));
1128
+ if(currentLauncher!==pointer.launcher_sha256) throw new Error('active pointer launcher digest mismatch');
1129
+ return verified;
1130
+ }
1131
+ async function loadPointerIfPresent(stateRoot){
1132
+ const file=path.join(path.resolve(stateRoot),'active-release.json');
1133
+ if(!existsSync(file)) return {file,pointer:null,snapshot:await snapshotFile(file)};
1134
+ const snapshot=await snapshotFile(file); const pointer=parseActivePointer(snapshot.bytes.toString('utf8'));
1135
+ await verifyActiveInstallation(stateRoot,pointer); return {file,pointer,snapshot};
1136
+ }
1137
+ async function loadControlInputs({stateRoot,sourceRoot,pointerInfo,bridgeBootstrap=null}){
1138
+ let configPath,ctx;
1139
+ if(pointerInfo.pointer){
1140
+ configPath=pointerInfo.pointer.config_path; ctx=slotContext(stateRoot,pointerInfo.pointer);
1141
+ } else {
1142
+ if(!sourceRoot) throw new Error('--source-root is required for initial slot migration');
1143
+ ctx=legacyContext(sourceRoot); configPath=path.join(ctx.source_root,'controller','deadbyte-controller.config.psd1');
1144
+ }
1145
+ let config=await loadPowerShellDataFile(configPath);
1146
+ if(bridgeBootstrap){
1147
+ if(!bridgeBootstrap.root||!path.isAbsolute(bridgeBootstrap.root)||!HEX64.test(String(bridgeBootstrap.scriptSha256??''))) throw new Error('bridge bootstrap requires absolute root plus lowercase script SHA-256');
1148
+ const bootstrapRoot=path.resolve(bridgeBootstrap.root);
1149
+ const bootstrapScript=path.join(bootstrapRoot,'src','bridge.mjs');
1150
+ const bootstrapActual=await sha256File(bootstrapScript);
1151
+ if(bootstrapActual!==String(bridgeBootstrap.scriptSha256).toLowerCase()) throw new Error(`bridge script SHA-256 mismatch: expected ${String(bridgeBootstrap.scriptSha256).toLowerCase()}, found ${bootstrapActual}`);
1152
+ if(!config.Bridge||typeof config.Bridge!=='object') throw new Error('Bridge launch specification missing');
1153
+ config.Bridge.WorkingDirectory=bootstrapRoot;
1154
+ config.Bridge.Arguments='src\\bridge.mjs';
1155
+ config.Bridge.ScriptPath=bootstrapScript;
1156
+ config.Bridge.ScriptSha256=bootstrapActual;
1157
+ }
1158
+ config=await normalizeComponentIdentities(config);
1159
+ const codingPath=String(config.CodingPolicyFile??''); const autonomousPath=String(config.AutonomousPolicyFile??''); const hostPath=String(config.HostPolicyFile??''); const processPath=String(config.ProcessPolicyFile??''); const desktopPath=String(config.DesktopPolicyFile??'');
1160
+ if(!path.isAbsolute(codingPath)||!path.isAbsolute(autonomousPath)) throw new Error('coding/autonomous policy paths must be absolute');
1161
+ if(processPath&&!path.isAbsolute(processPath)) throw new Error('process policy path must be absolute');
1162
+ if(desktopPath&&!path.isAbsolute(desktopPath)) throw new Error('desktop policy path must be absolute');
1163
+ const codingPolicy=await readJson(codingPath); const autonomousPolicy=await readJson(autonomousPath); const hostPolicy=hostPath?await readJson(hostPath):null; const processPolicy=processPath?await readJson(processPath):null; const desktopPolicy=desktopPath?await readJson(desktopPath):null;
1164
+ const legacyCore=typeof config.CoreRoot==='string'&&config.CoreRoot?path.resolve(config.CoreRoot):ctx.core_root;
1165
+ const workspaceRoot=typeof config.WorkspaceRoot==='string'&&config.WorkspaceRoot?path.resolve(config.WorkspaceRoot):path.join(legacyCore,'workspaces');
1166
+ return {ctx,configPath,config,codingPolicy,autonomousPolicy,hostPolicy,processPolicy,desktopPolicy,workspaceRoot};
1167
+ }
1168
+ export function assertDevelopmentRootSafe(developmentRoot,stateRoot){
1169
+ const dev=path.resolve(developmentRoot),state=path.resolve(stateRoot);
1170
+ const forbidden=[path.join(state,'releases'),path.join(state,'generations'),path.join(state,'runtime')].map(item=>path.resolve(item).toLowerCase());
1171
+ const lower=dev.toLowerCase();
1172
+ if(forbidden.some(prefix=>lower===prefix||lower.startsWith(`${prefix}${path.sep}`))) throw new Error('development root cannot live inside immutable/runtime state stores');
1173
+ return dev;
1174
+ }
1175
+ async function workspacePlan(currentWorkspace,stateRoot){
1176
+ const source=path.resolve(currentWorkspace),target=path.join(path.resolve(stateRoot),'workspaces');
1177
+ if(source.toLowerCase()===path.resolve(target).toLowerCase()) return {source,target,mode:'existing',moved:false};
1178
+ const sourceExists=existsSync(source),targetExists=existsSync(target);
1179
+ if(targetExists){
1180
+ const info=await lstat(target); if(!info.isDirectory()||info.isSymbolicLink()) throw new Error('stable workspace root must be a real directory');
1181
+ if(sourceExists) throw new Error(`workspace migration conflict: both source and stable target exist (${source}, ${target})`);
1182
+ return {source,target,mode:'target-existing',moved:false};
1183
+ }
1184
+ if(!sourceExists) return {source,target,mode:'create',moved:false};
1185
+ const info=await lstat(source); if(!info.isDirectory()||info.isSymbolicLink()) throw new Error('legacy workspace root must be a real directory');
1186
+ if(path.parse(source).root.toLowerCase()!==path.parse(target).root.toLowerCase()) throw new Error('workspace migration requires same-volume rename');
1187
+ return {source,target,mode:'rename',moved:false};
1188
+ }
1189
+ async function applyWorkspacePlan(plan){
1190
+ if(plan.mode==='existing'||plan.mode==='target-existing') return plan;
1191
+ const sourceExists=existsSync(plan.source),targetExists=existsSync(plan.target);
1192
+ if(plan.mode==='create'){
1193
+ if(targetExists){ plan.created=true; return plan; }
1194
+ await mkdir(path.dirname(plan.target),{recursive:true});
1195
+ await mkdir(plan.target,{recursive:false}); plan.created=true; return plan;
1196
+ }
1197
+ if(targetExists&&!sourceExists){ plan.moved=true; return plan; }
1198
+ if(sourceExists&&targetExists) throw new Error('workspace migration conflict: both source and stable target exist ('+plan.source+', '+plan.target+')');
1199
+ await mkdir(path.dirname(plan.target),{recursive:true});
1200
+ await rename(plan.source,plan.target); plan.moved=true; return plan;
1201
+ }
1202
+ async function rollbackWorkspacePlan(plan){
1203
+ if(plan.mode==='rename'&&existsSync(plan.target)&&!existsSync(plan.source)){ await rename(plan.target,plan.source); plan.moved=false; return true; }
1204
+ if(plan.mode==='create'&&existsSync(plan.target)){ await rm(plan.target,{recursive:true,force:true}); plan.created=false; return true; }
1205
+ return false;
1206
+ }
1207
+ async function launcherPlan(stateRoot){
1208
+ const bin=path.join(path.resolve(stateRoot),'bin');
1209
+ const ps1=path.join(bin,'DEADBYTE-MCP.ps1'),cmd=path.join(bin,'DEADBYTE-MCP.cmd'),top=TOP_LAUNCHER;
1210
+ const ps1Bytes=Buffer.from(renderStableLauncherPs1(),'utf8'),cmdBytes=Buffer.from(renderStableLauncherCmd(),'utf8');
1211
+ return {ps1,cmd,top,ps1Bytes,cmdBytes,launcher_sha256:sha256Bytes(ps1Bytes),
1212
+ snapshots:{ps1:await snapshotFile(ps1),cmd:await snapshotFile(cmd),top:await snapshotFile(top)},installed:false};
1213
+ }
1214
+ async function installLaunchers(plan){
1215
+ await atomicBytes(plan.ps1,plan.ps1Bytes); await atomicBytes(plan.cmd,plan.cmdBytes); await atomicBytes(plan.top,plan.cmdBytes);
1216
+ plan.installed=true;
1217
+ if(await sha256File(plan.ps1)!==plan.launcher_sha256) throw new Error('stable launcher post-install hash mismatch');
1218
+ return plan;
1219
+ }
1220
+ async function rollbackLaunchers(plan){
1221
+ if(!plan) return false;
1222
+ const touched=plan.installed===true || (existsSync(plan.ps1)&&sha256Bytes(await readFile(plan.ps1))===plan.launcher_sha256);
1223
+ if(!touched) return false;
1224
+ await restoreFile(plan.ps1,plan.snapshots.ps1); await restoreFile(plan.cmd,plan.snapshots.cmd); await restoreFile(plan.top,plan.snapshots.top);
1225
+ plan.installed=false; return true;
1226
+ }
1227
+ async function revokeAuthority(ctx,prior){
1228
+ let revoked=false;
1229
+ if(prior.desktop_armed){ controllerCaptured(ctx,'disarmdesktop',15000); revoked=true; }
1230
+ if(prior.autonomous_armed){ controllerCaptured(ctx,'disarmautonomous',15000); revoked=true; }
1231
+ if(prior.coding_armed){ controllerCaptured(ctx,'disarmcoding',15000); revoked=true; }
1232
+ if(prior.process_armed){ controllerCaptured(ctx,'disarmprocess',15000); revoked=true; }
1233
+ const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
1234
+ if(status.process_armed||status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority revoke gate failed');
1235
+ if(status.host_armed) throw new Error('host plane became armed during revoke');
1236
+ return {revoked,status};
1237
+ }
1238
+ export function standingAuthorityRestorePlan(prior,enabled){
1239
+ const before={
1240
+ process_armed:prior?.process_armed===true,
1241
+ coding_armed:prior?.coding_armed===true,
1242
+ autonomous_armed:prior?.autonomous_armed===true,
1243
+ desktop_armed:prior?.desktop_armed===true,
1244
+ host_armed:prior?.host_armed===true
1245
+ };
1246
+ if(before.autonomous_armed&&!before.coding_armed) throw new Error('invalid prior authority: autonomous armed without coding');
1247
+ const expected={process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false};
1248
+ const commands=[];
1249
+ if(enabled){
1250
+ expected.process_armed=before.process_armed;
1251
+ expected.coding_armed=before.coding_armed;
1252
+ expected.autonomous_armed=before.autonomous_armed;
1253
+ if(before.coding_armed) commands.push('armcoding');
1254
+ if(before.autonomous_armed) commands.push('armautonomous');
1255
+ if(before.process_armed) commands.push('armprocess');
1256
+ }
1257
+ return {commands,expected};
1258
+ }
1259
+ async function restoreStandingAuthority(ctx,prior,enabled){
1260
+ const plan=standingAuthorityRestorePlan(prior,enabled);
1261
+ for(const command of plan.commands) controllerCaptured(ctx,command,15000);
1262
+ const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
1263
+ for(const key of ['process_armed','coding_armed','autonomous_armed']){
1264
+ if(status[key]!==plan.expected[key]) throw new Error(`${key.replace('_armed','')} standing-grant restore gate failed`);
1265
+ }
1266
+ if(status.desktop_armed!==plan.expected.desktop_armed) throw new Error('desktop plane must remain disarmed after slot switch');
1267
+ if(status.host_armed!==plan.expected.host_armed) throw new Error('host plane became armed during slot switch');
1268
+ return status;
1269
+ }
1270
+ async function stopAndVerify(ctx,runtime){
1271
+ try{ controllerCaptured(ctx,'supervisorstop',15000); }catch(error){
1272
+ if(!/not running|no supervisor|offline|missing/i.test(String(error?.message??error))) throw error;
1273
+ }
1274
+ const output=controllerCaptured(ctx,'stop',30000);
1275
+ if(!(await waitCoreGone(runtime,10000))) throw new Error(`old downstream core ${runtime.core.pid} survived verified stop`);
1276
+ const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
1277
+ if(!status.offline) throw new Error(`controller stop did not reach OFFLINE: ${output}`);
1278
+ return {output:output.trim(),status};
1279
+ }
1280
+ async function loadUpdateKey(stateRoot){
1281
+ const trust=path.join(path.resolve(stateRoot),'trust');
1282
+ const privateKey=createPrivateKey(await readFile(path.join(trust,'ed25519-private.pem')));
1283
+ const publicKey=createPublicKey(await readFile(path.join(trust,'ed25519-public.pem'))); const derived=createPublicKey(privateKey);
1284
+ if(privateKey.asymmetricKeyType!=='ed25519'||publicKey.asymmetricKeyType!=='ed25519') throw new Error('slot-update trust key must be Ed25519');
1285
+ const der=key=>Buffer.from(key.export({type:'spki',format:'der'})); const keyId=sha256Bytes(der(publicKey));
1286
+ if(sha256Bytes(der(derived))!==keyId) throw new Error('slot-update private/public key mismatch');
1287
+ return {privateKey,publicKey,keyId};
1288
+ }
1289
+ function signEnvelope(body,key){
1290
+ const bodyBytes=Buffer.from(canonicalJson(body),'utf8'); const signature=cryptoSign(null,Buffer.concat([RECEIPT_DOMAIN,bodyBytes]),key.privateKey);
1291
+ const envelope={body,body_sha256:sha256Bytes(bodyBytes),attestation:{algorithm:'ed25519',domain:'DEADBYTE-R27-SLOT-UPDATE-V1',key_id:key.keyId,signature_base64:signature.toString('base64')}};
1292
+ envelope.receipt_sha256=sha256Bytes(Buffer.from(canonicalJson(envelope),'utf8'));
1293
+ if(!cryptoVerify(null,Buffer.concat([RECEIPT_DOMAIN,bodyBytes]),key.publicKey,signature)) throw new Error('slot-update signature self-check failed');
1294
+ return envelope;
1295
+ }
1296
+ export function verifySlotUpdateEnvelope(envelope,publicKey){
1297
+ if(!envelope?.body||!envelope?.attestation) return false; const bodyBytes=Buffer.from(canonicalJson(envelope.body),'utf8');
1298
+ if(sha256Bytes(bodyBytes)!==envelope.body_sha256) return false; const {receipt_sha256,...unsigned}=envelope;
1299
+ if(sha256Bytes(Buffer.from(canonicalJson(unsigned),'utf8'))!==receipt_sha256) return false;
1300
+ if(envelope.attestation.algorithm!=='ed25519'||envelope.attestation.domain!=='DEADBYTE-R27-SLOT-UPDATE-V1') return false;
1301
+ const keyId=sha256Bytes(Buffer.from(publicKey.export({type:'spki',format:'der'}))); if(keyId!==envelope.attestation.key_id) return false;
1302
+ return cryptoVerify(null,Buffer.concat([RECEIPT_DOMAIN,bodyBytes]),publicKey,Buffer.from(envelope.attestation.signature_base64,'base64'));
1303
+ }
1304
+ async function writeEnvelope(file,body,key){ const envelope=signEnvelope(body,key); await atomicBytes(file,Buffer.from(`${JSON.stringify(envelope,null,2)}\n`,'utf8')); return envelope; }
1305
+ async function writeState(file,state,phase,extra={}){
1306
+ let observed=null;
1307
+ try { observed=await readJson(file); } catch(error) { if(error?.code!=='ENOENT') throw error; }
1308
+ const observedRank=phaseRank(observed?.phase);
1309
+ const requestedRank=phaseRank(phase);
1310
+ const effectivePhase=observedRank>requestedRank?String(observed.phase):phase;
1311
+ if(observed) Object.assign(state,observed);
1312
+ Object.assign(state,extra,{phase:effectivePhase,updated_at_utc:new Date().toISOString()});
1313
+ await atomicBytes(file,Buffer.from(`${JSON.stringify(state,null,2)}\n`,'utf8'));
1314
+ }
1315
+ function generationTuple(runtime){
1316
+ if(!runtime) return null;
1317
+ return {bridge:{pid:runtime.bridge.pid,instance_nonce:runtime.bridge.instance_nonce,creation_time_utc:runtime.bridge.creation_time_utc,image_sha256:runtime.bridge.image_sha256},
1318
+ cloud:{pid:runtime.cloud.pid,instance_nonce:runtime.cloud.instance_nonce,creation_time_utc:runtime.cloud.creation_time_utc,image_sha256:runtime.cloud.image_sha256},
1319
+ core:{pid:runtime.core.pid,parent_pid:runtime.core.parent_pid,creation_date:runtime.core.creation_date,
1320
+ command_line:runtime.core.command_line,identity_basis:runtime.core.identity_basis}};
1321
+ }
1322
+ function publicPointer(pointer){
1323
+ if(!pointer) return null;
1324
+ return {schema:pointer.schema,release_id:pointer.release_id,version:pointer.version,manifest_sha256:pointer.manifest_sha256,
1325
+ release_root:pointer.release_root,generation_root:pointer.generation_root,generation_manifest_sha256:pointer.generation_manifest_sha256,
1326
+ launcher_sha256:pointer.launcher_sha256,config_path:pointer.config_path};
1327
+ }
1328
+
1329
+ function encodeSnapshot(snapshot){
1330
+ if(!snapshot||typeof snapshot!=='object') throw new Error('snapshot object required');
1331
+ return {exists:snapshot.exists===true,sha256:snapshot.sha256??null,bytes_base64:snapshot.exists&&snapshot.bytes?Buffer.from(snapshot.bytes).toString('base64'):null};
1332
+ }
1333
+ function decodeSnapshot(snapshot,label){
1334
+ if(!snapshot||typeof snapshot!=='object'||typeof snapshot.exists!=='boolean') throw new Error(label+' snapshot invalid');
1335
+ if(snapshot.exists){
1336
+ if(typeof snapshot.bytes_base64!=='string') throw new Error(label+' snapshot bytes missing');
1337
+ const bytes=Buffer.from(snapshot.bytes_base64,'base64'),actual=sha256Bytes(bytes);
1338
+ if(snapshot.sha256!==actual) throw new Error(label+' snapshot SHA-256 mismatch');
1339
+ return {exists:true,bytes,sha256:actual};
1340
+ }
1341
+ return {exists:false,bytes:null,sha256:null};
1342
+ }
1343
+ export function rollbackLauncherPlanFromHandoff(body,priorPointer,activePointer,stateRoot=DEFAULT_STATE_ROOT){
1344
+ if(!body||typeof body!=='object') throw new Error('rollback handoff body required');
1345
+ const handoffPrior=body.prior_pointer?parseActivePointer(JSON.stringify(body.prior_pointer)):null;
1346
+ const handoffActive=body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
1347
+ if(!samePublicPointer(handoffPrior,priorPointer)||!samePublicPointer(handoffActive,activePointer)) throw new Error('rollback handoff pointer binding mismatch');
1348
+ const state=path.resolve(stateRoot);
1349
+ const launchers={
1350
+ ps1:path.resolve(String(body.launchers?.ps1??'')),
1351
+ cmd:path.resolve(String(body.launchers?.cmd??'')),
1352
+ top:path.resolve(String(body.launchers?.top??TOP_LAUNCHER)),
1353
+ launcher_sha256:String(body.launchers?.launcher_sha256??''),
1354
+ ps1Bytes:Buffer.from(String(body.launchers?.ps1_bytes??''),'base64'),
1355
+ cmdBytes:Buffer.from(String(body.launchers?.cmd_bytes??''),'base64'),
1356
+ snapshots:{
1357
+ ps1:decodeSnapshot(body.launchers?.snapshots?.ps1,'rollback.launcher.ps1'),
1358
+ cmd:decodeSnapshot(body.launchers?.snapshots?.cmd,'rollback.launcher.cmd'),
1359
+ top:decodeSnapshot(body.launchers?.snapshots?.top,'rollback.launcher.top')
1360
+ },
1361
+ installed:true
1362
+ };
1363
+ if(launchers.ps1!==path.resolve(launcherPath(state))||
1364
+ launchers.cmd!==path.resolve(path.join(state,'bin','DEADBYTE-MCP.cmd'))||
1365
+ launchers.top!==path.resolve(TOP_LAUNCHER)) throw new Error('rollback launcher paths are not canonical');
1366
+ if(!HEX64.test(launchers.launcher_sha256)||sha256Bytes(launchers.ps1Bytes)!==launchers.launcher_sha256) throw new Error('rollback successor launcher bytes/hash mismatch');
1367
+ if(launchers.launcher_sha256!==activePointer.launcher_sha256) throw new Error('rollback successor launcher hash does not match active pointer');
1368
+ if(!launchers.snapshots.ps1.exists||launchers.snapshots.ps1.sha256!==priorPointer.launcher_sha256) throw new Error('rollback predecessor launcher snapshot does not match signed prior pointer');
1369
+ return launchers;
1370
+ }
1371
+ async function restoreRollbackLaunchers(plan){
1372
+ await restoreFile(plan.ps1,plan.snapshots.ps1);
1373
+ await restoreFile(plan.cmd,plan.snapshots.cmd);
1374
+ await restoreFile(plan.top,plan.snapshots.top);
1375
+ if(await sha256File(plan.ps1)!==plan.snapshots.ps1.sha256) throw new Error('rollback predecessor launcher restore hash mismatch');
1376
+ return plan;
1377
+ }
1378
+ const UPDATE_PHASE_RANK=Object.freeze({
1379
+ initial:0,candidate_prepared:10,old_runtime_verified:20,shutdown_handoff_armed:25,supervisor_spawned:30,
1380
+ authority_revoked:40,old_runtime_stopped:50,workspace_ready:60,launcher_installed:70,pointer_committed:80,
1381
+ fresh_runtime_verified:90,authority_verified:100,completed:110,superseded:120,failed:1000,failed_receipted:1001,failed_unreceipted:1002,precondition_rejected:1003
1382
+ });
1383
+ function phaseRank(phase){ return UPDATE_PHASE_RANK[String(phase)]??-1; }
1384
+ function activeTransitionPhase(phase){
1385
+ const rank=phaseRank(phase);
1386
+ return rank>=phaseRank('shutdown_handoff_armed')&&rank<phaseRank('completed')&&rank<phaseRank('failed');
1387
+ }
1388
+ function samePublicPointer(a,b){
1389
+ if(!a&&!b) return true;
1390
+ if(!a||!b) return false;
1391
+ return canonicalJson(publicPointer(a))===canonicalJson(publicPointer(b));
1392
+ }
1393
+ function statePointer(state,key){
1394
+ if(!state?.[key]) return null;
1395
+ return parseActivePointer(JSON.stringify(state[key]));
1396
+ }
1397
+ async function spawnDetachedSupervisor({script,handoffPath,cwd}){
1398
+ const absoluteScript=path.resolve(script),absoluteHandoff=path.resolve(handoffPath);
1399
+ const stat=await lstat(absoluteScript);
1400
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error('detached updater supervisor script must be a regular file');
1401
+ const child=await new Promise((resolve,reject)=>{
1402
+ const proc=spawn(process.execPath,[absoluteScript,'--resume-handoff',absoluteHandoff],{
1403
+ cwd:path.resolve(cwd),detached:true,windowsHide:true,stdio:'ignore'
1404
+ });
1405
+ const onError=error=>reject(error);
1406
+ proc.once('error',onError);
1407
+ proc.once('spawn',()=>{
1408
+ proc.removeListener('error',onError);
1409
+ proc.unref();
1410
+ resolve(proc);
1411
+ });
1412
+ });
1413
+ if(!child.pid||child.pid<1) throw new Error('detached updater supervisor did not return a valid PID');
1414
+ return {pid:child.pid,script_sha256:await sha256File(absoluteScript)};
1415
+ }
1416
+ async function acquireSupervisorLease(handoffPath){
1417
+ const lockDir=path.resolve(handoffPath)+'.lock',lockFile=path.join(lockDir,'owner.json');
1418
+ for(let attempt=0;attempt<2;attempt+=1){
1419
+ try{
1420
+ await mkdir(lockDir,{recursive:false});
1421
+ await atomicBytes(lockFile,Buffer.from(JSON.stringify({pid:process.pid,handoff:path.resolve(handoffPath),started_at_utc:new Date().toISOString()})+'\n','utf8'));
1422
+ return async()=>{ await rm(lockDir,{recursive:true,force:true}); };
1423
+ }catch(error){
1424
+ if(error?.code!=='EEXIST') throw error;
1425
+ let owner=null; try{ owner=await readJson(lockFile); }catch{}
1426
+ const ownerPid=Number(owner?.pid),needle=path.resolve(handoffPath).replaceAll('/','\\\\').toLowerCase();
1427
+ const alive=Number.isInteger(ownerPid)&&ownerPid>0&&nodeProcesses().some(item=>
1428
+ Number(item.ProcessId)===ownerPid&&String(item.CommandLine??'').replaceAll('/','\\\\').toLowerCase().includes(needle)
1429
+ );
1430
+ if(alive) throw new Error('slot update supervisor already active pid='+ownerPid);
1431
+ await rm(lockDir,{recursive:true,force:true});
1432
+ }
1433
+ }
1434
+ throw new Error('unable to acquire slot update supervisor lease');
1435
+ }
1436
+ export function parseResumeHandoffArgs(argv){
1437
+ let handoff=null;
1438
+ for(let index=0;index<argv.length;index+=1){
1439
+ const arg=argv[index];
1440
+ if(arg!=='--resume-handoff') throw new Error("unknown supervisor argument '"+arg+"'");
1441
+ if(handoff!==null) throw new Error("duplicate supervisor argument '--resume-handoff'");
1442
+ const value=argv[++index];
1443
+ if(!value) throw new Error('--resume-handoff requires a value');
1444
+ handoff=path.resolve(value);
1445
+ }
1446
+ if(!handoff) throw new Error('--resume-handoff is required');
1447
+ return {handoff};
1448
+ }
1449
+ async function readHandoffEnvelope(handoffPath,stateRoot){
1450
+ const envelope=await readJson(handoffPath),key=await loadUpdateKey(stateRoot);
1451
+ if(!verifySlotUpdateEnvelope(envelope,key.publicKey)) throw new Error('slot-update handoff signature invalid');
1452
+ if(envelope.body?.schema!==HANDOFF_SCHEMA) throw new Error('slot-update handoff schema invalid');
1453
+ const body=envelope.body;
1454
+ if(typeof body.update_id!=='string'||!HEX64.test(String(body.supervisor_script_sha256??''))||
1455
+ !path.isAbsolute(String(body.supervisor_script??''))||!path.isAbsolute(String(body.state_path??''))) {
1456
+ throw new Error('slot-update handoff identity invalid');
1457
+ }
1458
+ const actualSupervisorHash=await sha256File(path.resolve(body.supervisor_script));
1459
+ if(actualSupervisorHash!==body.supervisor_script_sha256) throw new Error('slot-update handoff supervisor hash drift');
1460
+ return {envelope,body,key};
1461
+ }
1462
+ async function writeHandoff({stateRoot,state,journal,statePath,evidenceDir,key,pointerInfo,newPointer,predecessor,priorStatus,workspace,launchers,stableMcpUrl,mcpUrlSnapshot,controlCtx}){
1463
+ const supervisorScript=path.join(path.resolve(newPointer.release_root),'scripts','runtime-self-update.mjs');
1464
+ const supervisorScriptSha256=await sha256File(supervisorScript);
1465
+ const handoffPath=path.join(path.resolve(evidenceDir),'handoff.json');
1466
+ const body={
1467
+ schema:HANDOFF_SCHEMA,update_id:state.update_id,created_at_utc:new Date().toISOString(),
1468
+ state_root:path.resolve(stateRoot),state_path:path.resolve(statePath),handoff_path:path.resolve(handoffPath),
1469
+ supervisor_script:path.resolve(supervisorScript),supervisor_script_sha256:supervisorScriptSha256,
1470
+ prior_pointer:publicPointer(pointerInfo.pointer),new_pointer:publicPointer(newPointer),predecessor_mode:predecessor.mode,
1471
+ old_runtime:generationTuple(predecessor.runtime),
1472
+ prior_authority:{
1473
+ process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,
1474
+ desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed
1475
+ },
1476
+ pointer_snapshot:encodeSnapshot(pointerInfo.snapshot),
1477
+ workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode,moved:workspace.moved===true,created:workspace.created===true},
1478
+ launchers:{
1479
+ ps1:launchers.ps1,cmd:launchers.cmd,top:launchers.top,launcher_sha256:launchers.launcher_sha256,
1480
+ ps1_bytes:Buffer.from(launchers.ps1Bytes).toString('base64'),cmd_bytes:Buffer.from(launchers.cmdBytes).toString('base64'),
1481
+ snapshots:{
1482
+ ps1:encodeSnapshot(launchers.snapshots.ps1),cmd:encodeSnapshot(launchers.snapshots.cmd),top:encodeSnapshot(launchers.snapshots.top)
1483
+ }
1484
+ },
1485
+ stable_mcp_url:stableMcpUrl?{path:stableMcpUrl.path,sha256:stableMcpUrl.sha256,origin:stableMcpUrl.origin,changed:stableMcpUrl.changed===true}:null,
1486
+ mcp_url_snapshot:stableMcpUrl?.changed?encodeSnapshot(mcpUrlSnapshot):null,
1487
+ restore_standing_grants:state.restore_standing_grants===true
1488
+ };
1489
+ const receipt=await writeEnvelope(handoffPath,body,key);
1490
+ await writeState(statePath,journal,'shutdown_handoff_armed',{
1491
+ handoff_path:handoffPath,handoff_receipt_sha256:receipt.receipt_sha256,
1492
+ supervisor_script:supervisorScript,supervisor_script_sha256:supervisorScriptSha256
1493
+ });
1494
+ return {path:handoffPath,body,receipt};
1495
+ }
1496
+ async function parseCurrentPointer(file){
1497
+ if(!existsSync(file)) return null;
1498
+ return parseActivePointer(await readFile(file,'utf8'));
1499
+ }
1500
+ async function ensurePointerCommitted(pointerFile,priorPointer,newPointer){
1501
+ const current=await parseCurrentPointer(pointerFile);
1502
+ if(samePublicPointer(current,newPointer)) return {committed:true,already:true};
1503
+ if(!samePublicPointer(current,priorPointer)) throw new Error('active pointer changed unexpectedly before slot commit');
1504
+ await atomicWriteActivePointer(pointerFile,newPointer);
1505
+ const verify=await parseCurrentPointer(pointerFile);
1506
+ if(!samePublicPointer(verify,newPointer)) throw new Error('active pointer commit readback mismatch');
1507
+ return {committed:true,already:false};
1508
+ }
1509
+ async function coreGone(runtime){
1510
+ return !runtime||!(await nodeProcesses()).some(item=>
1511
+ Number(item.ProcessId)===Number(runtime.core.pid)&&String(item.CreationDate??'')===String(runtime.core.creation_date)
1512
+ );
1513
+ }
1514
+ async function runtimeAlreadyOnline(ctx,oldRuntime){
1515
+ const statusText=controllerCaptured(ctx,'status',15000),status=parseControllerStatus(statusText);
1516
+ if(!status.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) return null;
1517
+ const bridge=await readLedger(ctx,'bridge'),cloud=await readLedger(ctx,'cloud'),core=coreProcess(ctx.core_root,bridge);
1518
+ if(oldRuntime?.bridge&&sameGeneration(oldRuntime.bridge,bridge)) throw new Error('fresh bridge generation gate failed');
1519
+ if(oldRuntime?.cloud&&sameGeneration(oldRuntime.cloud,cloud)) throw new Error('fresh cloud generation gate failed');
1520
+ if(oldRuntime?.core&&oldRuntime.core.pid===core.pid&&oldRuntime.core.creation_date===core.creation_date) {
1521
+ throw new Error('fresh downstream core generation gate failed');
1522
+ }
1523
+ return {status,status_text:statusText.trim(),bridge,cloud,core};
1524
+ }
1525
+ async function ensureFreshRuntime(ctx,oldRuntime){
1526
+ let existing=null;
1527
+ try{ existing=await runtimeAlreadyOnline(ctx,oldRuntime); }
1528
+ catch(error){ if(/generation gate/.test(String(error?.message??''))) throw error; }
1529
+ return existing??await startAndVerify(ctx,oldRuntime);
1530
+ }
1531
+ async function ensureAuthorityRevoked(ctx,prior){
1532
+ if(!prior.process_armed&&!prior.coding_armed&&!prior.autonomous_armed&&!prior.desktop_armed&&!prior.host_armed){
1533
+ if(prior.online&&prior.bridge_pid&&prior.cloud_pid) return {revoked:false,status:prior};
1534
+ if(existsSync(path.join(ctx.runtime_root,'bridge.instance.json'))&&existsSync(path.join(ctx.runtime_root,'cloud.instance.json'))){
1535
+ try{
1536
+ const bridge=await readLedger(ctx,'bridge'),cloud=await readLedger(ctx,'cloud');
1537
+ return {revoked:false,status:{online:true,offline:false,bridge_pid:bridge.pid,cloud_pid:cloud.pid,local_ready:true,
1538
+ process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false}};
1539
+ }catch{}
1540
+ }
1541
+ const clean=cleanOfflinePredecessor(ctx);
1542
+ return {revoked:false,status:clean};
1543
+ }
1544
+ const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
1545
+ if(status.host_armed) throw new Error('host plane armed during authority revoke recovery');
1546
+ if(!status.process_armed&&!status.coding_armed&&!status.autonomous_armed&&!status.desktop_armed) {
1547
+ return {revoked:prior.process_armed||prior.coding_armed||prior.autonomous_armed||prior.desktop_armed,status};
1548
+ }
1549
+ return revokeAuthority(ctx,status);
1550
+ }
1551
+ function handoffPriorPointer(body){
1552
+ return body.prior_pointer?parseActivePointer(JSON.stringify(body.prior_pointer)):null;
1553
+ }
1554
+ function handoffNewPointer(body){
1555
+ return body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
1556
+ }
1557
+ async function verifyCompletedUpdateReceipt(body,state,statePath,key){
1558
+ const candidate=handoffNewPointer(body),prior=handoffPriorPointer(body);
1559
+ if(!candidate||!samePublicPointer(candidate,statePointer(state,'new_pointer'))||!samePublicPointer(prior,statePointer(state,'prior_pointer'))) throw new Error('completed update handoff pointer binding mismatch');
1560
+ if(state.expected_version!==candidate.version||state.expected_manifest_sha256!==candidate.manifest_sha256) throw new Error('completed update journal subject mismatch');
1561
+ const expectedPath=path.join(path.dirname(statePath),'receipt.json');
1562
+ if(typeof state.receipt_path!=='string'||!path.isAbsolute(state.receipt_path)||path.resolve(state.receipt_path)!==expectedPath||!HEX64.test(String(state.receipt_sha256??''))) throw new Error('completed update receipt reference invalid');
1563
+ const info=await lstat(expectedPath);
1564
+ if(!info.isFile()||info.isSymbolicLink()) throw new Error('completed update receipt must be a regular file');
1565
+ const receipt=await readJson(expectedPath);
1566
+ if(!verifySlotUpdateEnvelope(receipt,key.publicKey)||receipt.receipt_sha256!==state.receipt_sha256) throw new Error('completed update receipt signature or digest mismatch');
1567
+ const subject=receipt.body;
1568
+ if(subject.schema!==UPDATE_SCHEMA||subject.rollback_performed!==false||subject.update_id!==body.update_id||subject.release_version!==candidate.version||subject.candidate_manifest_sha256!==candidate.manifest_sha256||
1569
+ !samePublicPointer(statePointer(subject,'active_pointer'),candidate)||!samePublicPointer(statePointer(subject,'prior_pointer'),prior)||
1570
+ subject.release_slot?.release_id!==candidate.release_id||subject.release_slot?.release_root!==candidate.release_root||subject.release_slot?.manifest_sha256!==candidate.manifest_sha256||
1571
+ subject.generation?.generation_root!==candidate.generation_root||subject.generation?.generation_manifest_sha256!==candidate.generation_manifest_sha256||subject.launcher?.sha256!==candidate.launcher_sha256) throw new Error('completed update receipt subject binding mismatch');
1572
+ return {receipt_path:expectedPath,receipt_sha256:receipt.receipt_sha256};
1573
+ }
1574
+ async function resumeSlotUpdate({handoffPath}){
1575
+ const absoluteHandoff=path.resolve(handoffPath),probe=await readJson(absoluteHandoff);
1576
+ const stateRoot=path.resolve(String(probe?.body?.state_root??path.dirname(path.dirname(path.dirname(path.dirname(absoluteHandoff))))));
1577
+ const {body,key}=await readHandoffEnvelope(absoluteHandoff,stateRoot);
1578
+ const statePath=path.resolve(body.state_path),releaseState=await readJson(statePath);
1579
+ if(releaseState.schema!=='deadbyte.r27-slot-update-state.v1'||releaseState.update_id!==body.update_id) throw new Error('handoff state binding mismatch');
1580
+ let phase=String(releaseState.phase??'');
1581
+ if(phase==='completed'){
1582
+ const verifiedReceipt=await verifyCompletedUpdateReceipt(body,releaseState,statePath,key);
1583
+ return {ok:true,resumed:false,completed:true,update_id:body.update_id,state_path:statePath,
1584
+ ...verifiedReceipt,historical_receipt_verified:true,current_runtime_verified:false};
1585
+ }
1586
+ if(phase.startsWith('failed')) throw new Error('slot update already failed at phase='+phase);
1587
+ const priorPointer=handoffPriorPointer(body),candidatePointer=handoffNewPointer(body);
1588
+ const statePrior=statePointer(releaseState,'prior_pointer'),stateCandidate=statePointer(releaseState,'new_pointer');
1589
+ if(!samePublicPointer(priorPointer,statePrior)||!samePublicPointer(candidatePointer,stateCandidate)) throw new Error('handoff pointer binding mismatch');
1590
+ if(!candidatePointer) throw new Error('handoff candidate pointer missing');
1591
+ const pointerFile=path.join(stateRoot,'active-release.json'),currentPointer=await parseCurrentPointer(pointerFile);
1592
+ if(!samePublicPointer(currentPointer,priorPointer)&&!samePublicPointer(currentPointer,candidatePointer)) throw new Error('active pointer is neither handoff predecessor nor candidate');
1593
+ const pointerInfo={file:pointerFile,pointer:currentPointer,snapshot:decodeSnapshot(body.pointer_snapshot,'active pointer')};
1594
+ const ctx=slotContext(stateRoot,priorPointer),newCtx=slotContext(stateRoot,candidatePointer);
1595
+ const priorStatus={
1596
+ process_armed:body.prior_authority?.process_armed===true,coding_armed:body.prior_authority?.coding_armed===true,
1597
+ autonomous_armed:body.prior_authority?.autonomous_armed===true,desktop_armed:body.prior_authority?.desktop_armed===true,
1598
+ host_armed:body.prior_authority?.host_armed===true
1599
+ };
1600
+ if(priorStatus.autonomous_armed&&!priorStatus.coding_armed) throw new Error('invalid prior authority in handoff');
1601
+ const oldRuntime=body.old_runtime?{bridge:body.old_runtime.bridge??null,cloud:body.old_runtime.cloud??null,core:body.old_runtime.core??null}:null;
1602
+ if(oldRuntime?.core&&(!Number.isInteger(Number(oldRuntime.core.pid))||String(oldRuntime.core.creation_date??'').length<1)) throw new Error('handoff old runtime core identity invalid');
1603
+ const launchers={
1604
+ ps1:path.resolve(String(body.launchers?.ps1??'')),cmd:path.resolve(String(body.launchers?.cmd??'')),top:path.resolve(String(body.launchers?.top??TOP_LAUNCHER)),
1605
+ launcher_sha256:String(body.launchers?.launcher_sha256??''),
1606
+ ps1Bytes:Buffer.from(String(body.launchers?.ps1_bytes??''),'base64'),cmdBytes:Buffer.from(String(body.launchers?.cmd_bytes??''),'base64'),
1607
+ snapshots:{
1608
+ ps1:decodeSnapshot(body.launchers?.snapshots?.ps1,'launcher.ps1'),cmd:decodeSnapshot(body.launchers?.snapshots?.cmd,'launcher.cmd'),top:decodeSnapshot(body.launchers?.snapshots?.top,'launcher.top')
1609
+ },
1610
+ installed:phaseRank(phase)>=phaseRank('launcher_installed')
1611
+ };
1612
+ if(!HEX64.test(launchers.launcher_sha256)||sha256Bytes(launchers.ps1Bytes)!==launchers.launcher_sha256) throw new Error('handoff launcher bytes/hash mismatch');
1613
+ const workspace={
1614
+ source:path.resolve(String(body.workspace?.source??releaseState.workspace?.source??'')),
1615
+ target:path.resolve(String(body.workspace?.target??releaseState.workspace?.target??'')),
1616
+ mode:String(body.workspace?.mode??releaseState.workspace?.mode??''),
1617
+ moved:releaseState.workspace?.moved===true,created:releaseState.workspace?.created===true
1618
+ };
1619
+ if(!['existing','target-existing','create','rename'].includes(workspace.mode)) throw new Error('handoff workspace mode invalid');
1620
+ const stableMcpUrl=body.stable_mcp_url?{...body.stable_mcp_url}:releaseState.stable_mcp_url?{...releaseState.stable_mcp_url}:null;
1621
+ const mcpUrlSnapshot=body.mcp_url_snapshot?decodeSnapshot(body.mcp_url_snapshot,'MCP URL'):{exists:false,bytes:null,sha256:null};
1622
+ const cleanup=await acquireSupervisorLease(absoluteHandoff);
1623
+ const journal=releaseState;
1624
+ let pointerCommitted=phaseRank(phase)>=phaseRank('pointer_committed')||samePublicPointer(currentPointer,candidatePointer);
1625
+ let stopped=phaseRank(phase)>=phaseRank('old_runtime_stopped');
1626
+ try{
1627
+ if(phaseRank(phase)<phaseRank('supervisor_spawned')){
1628
+ await writeState(statePath,journal,'supervisor_spawned',{supervisor_pid:process.pid,supervisor_script:body.supervisor_script,supervisor_script_sha256:body.supervisor_script_sha256});
1629
+ phase='supervisor_spawned';
1630
+ }
1631
+ let revoked=phaseRank(phase)>=phaseRank('authority_revoked')||journal.revoked_before_switch===true;
1632
+ let newRuntime=null,stabilization=null;
1633
+
1634
+ if(phaseRank(phase)<phaseRank('authority_revoked')){
1635
+ const revoke=await ensureAuthorityRevoked(ctx,priorStatus);
1636
+ revoked=revoke.revoked;
1637
+ await writeState(statePath,journal,'authority_revoked',{revoked_before_switch:revoked});
1638
+ phase='authority_revoked';
1639
+ }
1640
+ if(!stopped){
1641
+ if(!oldRuntime){
1642
+ cleanOfflinePredecessor(ctx);
1643
+ await writeState(statePath,journal,'old_runtime_stopped',{stop_output:'recovery observed predecessor has no captured runtime and clean offline evidence'});
1644
+ }else{
1645
+ const status=parseControllerStatus(controllerCaptured(ctx,'status',15000)),gone=await coreGone(oldRuntime);
1646
+ if(status.offline&&gone){
1647
+ await writeState(statePath,journal,'old_runtime_stopped',{stop_output:'recovery observed predecessor already OFFLINE'});
1648
+ }else{
1649
+ const stoppedResult=await stopAndVerify(ctx,oldRuntime);
1650
+ await writeState(statePath,journal,'old_runtime_stopped',{stop_output:stoppedResult.output});
1651
+ }
1652
+ }
1653
+ stopped=true; phase='old_runtime_stopped';
1654
+ }
1655
+ if(phaseRank(phase)<phaseRank('workspace_ready')){
1656
+ await applyWorkspacePlan(workspace);
1657
+ await writeState(statePath,journal,'workspace_ready',{workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode,moved:workspace.moved===true,created:workspace.created===true}});
1658
+ phase='workspace_ready';
1659
+ }else{
1660
+ workspace.moved=journal.workspace?.moved===true; workspace.created=journal.workspace?.created===true;
1661
+ }
1662
+ if(phaseRank(phase)<phaseRank('launcher_installed')){
1663
+ await installLaunchers(launchers);
1664
+ await writeState(statePath,journal,'launcher_installed',{launcher_sha256:launchers.launcher_sha256,launcher_path:launchers.ps1,top_launcher:launchers.top});
1665
+ phase='launcher_installed';
1666
+ }
1667
+ if(!pointerCommitted){
1668
+ await ensurePointerCommitted(pointerFile,priorPointer,candidatePointer);
1669
+ pointerCommitted=true;
1670
+ await writeState(statePath,journal,'pointer_committed',{active_pointer:publicPointer(candidatePointer)});
1671
+ phase='pointer_committed';
1672
+ }
1673
+ await verifyActiveInstallation(stateRoot,candidatePointer);
1674
+ newRuntime=await ensureFreshRuntime(newCtx,oldRuntime);
1675
+ stabilization=await stabilizeRuntime(newCtx,newRuntime);
1676
+ await writeState(statePath,journal,'fresh_runtime_verified',{new_runtime:generationTuple(newRuntime),stabilization});
1677
+ phase='fresh_runtime_verified';
1678
+
1679
+ const finalAuthority=await restoreStandingAuthority(newCtx,priorStatus,journal.restore_standing_grants===true);
1680
+ await writeState(statePath,journal,'authority_verified',{final_authority:{
1681
+ process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,
1682
+ desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed
1683
+ }});
1684
+ phase='authority_verified';
1685
+
1686
+ const bodyReceipt={
1687
+ schema:UPDATE_SCHEMA,update_id:body.update_id,release_version:journal.expected_version,candidate_manifest_sha256:journal.expected_manifest_sha256,
1688
+ completed_at_utc:new Date().toISOString(),activation_boundary:'active-release-pointer',
1689
+ mechanism:'immutable-release-slot-plus-atomic-pointer-rename-plus-detached-supervisor-handoff',
1690
+ predecessor_mode:journal.predecessor_mode??body.predecessor_mode,prior_pointer:publicPointer(priorPointer),active_pointer:publicPointer(candidatePointer),
1691
+ release_slot:journal.release_slot,generation:journal.generation,
1692
+ launcher:{path:launchers.ps1,sha256:launchers.launcher_sha256,top_path:launchers.top},
1693
+ workspace:{root:workspace.target,migrated:workspace.moved===true},development_root:journal.development_root,
1694
+ host_repin:journal.host_repin??{requested:false,repinned:false},coding_repin:journal.coding_repin??{requested:false,repinned:false},
1695
+ old_runtime:journal.old_runtime??body.old_runtime,new_runtime:generationTuple(newRuntime),stabilization,
1696
+ authority:{
1697
+ prior:priorStatus,revoked_before_switch:revoked,restore_requested:journal.restore_standing_grants===true,desktop_restore_policy:'explicit-rearm-required',
1698
+ final:{
1699
+ process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,
1700
+ desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed
1701
+ }
1702
+ },
1703
+ rollback_performed:false
1704
+ };
1705
+ const receiptPath=path.join(path.dirname(statePath),'receipt.json'),receipt=await writeEnvelope(receiptPath,bodyReceipt,key);
1706
+ await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
1707
+ return {ok:true,resumed:true,completed:true,update_id:body.update_id,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,
1708
+ pointer:candidatePointer,new_runtime:newRuntime,final_authority:finalAuthority,workspace_root:workspace.target};
1709
+ }catch(error){
1710
+ let controllerStartFailure=null,controllerStartFailurePreservationError=null;
1711
+ if(pointerCommitted){
1712
+ try{
1713
+ controllerStartFailure=await preserveControllerStartFailure(newCtx,path.dirname(statePath));
1714
+ const claimed=/evidence_sha256=([0-9a-f]{64})/.exec(String(error?.message??error));
1715
+ if(claimed&&(!controllerStartFailure||controllerStartFailure.sha256!==claimed[1])){
1716
+ controllerStartFailurePreservationError='controller start failure evidence claim mismatch expected='+claimed[1]+' preserved='+(controllerStartFailure?.sha256??'missing');
1717
+ }
1718
+ }catch(preserveError){ controllerStartFailurePreservationError=String(preserveError?.message??preserveError); }
1719
+ }
1720
+ const rollback={attempted:false,new_runtime_stopped:false,pointer_restored:false,launchers_restored:false,workspace_restored:false,mcp_url_restored:false,old_runtime_restarted:false,authority_restored:false,errors:[]};
1721
+ try{
1722
+ if(pointerCommitted){
1723
+ rollback.attempted=true;
1724
+ try{ controllerCaptured(newCtx,'stop',30000); rollback.new_runtime_stopped=true; }catch(stopError){ rollback.errors.push('stop-new:'+stopError.message); }
1725
+ }
1726
+ const currentAfter=await parseCurrentPointer(pointerFile);
1727
+ if(samePublicPointer(currentAfter,candidatePointer)){
1728
+ try{ await restoreFile(pointerFile,pointerInfo.snapshot); rollback.pointer_restored=true; }catch(pointerError){ rollback.errors.push('pointer:'+pointerError.message); }
1729
+ }
1730
+ if(stableMcpUrl?.changed&&mcpUrlSnapshot){
1731
+ try{ await restoreFile(stableMcpUrl.path,mcpUrlSnapshot); rollback.mcp_url_restored=true; }catch(mcpUrlError){ rollback.errors.push('mcp-url:'+mcpUrlError.message); }
1732
+ }
1733
+ if(phaseRank(journal.phase)>=phaseRank('launcher_installed')||launchers.installed){
1734
+ try{ await rollbackLaunchers({...launchers,installed:true}); rollback.launchers_restored=true; }catch(launcherError){ rollback.errors.push('launcher:'+launcherError.message); }
1735
+ }
1736
+ if(phaseRank(journal.phase)>=phaseRank('workspace_ready')||workspace.moved||workspace.created){
1737
+ try{ rollback.workspace_restored=await rollbackWorkspacePlan(workspace); }catch(workspaceError){ rollback.errors.push('workspace:'+workspaceError.message); }
1738
+ }
1739
+ if(stopped&&oldRuntime&&rollback.pointer_restored){
1740
+ try{ await startAndVerify(ctx,oldRuntime); rollback.old_runtime_restarted=true; }catch(startError){ rollback.errors.push('restart-old:'+startError.message); }
1741
+ }
1742
+ if((!stopped||rollback.old_runtime_restarted)&&journal.restore_standing_grants===true){
1743
+ try{ await restoreStandingAuthority(ctx,priorStatus,true); rollback.authority_restored=true; }catch(authorityError){ rollback.errors.push('restore-authority:'+authorityError.message); }
1744
+ }
1745
+ }catch(rollbackError){ rollback.errors.push('rollback:'+rollbackError.message); }
1746
+ const failure={
1747
+ schema:FAILURE_SCHEMA,update_id:body.update_id,release_version:journal.expected_version,candidate_manifest_sha256:journal.expected_manifest_sha256,
1748
+ failed_at_utc:new Date().toISOString(),phase:journal.phase??phase,error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
1749
+ controller_start_failure:controllerStartFailure,controller_start_failure_preservation_error:controllerStartFailurePreservationError,
1750
+ predecessor_mode:journal.predecessor_mode??body.predecessor_mode,prior_pointer:publicPointer(priorPointer),attempted_pointer:publicPointer(candidatePointer),
1751
+ old_runtime:journal.old_runtime??body.old_runtime,rollback
1752
+ };
1753
+ await writeState(statePath,journal,'failed',{failure,rollback}).catch(()=>{});
1754
+ let failureReceipt=null;
1755
+ try{
1756
+ const failurePath=path.join(path.dirname(statePath),'failure-receipt.json');
1757
+ failureReceipt=await writeEnvelope(failurePath,failure,key);
1758
+ await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});
1759
+ }catch(receiptError){
1760
+ rollback.errors.push('failure-receipt:'+receiptError.message);
1761
+ await writeState(statePath,journal,'failed_unreceipted',{rollback}).catch(()=>{});
1762
+ }
1763
+ const wrapped=new Error('R27 slot update failed in detached supervisor: '+(error?.message??error));
1764
+ wrapped.cause=error; wrapped.statePath=statePath; wrapped.failureReceiptPath=failureReceipt?path.join(path.dirname(statePath),'failure-receipt.json'):null; throw wrapped;
1765
+ }finally{
1766
+ await cleanup();
1767
+ }
1768
+ }
1769
+ export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROOT}={}){
1770
+ stateRoot=resolveStateRoot(stateRoot);
1771
+ const evidenceRoot=path.join(path.resolve(stateRoot),'evidence','updates');
1772
+ if(!existsSync(evidenceRoot)) return {ok:true,launched:0,updates:[]};
1773
+ const entries=await readdir(evidenceRoot,{withFileTypes:true});
1774
+ const pointerPath=path.join(path.resolve(stateRoot),'active-release.json');
1775
+ const activePointer=existsSync(pointerPath)?await parseCurrentPointer(pointerPath):null;
1776
+ let launched=0;const updates=[];
1777
+ for(const entry of entries){
1778
+ if(!entry.isDirectory()) continue;
1779
+ const statePath=path.join(evidenceRoot,entry.name,'state.json');
1780
+ if(!existsSync(statePath)) continue;
1781
+ let state;try{ state=await readJson(statePath); }catch{ continue; }
1782
+ if(!activeTransitionPhase(state?.phase)) continue;
1783
+ if(state?.failure || existsSync(path.join(path.dirname(statePath),'failure-receipt.json'))) continue;
1784
+ const pendingPointer=statePointer(state,'new_pointer');
1785
+ if(activePointer&&pendingPointer&&samePublicPointer(activePointer,pendingPointer)){
1786
+ await writeState(statePath,state,'superseded',{superseded_at_utc:new Date().toISOString(),superseded_by_pointer:publicPointer(activePointer)});
1787
+ continue;
1788
+ }
1789
+ const handoffPath=String(state?.handoff_path??'');
1790
+ if(!handoffPath||!path.isAbsolute(handoffPath)||!existsSync(handoffPath)) continue;
1791
+ const handoff=await readJson(handoffPath);
1792
+ if(handoff?.body?.schema!==HANDOFF_SCHEMA) continue;
1793
+ const script=path.resolve(String(handoff.body.supervisor_script??''));
1794
+ if(await sha256File(script)!==handoff.body.supervisor_script_sha256) throw new Error('interrupted update supervisor hash drift: '+state.update_id);
1795
+ const result=await spawnDetachedSupervisor({script,handoffPath,cwd:path.dirname(script)});
1796
+ launched+=1;updates.push({update_id:state.update_id,state_path:statePath,handoff_path:handoffPath,supervisor_pid:result.pid});
1797
+ await atomicBytes(path.join(path.dirname(statePath),'recovery-launch.json'),Buffer.from(JSON.stringify({update_id:state.update_id,launched_at_utc:new Date().toISOString(),supervisor_pid:result.pid})+'\n','utf8'));
1798
+ }
1799
+ return {ok:true,launched,updates};
1800
+ }
1801
+
1802
+ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,hostPolicyBootstrap=null,codingPolicyBootstrap=null,autonomousPolicyBootstrap=null,ownerApprovalBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
1803
+ // R64 grants have no signed revocation epoch. The historical boolean restore
1804
+ // cannot prove continuity, and must never materialize a successor grant.
1805
+ if(restoreStandingGrants)throw new Error('R65 standing grant auto-restore disabled: explicit reauthorization required');
1806
+ if(Boolean(autonomousPolicyBootstrap)!==Boolean(ownerApprovalBootstrap)) throw new Error('autonomous policy bootstrap requires pinned owner approval');
1807
+ stateRoot=resolveStateRoot(stateRoot);
1808
+ if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
1809
+ const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
1810
+ const key=await loadUpdateKey(state),updateId='r27-'+expectedManifest.slice(0,16)+'-'+Date.now().toString(36)+'-'+randomBytes(4).toString('hex');
1811
+ const evidenceDir=path.join(state,'evidence','updates',updateId),statePath=path.join(evidenceDir,'state.json'),failurePath=path.join(evidenceDir,'failure-receipt.json');
1812
+ await mkdir(evidenceDir,{recursive:true});
1813
+ const journal={schema:'deadbyte.r27-slot-update-state.v1',update_id:updateId,stage_root:stageRoot,development_root:development,
1814
+ source_root:sourceRoot?path.resolve(sourceRoot):null,expected_version:expectedVersion,expected_manifest_sha256:expectedManifest,
1815
+ restore_standing_grants:restoreStandingGrants,recover_offline_predecessor:recoverOfflinePredecessor,created_at_utc:new Date().toISOString()};
1816
+ await writeState(statePath,journal,'initial');
1817
+ let pointerInfo=null,control=null,predecessor=null,oldRuntime=null,priorStatus=null,authorityPreparation=null,workspace=null,launchers=null,newPointer=null;
1818
+ let stableMcpUrl=null,mcpUrlSnapshot=null;
1819
+ let mutationBegan=false;
1820
+ try{
1821
+ pointerInfo=await loadPointerIfPresent(state);
1822
+ const stageVerified=await verifyPackageRoot(stageRoot,expectedVersion,expectedManifest,'stage');
1823
+ const developmentVerified=await verifyPackageRoot(development,expectedVersion,expectedManifest,'development');
1824
+ control=await loadControlInputs({stateRoot:state,sourceRoot,pointerInfo,bridgeBootstrap});
1825
+ const codingBootstrap=await prepareCodingPolicyBootstrap(codingPolicyBootstrap);
1826
+ const autonomousBootstrap=await prepareAutonomousPolicyBootstrap(autonomousPolicyBootstrap);
1827
+ let ownerApprovalProof=null;
1828
+ if(autonomousBootstrap){
1829
+ if(hostCommandPins.length>0) throw new Error('autonomous bootstrap cannot combine with command repinning');
1830
+ const selectedCoding=codingBootstrap?.policy??control.codingPolicy;
1831
+ const selectedCodingSha256=codingBootstrap?.sha256??await sha256File(control.config.CodingPolicyFile);
1832
+ ownerApprovalProof=await verifyAutonomousOwnerBootstrap({
1833
+ stateRoot:state,bootstrap:ownerApprovalBootstrap,autonomousPolicy:autonomousBootstrap.policy,
1834
+ codingPolicy:selectedCoding,codingPolicySha256:selectedCodingSha256,
1835
+ autonomousPolicySha256:autonomousBootstrap.sha256,expectedManifest
1836
+ });
1837
+ }
1838
+ // Read-only predecessor authority and process identity must be safe before
1839
+ // materializing a slot or changing any stable runtime file.
1840
+ await capturePredecessor(control.ctx,recoverOfflinePredecessor,{allowHostDisarm:false});
1841
+ mutationBegan=true;
1842
+ const slot=await materializeReleaseSlot({source:stageRoot,stateRoot:state,version:expectedVersion,manifestSha256:expectedManifest});
1843
+ const hostBootstrap=await prepareHostPolicyBootstrap(hostPolicyBootstrap);if(hostBootstrap) control.hostPolicy=hostBootstrap.policy;
1844
+ if(codingBootstrap) control.codingPolicy=codingBootstrap.policy;
1845
+ if(autonomousBootstrap) control.autonomousPolicy=autonomousBootstrap.policy;
1846
+ const processBootstrap=await prepareProcessPolicyBootstrap(processPolicyBootstrap);
1847
+ if(processBootstrap){ control.processPolicy=processBootstrap.policy;control.config.ProcessRuntimeEnabled=true; }
1848
+ const desktopBootstrap=await prepareDesktopPolicyBootstrap(desktopPolicyBootstrap);if(desktopBootstrap) control.desktopPolicy=desktopBootstrap.policy;
1849
+ const semanticBootstrap=await prepareSemanticLspConfigBootstrap(semanticLspBootstrap);
1850
+ if(semanticBootstrap){ control.config.SemanticLspConfigFile=semanticBootstrap.path;control.config.SemanticLspConfigSha256=semanticBootstrap.sha256; }
1851
+ if(enableMachineFs){
1852
+ if(control.config.CodingRuntimeEnabled!==true) throw new Error('MachineFsRuntimeEnabled requires CodingRuntimeEnabled');
1853
+ control.config.MachineFsRuntimeEnabled=true;
1854
+ }
1855
+ if(enableObservation){
1856
+ if(control.config.MachineRuntimeEnabled===false) throw new Error('ObservationRuntimeEnabled requires MachineRuntimeEnabled');
1857
+ if(control.config.CodingRuntimeEnabled!==true) throw new Error('ObservationRuntimeEnabled requires CodingRuntimeEnabled');
1858
+ control.config.ObservationRuntimeEnabled=true;
1859
+ }
1860
+ if(enableDesktop){
1861
+ if(!control.desktopPolicy) throw new Error('DesktopRuntimeEnabled requires pinned desktop policy bootstrap or existing policy');
1862
+ control.config.DesktopRuntimeEnabled=true;
1863
+ }
1864
+ control.config=await prepareReleaseBridgeSpec(control.config,bridgeBootstrap,slot.release_root);
1865
+ control.config=await prepareNamedTunnelCloudSpec(control.config,{releaseRoot:slot.release_root,bootstrap:cloudTransport});
1866
+ control.config=await prepareAutonomousSupervisorSpec(control.config,{releaseRoot:slot.release_root,enable:enableAutonomousSupervisor});
1867
+ authorityPreparation=await prepareAuthorityPolicies({hostPolicy:control.hostPolicy,codingPolicy:control.codingPolicy},{commandPins:hostCommandPins});
1868
+ control.hostPolicy=authorityPreparation.hostPolicy;control.codingPolicy=authorityPreparation.codingPolicy;
1869
+ const stableMcp=await ensureStableMcpPath({stateRoot:state,sourceRoot:pointerInfo.pointer?null:sourceRoot});
1870
+ control.config.McpPathFile=stableMcp.path;
1871
+ if(cloudTransport?.publicOrigin){
1872
+ const mcpUrlFile=path.join(state,'bridge','mcp-url.txt');mcpUrlSnapshot=await snapshotFile(mcpUrlFile);
1873
+ stableMcpUrl=await ensureStableMcpUrl({stateRoot:state,publicOrigin:cloudTransport.publicOrigin,mcpPathFile:stableMcp.path});
1874
+ control.config.McpUrlFile=stableMcpUrl.path;control.config.McpUrlFileSha256=stableMcpUrl.sha256;
1875
+ }else if(control.config.Cloud?.Transport==='deadbyte.named-tunnel.v2'){
1876
+ if(typeof control.config.McpUrlFile!=='string'||!path.isAbsolute(control.config.McpUrlFile)||!HEX64.test(String(control.config.McpUrlFileSha256??''))) throw new Error('existing named tunnel v2 requires pinned McpUrlFile before update');
1877
+ const pinned=await verifyPinnedRegularFile(control.config.McpUrlFile,control.config.McpUrlFileSha256,'existing named tunnel MCP URL file');
1878
+ control.config.McpUrlFile=pinned.path;control.config.McpUrlFileSha256=pinned.sha256;
1879
+ }
1880
+ workspace=await workspacePlan(control.workspaceRoot,state);launchers=await launcherPlan(state);
1881
+ const generation=await createGenerationBundle({stateRoot:state,releaseRoot:slot.release_root,developmentRoot:development,workspaceRoot:workspace.target,releaseId:slot.release_id,currentConfig:control.config,codingPolicy:control.codingPolicy,autonomousPolicy:control.autonomousPolicy,hostPolicy:control.hostPolicy,processPolicy:control.processPolicy,desktopPolicy:control.desktopPolicy});
1882
+ newPointer={schema:'deadbyte.active-release.v1',release_id:slot.release_id,version:expectedVersion,manifest_sha256:expectedManifest,release_root:slot.release_root,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,launcher_sha256:launchers.launcher_sha256,config_path:generation.config_path};
1883
+ parseActivePointer(JSON.stringify(newPointer));
1884
+ await writeState(statePath,journal,'candidate_prepared',{prior_pointer:publicPointer(pointerInfo.pointer),stage:stageVerified,development:developmentVerified,
1885
+ release_slot:slot,generation:{generation_id:generation.generation_id,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,files:generation.files},
1886
+ workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode},stable_mcp_path:{path:stableMcp.path,sha256:stableMcp.sha256},
1887
+ stable_mcp_url:stableMcpUrl?{path:stableMcpUrl.path,sha256:stableMcpUrl.sha256,origin:stableMcpUrl.origin,changed:stableMcpUrl.changed}:null,
1888
+ bridge:{working_directory:control.config.Bridge.WorkingDirectory,script_path:control.config.Bridge.ScriptPath,script_sha256:control.config.Bridge.ScriptSha256},
1889
+ host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},
1890
+ owner_approval:ownerApprovalProof?{subject_sha256:ownerApprovalProof.subject_sha256,owner_key_id:ownerApprovalProof.owner_key_id,
1891
+ root_id:ownerApprovalProof.root_id,scope:ownerApprovalProof.scope,expires_at_utc:ownerApprovalProof.expires_at_utc}:null,
1892
+ new_pointer:publicPointer(newPointer)});
1893
+ predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor,{allowHostDisarm:false});oldRuntime=predecessor.runtime;priorStatus=predecessor.status;
1894
+ await writeState(statePath,journal,'old_runtime_verified',{predecessor_mode:predecessor.mode,old_runtime:generationTuple(oldRuntime),
1895
+ prior_authority:{process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed}});
1896
+ const handoff=await writeHandoff({stateRoot:state,state:journal,journal,statePath,evidenceDir,key,pointerInfo,newPointer,predecessor,priorStatus,workspace,launchers,stableMcpUrl,mcpUrlSnapshot,controlCtx:control.ctx});
1897
+ const supervisor=await spawnDetachedSupervisor({script:handoff.body.supervisor_script,handoffPath:handoff.path,cwd:control.ctx.core_root});
1898
+ return {ok:true,handed_off:true,completed:false,update_id:updateId,state_path:statePath,handoff_path:handoff.path,handoff_receipt_sha256:handoff.receipt.receipt_sha256,supervisor_pid:supervisor.pid,pointer:newPointer};
1899
+ }catch(error){
1900
+ const failure={schema:FAILURE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,failed_at_utc:new Date().toISOString(),
1901
+ result:mutationBegan?'MUTATION_OUTCOME_REQUIRES_RECONCILIATION':'PRECONDITION_REJECTED',
1902
+ phase:journal.phase??'unknown',error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
1903
+ predecessor_mode:predecessor?.mode??null,prior_pointer:publicPointer(pointerInfo?.pointer??null),attempted_pointer:publicPointer(newPointer),old_runtime:generationTuple(oldRuntime),
1904
+ rollback:{attempted:false,new_runtime_stopped:false,pointer_restored:false,launchers_restored:false,workspace_restored:false,mcp_url_restored:false,old_runtime_restarted:false,authority_restored:false,errors:[]}};
1905
+ await writeState(statePath,journal,'failed',{failure}).catch(()=>{});
1906
+ let failureReceipt=null;
1907
+ try{ failureReceipt=await writeEnvelope(failurePath,failure,key);await writeState(statePath,journal,mutationBegan?'failed_receipted':'precondition_rejected',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});}
1908
+ catch(receiptError){ await writeState(statePath,journal,'failed_unreceipted',{failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{}); }
1909
+ const wrapped=new Error('R27 slot update handoff failed: '+(error?.message??error));wrapped.cause=error;wrapped.statePath=statePath;wrapped.failureReceiptPath=failureReceipt?failurePath:null;throw wrapped;
1910
+ }
1911
+ }
1912
+
1913
+ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_STATE_ROOT}){
1914
+ stateRoot=resolveStateRoot(stateRoot);
1915
+ if(process.platform!=='win32') throw new Error('R31 slot rollback is Windows-only');
1916
+ const state=path.resolve(stateRoot),receiptFile=path.resolve(activationReceipt);
1917
+ const key=await loadUpdateKey(state);
1918
+ const activationEnvelope=await readJson(receiptFile);
1919
+ if(!verifySlotUpdateEnvelope(activationEnvelope,key.publicKey)) throw new Error('activation receipt signature invalid');
1920
+ if(activationEnvelope.body?.schema!==UPDATE_SCHEMA||activationEnvelope.body?.rollback_performed!==false) throw new Error('activation receipt is not one successful slot activation');
1921
+ const priorPointer=parseActivePointer(JSON.stringify(activationEnvelope.body.prior_pointer));
1922
+ const activePointer=parseActivePointer(JSON.stringify(activationEnvelope.body.active_pointer));
1923
+ const pointerInfo=await loadPointerIfPresent(state);
1924
+ if(!pointerInfo.pointer||canonicalJson(publicPointer(pointerInfo.pointer))!==canonicalJson(publicPointer(activePointer))) throw new Error('rollback current pointer does not match signed activation pointer');
1925
+
1926
+ const handoffPath=path.join(path.dirname(receiptFile),'handoff.json');
1927
+ const {body:handoffBody}=await readHandoffEnvelope(handoffPath,state);
1928
+ if(handoffBody.update_id!==activationEnvelope.body.update_id) throw new Error('rollback handoff update identity mismatch');
1929
+ const rollbackLaunchers=rollbackLauncherPlanFromHandoff(handoffBody,priorPointer,activePointer,state);
1930
+ await verifyStoredInstallation(state,priorPointer);
1931
+
1932
+ const currentCtx=slotContext(state,pointerInfo.pointer),currentRuntime=await captureRuntime(currentCtx);
1933
+ const revoked=await revokeAuthority(currentCtx,currentRuntime.status);
1934
+ const rollbackId=`r31-rollback-${activationEnvelope.receipt_sha256.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
1935
+ const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json'),failurePath=path.join(evidenceDir,'failure-receipt.json');
1936
+ await mkdir(evidenceDir,{recursive:true});
1937
+ const journal={schema:'deadbyte.r31-slot-rollback-state.v1',rollback_id:rollbackId,activation_receipt:path.resolve(receiptFile),
1938
+ activation_receipt_sha256:activationEnvelope.receipt_sha256,handoff_path:handoffPath,
1939
+ from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),created_at_utc:new Date().toISOString()};
1940
+ await writeState(statePath,journal,'verified');
1941
+ let stopped=false,launchersRestored=false,pointerCommitted=false,priorCtx=null,priorRuntime=null;
1942
+ try {
1943
+ await stopAndVerify(currentCtx,currentRuntime); stopped=true;
1944
+ await writeState(statePath,journal,'successor_stopped');
1945
+ await restoreRollbackLaunchers(rollbackLaunchers); launchersRestored=true;
1946
+ await writeState(statePath,journal,'predecessor_launcher_restored',{launcher_sha256:priorPointer.launcher_sha256});
1947
+ await atomicWriteActivePointer(pointerInfo.file,priorPointer); pointerCommitted=true;
1948
+ await verifyActiveInstallation(state,priorPointer);
1949
+ await writeState(statePath,journal,'prior_pointer_restored',{active_pointer:publicPointer(priorPointer)});
1950
+ priorCtx=slotContext(state,priorPointer);
1951
+ priorRuntime=await startAndVerify(priorCtx,currentRuntime);
1952
+ const stabilization=await stabilizeRuntime(priorCtx,priorRuntime);
1953
+ const finalStatus=parseControllerStatus(controllerCaptured(priorCtx,'status',15000));
1954
+ if(finalStatus.process_armed||finalStatus.coding_armed||finalStatus.autonomous_armed||finalStatus.desktop_armed||finalStatus.host_armed) throw new Error('rollback predecessor authority must remain disarmed');
1955
+ const body={schema:ROLLBACK_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
1956
+ completed_at_utc:new Date().toISOString(),from_pointer:publicPointer(activePointer),active_pointer:publicPointer(priorPointer),
1957
+ launcher_restored:true,old_runtime:generationTuple(currentRuntime),new_runtime:generationTuple(priorRuntime),stabilization,
1958
+ authority_revoked:revoked.revoked,rollback_performed:true};
1959
+ const receipt=await writeEnvelope(receiptPath,body,key);
1960
+ await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
1961
+ return {ok:true,rollback_id:rollbackId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,pointer:priorPointer,new_runtime:priorRuntime};
1962
+ } catch(error){
1963
+ const recovery={attempted:false,prior_runtime_stopped:false,successor_launcher_restored:false,successor_pointer_restored:false,successor_runtime_restarted:false,errors:[]};
1964
+ if(stopped){
1965
+ recovery.attempted=true;
1966
+ if(pointerCommitted&&priorCtx){ try { controllerCaptured(priorCtx,'stop',30000); recovery.prior_runtime_stopped=true; } catch(recoveryError){ recovery.errors.push(`stop-prior:${recoveryError.message}`); } }
1967
+ if(launchersRestored){
1968
+ try { await installLaunchers(rollbackLaunchers); recovery.successor_launcher_restored=true; }
1969
+ catch(recoveryError){ recovery.errors.push(`restore-successor-launcher:${recoveryError.message}`); }
1970
+ }
1971
+ if(pointerCommitted){
1972
+ try { await atomicWriteActivePointer(pointerInfo.file,activePointer); await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; }
1973
+ catch(recoveryError){ recovery.errors.push(`restore-pointer:${recoveryError.message}`); }
1974
+ }else if(recovery.successor_launcher_restored){
1975
+ try { await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; }
1976
+ catch(recoveryError){ recovery.errors.push(`verify-successor-pointer:${recoveryError.message}`); }
1977
+ }
1978
+ try { await startAndVerify(currentCtx,currentRuntime); recovery.successor_runtime_restarted=true; } catch(recoveryError){ recovery.errors.push(`restart-successor:${recoveryError.message}`); }
1979
+ }
1980
+ const failure={schema:ROLLBACK_FAILURE_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
1981
+ failed_at_utc:new Date().toISOString(),phase:journal.phase??'unknown',from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),
1982
+ error:{name:error?.name??'Error',message:String(error?.message??error)},recovery};
1983
+ await writeState(statePath,journal,'failed',{error:failure.error,recovery}).catch(()=>{});
1984
+ let failureReceipt=null;
1985
+ try{
1986
+ failureReceipt=await writeEnvelope(failurePath,failure,key);
1987
+ await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});
1988
+ }catch(receiptError){
1989
+ recovery.errors.push('failure-receipt:'+receiptError.message);
1990
+ await writeState(statePath,journal,'failed_unreceipted',{recovery,failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{});
1991
+ }
1992
+ const wrapped=new Error(`R31 slot rollback failed: ${error?.message??error}`); wrapped.cause=error; wrapped.statePath=statePath; wrapped.failureReceiptPath=failureReceipt?failurePath:null; throw wrapped;
1993
+ }
1994
+ }
1995
+
1996
+ export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEFAULT_STATE_ROOT}={}){
1997
+ stateRoot=resolveStateRoot(stateRoot);
1998
+ if(argv.includes('--rollback-receipt')){
1999
+ const args=parseRollbackArgs(argv);
2000
+ const result=await performSlotRollback({activationReceipt:args.activationReceipt,stateRoot});
2001
+ console.log('R31_SLOT_ROLLBACK_PASS');
2002
+ console.log('ROLLBACK_ID '+result.rollback_id); console.log('RECEIPT_SHA256 '+result.receipt_sha256);
2003
+ console.log('STATE_PATH '+result.state_path); console.log('ACTIVE_RELEASE '+result.pointer.release_id);
2004
+ return result;
2005
+ }
2006
+ if(argv.includes('--resume-handoff')){
2007
+ const args=parseResumeHandoffArgs(argv);
2008
+ const result=await resumeSlotUpdate({handoffPath:args.handoff});
2009
+ console.log(result.completed?'R27_SLOT_UPDATE_RESUME_PASS':'R27_SLOT_UPDATE_RESUME_DEFERRED');
2010
+ console.log('UPDATE_ID '+result.update_id);
2011
+ console.log('STATE_PATH '+result.state_path);
2012
+ if(result.receipt_sha256) console.log('RECEIPT_SHA256 '+result.receipt_sha256);
2013
+ if(result.historical_receipt_verified===true) console.log('VERIFICATION_SCOPE historical-signed-receipt; current-runtime-not-observed');
2014
+ return result;
2015
+ }
2016
+ if(argv.includes('--recover-interrupted')){
2017
+ const result=await recoverInterruptedSlotUpdates({stateRoot});
2018
+ console.log('R27_SLOT_UPDATE_RECOVERY launched='+result.launched);
2019
+ for(const update of result.updates) console.log('UPDATE_ID '+update.update_id+' SUPERVISOR_PID '+update.supervisor_pid);
2020
+ return result;
2021
+ }
2022
+ const args=parseUpdateArgs(argv);
2023
+ const result=await performSlotUpdate({
2024
+ stage:args.stage,developmentRoot:args.developmentRoot,expectedVersion:args.expectedVersion,expectedManifest:args.expectedManifest,
2025
+ sourceRoot:args.sourceRoot,stateRoot,restoreStandingGrants:args.restoreStandingGrants,recoverOfflinePredecessor:args.recoverOfflinePredecessor,
2026
+ cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,
2027
+ hostPolicyBootstrap:args.hostPolicyBootstrap,codingPolicyBootstrap:args.codingPolicyBootstrap,
2028
+ autonomousPolicyBootstrap:args.autonomousPolicyBootstrap,ownerApprovalBootstrap:args.ownerApprovalBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,
2029
+ semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,enableObservation:args.enableObservation,
2030
+ enableDesktop:args.enableDesktop,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins
2031
+ });
2032
+ if(result.handed_off){
2033
+ console.log('R27_SLOT_UPDATE_HANDOFF version='+args.expectedVersion);
2034
+ console.log('UPDATE_ID '+result.update_id);
2035
+ console.log('HANDOFF_PATH '+result.handoff_path);
2036
+ console.log('HANDOFF_RECEIPT_SHA256 '+result.handoff_receipt_sha256);
2037
+ console.log('SUPERVISOR_PID '+result.supervisor_pid);
2038
+ }else{
2039
+ console.log('R27_SLOT_UPDATE_PASS version='+args.expectedVersion);
2040
+ console.log('UPDATE_ID '+result.update_id);
2041
+ console.log('RECEIPT_SHA256 '+result.receipt_sha256);
2042
+ console.log('STATE_PATH '+result.state_path);
2043
+ console.log('ACTIVE_RELEASE '+result.pointer.release_id);
2044
+ }
2045
+ return result;
2046
+ }
2047
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===path.resolve(fileURLToPath(import.meta.url));
2048
+ if(invoked){
2049
+ try { await runSlotUpdateCli(); }
2050
+ catch(error){
2051
+ console.error(`R27_SLOT_UPDATE_FAIL ${error?.message??error}`);
2052
+ if(error?.statePath) console.error(`STATE_PATH ${error.statePath}`);
2053
+ if(error?.failureReceiptPath) console.error(`FAILURE_RECEIPT_PATH ${error.failureReceiptPath}`);
2054
+ process.exitCode=1;
2055
+ }
2056
+ }