deadbyte-mcp 0.15.1 → 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.
Files changed (96) hide show
  1. package/MANIFEST.SHA256 +95 -64
  2. package/PROVENANCE.json +6 -5
  3. package/README.txt +36 -20
  4. package/RELEASE.json +24 -8
  5. package/SBOM.cdx.json +3 -3
  6. package/controller/README.TXT +2 -2
  7. package/controller/deadbyte-controller.ps1 +50 -2
  8. package/controller/deadbyte-process-policy.json +63 -1
  9. package/docs/ARCHITECTURE.md +3 -4
  10. package/package.json +88 -86
  11. package/scripts/autonomous-build.ps1 +1 -0
  12. package/scripts/autonomous-tests.ps1 +1 -0
  13. package/scripts/check-agent-backend-r46.mjs +77 -0
  14. package/scripts/check-atomic-resource-r46.mjs +84 -0
  15. package/scripts/check-autonomous-race-r46.mjs +160 -0
  16. package/scripts/check-coding-task-deadline-r46.mjs +59 -0
  17. package/scripts/check-deadline-cancellation-r46.mjs +49 -0
  18. package/scripts/check-process-deadline-r46.mjs +52 -0
  19. package/scripts/check-r46-background.mjs +5 -0
  20. package/scripts/deferred-slot-operation-r46.mjs +126 -0
  21. package/scripts/extract-r46-failures.mjs +5 -0
  22. package/scripts/final-closure-r46.mjs +98 -0
  23. package/scripts/final-closure-verify-r46.mjs +119 -0
  24. package/scripts/full-regression-r46.mjs +28 -0
  25. package/scripts/gate-windows-r46.ps1 +58 -0
  26. package/scripts/inspect-r46-active.mjs +2 -0
  27. package/scripts/r47-core-failures.mjs +1 -0
  28. package/scripts/refresh-process-policy.mjs +64 -7
  29. package/scripts/release-manifest.mjs +1 -1
  30. package/scripts/release-parity-r46.mjs +187 -0
  31. package/scripts/run-r46-full-regression-background.mjs +7 -0
  32. package/scripts/run-r46-windows-gate-background.mjs +6 -0
  33. package/scripts/run-r46-windows-gate-background.ps1 +10 -0
  34. package/scripts/windows-gate-evidence-r46.mjs +111 -0
  35. package/scripts/workload-contract-regression-r47.mjs +14 -0
  36. package/scripts/workload-stream-regression.mjs +16 -0
  37. package/scripts/workload-substrate-r47-regression.mjs +12 -0
  38. package/src/agent-backend-r44.mjs +75 -10
  39. package/src/atomic-mutation.mjs +55 -0
  40. package/src/autonomous-mcp-tools.mjs +4 -3
  41. package/src/autonomous-output-contracts.mjs +16 -3
  42. package/src/autonomous-runtime.mjs +83 -30
  43. package/src/capability-ledger.mjs +1 -0
  44. package/src/coding-mcp-tools.mjs +2 -2
  45. package/src/coding-output-contracts.mjs +18 -4
  46. package/src/coding-plane.mjs +3 -24
  47. package/src/coding-search.mjs +11 -2
  48. package/src/coding-task-runtime.mjs +41 -6
  49. package/src/compact-gateway-mcp-tools.mjs +7 -5
  50. package/src/deadbyte-cli.mjs +7 -3
  51. package/src/deadline-cancellation.mjs +83 -0
  52. package/src/machine-fs-mcp-tools.mjs +3 -3
  53. package/src/machine-fs-output-contracts.mjs +1 -1
  54. package/src/machine-fs-read-worker.mjs +46 -0
  55. package/src/machine-fs-smoke-client.mjs +1 -1
  56. package/src/machine-fs.mjs +66 -33
  57. package/src/mcp-server.mjs +10 -0
  58. package/src/mcp-smoke-client.mjs +4 -1
  59. package/src/observation-runtime.mjs +25 -4
  60. package/src/observation-smoke-client.mjs +1 -1
  61. package/src/process-mcp-tools.mjs +29 -8
  62. package/src/process-output-contracts.mjs +43 -3
  63. package/src/process-policy.mjs +3 -1
  64. package/src/process-runtime.mjs +152 -23
  65. package/src/process-smoke-client.mjs +67 -22
  66. package/src/release-generation.mjs +22 -3
  67. package/src/remote-device-r44.mjs +53 -15
  68. package/src/remote-mcp-smoke-client.mjs +1 -1
  69. package/src/resource-identity.mjs +59 -0
  70. package/src/runtime-update.mjs +526 -162
  71. package/src/stdio-console-guard.mjs +24 -0
  72. package/src/version.mjs +1 -1
  73. package/src/workload-substrate.mjs +87 -0
  74. package/test/cli-entrypoint.test.mjs +3 -3
  75. package/test/coding-runtime.test.mjs +12 -0
  76. package/test/coding-search-r36.test.mjs +7 -0
  77. package/test/compact-gateway-r36.test.mjs +28 -0
  78. package/test/core.test.mjs +1 -1
  79. package/test/full-max-catalog-r46.test.mjs +92 -0
  80. package/test/helpers/autonomous-r34-fixture.mjs +10 -1
  81. package/test/machine-fs-mcp-tools.test.mjs +4 -1
  82. package/test/machine-fs-runtime.test.mjs +18 -2
  83. package/test/observation-runtime.test.mjs +29 -2
  84. package/test/process-mcp-tools.test.mjs +27 -6
  85. package/test/process-policy-refresh.test.mjs +87 -10
  86. package/test/process-runtime.test.mjs +129 -2
  87. package/test/process-supervision.test.mjs +4 -2
  88. package/test/production-docs.test.mjs +3 -3
  89. package/test/r33-finalization.test.mjs +3 -3
  90. package/test/r45-finalization.test.mjs +1 -1
  91. package/test/release-generation.test.mjs +25 -11
  92. package/test/release-version.test.mjs +99 -44
  93. package/test/remote-device-r44.test.mjs +75 -0
  94. package/test/remote-r24-catalog.test.mjs +2 -2
  95. package/test/runtime-self-update.test.mjs +118 -1
  96. package/test/workload-substrate.test.mjs +22 -0
@@ -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.moved&&existsSync(plan.target)&&!existsSync(plan.source)){ await rename(plan.target,plan.source); plan.moved=false; return true; }
778
- if(plan.created&&existsSync(plan.target)){ await rm(plan.target,{recursive:true,force:true}); plan.created=false; return true; }
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?.installed) return false;
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
- Object.assign(state,extra,{phase,updated_at_utc:new Date().toISOString()});
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
- 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,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
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); const updateId=`r27-${expectedManifest.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
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,newCtx=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
- let revoked=false,stopped=false,pointerCommitted=false,newStarted=false;
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(hostBootstrap) control.hostPolicy=hostBootstrap.policy;
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
- control.processPolicy=processBootstrap.policy;
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
- control.config.McpUrlFileSha256=stableMcpUrl.sha256;
952
- } else if(control.config.Cloud?.Transport==='deadbyte.named-tunnel.v2') {
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
- launchers=await launcherPlan(state);
962
- const generation=await createGenerationBundle({stateRoot:state,releaseRoot:slot.release_root,developmentRoot:development,
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
- development:developmentVerified,release_slot:slot,generation:{generation_id:generation.generation_id,generation_root:generation.generation_root,
971
- generation_manifest_sha256:generation.generation_manifest_sha256,files:generation.files},workspace:{source:workspace.source,target:workspace.target,mode:workspace.mode},
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
- coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},new_pointer:publicPointer(newPointer)});
977
- predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor); oldRuntime=predecessor.runtime; priorStatus=predecessor.status;
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 revoke=await revokeAuthority(control.ctx,priorStatus); revoked=revoke.revoked;
981
- await writeState(statePath,journal,'authority_revoked',{revoked_before_switch:revoked});
982
- if(oldRuntime){
983
- const stoppedResult=await stopAndVerify(control.ctx,oldRuntime); stopped=true;
984
- await writeState(statePath,journal,'old_runtime_stopped',{stop_output:stoppedResult.output});
985
- } else {
986
- await writeState(statePath,journal,'old_runtime_stopped',{stop_output:'predecessor already OFFLINE; recovery mode performed no stop'});
987
- }
988
- await applyWorkspacePlan(workspace);
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 { failureReceipt=await writeEnvelope(failurePath,failure,key); await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256}); }
1075
- catch(receiptError){ rollback.errors.push(`failure-receipt:${receiptError.message}`); await writeState(statePath,journal,'failed_unreceipted',{rollback}).catch(()=>{}); }
1076
- const wrapped=new Error(`R27 slot update failed: ${error?.message??error}`); wrapped.cause=error; wrapped.statePath=statePath;
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(`ROLLBACK_ID ${result.rollback_id}`); console.log(`RECEIPT_SHA256 ${result.receipt_sha256}`);
1138
- console.log(`STATE_PATH ${result.state_path}`); console.log(`ACTIVE_RELEASE ${result.pointer.release_id}`);
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({stage:args.stage,developmentRoot:args.developmentRoot,expectedVersion:args.expectedVersion,
1143
- expectedManifest:args.expectedManifest,sourceRoot:args.sourceRoot,stateRoot,restoreStandingGrants:args.restoreStandingGrants,recoverOfflinePredecessor:args.recoverOfflinePredecessor,
1144
- cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,hostPolicyBootstrap:args.hostPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
1145
- enableObservation:args.enableObservation,enableDesktop:args.enableDesktop,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins});
1146
- console.log(`R27_SLOT_UPDATE_PASS version=${args.expectedVersion}`);
1147
- console.log(`UPDATE_ID ${result.update_id}`); console.log(`RECEIPT_SHA256 ${result.receipt_sha256}`);
1148
- console.log(`STATE_PATH ${result.state_path}`); console.log(`ACTIVE_RELEASE ${result.pointer.release_id}`);
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));