deadbyte-mcp 0.15.2 → 0.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +54 -33
- package/package.json +88 -88
- package/scripts/autonomous-build.ps1 +1 -0
- package/scripts/autonomous-tests.ps1 +1 -0
- package/scripts/check-agent-backend-r46.mjs +77 -0
- package/scripts/check-atomic-resource-r46.mjs +84 -0
- package/scripts/check-autonomous-race-r46.mjs +160 -0
- package/scripts/check-coding-task-deadline-r46.mjs +59 -0
- package/scripts/check-deadline-cancellation-r46.mjs +49 -0
- package/scripts/check-process-deadline-r46.mjs +52 -0
- package/scripts/check-r46-background.mjs +5 -0
- package/scripts/extract-r46-failures.mjs +5 -0
- package/scripts/full-regression-r46.mjs +20 -12
- package/scripts/gate-windows-r46.ps1 +2 -0
- package/scripts/inspect-r46-active.mjs +2 -0
- package/scripts/r47-core-failures.mjs +1 -0
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/run-r46-full-regression-background.mjs +7 -0
- package/scripts/run-r46-windows-gate-background.mjs +6 -0
- package/scripts/run-r46-windows-gate-background.ps1 +10 -0
- package/scripts/windows-gate-evidence-r46.mjs +4 -3
- package/scripts/workload-contract-regression-r47.mjs +14 -0
- package/scripts/workload-stream-regression.mjs +16 -0
- package/scripts/workload-substrate-r47-regression.mjs +12 -0
- package/src/agent-backend-r44.mjs +75 -10
- package/src/atomic-mutation.mjs +55 -0
- package/src/autonomous-mcp-tools.mjs +4 -3
- package/src/autonomous-output-contracts.mjs +16 -3
- package/src/autonomous-runtime.mjs +83 -30
- package/src/coding-mcp-tools.mjs +2 -2
- package/src/coding-output-contracts.mjs +18 -4
- package/src/coding-plane.mjs +3 -24
- package/src/coding-search.mjs +11 -2
- package/src/coding-task-runtime.mjs +41 -6
- package/src/deadline-cancellation.mjs +83 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +3 -23
- package/src/mcp-server.mjs +6 -0
- package/src/observation-smoke-client.mjs +1 -1
- package/src/process-mcp-tools.mjs +11 -3
- package/src/process-output-contracts.mjs +23 -0
- package/src/process-runtime.mjs +54 -5
- package/src/process-smoke-client.mjs +1 -1
- package/src/resource-identity.mjs +59 -0
- package/src/runtime-update.mjs +526 -162
- package/src/workload-substrate.mjs +87 -0
- package/test/coding-runtime.test.mjs +12 -0
- package/test/coding-search-r36.test.mjs +7 -0
- package/test/full-max-catalog-r46.test.mjs +2 -2
- package/test/helpers/autonomous-r34-fixture.mjs +10 -1
- package/test/process-mcp-tools.test.mjs +5 -2
- package/test/process-runtime.test.mjs +18 -1
- package/test/r45-finalization.test.mjs +1 -1
- package/test/release-version.test.mjs +1 -1
- package/test/runtime-self-update.test.mjs +118 -1
- package/test/workload-substrate.test.mjs +22 -0
package/src/runtime-update.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, createPrivateKey, createPublicKey, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { lstat, mkdir, open, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
5
5
|
import os from 'node:os';
|
|
@@ -22,6 +22,7 @@ const HEX64=/^[0-9a-f]{64}$/;
|
|
|
22
22
|
const SEMVER=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
23
23
|
const RECEIPT_DOMAIN=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0','utf8');
|
|
24
24
|
const UPDATE_SCHEMA='deadbyte.r27-slot-update-receipt.v1';
|
|
25
|
+
const HANDOFF_SCHEMA='deadbyte.r27-slot-update-handoff.v1';
|
|
25
26
|
const FAILURE_SCHEMA='deadbyte.r27-slot-update-failure.v1';
|
|
26
27
|
const ROLLBACK_SCHEMA='deadbyte.r31-slot-rollback-receipt.v1';
|
|
27
28
|
const DEFAULT_STATE_ROOT=path.join(os.homedir(),'.deadbyte-mcp');
|
|
@@ -45,8 +46,8 @@ function normalizePersistentPublicOrigin(value){
|
|
|
45
46
|
return publicOrigin.origin;
|
|
46
47
|
}
|
|
47
48
|
export function parseUpdateArgs(argv){
|
|
48
|
-
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,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
|
|
49
|
-
const cloud={},bridge={},processPolicy={},hostPolicy={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
|
|
49
|
+
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:[]};
|
|
50
|
+
const cloud={},bridge={},processPolicy={},hostPolicy={},codingPolicy={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
|
|
50
51
|
const bridgeFlags=new Map([
|
|
51
52
|
['--bridge-root','root'],['--bridge-script-sha256','scriptSha256']
|
|
52
53
|
]);
|
|
@@ -56,6 +57,9 @@ export function parseUpdateArgs(argv){
|
|
|
56
57
|
const hostPolicyFlags=new Map([
|
|
57
58
|
['--host-policy','path'],['--host-policy-sha256','sha256']
|
|
58
59
|
]);
|
|
60
|
+
const codingPolicyFlags=new Map([
|
|
61
|
+
['--coding-policy','path'],['--coding-policy-sha256','sha256']
|
|
62
|
+
]);
|
|
59
63
|
const desktopPolicyFlags=new Map([
|
|
60
64
|
['--desktop-policy','path'],['--desktop-policy-sha256','sha256']
|
|
61
65
|
]);
|
|
@@ -67,7 +71,7 @@ export function parseUpdateArgs(argv){
|
|
|
67
71
|
['--cloud-token-file','tokenFilePath'],['--cloud-token-sha256','tokenFileSha256'],
|
|
68
72
|
['--cloud-child-working-directory','childWorkingDirectory'],['--cloud-public-origin','publicOrigin']
|
|
69
73
|
]);
|
|
70
|
-
const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...hostPolicyFlags.keys(),...desktopPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
|
|
74
|
+
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()]);
|
|
71
75
|
for(let index=0;index<argv.length;index++){
|
|
72
76
|
const arg=argv[index];
|
|
73
77
|
if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-desktop'||arg==='--enable-autonomous-supervisor'){
|
|
@@ -103,6 +107,7 @@ export function parseUpdateArgs(argv){
|
|
|
103
107
|
else if(bridgeFlags.has(arg)) bridge[bridgeFlags.get(arg)]=value;
|
|
104
108
|
else if(processPolicyFlags.has(arg)) processPolicy[processPolicyFlags.get(arg)]=value;
|
|
105
109
|
else if(hostPolicyFlags.has(arg)) hostPolicy[hostPolicyFlags.get(arg)]=value;
|
|
110
|
+
else if(codingPolicyFlags.has(arg)) codingPolicy[codingPolicyFlags.get(arg)]=value;
|
|
106
111
|
else if(desktopPolicyFlags.has(arg)) desktopPolicy[desktopPolicyFlags.get(arg)]=value;
|
|
107
112
|
else if(semanticLspFlags.has(arg)) semanticLsp[semanticLspFlags.get(arg)]=value;
|
|
108
113
|
else cloud[cloudFlags.get(arg)]=value;
|
|
@@ -132,6 +137,13 @@ export function parseUpdateArgs(argv){
|
|
|
132
137
|
if(!HEX64.test(sha256)) throw new Error('host policy SHA-256 must be lowercase hex');
|
|
133
138
|
out.hostPolicyBootstrap={path:path.resolve(hostPolicy.path),sha256};
|
|
134
139
|
}
|
|
140
|
+
const codingPolicyFields=[...codingPolicyFlags.values()],codingPolicyPresent=codingPolicyFields.filter(key=>codingPolicy[key]!==undefined);
|
|
141
|
+
if(codingPolicyPresent.length!==0&&codingPolicyPresent.length!==codingPolicyFields.length) throw new Error('coding policy bootstrap requires path plus SHA-256 as one pair');
|
|
142
|
+
if(codingPolicyPresent.length===codingPolicyFields.length){
|
|
143
|
+
const sha256=String(codingPolicy.sha256).toLowerCase();
|
|
144
|
+
if(!HEX64.test(sha256)) throw new Error('coding policy SHA-256 must be lowercase hex');
|
|
145
|
+
out.codingPolicyBootstrap={path:path.resolve(codingPolicy.path),sha256};
|
|
146
|
+
}
|
|
135
147
|
const desktopPolicyFields=[...desktopPolicyFlags.values()],desktopPolicyPresent=desktopPolicyFields.filter(key=>desktopPolicy[key]!==undefined);
|
|
136
148
|
if(desktopPolicyPresent.length!==0&&desktopPolicyPresent.length!==desktopPolicyFields.length) throw new Error('desktop policy bootstrap requires path plus SHA-256 as one pair');
|
|
137
149
|
if(desktopPolicyPresent.length===desktopPolicyFields.length){
|
|
@@ -358,6 +370,18 @@ export async function prepareHostPolicyBootstrap(bootstrap){
|
|
|
358
370
|
return {...pinned,policy};
|
|
359
371
|
}
|
|
360
372
|
|
|
373
|
+
export async function prepareCodingPolicyBootstrap(bootstrap){
|
|
374
|
+
if(!bootstrap) return null;
|
|
375
|
+
const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'coding policy');
|
|
376
|
+
const policy=await readJson(pinned.path);
|
|
377
|
+
if(policy?.schema!=='deadbyte.coding-policy.v1'||policy?.enabled!==true||
|
|
378
|
+
!policy?.roots||typeof policy.roots!=='object'||Array.isArray(policy.roots)||
|
|
379
|
+
!policy?.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
|
|
380
|
+
throw new Error('coding policy schema/enabled/roots/commands mismatch');
|
|
381
|
+
}
|
|
382
|
+
return {...pinned,policy};
|
|
383
|
+
}
|
|
384
|
+
|
|
361
385
|
export async function prepareDesktopPolicyBootstrap(bootstrap){
|
|
362
386
|
if(!bootstrap) return null;
|
|
363
387
|
const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'desktop policy');
|
|
@@ -769,13 +793,20 @@ async function workspacePlan(currentWorkspace,stateRoot){
|
|
|
769
793
|
}
|
|
770
794
|
async function applyWorkspacePlan(plan){
|
|
771
795
|
if(plan.mode==='existing'||plan.mode==='target-existing') return plan;
|
|
796
|
+
const sourceExists=existsSync(plan.source),targetExists=existsSync(plan.target);
|
|
797
|
+
if(plan.mode==='create'){
|
|
798
|
+
if(targetExists){ plan.created=true; return plan; }
|
|
799
|
+
await mkdir(path.dirname(plan.target),{recursive:true});
|
|
800
|
+
await mkdir(plan.target,{recursive:false}); plan.created=true; return plan;
|
|
801
|
+
}
|
|
802
|
+
if(targetExists&&!sourceExists){ plan.moved=true; return plan; }
|
|
803
|
+
if(sourceExists&&targetExists) throw new Error('workspace migration conflict: both source and stable target exist ('+plan.source+', '+plan.target+')');
|
|
772
804
|
await mkdir(path.dirname(plan.target),{recursive:true});
|
|
773
|
-
if(plan.mode==='create'){ await mkdir(plan.target,{recursive:false}); plan.created=true; return plan; }
|
|
774
805
|
await rename(plan.source,plan.target); plan.moved=true; return plan;
|
|
775
806
|
}
|
|
776
807
|
async function rollbackWorkspacePlan(plan){
|
|
777
|
-
if(plan.
|
|
778
|
-
if(plan.
|
|
808
|
+
if(plan.mode==='rename'&&existsSync(plan.target)&&!existsSync(plan.source)){ await rename(plan.target,plan.source); plan.moved=false; return true; }
|
|
809
|
+
if(plan.mode==='create'&&existsSync(plan.target)){ await rm(plan.target,{recursive:true,force:true}); plan.created=false; return true; }
|
|
779
810
|
return false;
|
|
780
811
|
}
|
|
781
812
|
async function launcherPlan(stateRoot){
|
|
@@ -792,7 +823,9 @@ async function installLaunchers(plan){
|
|
|
792
823
|
return plan;
|
|
793
824
|
}
|
|
794
825
|
async function rollbackLaunchers(plan){
|
|
795
|
-
if(!plan
|
|
826
|
+
if(!plan) return false;
|
|
827
|
+
const touched=plan.installed===true || (existsSync(plan.ps1)&&sha256Bytes(await readFile(plan.ps1))===plan.launcher_sha256);
|
|
828
|
+
if(!touched) return false;
|
|
796
829
|
await restoreFile(plan.ps1,plan.snapshots.ps1); await restoreFile(plan.cmd,plan.snapshots.cmd); await restoreFile(plan.top,plan.snapshots.top);
|
|
797
830
|
plan.installed=false; return true;
|
|
798
831
|
}
|
|
@@ -872,7 +905,13 @@ export function verifySlotUpdateEnvelope(envelope,publicKey){
|
|
|
872
905
|
}
|
|
873
906
|
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; }
|
|
874
907
|
async function writeState(file,state,phase,extra={}){
|
|
875
|
-
|
|
908
|
+
let observed=null;
|
|
909
|
+
try { observed=await readJson(file); } catch(error) { if(error?.code!=='ENOENT') throw error; }
|
|
910
|
+
const observedRank=phaseRank(observed?.phase);
|
|
911
|
+
const requestedRank=phaseRank(phase);
|
|
912
|
+
const effectivePhase=observedRank>requestedRank?String(observed.phase):phase;
|
|
913
|
+
if(observed) Object.assign(state,observed);
|
|
914
|
+
Object.assign(state,extra,{phase:effectivePhase,updated_at_utc:new Date().toISOString()});
|
|
876
915
|
await atomicBytes(file,Buffer.from(`${JSON.stringify(state,null,2)}\n`,'utf8'));
|
|
877
916
|
}
|
|
878
917
|
function generationTuple(runtime){
|
|
@@ -888,40 +927,433 @@ function publicPointer(pointer){
|
|
|
888
927
|
release_root:pointer.release_root,generation_root:pointer.generation_root,generation_manifest_sha256:pointer.generation_manifest_sha256,
|
|
889
928
|
launcher_sha256:pointer.launcher_sha256,config_path:pointer.config_path};
|
|
890
929
|
}
|
|
891
|
-
|
|
930
|
+
|
|
931
|
+
function encodeSnapshot(snapshot){
|
|
932
|
+
if(!snapshot||typeof snapshot!=='object') throw new Error('snapshot object required');
|
|
933
|
+
return {exists:snapshot.exists===true,sha256:snapshot.sha256??null,bytes_base64:snapshot.exists&&snapshot.bytes?Buffer.from(snapshot.bytes).toString('base64'):null};
|
|
934
|
+
}
|
|
935
|
+
function decodeSnapshot(snapshot,label){
|
|
936
|
+
if(!snapshot||typeof snapshot!=='object'||typeof snapshot.exists!=='boolean') throw new Error(label+' snapshot invalid');
|
|
937
|
+
if(snapshot.exists){
|
|
938
|
+
if(typeof snapshot.bytes_base64!=='string') throw new Error(label+' snapshot bytes missing');
|
|
939
|
+
const bytes=Buffer.from(snapshot.bytes_base64,'base64'),actual=sha256Bytes(bytes);
|
|
940
|
+
if(snapshot.sha256!==actual) throw new Error(label+' snapshot SHA-256 mismatch');
|
|
941
|
+
return {exists:true,bytes,sha256:actual};
|
|
942
|
+
}
|
|
943
|
+
return {exists:false,bytes:null,sha256:null};
|
|
944
|
+
}
|
|
945
|
+
const UPDATE_PHASE_RANK=Object.freeze({
|
|
946
|
+
initial:0,candidate_prepared:10,old_runtime_verified:20,shutdown_handoff_armed:25,supervisor_spawned:30,
|
|
947
|
+
authority_revoked:40,old_runtime_stopped:50,workspace_ready:60,launcher_installed:70,pointer_committed:80,
|
|
948
|
+
fresh_runtime_verified:90,authority_verified:100,completed:110,superseded:120,failed:1000,failed_receipted:1001,failed_unreceipted:1002
|
|
949
|
+
});
|
|
950
|
+
function phaseRank(phase){ return UPDATE_PHASE_RANK[String(phase)]??-1; }
|
|
951
|
+
function activeTransitionPhase(phase){
|
|
952
|
+
const rank=phaseRank(phase);
|
|
953
|
+
return rank>=phaseRank('shutdown_handoff_armed')&&rank<phaseRank('completed')&&rank<phaseRank('failed');
|
|
954
|
+
}
|
|
955
|
+
function samePublicPointer(a,b){
|
|
956
|
+
if(!a&&!b) return true;
|
|
957
|
+
if(!a||!b) return false;
|
|
958
|
+
return canonicalJson(publicPointer(a))===canonicalJson(publicPointer(b));
|
|
959
|
+
}
|
|
960
|
+
function statePointer(state,key){
|
|
961
|
+
if(!state?.[key]) return null;
|
|
962
|
+
return parseActivePointer(JSON.stringify(state[key]));
|
|
963
|
+
}
|
|
964
|
+
async function spawnDetachedSupervisor({script,handoffPath,cwd}){
|
|
965
|
+
const absoluteScript=path.resolve(script),absoluteHandoff=path.resolve(handoffPath);
|
|
966
|
+
const stat=await lstat(absoluteScript);
|
|
967
|
+
if(!stat.isFile()||stat.isSymbolicLink()) throw new Error('detached updater supervisor script must be a regular file');
|
|
968
|
+
const child=await new Promise((resolve,reject)=>{
|
|
969
|
+
const proc=spawn(process.execPath,[absoluteScript,'--resume-handoff',absoluteHandoff],{
|
|
970
|
+
cwd:path.resolve(cwd),detached:true,windowsHide:true,stdio:'ignore'
|
|
971
|
+
});
|
|
972
|
+
const onError=error=>reject(error);
|
|
973
|
+
proc.once('error',onError);
|
|
974
|
+
proc.once('spawn',()=>{
|
|
975
|
+
proc.removeListener('error',onError);
|
|
976
|
+
proc.unref();
|
|
977
|
+
resolve(proc);
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
if(!child.pid||child.pid<1) throw new Error('detached updater supervisor did not return a valid PID');
|
|
981
|
+
return {pid:child.pid,script_sha256:await sha256File(absoluteScript)};
|
|
982
|
+
}
|
|
983
|
+
async function acquireSupervisorLease(handoffPath){
|
|
984
|
+
const lockDir=path.resolve(handoffPath)+'.lock',lockFile=path.join(lockDir,'owner.json');
|
|
985
|
+
for(let attempt=0;attempt<2;attempt+=1){
|
|
986
|
+
try{
|
|
987
|
+
await mkdir(lockDir,{recursive:false});
|
|
988
|
+
await atomicBytes(lockFile,Buffer.from(JSON.stringify({pid:process.pid,handoff:path.resolve(handoffPath),started_at_utc:new Date().toISOString()})+'\n','utf8'));
|
|
989
|
+
return async()=>{ await rm(lockDir,{recursive:true,force:true}); };
|
|
990
|
+
}catch(error){
|
|
991
|
+
if(error?.code!=='EEXIST') throw error;
|
|
992
|
+
let owner=null; try{ owner=await readJson(lockFile); }catch{}
|
|
993
|
+
const ownerPid=Number(owner?.pid),needle=path.resolve(handoffPath).replaceAll('/','\\\\').toLowerCase();
|
|
994
|
+
const alive=Number.isInteger(ownerPid)&&ownerPid>0&&nodeProcesses().some(item=>
|
|
995
|
+
Number(item.ProcessId)===ownerPid&&String(item.CommandLine??'').replaceAll('/','\\\\').toLowerCase().includes(needle)
|
|
996
|
+
);
|
|
997
|
+
if(alive) throw new Error('slot update supervisor already active pid='+ownerPid);
|
|
998
|
+
await rm(lockDir,{recursive:true,force:true});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
throw new Error('unable to acquire slot update supervisor lease');
|
|
1002
|
+
}
|
|
1003
|
+
export function parseResumeHandoffArgs(argv){
|
|
1004
|
+
let handoff=null;
|
|
1005
|
+
for(let index=0;index<argv.length;index+=1){
|
|
1006
|
+
const arg=argv[index];
|
|
1007
|
+
if(arg!=='--resume-handoff') throw new Error("unknown supervisor argument '"+arg+"'");
|
|
1008
|
+
if(handoff!==null) throw new Error("duplicate supervisor argument '--resume-handoff'");
|
|
1009
|
+
const value=argv[++index];
|
|
1010
|
+
if(!value) throw new Error('--resume-handoff requires a value');
|
|
1011
|
+
handoff=path.resolve(value);
|
|
1012
|
+
}
|
|
1013
|
+
if(!handoff) throw new Error('--resume-handoff is required');
|
|
1014
|
+
return {handoff};
|
|
1015
|
+
}
|
|
1016
|
+
async function readHandoffEnvelope(handoffPath,stateRoot){
|
|
1017
|
+
const envelope=await readJson(handoffPath),key=await loadUpdateKey(stateRoot);
|
|
1018
|
+
if(!verifySlotUpdateEnvelope(envelope,key.publicKey)) throw new Error('slot-update handoff signature invalid');
|
|
1019
|
+
if(envelope.body?.schema!==HANDOFF_SCHEMA) throw new Error('slot-update handoff schema invalid');
|
|
1020
|
+
const body=envelope.body;
|
|
1021
|
+
if(typeof body.update_id!=='string'||!HEX64.test(String(body.supervisor_script_sha256??''))||
|
|
1022
|
+
!path.isAbsolute(String(body.supervisor_script??''))||!path.isAbsolute(String(body.state_path??''))) {
|
|
1023
|
+
throw new Error('slot-update handoff identity invalid');
|
|
1024
|
+
}
|
|
1025
|
+
const actualSupervisorHash=await sha256File(path.resolve(body.supervisor_script));
|
|
1026
|
+
if(actualSupervisorHash!==body.supervisor_script_sha256) throw new Error('slot-update handoff supervisor hash drift');
|
|
1027
|
+
return {envelope,body,key};
|
|
1028
|
+
}
|
|
1029
|
+
async function writeHandoff({stateRoot,state,journal,statePath,evidenceDir,key,pointerInfo,newPointer,predecessor,priorStatus,workspace,launchers,stableMcpUrl,mcpUrlSnapshot,controlCtx}){
|
|
1030
|
+
const supervisorScript=path.join(path.resolve(newPointer.release_root),'scripts','runtime-self-update.mjs');
|
|
1031
|
+
const supervisorScriptSha256=await sha256File(supervisorScript);
|
|
1032
|
+
const handoffPath=path.join(path.resolve(evidenceDir),'handoff.json');
|
|
1033
|
+
const body={
|
|
1034
|
+
schema:HANDOFF_SCHEMA,update_id:state.update_id,created_at_utc:new Date().toISOString(),
|
|
1035
|
+
state_root:path.resolve(stateRoot),state_path:path.resolve(statePath),handoff_path:path.resolve(handoffPath),
|
|
1036
|
+
supervisor_script:path.resolve(supervisorScript),supervisor_script_sha256:supervisorScriptSha256,
|
|
1037
|
+
prior_pointer:publicPointer(pointerInfo.pointer),new_pointer:publicPointer(newPointer),predecessor_mode:predecessor.mode,
|
|
1038
|
+
old_runtime:generationTuple(predecessor.runtime),
|
|
1039
|
+
prior_authority:{
|
|
1040
|
+
process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,
|
|
1041
|
+
desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed
|
|
1042
|
+
},
|
|
1043
|
+
pointer_snapshot:encodeSnapshot(pointerInfo.snapshot),
|
|
1044
|
+
workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode,moved:workspace.moved===true,created:workspace.created===true},
|
|
1045
|
+
launchers:{
|
|
1046
|
+
ps1:launchers.ps1,cmd:launchers.cmd,top:launchers.top,launcher_sha256:launchers.launcher_sha256,
|
|
1047
|
+
ps1_bytes:Buffer.from(launchers.ps1Bytes).toString('base64'),cmd_bytes:Buffer.from(launchers.cmdBytes).toString('base64'),
|
|
1048
|
+
snapshots:{
|
|
1049
|
+
ps1:encodeSnapshot(launchers.snapshots.ps1),cmd:encodeSnapshot(launchers.snapshots.cmd),top:encodeSnapshot(launchers.snapshots.top)
|
|
1050
|
+
}
|
|
1051
|
+
},
|
|
1052
|
+
stable_mcp_url:stableMcpUrl?{path:stableMcpUrl.path,sha256:stableMcpUrl.sha256,origin:stableMcpUrl.origin,changed:stableMcpUrl.changed===true}:null,
|
|
1053
|
+
mcp_url_snapshot:stableMcpUrl?.changed?encodeSnapshot(mcpUrlSnapshot):null,
|
|
1054
|
+
restore_standing_grants:state.restore_standing_grants===true
|
|
1055
|
+
};
|
|
1056
|
+
const receipt=await writeEnvelope(handoffPath,body,key);
|
|
1057
|
+
await writeState(statePath,journal,'shutdown_handoff_armed',{
|
|
1058
|
+
handoff_path:handoffPath,handoff_receipt_sha256:receipt.receipt_sha256,
|
|
1059
|
+
supervisor_script:supervisorScript,supervisor_script_sha256:supervisorScriptSha256
|
|
1060
|
+
});
|
|
1061
|
+
return {path:handoffPath,body,receipt};
|
|
1062
|
+
}
|
|
1063
|
+
async function parseCurrentPointer(file){
|
|
1064
|
+
if(!existsSync(file)) return null;
|
|
1065
|
+
return parseActivePointer(await readFile(file,'utf8'));
|
|
1066
|
+
}
|
|
1067
|
+
async function ensurePointerCommitted(pointerFile,priorPointer,newPointer){
|
|
1068
|
+
const current=await parseCurrentPointer(pointerFile);
|
|
1069
|
+
if(samePublicPointer(current,newPointer)) return {committed:true,already:true};
|
|
1070
|
+
if(!samePublicPointer(current,priorPointer)) throw new Error('active pointer changed unexpectedly before slot commit');
|
|
1071
|
+
await atomicWriteActivePointer(pointerFile,newPointer);
|
|
1072
|
+
const verify=await parseCurrentPointer(pointerFile);
|
|
1073
|
+
if(!samePublicPointer(verify,newPointer)) throw new Error('active pointer commit readback mismatch');
|
|
1074
|
+
return {committed:true,already:false};
|
|
1075
|
+
}
|
|
1076
|
+
async function coreGone(runtime){
|
|
1077
|
+
return !runtime||!(await nodeProcesses()).some(item=>
|
|
1078
|
+
Number(item.ProcessId)===Number(runtime.core.pid)&&String(item.CreationDate??'')===String(runtime.core.creation_date)
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
async function runtimeAlreadyOnline(ctx,oldRuntime){
|
|
1082
|
+
const statusText=controllerCaptured(ctx,'status',15000),status=parseControllerStatus(statusText);
|
|
1083
|
+
if(!status.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) return null;
|
|
1084
|
+
const bridge=await readLedger(ctx,'bridge'),cloud=await readLedger(ctx,'cloud'),core=coreProcess(ctx.core_root,bridge);
|
|
1085
|
+
if(oldRuntime?.bridge&&sameGeneration(oldRuntime.bridge,bridge)) throw new Error('fresh bridge generation gate failed');
|
|
1086
|
+
if(oldRuntime?.cloud&&sameGeneration(oldRuntime.cloud,cloud)) throw new Error('fresh cloud generation gate failed');
|
|
1087
|
+
if(oldRuntime?.core&&oldRuntime.core.pid===core.pid&&oldRuntime.core.creation_date===core.creation_date) {
|
|
1088
|
+
throw new Error('fresh downstream core generation gate failed');
|
|
1089
|
+
}
|
|
1090
|
+
return {status,status_text:statusText.trim(),bridge,cloud,core};
|
|
1091
|
+
}
|
|
1092
|
+
async function ensureFreshRuntime(ctx,oldRuntime){
|
|
1093
|
+
let existing=null;
|
|
1094
|
+
try{ existing=await runtimeAlreadyOnline(ctx,oldRuntime); }
|
|
1095
|
+
catch(error){ if(/generation gate/.test(String(error?.message??''))) throw error; }
|
|
1096
|
+
return existing??await startAndVerify(ctx,oldRuntime);
|
|
1097
|
+
}
|
|
1098
|
+
async function ensureAuthorityRevoked(ctx,prior){
|
|
1099
|
+
const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
|
|
1100
|
+
if(status.host_armed) throw new Error('host plane armed during authority revoke recovery');
|
|
1101
|
+
if(!status.process_armed&&!status.coding_armed&&!status.autonomous_armed&&!status.desktop_armed) {
|
|
1102
|
+
return {revoked:prior.process_armed||prior.coding_armed||prior.autonomous_armed||prior.desktop_armed,status};
|
|
1103
|
+
}
|
|
1104
|
+
return revokeAuthority(ctx,status);
|
|
1105
|
+
}
|
|
1106
|
+
function handoffPriorPointer(body){
|
|
1107
|
+
return body.prior_pointer?parseActivePointer(JSON.stringify(body.prior_pointer)):null;
|
|
1108
|
+
}
|
|
1109
|
+
function handoffNewPointer(body){
|
|
1110
|
+
return body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
|
|
1111
|
+
}
|
|
1112
|
+
async function resumeSlotUpdate({handoffPath}){
|
|
1113
|
+
const absoluteHandoff=path.resolve(handoffPath),probe=await readJson(absoluteHandoff);
|
|
1114
|
+
const stateRoot=path.resolve(String(probe?.body?.state_root??path.dirname(path.dirname(path.dirname(path.dirname(absoluteHandoff))))));
|
|
1115
|
+
const {body,key}=await readHandoffEnvelope(absoluteHandoff,stateRoot);
|
|
1116
|
+
const statePath=path.resolve(body.state_path),releaseState=await readJson(statePath);
|
|
1117
|
+
if(releaseState.schema!=='deadbyte.r27-slot-update-state.v1'||releaseState.update_id!==body.update_id) throw new Error('handoff state binding mismatch');
|
|
1118
|
+
let phase=String(releaseState.phase??'');
|
|
1119
|
+
if(phase==='completed'){
|
|
1120
|
+
return {ok:true,resumed:false,completed:true,update_id:body.update_id,state_path:statePath,
|
|
1121
|
+
receipt_path:releaseState.receipt_path??null,receipt_sha256:releaseState.receipt_sha256??null};
|
|
1122
|
+
}
|
|
1123
|
+
if(phase.startsWith('failed')) throw new Error('slot update already failed at phase='+phase);
|
|
1124
|
+
const priorPointer=handoffPriorPointer(body),candidatePointer=handoffNewPointer(body);
|
|
1125
|
+
const statePrior=statePointer(releaseState,'prior_pointer'),stateCandidate=statePointer(releaseState,'new_pointer');
|
|
1126
|
+
if(!samePublicPointer(priorPointer,statePrior)||!samePublicPointer(candidatePointer,stateCandidate)) throw new Error('handoff pointer binding mismatch');
|
|
1127
|
+
if(!candidatePointer) throw new Error('handoff candidate pointer missing');
|
|
1128
|
+
const pointerFile=path.join(stateRoot,'active-release.json'),currentPointer=await parseCurrentPointer(pointerFile);
|
|
1129
|
+
if(!samePublicPointer(currentPointer,priorPointer)&&!samePublicPointer(currentPointer,candidatePointer)) throw new Error('active pointer is neither handoff predecessor nor candidate');
|
|
1130
|
+
const pointerInfo={file:pointerFile,pointer:currentPointer,snapshot:decodeSnapshot(body.pointer_snapshot,'active pointer')};
|
|
1131
|
+
const ctx=slotContext(stateRoot,priorPointer),newCtx=slotContext(stateRoot,candidatePointer);
|
|
1132
|
+
const priorStatus={
|
|
1133
|
+
process_armed:body.prior_authority?.process_armed===true,coding_armed:body.prior_authority?.coding_armed===true,
|
|
1134
|
+
autonomous_armed:body.prior_authority?.autonomous_armed===true,desktop_armed:body.prior_authority?.desktop_armed===true,
|
|
1135
|
+
host_armed:body.prior_authority?.host_armed===true
|
|
1136
|
+
};
|
|
1137
|
+
if(priorStatus.autonomous_armed&&!priorStatus.coding_armed) throw new Error('invalid prior authority in handoff');
|
|
1138
|
+
const oldRuntime=body.old_runtime?{bridge:body.old_runtime.bridge??null,cloud:body.old_runtime.cloud??null,core:body.old_runtime.core??null}:null;
|
|
1139
|
+
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');
|
|
1140
|
+
const launchers={
|
|
1141
|
+
ps1:path.resolve(String(body.launchers?.ps1??'')),cmd:path.resolve(String(body.launchers?.cmd??'')),top:path.resolve(String(body.launchers?.top??TOP_LAUNCHER)),
|
|
1142
|
+
launcher_sha256:String(body.launchers?.launcher_sha256??''),
|
|
1143
|
+
ps1Bytes:Buffer.from(String(body.launchers?.ps1_bytes??''),'base64'),cmdBytes:Buffer.from(String(body.launchers?.cmd_bytes??''),'base64'),
|
|
1144
|
+
snapshots:{
|
|
1145
|
+
ps1:decodeSnapshot(body.launchers?.snapshots?.ps1,'launcher.ps1'),cmd:decodeSnapshot(body.launchers?.snapshots?.cmd,'launcher.cmd'),top:decodeSnapshot(body.launchers?.snapshots?.top,'launcher.top')
|
|
1146
|
+
},
|
|
1147
|
+
installed:phaseRank(phase)>=phaseRank('launcher_installed')
|
|
1148
|
+
};
|
|
1149
|
+
if(!HEX64.test(launchers.launcher_sha256)||sha256Bytes(launchers.ps1Bytes)!==launchers.launcher_sha256) throw new Error('handoff launcher bytes/hash mismatch');
|
|
1150
|
+
const workspace={
|
|
1151
|
+
source:path.resolve(String(body.workspace?.source??releaseState.workspace?.source??'')),
|
|
1152
|
+
target:path.resolve(String(body.workspace?.target??releaseState.workspace?.target??'')),
|
|
1153
|
+
mode:String(body.workspace?.mode??releaseState.workspace?.mode??''),
|
|
1154
|
+
moved:releaseState.workspace?.moved===true,created:releaseState.workspace?.created===true
|
|
1155
|
+
};
|
|
1156
|
+
if(!['existing','target-existing','create','rename'].includes(workspace.mode)) throw new Error('handoff workspace mode invalid');
|
|
1157
|
+
const stableMcpUrl=body.stable_mcp_url?{...body.stable_mcp_url}:releaseState.stable_mcp_url?{...releaseState.stable_mcp_url}:null;
|
|
1158
|
+
const mcpUrlSnapshot=body.mcp_url_snapshot?decodeSnapshot(body.mcp_url_snapshot,'MCP URL'):{exists:false,bytes:null,sha256:null};
|
|
1159
|
+
const cleanup=await acquireSupervisorLease(absoluteHandoff);
|
|
1160
|
+
try{
|
|
1161
|
+
const journal=releaseState;
|
|
1162
|
+
if(phaseRank(phase)<phaseRank('supervisor_spawned')){
|
|
1163
|
+
await writeState(statePath,journal,'supervisor_spawned',{supervisor_pid:process.pid,supervisor_script:body.supervisor_script,supervisor_script_sha256:body.supervisor_script_sha256});
|
|
1164
|
+
phase='supervisor_spawned';
|
|
1165
|
+
}
|
|
1166
|
+
let revoked=phaseRank(phase)>=phaseRank('authority_revoked')||journal.revoked_before_switch===true;
|
|
1167
|
+
let stopped=phaseRank(phase)>=phaseRank('old_runtime_stopped');
|
|
1168
|
+
let pointerCommitted=phaseRank(phase)>=phaseRank('pointer_committed')||samePublicPointer(currentPointer,candidatePointer);
|
|
1169
|
+
let newRuntime=null,stabilization=null;
|
|
1170
|
+
|
|
1171
|
+
if(phaseRank(phase)<phaseRank('authority_revoked')){
|
|
1172
|
+
const revoke=await ensureAuthorityRevoked(ctx,priorStatus);
|
|
1173
|
+
revoked=revoke.revoked;
|
|
1174
|
+
await writeState(statePath,journal,'authority_revoked',{revoked_before_switch:revoked});
|
|
1175
|
+
phase='authority_revoked';
|
|
1176
|
+
}
|
|
1177
|
+
if(!stopped){
|
|
1178
|
+
const status=parseControllerStatus(controllerCaptured(ctx,'status',15000)),gone=await coreGone(oldRuntime);
|
|
1179
|
+
if(!oldRuntime||(status.offline&&gone)){
|
|
1180
|
+
await writeState(statePath,journal,'old_runtime_stopped',{stop_output:'recovery observed predecessor already OFFLINE'});
|
|
1181
|
+
}else{
|
|
1182
|
+
const stoppedResult=await stopAndVerify(ctx,oldRuntime);
|
|
1183
|
+
await writeState(statePath,journal,'old_runtime_stopped',{stop_output:stoppedResult.output});
|
|
1184
|
+
}
|
|
1185
|
+
stopped=true; phase='old_runtime_stopped';
|
|
1186
|
+
}
|
|
1187
|
+
if(phaseRank(phase)<phaseRank('workspace_ready')){
|
|
1188
|
+
await applyWorkspacePlan(workspace);
|
|
1189
|
+
await writeState(statePath,journal,'workspace_ready',{workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode,moved:workspace.moved===true,created:workspace.created===true}});
|
|
1190
|
+
phase='workspace_ready';
|
|
1191
|
+
}else{
|
|
1192
|
+
workspace.moved=journal.workspace?.moved===true; workspace.created=journal.workspace?.created===true;
|
|
1193
|
+
}
|
|
1194
|
+
if(phaseRank(phase)<phaseRank('launcher_installed')){
|
|
1195
|
+
await installLaunchers(launchers);
|
|
1196
|
+
await writeState(statePath,journal,'launcher_installed',{launcher_sha256:launchers.launcher_sha256,launcher_path:launchers.ps1,top_launcher:launchers.top});
|
|
1197
|
+
phase='launcher_installed';
|
|
1198
|
+
}
|
|
1199
|
+
if(!pointerCommitted){
|
|
1200
|
+
await ensurePointerCommitted(pointerFile,priorPointer,candidatePointer);
|
|
1201
|
+
pointerCommitted=true;
|
|
1202
|
+
await writeState(statePath,journal,'pointer_committed',{active_pointer:publicPointer(candidatePointer)});
|
|
1203
|
+
phase='pointer_committed';
|
|
1204
|
+
}
|
|
1205
|
+
await verifyActiveInstallation(stateRoot,candidatePointer);
|
|
1206
|
+
newRuntime=await ensureFreshRuntime(newCtx,oldRuntime);
|
|
1207
|
+
stabilization=await stabilizeRuntime(newCtx,newRuntime);
|
|
1208
|
+
await writeState(statePath,journal,'fresh_runtime_verified',{new_runtime:generationTuple(newRuntime),stabilization});
|
|
1209
|
+
phase='fresh_runtime_verified';
|
|
1210
|
+
|
|
1211
|
+
const finalAuthority=await restoreStandingAuthority(newCtx,priorStatus,journal.restore_standing_grants===true);
|
|
1212
|
+
await writeState(statePath,journal,'authority_verified',{final_authority:{
|
|
1213
|
+
process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,
|
|
1214
|
+
desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed
|
|
1215
|
+
}});
|
|
1216
|
+
phase='authority_verified';
|
|
1217
|
+
|
|
1218
|
+
const bodyReceipt={
|
|
1219
|
+
schema:UPDATE_SCHEMA,update_id:body.update_id,release_version:journal.expected_version,candidate_manifest_sha256:journal.expected_manifest_sha256,
|
|
1220
|
+
completed_at_utc:new Date().toISOString(),activation_boundary:'active-release-pointer',
|
|
1221
|
+
mechanism:'immutable-release-slot-plus-atomic-pointer-rename-plus-detached-supervisor-handoff',
|
|
1222
|
+
predecessor_mode:journal.predecessor_mode??body.predecessor_mode,prior_pointer:publicPointer(priorPointer),active_pointer:publicPointer(candidatePointer),
|
|
1223
|
+
release_slot:journal.release_slot,generation:journal.generation,
|
|
1224
|
+
launcher:{path:launchers.ps1,sha256:launchers.launcher_sha256,top_path:launchers.top},
|
|
1225
|
+
workspace:{root:workspace.target,migrated:workspace.moved===true},development_root:journal.development_root,
|
|
1226
|
+
host_repin:journal.host_repin??{requested:false,repinned:false},coding_repin:journal.coding_repin??{requested:false,repinned:false},
|
|
1227
|
+
old_runtime:journal.old_runtime??body.old_runtime,new_runtime:generationTuple(newRuntime),stabilization,
|
|
1228
|
+
authority:{
|
|
1229
|
+
prior:priorStatus,revoked_before_switch:revoked,restore_requested:journal.restore_standing_grants===true,desktop_restore_policy:'explicit-rearm-required',
|
|
1230
|
+
final:{
|
|
1231
|
+
process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,
|
|
1232
|
+
desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
rollback_performed:false
|
|
1236
|
+
};
|
|
1237
|
+
const receiptPath=path.join(path.dirname(statePath),'receipt.json'),receipt=await writeEnvelope(receiptPath,bodyReceipt,key);
|
|
1238
|
+
await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
|
|
1239
|
+
return {ok:true,resumed:true,completed:true,update_id:body.update_id,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,
|
|
1240
|
+
pointer:candidatePointer,new_runtime:newRuntime,final_authority:finalAuthority,workspace_root:workspace.target};
|
|
1241
|
+
}catch(error){
|
|
1242
|
+
let controllerStartFailure=null,controllerStartFailurePreservationError=null;
|
|
1243
|
+
if(pointerCommitted){
|
|
1244
|
+
try{
|
|
1245
|
+
controllerStartFailure=await preserveControllerStartFailure(newCtx,path.dirname(statePath));
|
|
1246
|
+
const claimed=/evidence_sha256=([0-9a-f]{64})/.exec(String(error?.message??error));
|
|
1247
|
+
if(claimed&&(!controllerStartFailure||controllerStartFailure.sha256!==claimed[1])){
|
|
1248
|
+
controllerStartFailurePreservationError='controller start failure evidence claim mismatch expected='+claimed[1]+' preserved='+(controllerStartFailure?.sha256??'missing');
|
|
1249
|
+
}
|
|
1250
|
+
}catch(preserveError){ controllerStartFailurePreservationError=String(preserveError?.message??preserveError); }
|
|
1251
|
+
}
|
|
1252
|
+
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:[]};
|
|
1253
|
+
try{
|
|
1254
|
+
if(pointerCommitted){
|
|
1255
|
+
rollback.attempted=true;
|
|
1256
|
+
try{ controllerCaptured(newCtx,'stop',30000); rollback.new_runtime_stopped=true; }catch(stopError){ rollback.errors.push('stop-new:'+stopError.message); }
|
|
1257
|
+
}
|
|
1258
|
+
const currentAfter=await parseCurrentPointer(pointerFile);
|
|
1259
|
+
if(samePublicPointer(currentAfter,candidatePointer)){
|
|
1260
|
+
try{ await restoreFile(pointerFile,pointerInfo.snapshot); rollback.pointer_restored=true; }catch(pointerError){ rollback.errors.push('pointer:'+pointerError.message); }
|
|
1261
|
+
}
|
|
1262
|
+
if(stableMcpUrl?.changed&&mcpUrlSnapshot){
|
|
1263
|
+
try{ await restoreFile(stableMcpUrl.path,mcpUrlSnapshot); rollback.mcp_url_restored=true; }catch(mcpUrlError){ rollback.errors.push('mcp-url:'+mcpUrlError.message); }
|
|
1264
|
+
}
|
|
1265
|
+
if(phaseRank(journal.phase)>=phaseRank('launcher_installed')||launchers.installed){
|
|
1266
|
+
try{ await rollbackLaunchers({...launchers,installed:true}); rollback.launchers_restored=true; }catch(launcherError){ rollback.errors.push('launcher:'+launcherError.message); }
|
|
1267
|
+
}
|
|
1268
|
+
if(phaseRank(journal.phase)>=phaseRank('workspace_ready')||workspace.moved||workspace.created){
|
|
1269
|
+
try{ rollback.workspace_restored=await rollbackWorkspacePlan(workspace); }catch(workspaceError){ rollback.errors.push('workspace:'+workspaceError.message); }
|
|
1270
|
+
}
|
|
1271
|
+
if(stopped&&oldRuntime&&rollback.pointer_restored){
|
|
1272
|
+
try{ await startAndVerify(ctx,oldRuntime); rollback.old_runtime_restarted=true; }catch(startError){ rollback.errors.push('restart-old:'+startError.message); }
|
|
1273
|
+
}
|
|
1274
|
+
if((!stopped||rollback.old_runtime_restarted)&&journal.restore_standing_grants===true){
|
|
1275
|
+
try{ await restoreStandingAuthority(ctx,priorStatus,true); rollback.authority_restored=true; }catch(authorityError){ rollback.errors.push('restore-authority:'+authorityError.message); }
|
|
1276
|
+
}
|
|
1277
|
+
}catch(rollbackError){ rollback.errors.push('rollback:'+rollbackError.message); }
|
|
1278
|
+
const failure={
|
|
1279
|
+
schema:FAILURE_SCHEMA,update_id:body.update_id,release_version:journal.expected_version,candidate_manifest_sha256:journal.expected_manifest_sha256,
|
|
1280
|
+
failed_at_utc:new Date().toISOString(),phase:journal.phase??phase,error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
|
|
1281
|
+
controller_start_failure:controllerStartFailure,controller_start_failure_preservation_error:controllerStartFailurePreservationError,
|
|
1282
|
+
predecessor_mode:journal.predecessor_mode??body.predecessor_mode,prior_pointer:publicPointer(priorPointer),attempted_pointer:publicPointer(candidatePointer),
|
|
1283
|
+
old_runtime:journal.old_runtime??body.old_runtime,rollback
|
|
1284
|
+
};
|
|
1285
|
+
await writeState(statePath,journal,'failed',{failure,rollback}).catch(()=>{});
|
|
1286
|
+
let failureReceipt=null;
|
|
1287
|
+
try{
|
|
1288
|
+
const failurePath=path.join(path.dirname(statePath),'failure-receipt.json');
|
|
1289
|
+
failureReceipt=await writeEnvelope(failurePath,failure,key);
|
|
1290
|
+
await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});
|
|
1291
|
+
}catch(receiptError){
|
|
1292
|
+
rollback.errors.push('failure-receipt:'+receiptError.message);
|
|
1293
|
+
await writeState(statePath,journal,'failed_unreceipted',{rollback}).catch(()=>{});
|
|
1294
|
+
}
|
|
1295
|
+
const wrapped=new Error('R27 slot update failed in detached supervisor: '+(error?.message??error));
|
|
1296
|
+
wrapped.cause=error; wrapped.statePath=statePath; wrapped.failureReceiptPath=failureReceipt?path.join(path.dirname(statePath),'failure-receipt.json'):null; throw wrapped;
|
|
1297
|
+
}finally{
|
|
1298
|
+
await cleanup();
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROOT}={}){
|
|
1302
|
+
const evidenceRoot=path.join(path.resolve(stateRoot),'evidence','updates');
|
|
1303
|
+
if(!existsSync(evidenceRoot)) return {ok:true,launched:0,updates:[]};
|
|
1304
|
+
const entries=await readdir(evidenceRoot,{withFileTypes:true});
|
|
1305
|
+
const pointerPath=path.join(path.resolve(stateRoot),'active-release.json');
|
|
1306
|
+
const activePointer=existsSync(pointerPath)?await parseCurrentPointer(pointerPath):null;
|
|
1307
|
+
let launched=0;const updates=[];
|
|
1308
|
+
for(const entry of entries){
|
|
1309
|
+
if(!entry.isDirectory()) continue;
|
|
1310
|
+
const statePath=path.join(evidenceRoot,entry.name,'state.json');
|
|
1311
|
+
if(!existsSync(statePath)) continue;
|
|
1312
|
+
let state;try{ state=await readJson(statePath); }catch{ continue; }
|
|
1313
|
+
if(!activeTransitionPhase(state?.phase)) continue;
|
|
1314
|
+
const pendingPointer=statePointer(state,'new_pointer');
|
|
1315
|
+
if(activePointer&&pendingPointer&&samePublicPointer(activePointer,pendingPointer)){
|
|
1316
|
+
await writeState(statePath,state,'superseded',{superseded_at_utc:new Date().toISOString(),superseded_by_pointer:publicPointer(activePointer)});
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
const handoffPath=String(state?.handoff_path??'');
|
|
1320
|
+
if(!handoffPath||!path.isAbsolute(handoffPath)||!existsSync(handoffPath)) continue;
|
|
1321
|
+
const handoff=await readJson(handoffPath);
|
|
1322
|
+
if(handoff?.body?.schema!==HANDOFF_SCHEMA) continue;
|
|
1323
|
+
const script=path.resolve(String(handoff.body.supervisor_script??''));
|
|
1324
|
+
if(await sha256File(script)!==handoff.body.supervisor_script_sha256) throw new Error('interrupted update supervisor hash drift: '+state.update_id);
|
|
1325
|
+
const result=await spawnDetachedSupervisor({script,handoffPath,cwd:path.dirname(script)});
|
|
1326
|
+
launched+=1;updates.push({update_id:state.update_id,state_path:statePath,handoff_path:handoffPath,supervisor_pid:result.pid});
|
|
1327
|
+
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'));
|
|
1328
|
+
}
|
|
1329
|
+
return {ok:true,launched,updates};
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
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=[]}){
|
|
892
1333
|
if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
|
|
893
1334
|
const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
|
|
894
|
-
const key=await loadUpdateKey(state)
|
|
895
|
-
const evidenceDir=path.join(state,'evidence','updates',updateId),statePath=path.join(evidenceDir,'state.json');
|
|
896
|
-
const receiptPath=path.join(evidenceDir,'receipt.json'),failurePath=path.join(evidenceDir,'failure-receipt.json');
|
|
1335
|
+
const key=await loadUpdateKey(state),updateId='r27-'+expectedManifest.slice(0,16)+'-'+Date.now().toString(36)+'-'+randomBytes(4).toString('hex');
|
|
1336
|
+
const evidenceDir=path.join(state,'evidence','updates',updateId),statePath=path.join(evidenceDir,'state.json'),failurePath=path.join(evidenceDir,'failure-receipt.json');
|
|
897
1337
|
await mkdir(evidenceDir,{recursive:true});
|
|
898
1338
|
const journal={schema:'deadbyte.r27-slot-update-state.v1',update_id:updateId,stage_root:stageRoot,development_root:development,
|
|
899
1339
|
source_root:sourceRoot?path.resolve(sourceRoot):null,expected_version:expectedVersion,expected_manifest_sha256:expectedManifest,
|
|
900
1340
|
restore_standing_grants:restoreStandingGrants,recover_offline_predecessor:recoverOfflinePredecessor,created_at_utc:new Date().toISOString()};
|
|
901
1341
|
await writeState(statePath,journal,'initial');
|
|
902
|
-
let pointerInfo=null,control=null,predecessor=null,oldRuntime=null,priorStatus=null,authorityPreparation=null,workspace=null,launchers=null,newPointer=null
|
|
1342
|
+
let pointerInfo=null,control=null,predecessor=null,oldRuntime=null,priorStatus=null,authorityPreparation=null,workspace=null,launchers=null,newPointer=null;
|
|
903
1343
|
let stableMcpUrl=null,mcpUrlSnapshot=null;
|
|
904
|
-
|
|
905
|
-
try {
|
|
1344
|
+
try{
|
|
906
1345
|
pointerInfo=await loadPointerIfPresent(state);
|
|
907
1346
|
const stageVerified=await verifyPackageRoot(stageRoot,expectedVersion,expectedManifest,'stage');
|
|
908
1347
|
const developmentVerified=await verifyPackageRoot(development,expectedVersion,expectedManifest,'development');
|
|
909
1348
|
const slot=await materializeReleaseSlot({source:stageRoot,stateRoot:state,version:expectedVersion,manifestSha256:expectedManifest});
|
|
910
1349
|
control=await loadControlInputs({stateRoot:state,sourceRoot,pointerInfo});
|
|
911
|
-
const hostBootstrap=await prepareHostPolicyBootstrap(hostPolicyBootstrap);
|
|
912
|
-
if(
|
|
1350
|
+
const hostBootstrap=await prepareHostPolicyBootstrap(hostPolicyBootstrap);if(hostBootstrap) control.hostPolicy=hostBootstrap.policy;
|
|
1351
|
+
const codingBootstrap=await prepareCodingPolicyBootstrap(codingPolicyBootstrap);if(codingBootstrap) control.codingPolicy=codingBootstrap.policy;
|
|
913
1352
|
const processBootstrap=await prepareProcessPolicyBootstrap(processPolicyBootstrap);
|
|
914
|
-
if(processBootstrap){
|
|
915
|
-
|
|
916
|
-
control.config.ProcessRuntimeEnabled=true;
|
|
917
|
-
}
|
|
918
|
-
const desktopBootstrap=await prepareDesktopPolicyBootstrap(desktopPolicyBootstrap);
|
|
919
|
-
if(desktopBootstrap) control.desktopPolicy=desktopBootstrap.policy;
|
|
1353
|
+
if(processBootstrap){ control.processPolicy=processBootstrap.policy;control.config.ProcessRuntimeEnabled=true; }
|
|
1354
|
+
const desktopBootstrap=await prepareDesktopPolicyBootstrap(desktopPolicyBootstrap);if(desktopBootstrap) control.desktopPolicy=desktopBootstrap.policy;
|
|
920
1355
|
const semanticBootstrap=await prepareSemanticLspConfigBootstrap(semanticLspBootstrap);
|
|
921
|
-
if(semanticBootstrap){
|
|
922
|
-
control.config.SemanticLspConfigFile=semanticBootstrap.path;
|
|
923
|
-
control.config.SemanticLspConfigSha256=semanticBootstrap.sha256;
|
|
924
|
-
}
|
|
1356
|
+
if(semanticBootstrap){ control.config.SemanticLspConfigFile=semanticBootstrap.path;control.config.SemanticLspConfigSha256=semanticBootstrap.sha256; }
|
|
925
1357
|
if(enableMachineFs){
|
|
926
1358
|
if(control.config.CodingRuntimeEnabled!==true) throw new Error('MachineFsRuntimeEnabled requires CodingRuntimeEnabled');
|
|
927
1359
|
control.config.MachineFsRuntimeEnabled=true;
|
|
@@ -939,142 +1371,45 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
939
1371
|
control.config=await prepareNamedTunnelCloudSpec(control.config,{releaseRoot:slot.release_root,bootstrap:cloudTransport});
|
|
940
1372
|
control.config=await prepareAutonomousSupervisorSpec(control.config,{releaseRoot:slot.release_root,enable:enableAutonomousSupervisor});
|
|
941
1373
|
authorityPreparation=await prepareAuthorityPolicies({hostPolicy:control.hostPolicy,codingPolicy:control.codingPolicy},{commandPins:hostCommandPins});
|
|
942
|
-
control.hostPolicy=authorityPreparation.hostPolicy;
|
|
943
|
-
control.codingPolicy=authorityPreparation.codingPolicy;
|
|
1374
|
+
control.hostPolicy=authorityPreparation.hostPolicy;control.codingPolicy=authorityPreparation.codingPolicy;
|
|
944
1375
|
const stableMcp=await ensureStableMcpPath({stateRoot:state,sourceRoot:pointerInfo.pointer?null:sourceRoot});
|
|
945
1376
|
control.config.McpPathFile=stableMcp.path;
|
|
946
1377
|
if(cloudTransport?.publicOrigin){
|
|
947
|
-
const mcpUrlFile=path.join(state,'bridge','mcp-url.txt');
|
|
948
|
-
mcpUrlSnapshot=await snapshotFile(mcpUrlFile);
|
|
1378
|
+
const mcpUrlFile=path.join(state,'bridge','mcp-url.txt');mcpUrlSnapshot=await snapshotFile(mcpUrlFile);
|
|
949
1379
|
stableMcpUrl=await ensureStableMcpUrl({stateRoot:state,publicOrigin:cloudTransport.publicOrigin,mcpPathFile:stableMcp.path});
|
|
950
|
-
control.config.McpUrlFile=stableMcpUrl.path;
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
if(typeof control.config.McpUrlFile!=='string'||!path.isAbsolute(control.config.McpUrlFile)||!HEX64.test(String(control.config.McpUrlFileSha256??''))) {
|
|
954
|
-
throw new Error('existing named tunnel v2 requires pinned McpUrlFile before update');
|
|
955
|
-
}
|
|
1380
|
+
control.config.McpUrlFile=stableMcpUrl.path;control.config.McpUrlFileSha256=stableMcpUrl.sha256;
|
|
1381
|
+
}else if(control.config.Cloud?.Transport==='deadbyte.named-tunnel.v2'){
|
|
1382
|
+
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');
|
|
956
1383
|
const pinned=await verifyPinnedRegularFile(control.config.McpUrlFile,control.config.McpUrlFileSha256,'existing named tunnel MCP URL file');
|
|
957
|
-
control.config.McpUrlFile=pinned.path;
|
|
958
|
-
control.config.McpUrlFileSha256=pinned.sha256;
|
|
1384
|
+
control.config.McpUrlFile=pinned.path;control.config.McpUrlFileSha256=pinned.sha256;
|
|
959
1385
|
}
|
|
960
|
-
workspace=await workspacePlan(control.workspaceRoot,state);
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
workspaceRoot:workspace.target,releaseId:slot.release_id,currentConfig:control.config,codingPolicy:control.codingPolicy,
|
|
964
|
-
autonomousPolicy:control.autonomousPolicy,hostPolicy:control.hostPolicy,processPolicy:control.processPolicy,desktopPolicy:control.desktopPolicy});
|
|
965
|
-
newPointer={schema:'deadbyte.active-release.v1',release_id:slot.release_id,version:expectedVersion,manifest_sha256:expectedManifest,
|
|
966
|
-
release_root:slot.release_root,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,
|
|
967
|
-
launcher_sha256:launchers.launcher_sha256,config_path:generation.config_path};
|
|
1386
|
+
workspace=await workspacePlan(control.workspaceRoot,state);launchers=await launcherPlan(state);
|
|
1387
|
+
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});
|
|
1388
|
+
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};
|
|
968
1389
|
parseActivePointer(JSON.stringify(newPointer));
|
|
969
|
-
await writeState(statePath,journal,'candidate_prepared',{prior_pointer:publicPointer(pointerInfo.pointer),stage:stageVerified,
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
stable_mcp_path:{path:stableMcp.path,sha256:stableMcp.sha256},
|
|
1390
|
+
await writeState(statePath,journal,'candidate_prepared',{prior_pointer:publicPointer(pointerInfo.pointer),stage:stageVerified,development:developmentVerified,
|
|
1391
|
+
release_slot:slot,generation:{generation_id:generation.generation_id,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,files:generation.files},
|
|
1392
|
+
workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode},stable_mcp_path:{path:stableMcp.path,sha256:stableMcp.sha256},
|
|
973
1393
|
stable_mcp_url:stableMcpUrl?{path:stableMcpUrl.path,sha256:stableMcpUrl.sha256,origin:stableMcpUrl.origin,changed:stableMcpUrl.changed}:null,
|
|
974
1394
|
bridge:{working_directory:control.config.Bridge.WorkingDirectory,script_path:control.config.Bridge.ScriptPath,script_sha256:control.config.Bridge.ScriptSha256},
|
|
975
|
-
host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},
|
|
976
|
-
|
|
977
|
-
predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor);
|
|
1395
|
+
host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},
|
|
1396
|
+
new_pointer:publicPointer(newPointer)});
|
|
1397
|
+
predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor);oldRuntime=predecessor.runtime;priorStatus=predecessor.status;
|
|
978
1398
|
await writeState(statePath,journal,'old_runtime_verified',{predecessor_mode:predecessor.mode,old_runtime:generationTuple(oldRuntime),
|
|
979
|
-
prior_authority:{coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed}});
|
|
980
|
-
const
|
|
981
|
-
await
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
await
|
|
989
|
-
await writeState(statePath,journal,'workspace_ready',{workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode,moved:workspace.moved===true,created:workspace.created===true}});
|
|
990
|
-
await installLaunchers(launchers);
|
|
991
|
-
await writeState(statePath,journal,'launcher_installed',{launcher_sha256:launchers.launcher_sha256,launcher_path:launchers.ps1,top_launcher:launchers.top});
|
|
992
|
-
await atomicWriteActivePointer(pointerInfo.file,newPointer); pointerCommitted=true;
|
|
993
|
-
await writeState(statePath,journal,'pointer_committed',{active_pointer:publicPointer(newPointer)});
|
|
994
|
-
await verifyActiveInstallation(state,newPointer);
|
|
995
|
-
newCtx=slotContext(state,newPointer);
|
|
996
|
-
const newRuntime=await startAndVerify(newCtx,oldRuntime); newStarted=true;
|
|
997
|
-
const stabilization=await stabilizeRuntime(newCtx,newRuntime);
|
|
998
|
-
await writeState(statePath,journal,'fresh_runtime_verified',{new_runtime:generationTuple(newRuntime),stabilization});
|
|
999
|
-
const finalAuthority=await restoreStandingAuthority(newCtx,priorStatus,restoreStandingGrants);
|
|
1000
|
-
await writeState(statePath,journal,'authority_verified',{final_authority:{process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,
|
|
1001
|
-
autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}});
|
|
1002
|
-
const body={schema:UPDATE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,
|
|
1003
|
-
completed_at_utc:new Date().toISOString(),activation_boundary:'active-release-pointer',mechanism:'immutable-release-slot-plus-atomic-pointer-rename',
|
|
1004
|
-
predecessor_mode:predecessor.mode,prior_pointer:publicPointer(pointerInfo.pointer),active_pointer:publicPointer(newPointer),release_slot:{release_id:slot.release_id,release_root:slot.release_root,
|
|
1005
|
-
adopted:slot.adopted,files:slot.files},generation:{generation_id:generation.generation_id,generation_root:generation.generation_root,
|
|
1006
|
-
generation_manifest_sha256:generation.generation_manifest_sha256,adopted:generation.adopted,files:generation.files},
|
|
1007
|
-
launcher:{path:launchers.ps1,sha256:launchers.launcher_sha256,top_path:launchers.top},workspace:{root:workspace.target,migrated:workspace.moved===true},
|
|
1008
|
-
development_root:development,
|
|
1009
|
-
host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},
|
|
1010
|
-
coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},old_runtime:generationTuple(oldRuntime),new_runtime:generationTuple(newRuntime),stabilization,
|
|
1011
|
-
authority:{prior:{process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,
|
|
1012
|
-
autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed},revoked_before_switch:revoked,restore_requested:restoreStandingGrants,
|
|
1013
|
-
desktop_restore_policy:'explicit-rearm-required',
|
|
1014
|
-
final:{process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}},rollback_performed:false};
|
|
1015
|
-
const receipt=await writeEnvelope(receiptPath,body,key);
|
|
1016
|
-
await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
|
|
1017
|
-
return {ok:true,update_id:updateId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,
|
|
1018
|
-
pointer:newPointer,new_runtime:newRuntime,final_authority:finalAuthority,workspace_root:workspace.target};
|
|
1019
|
-
} catch(error){
|
|
1020
|
-
let controllerStartFailure=null,controllerStartFailurePreservationError=null;
|
|
1021
|
-
if(pointerCommitted&&newCtx){
|
|
1022
|
-
try {
|
|
1023
|
-
controllerStartFailure=await preserveControllerStartFailure(newCtx,evidenceDir);
|
|
1024
|
-
const claimed=/evidence_sha256=([0-9a-f]{64})/.exec(String(error?.message??error));
|
|
1025
|
-
if(claimed&&(!controllerStartFailure||controllerStartFailure.sha256!==claimed[1])) {
|
|
1026
|
-
controllerStartFailurePreservationError=`controller start failure evidence claim mismatch expected=${claimed[1]} preserved=${controllerStartFailure?.sha256??'missing'}`;
|
|
1027
|
-
}
|
|
1028
|
-
} catch(preserveError) {
|
|
1029
|
-
controllerStartFailurePreservationError=String(preserveError?.message??preserveError);
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
const rollback={attempted:false,new_runtime_stopped:false,pointer_restored:false,launchers_restored:false,workspace_restored:false,
|
|
1033
|
-
mcp_url_restored:false,old_runtime_restarted:false,authority_restored:false,errors:[]};
|
|
1034
|
-
try {
|
|
1035
|
-
if(pointerCommitted&&newCtx){
|
|
1036
|
-
rollback.attempted=true;
|
|
1037
|
-
try { controllerCaptured(newCtx,'stop',30000); rollback.new_runtime_stopped=true; }
|
|
1038
|
-
catch(stopError){ rollback.errors.push(`stop-new:${stopError.message}`); }
|
|
1039
|
-
}
|
|
1040
|
-
if(pointerCommitted&&pointerInfo){
|
|
1041
|
-
try { await restoreFile(pointerInfo.file,pointerInfo.snapshot); rollback.pointer_restored=true; }
|
|
1042
|
-
catch(pointerError){ rollback.errors.push(`pointer:${pointerError.message}`); }
|
|
1043
|
-
}
|
|
1044
|
-
if(stableMcpUrl?.changed&&mcpUrlSnapshot){
|
|
1045
|
-
rollback.attempted=true;
|
|
1046
|
-
try { await restoreFile(stableMcpUrl.path,mcpUrlSnapshot); rollback.mcp_url_restored=true; }
|
|
1047
|
-
catch(mcpUrlError){ rollback.errors.push(`mcp-url:${mcpUrlError.message}`); }
|
|
1048
|
-
}
|
|
1049
|
-
if(launchers?.installed){
|
|
1050
|
-
try { await rollbackLaunchers(launchers); rollback.launchers_restored=true; }
|
|
1051
|
-
catch(launcherError){ rollback.errors.push(`launcher:${launcherError.message}`); }
|
|
1052
|
-
}
|
|
1053
|
-
if(workspace){
|
|
1054
|
-
try { rollback.workspace_restored=await rollbackWorkspacePlan(workspace); }
|
|
1055
|
-
catch(workspaceError){ rollback.errors.push(`workspace:${workspaceError.message}`); }
|
|
1056
|
-
}
|
|
1057
|
-
if(stopped&&control?.ctx){
|
|
1058
|
-
try { await startAndVerify(control.ctx,oldRuntime); rollback.old_runtime_restarted=true; }
|
|
1059
|
-
catch(startError){ rollback.errors.push(`restart-old:${startError.message}`); }
|
|
1060
|
-
}
|
|
1061
|
-
const predecessorReadyForAuthorityRestore=!stopped||rollback.old_runtime_restarted;
|
|
1062
|
-
if(predecessorReadyForAuthorityRestore&&priorStatus&&restoreStandingGrants){
|
|
1063
|
-
try { await restoreStandingAuthority(control.ctx,priorStatus,true); rollback.authority_restored=true; }
|
|
1064
|
-
catch(authorityError){ rollback.errors.push(`restore-authority:${authorityError.message}`); }
|
|
1065
|
-
}
|
|
1066
|
-
} catch(rollbackError){ rollback.errors.push(`rollback:${rollbackError.message}`); }
|
|
1067
|
-
const failure={schema:FAILURE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,
|
|
1068
|
-
failed_at_utc:new Date().toISOString(),phase:journal.phase??'unknown',error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
|
|
1069
|
-
controller_start_failure:controllerStartFailure,
|
|
1070
|
-
controller_start_failure_preservation_error:controllerStartFailurePreservationError,
|
|
1071
|
-
predecessor_mode:predecessor?.mode??null,prior_pointer:publicPointer(pointerInfo?.pointer??null),attempted_pointer:publicPointer(newPointer),old_runtime:generationTuple(oldRuntime),rollback};
|
|
1072
|
-
await writeState(statePath,journal,'failed',{failure,rollback}).catch(()=>{});
|
|
1399
|
+
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}});
|
|
1400
|
+
const handoff=await writeHandoff({stateRoot:state,state:journal,journal,statePath,evidenceDir,key,pointerInfo,newPointer,predecessor,priorStatus,workspace,launchers,stableMcpUrl,mcpUrlSnapshot,controlCtx:control.ctx});
|
|
1401
|
+
const supervisor=await spawnDetachedSupervisor({script:handoff.body.supervisor_script,handoffPath:handoff.path,cwd:control.ctx.core_root});
|
|
1402
|
+
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};
|
|
1403
|
+
}catch(error){
|
|
1404
|
+
const failure={schema:FAILURE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,failed_at_utc:new Date().toISOString(),
|
|
1405
|
+
phase:journal.phase??'unknown',error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
|
|
1406
|
+
predecessor_mode:predecessor?.mode??null,prior_pointer:publicPointer(pointerInfo?.pointer??null),attempted_pointer:publicPointer(newPointer),old_runtime:generationTuple(oldRuntime),
|
|
1407
|
+
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:[]}};
|
|
1408
|
+
await writeState(statePath,journal,'failed',{failure}).catch(()=>{});
|
|
1073
1409
|
let failureReceipt=null;
|
|
1074
|
-
try
|
|
1075
|
-
catch(receiptError){
|
|
1076
|
-
const wrapped=new Error(
|
|
1077
|
-
wrapped.failureReceiptPath=failureReceipt?failurePath:null; throw wrapped;
|
|
1410
|
+
try{ failureReceipt=await writeEnvelope(failurePath,failure,key);await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});}
|
|
1411
|
+
catch(receiptError){ await writeState(statePath,journal,'failed_unreceipted',{failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{}); }
|
|
1412
|
+
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;
|
|
1078
1413
|
}
|
|
1079
1414
|
}
|
|
1080
1415
|
|
|
@@ -1134,18 +1469,47 @@ export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEF
|
|
|
1134
1469
|
const args=parseRollbackArgs(argv);
|
|
1135
1470
|
const result=await performSlotRollback({activationReceipt:args.activationReceipt,stateRoot});
|
|
1136
1471
|
console.log('R31_SLOT_ROLLBACK_PASS');
|
|
1137
|
-
console.log(
|
|
1138
|
-
console.log(
|
|
1472
|
+
console.log('ROLLBACK_ID '+result.rollback_id); console.log('RECEIPT_SHA256 '+result.receipt_sha256);
|
|
1473
|
+
console.log('STATE_PATH '+result.state_path); console.log('ACTIVE_RELEASE '+result.pointer.release_id);
|
|
1474
|
+
return result;
|
|
1475
|
+
}
|
|
1476
|
+
if(argv.includes('--resume-handoff')){
|
|
1477
|
+
const args=parseResumeHandoffArgs(argv);
|
|
1478
|
+
const result=await resumeSlotUpdate({handoffPath:args.handoff});
|
|
1479
|
+
console.log(result.completed?'R27_SLOT_UPDATE_RESUME_PASS':'R27_SLOT_UPDATE_RESUME_DEFERRED');
|
|
1480
|
+
console.log('UPDATE_ID '+result.update_id);
|
|
1481
|
+
console.log('STATE_PATH '+result.state_path);
|
|
1482
|
+
if(result.receipt_sha256) console.log('RECEIPT_SHA256 '+result.receipt_sha256);
|
|
1483
|
+
return result;
|
|
1484
|
+
}
|
|
1485
|
+
if(argv.includes('--recover-interrupted')){
|
|
1486
|
+
const result=await recoverInterruptedSlotUpdates({stateRoot});
|
|
1487
|
+
console.log('R27_SLOT_UPDATE_RECOVERY launched='+result.launched);
|
|
1488
|
+
for(const update of result.updates) console.log('UPDATE_ID '+update.update_id+' SUPERVISOR_PID '+update.supervisor_pid);
|
|
1139
1489
|
return result;
|
|
1140
1490
|
}
|
|
1141
1491
|
const args=parseUpdateArgs(argv);
|
|
1142
|
-
const result=await performSlotUpdate({
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1492
|
+
const result=await performSlotUpdate({
|
|
1493
|
+
stage:args.stage,developmentRoot:args.developmentRoot,expectedVersion:args.expectedVersion,expectedManifest:args.expectedManifest,
|
|
1494
|
+
sourceRoot:args.sourceRoot,stateRoot,restoreStandingGrants:args.restoreStandingGrants,recoverOfflinePredecessor:args.recoverOfflinePredecessor,
|
|
1495
|
+
cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,
|
|
1496
|
+
hostPolicyBootstrap:args.hostPolicyBootstrap,codingPolicyBootstrap:args.codingPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,
|
|
1497
|
+
semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,enableObservation:args.enableObservation,
|
|
1498
|
+
enableDesktop:args.enableDesktop,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins
|
|
1499
|
+
});
|
|
1500
|
+
if(result.handed_off){
|
|
1501
|
+
console.log('R27_SLOT_UPDATE_HANDOFF version='+args.expectedVersion);
|
|
1502
|
+
console.log('UPDATE_ID '+result.update_id);
|
|
1503
|
+
console.log('HANDOFF_PATH '+result.handoff_path);
|
|
1504
|
+
console.log('HANDOFF_RECEIPT_SHA256 '+result.handoff_receipt_sha256);
|
|
1505
|
+
console.log('SUPERVISOR_PID '+result.supervisor_pid);
|
|
1506
|
+
}else{
|
|
1507
|
+
console.log('R27_SLOT_UPDATE_PASS version='+args.expectedVersion);
|
|
1508
|
+
console.log('UPDATE_ID '+result.update_id);
|
|
1509
|
+
console.log('RECEIPT_SHA256 '+result.receipt_sha256);
|
|
1510
|
+
console.log('STATE_PATH '+result.state_path);
|
|
1511
|
+
console.log('ACTIVE_RELEASE '+result.pointer.release_id);
|
|
1512
|
+
}
|
|
1149
1513
|
return result;
|
|
1150
1514
|
}
|
|
1151
1515
|
const invoked=process.argv[1]&&path.resolve(process.argv[1])===path.resolve(fileURLToPath(import.meta.url));
|