deadbyte-mcp 0.15.32 → 0.15.34

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.
@@ -0,0 +1,27 @@
1
+ $ErrorActionPreference = 'Stop'
2
+ Set-StrictMode -Version 3.0
3
+
4
+ function Invoke-GateStep {
5
+ param([Parameter(Mandatory)][string]$Name,[Parameter(Mandatory)][string[]]$NpmArgs)
6
+ Write-Host "==== $Name ===="
7
+ & npm.cmd @NpmArgs
8
+ if ($LASTEXITCODE -ne 0) { throw "DEADBYTE 0.15.34 / R57 gate failed at $Name with exit code $LASTEXITCODE" }
9
+ }
10
+ function Invoke-NodeTest {
11
+ param([Parameter(Mandatory)][string]$Name,[Parameter(Mandatory)][string[]]$TestFiles)
12
+ Write-Host "==== $Name ===="
13
+ & node.exe --test --test-concurrency=1 @TestFiles
14
+ if ($LASTEXITCODE -ne 0) { throw "DEADBYTE 0.15.34 / R57 gate failed at $Name with exit code $LASTEXITCODE" }
15
+ }
16
+
17
+ Invoke-NodeTest 'r57-package-boundary' @('test/package-boundary.test.mjs')
18
+ Invoke-GateStep 'r57-focused-contracts' @('run','r57:focused:test')
19
+ Write-Host '==== full-regression-evidence ===='
20
+ $matrix=Get-Content -LiteralPath (Join-Path (Split-Path $PSScriptRoot -Parent) 'TEST-MATRIX.json') -Raw | ConvertFrom-Json
21
+ if($matrix.observed_pre_release_evidence.full_regression.status -ne 'PASS'){ throw 'R57 full regression evidence is not PASS' }
22
+ Invoke-GateStep 'manifest-write' @('run','manifest:write')
23
+ Invoke-GateStep 'manifest-verify' @('run','manifest:verify')
24
+ Invoke-GateStep 'doctor' @('run','doctor')
25
+ & node.exe (Join-Path $PSScriptRoot 'windows-gate-evidence-r57.mjs')
26
+ if ($LASTEXITCODE -ne 0) { throw "R57 evidence writer failed with exit code $LASTEXITCODE" }
27
+ Write-Host '==== DEADBYTE 0.15.34 / R57 WINDOWS GATE: PASS ===='
@@ -0,0 +1,81 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, open, readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { doctor } from '../src/doctor-r45.mjs';
7
+
8
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
9
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
10
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ const insist=(ok,message)=>{if(!ok)throw new Error(message);};
12
+ const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
13
+ const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
14
+ const provenance=JSON.parse(await readFile(path.join(root,'PROVENANCE.json'),'utf8'));
15
+ const matrix=JSON.parse(await readFile(path.join(root,'TEST-MATRIX.json'),'utf8'));
16
+ const active=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
17
+ const manifestSha256=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
18
+
19
+ insist(pkg.version==='0.15.33'&&release.version===pkg.version&&release.train==='R56','R56 candidate identity mismatch');
20
+ insist(release.parent.version==='0.15.32','R56 parent mismatch');
21
+ insist(release.parent.manifest_sha256==='e7223f467777f5dfeb20fd2cc7a6eca22619a44ed95e5bc0b32d9c7aabba361b','R56 parent manifest mismatch');
22
+ insist(release.parent.generation_manifest_sha256==='740443fd207c5e6ffd5952079214bf6e81e9281a6ec880de9bbe3b8ce1deb00e','R56 parent generation mismatch');
23
+ insist(release.parent.tarball_sha256==='c9e1f5875dd2177cee0089305b40321c128c54ce7f5522e34e507d4ecfb22638','R56 parent tarball mismatch');
24
+ insist(
25
+ active.release_id===release.runtime_predecessor.release_id&&
26
+ active.manifest_sha256===release.runtime_predecessor.manifest_sha256&&
27
+ active.generation_manifest_sha256===release.runtime_predecessor.generation_manifest_sha256,
28
+ 'active runtime is not exact sealed 0.15.32 predecessor'
29
+ );
30
+ insist(release.hotfix?.signed_handoff_launcher_snapshot_required===true,'R56 signed rollback handoff launcher requirement missing');
31
+ insist(release.hotfix?.stored_predecessor_verified_without_live_launcher===true,'R56 stored predecessor verification split missing');
32
+ insist(release.hotfix?.predecessor_launcher_restored_before_pointer_commit===true,'R56 predecessor launcher ordering missing');
33
+ insist(release.hotfix?.successor_launcher_restored_before_recovery_pointer===true,'R56 successor recovery ordering missing');
34
+ insist(release.hotfix?.full_regression==='PASS','R56 full regression metadata is not PASS');
35
+ insist(matrix.observed_pre_release_evidence?.full_regression?.status==='PASS','R56 full regression evidence is not PASS');
36
+ insist(provenance.operator_override?.stable_final_claim===false,'R56 must not claim stable/final');
37
+ insist(matrix.operator_override?.four_hour_soak==='NOT_COMPLETED','R56 matrix must preserve soak bypass');
38
+
39
+ const health=await doctor();
40
+ insist(health.status==='ok'&&health.package_version===pkg.version&&health.manifest?.verified===true,'R56 doctor evidence invalid');
41
+
42
+ const evidence={
43
+ schema:'deadbyte.r56-windows-gate.v1',
44
+ status:'passed',
45
+ exit_code:0,
46
+ version:pkg.version,
47
+ train:'R56',
48
+ completed_at_utc:new Date().toISOString(),
49
+ manifest_sha256:manifestSha256,
50
+ parent:release.parent,
51
+ runtime_predecessor:release.runtime_predecessor,
52
+ hotfix:release.hotfix,
53
+ operator_override:provenance.operator_override,
54
+ active_predecessor:{
55
+ release_id:active.release_id,
56
+ version:active.version,
57
+ manifest_sha256:active.manifest_sha256,
58
+ generation_manifest_sha256:active.generation_manifest_sha256
59
+ },
60
+ doctor:{verified_entries:health.manifest.verified_entries},
61
+ gates:{
62
+ r56_focused:true,
63
+ package_boundary:true,
64
+ full_regression:true,
65
+ manifest_final_verify:true,
66
+ four_hour_soak_completed:false
67
+ }
68
+ };
69
+ const bytes=Buffer.from(JSON.stringify(evidence,null,2)+'\n');
70
+ const dir=path.join(stateRoot,'evidence','release');
71
+ await mkdir(dir,{recursive:true});
72
+ const target=path.join(dir,`r56-windows-gate-${manifestSha256.slice(0,16)}.json`);
73
+ try{
74
+ const handle=await open(target,'wx',0o600);
75
+ try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
76
+ }catch(error){
77
+ if(error?.code!=='EEXIST')throw error;
78
+ const prior=JSON.parse(await readFile(target,'utf8'));
79
+ insist(prior.schema===evidence.schema&&prior.status==='passed'&&prior.version===evidence.version&&prior.manifest_sha256===manifestSha256,'conflicting R56 gate evidence identity');
80
+ }
81
+ console.log(JSON.stringify({status:'passed',path:target,sha256:sha256(bytes),manifest_sha256:manifestSha256}));
@@ -0,0 +1,81 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, open, readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { doctor } from '../src/doctor-r45.mjs';
7
+
8
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
9
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
10
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ const insist=(ok,message)=>{if(!ok)throw new Error(message);};
12
+ const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
13
+ const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
14
+ const provenance=JSON.parse(await readFile(path.join(root,'PROVENANCE.json'),'utf8'));
15
+ const matrix=JSON.parse(await readFile(path.join(root,'TEST-MATRIX.json'),'utf8'));
16
+ const active=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
17
+ const manifestSha256=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
18
+
19
+ insist(pkg.version==='0.15.34'&&release.version===pkg.version&&release.train==='R57','R57 candidate identity mismatch');
20
+ insist(release.parent.version==='0.15.33','R57 parent mismatch');
21
+ insist(release.parent.manifest_sha256==='776a51cb916e28ddc3cbb2ecd3857151a0e73e0aaf83d4e6308e6674125dd01c','R57 parent manifest mismatch');
22
+ insist(release.parent.generation_manifest_sha256==='20ccc5e4bbf8470b61e0a6d2ad181dad5e8533da980ceba7749e1d6f48d9dbd3','R57 parent generation mismatch');
23
+ insist(release.parent.tarball_sha256==='d84a6df0a972a43c50d5e31242d9217f59858b2d73c4284af296da76d2048e5f','R57 parent tarball mismatch');
24
+ insist(
25
+ active.release_id===release.runtime_predecessor.release_id&&
26
+ active.manifest_sha256===release.runtime_predecessor.manifest_sha256&&
27
+ active.generation_manifest_sha256===release.runtime_predecessor.generation_manifest_sha256,
28
+ 'active runtime is not exact sealed 0.15.33 predecessor'
29
+ );
30
+ insist(release.hotfix?.bounded_core_discovery_after_online===true,'R57 bounded core discovery missing');
31
+ insist(release.hotfix?.retry_only_missing_core===true,'R57 zero-child-only retry contract missing');
32
+ insist(release.hotfix?.ambiguous_core_fail_closed===true,'R57 ambiguous-core fail-closed contract missing');
33
+ insist(release.hotfix?.timeout_fail_closed===true,'R57 timeout fail-closed contract missing');
34
+ insist(release.hotfix?.full_regression==='PASS','R57 full regression metadata is not PASS');
35
+ insist(matrix.observed_pre_release_evidence?.full_regression?.status==='PASS','R57 full regression evidence is not PASS');
36
+ insist(provenance.operator_override?.stable_final_claim===false,'R57 must not claim stable/final');
37
+ insist(matrix.operator_override?.four_hour_soak==='NOT_COMPLETED','R57 matrix must preserve soak bypass');
38
+
39
+ const health=await doctor();
40
+ insist(health.status==='ok'&&health.package_version===pkg.version&&health.manifest?.verified===true,'R57 doctor evidence invalid');
41
+
42
+ const evidenceBody={
43
+ schema:'deadbyte.r57-windows-gate.v1',
44
+ status:'passed',
45
+ exit_code:0,
46
+ version:pkg.version,
47
+ train:'R57',
48
+ completed_at_utc:new Date().toISOString(),
49
+ manifest_sha256:manifestSha256,
50
+ parent:release.parent,
51
+ runtime_predecessor:release.runtime_predecessor,
52
+ hotfix:release.hotfix,
53
+ operator_override:provenance.operator_override,
54
+ active_predecessor:{
55
+ release_id:active.release_id,
56
+ version:active.version,
57
+ manifest_sha256:active.manifest_sha256,
58
+ generation_manifest_sha256:active.generation_manifest_sha256
59
+ },
60
+ doctor:{verified_entries:health.manifest.verified_entries},
61
+ gates:{
62
+ r57_focused:true,
63
+ package_boundary:true,
64
+ full_regression:true,
65
+ manifest_final_verify:true,
66
+ four_hour_soak_completed:false
67
+ }
68
+ };
69
+ const bytes=Buffer.from(JSON.stringify(evidenceBody,null,2)+'\n');
70
+ const dir=path.join(stateRoot,'evidence','release');
71
+ await mkdir(dir,{recursive:true});
72
+ const target=path.join(dir,`r57-windows-gate-${manifestSha256.slice(0,16)}.json`);
73
+ try{
74
+ const handle=await open(target,'wx',0o600);
75
+ try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
76
+ }catch(error){
77
+ if(error?.code!=='EEXIST')throw error;
78
+ const prior=JSON.parse(await readFile(target,'utf8'));
79
+ insist(prior.schema===evidenceBody.schema&&prior.status==='passed'&&prior.version===evidenceBody.version&&prior.manifest_sha256===manifestSha256,'conflicting R57 gate evidence identity');
80
+ }
81
+ console.log(JSON.stringify({status:'passed',path:target,sha256:sha256(bytes),manifest_sha256:manifestSha256}));
@@ -743,6 +743,37 @@ export function selectCoreProcess(processes,coreRoot,bridgePid,verifiedBridge=nu
743
743
  function coreProcess(coreRoot,bridge){
744
744
  return selectCoreProcess(nodeProcesses(),coreRoot,bridge.pid,bridge);
745
745
  }
746
+ export async function waitForCoreProcess(coreRoot,bridge,{
747
+ timeoutMs=10000,
748
+ pollMs=100,
749
+ processProvider=nodeProcesses,
750
+ sleepFn=sleep,
751
+ nowFn=Date.now
752
+ }={}){
753
+ const bridgePid=Number(bridge?.pid);
754
+ if(!Number.isInteger(bridgePid)||bridgePid<1) throw new Error('downstream core wait requires verified bridge PID');
755
+ if(!Number.isFinite(timeoutMs)||timeoutMs<0) throw new Error('downstream core wait timeout invalid');
756
+ if(!Number.isFinite(pollMs)||pollMs<1) throw new Error('downstream core wait poll invalid');
757
+ if(typeof processProvider!=='function'||typeof sleepFn!=='function'||typeof nowFn!=='function') throw new Error('downstream core wait hooks invalid');
758
+ const started=Number(nowFn());
759
+ let lastError=null;
760
+ while(true){
761
+ try {
762
+ const processes=await processProvider();
763
+ return selectCoreProcess(processes,coreRoot,bridgePid,bridge);
764
+ } catch(error){
765
+ const message=String(error?.message??error);
766
+ const transient=`expected one unambiguous downstream core for bridge ${bridgePid}, found children=0 visible_matches=0`;
767
+ if(message!==transient) throw error;
768
+ lastError=error;
769
+ }
770
+ const elapsed=Math.max(0,Number(nowFn())-started);
771
+ if(elapsed>=timeoutMs){
772
+ throw new Error(`downstream core did not appear for bridge ${bridgePid} within ${timeoutMs}ms: ${String(lastError?.message??lastError)}`);
773
+ }
774
+ await sleepFn(Math.min(pollMs,Math.max(1,timeoutMs-elapsed)));
775
+ }
776
+ }
746
777
  async function captureRuntime(ctx){
747
778
  const statusText=controllerCaptured(ctx,'status',15000); const status=parseControllerStatus(statusText);
748
779
  if(!status.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
@@ -826,7 +857,7 @@ async function startAndVerify(ctx,oldRuntime){
826
857
  catch {} await sleep(500);
827
858
  }
828
859
  if(!status?.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) throw new Error(`new runtime failed ONLINE gate: ${statusText}`);
829
- const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
860
+ const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=await waitForCoreProcess(ctx.core_root,bridge);
830
861
  if(oldRuntime?.bridge&&sameGeneration(oldRuntime.bridge,bridge)) throw new Error('fresh bridge generation gate failed');
831
862
  if(oldRuntime?.cloud&&sameGeneration(oldRuntime.cloud,cloud)) throw new Error('fresh cloud generation gate failed');
832
863
  if(oldRuntime?.core&&oldRuntime.core.pid===core.pid&&oldRuntime.core.creation_date===core.creation_date) throw new Error('fresh downstream core generation gate failed');
@@ -861,16 +892,21 @@ async function verifyPackageRoot(root,expectedVersion,expectedManifest,label){
861
892
  if(pkg?.name!=='deadbyte-mcp'||pkg?.version!==expectedVersion) throw new Error(`${label} package identity mismatch`);
862
893
  return {root:absolute,manifest_sha256:digest,entries:entries.size};
863
894
  }
864
- async function verifyActiveInstallation(stateRoot,pointer){
895
+ async function verifyStoredInstallation(stateRoot,pointer){
865
896
  const state=path.resolve(stateRoot); const expectedRelease=path.join(state,'releases',releaseId(pointer.version,pointer.manifest_sha256));
866
897
  if(path.resolve(pointer.release_root)!==path.resolve(expectedRelease)) throw new Error('active pointer release root is not content-addressed store path');
867
898
  const release=await verifyPackageRoot(pointer.release_root,pointer.version,pointer.manifest_sha256,'active release');
868
899
  const generation=await verifyGenerationBundle(pointer.generation_root,pointer.generation_manifest_sha256);
869
900
  const expectedConfig=path.join(path.resolve(pointer.generation_root),'controller-config.psd1');
870
901
  if(path.resolve(pointer.config_path)!==path.resolve(expectedConfig)) throw new Error('active pointer config path mismatch');
902
+ return {release,generation};
903
+ }
904
+ async function verifyActiveInstallation(stateRoot,pointer){
905
+ const state=path.resolve(stateRoot);
906
+ const verified=await verifyStoredInstallation(state,pointer);
871
907
  const currentLauncher=await sha256File(launcherPath(state));
872
908
  if(currentLauncher!==pointer.launcher_sha256) throw new Error('active pointer launcher digest mismatch');
873
- return {release,generation};
909
+ return verified;
874
910
  }
875
911
  async function loadPointerIfPresent(stateRoot){
876
912
  const file=path.join(path.resolve(stateRoot),'active-release.json');
@@ -1084,6 +1120,41 @@ function decodeSnapshot(snapshot,label){
1084
1120
  }
1085
1121
  return {exists:false,bytes:null,sha256:null};
1086
1122
  }
1123
+ export function rollbackLauncherPlanFromHandoff(body,priorPointer,activePointer,stateRoot=DEFAULT_STATE_ROOT){
1124
+ if(!body||typeof body!=='object') throw new Error('rollback handoff body required');
1125
+ const handoffPrior=body.prior_pointer?parseActivePointer(JSON.stringify(body.prior_pointer)):null;
1126
+ const handoffActive=body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
1127
+ if(!samePublicPointer(handoffPrior,priorPointer)||!samePublicPointer(handoffActive,activePointer)) throw new Error('rollback handoff pointer binding mismatch');
1128
+ const state=path.resolve(stateRoot);
1129
+ const launchers={
1130
+ ps1:path.resolve(String(body.launchers?.ps1??'')),
1131
+ cmd:path.resolve(String(body.launchers?.cmd??'')),
1132
+ top:path.resolve(String(body.launchers?.top??TOP_LAUNCHER)),
1133
+ launcher_sha256:String(body.launchers?.launcher_sha256??''),
1134
+ ps1Bytes:Buffer.from(String(body.launchers?.ps1_bytes??''),'base64'),
1135
+ cmdBytes:Buffer.from(String(body.launchers?.cmd_bytes??''),'base64'),
1136
+ snapshots:{
1137
+ ps1:decodeSnapshot(body.launchers?.snapshots?.ps1,'rollback.launcher.ps1'),
1138
+ cmd:decodeSnapshot(body.launchers?.snapshots?.cmd,'rollback.launcher.cmd'),
1139
+ top:decodeSnapshot(body.launchers?.snapshots?.top,'rollback.launcher.top')
1140
+ },
1141
+ installed:true
1142
+ };
1143
+ if(launchers.ps1!==path.resolve(launcherPath(state))||
1144
+ launchers.cmd!==path.resolve(path.join(state,'bin','DEADBYTE-MCP.cmd'))||
1145
+ launchers.top!==path.resolve(TOP_LAUNCHER)) throw new Error('rollback launcher paths are not canonical');
1146
+ if(!HEX64.test(launchers.launcher_sha256)||sha256Bytes(launchers.ps1Bytes)!==launchers.launcher_sha256) throw new Error('rollback successor launcher bytes/hash mismatch');
1147
+ if(launchers.launcher_sha256!==activePointer.launcher_sha256) throw new Error('rollback successor launcher hash does not match active pointer');
1148
+ if(!launchers.snapshots.ps1.exists||launchers.snapshots.ps1.sha256!==priorPointer.launcher_sha256) throw new Error('rollback predecessor launcher snapshot does not match signed prior pointer');
1149
+ return launchers;
1150
+ }
1151
+ async function restoreRollbackLaunchers(plan){
1152
+ await restoreFile(plan.ps1,plan.snapshots.ps1);
1153
+ await restoreFile(plan.cmd,plan.snapshots.cmd);
1154
+ await restoreFile(plan.top,plan.snapshots.top);
1155
+ if(await sha256File(plan.ps1)!==plan.snapshots.ps1.sha256) throw new Error('rollback predecessor launcher restore hash mismatch');
1156
+ return plan;
1157
+ }
1087
1158
  const UPDATE_PHASE_RANK=Object.freeze({
1088
1159
  initial:0,candidate_prepared:10,old_runtime_verified:20,shutdown_handoff_armed:25,supervisor_spawned:30,
1089
1160
  authority_revoked:40,old_runtime_stopped:50,workspace_ready:60,launcher_installed:70,pointer_committed:80,
@@ -1587,19 +1658,28 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
1587
1658
  const activePointer=parseActivePointer(JSON.stringify(activationEnvelope.body.active_pointer));
1588
1659
  const pointerInfo=await loadPointerIfPresent(state);
1589
1660
  if(!pointerInfo.pointer||canonicalJson(publicPointer(pointerInfo.pointer))!==canonicalJson(publicPointer(activePointer))) throw new Error('rollback current pointer does not match signed activation pointer');
1590
- await verifyActiveInstallation(state,priorPointer);
1661
+
1662
+ const handoffPath=path.join(path.dirname(receiptFile),'handoff.json');
1663
+ const {body:handoffBody}=await readHandoffEnvelope(handoffPath,state);
1664
+ if(handoffBody.update_id!==activationEnvelope.body.update_id) throw new Error('rollback handoff update identity mismatch');
1665
+ const rollbackLaunchers=rollbackLauncherPlanFromHandoff(handoffBody,priorPointer,activePointer,state);
1666
+ await verifyStoredInstallation(state,priorPointer);
1667
+
1591
1668
  const currentCtx=slotContext(state,pointerInfo.pointer),currentRuntime=await captureRuntime(currentCtx);
1592
1669
  const revoked=await revokeAuthority(currentCtx,currentRuntime.status);
1593
1670
  const rollbackId=`r31-rollback-${activationEnvelope.receipt_sha256.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
1594
1671
  const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json');
1595
1672
  await mkdir(evidenceDir,{recursive:true});
1596
1673
  const journal={schema:'deadbyte.r31-slot-rollback-state.v1',rollback_id:rollbackId,activation_receipt:path.resolve(receiptFile),
1597
- activation_receipt_sha256:activationEnvelope.receipt_sha256,from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),created_at_utc:new Date().toISOString()};
1674
+ activation_receipt_sha256:activationEnvelope.receipt_sha256,handoff_path:handoffPath,
1675
+ from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),created_at_utc:new Date().toISOString()};
1598
1676
  await writeState(statePath,journal,'verified');
1599
- let stopped=false,pointerCommitted=false,priorCtx=null,priorRuntime=null;
1677
+ let stopped=false,launchersRestored=false,pointerCommitted=false,priorCtx=null,priorRuntime=null;
1600
1678
  try {
1601
1679
  await stopAndVerify(currentCtx,currentRuntime); stopped=true;
1602
1680
  await writeState(statePath,journal,'successor_stopped');
1681
+ await restoreRollbackLaunchers(rollbackLaunchers); launchersRestored=true;
1682
+ await writeState(statePath,journal,'predecessor_launcher_restored',{launcher_sha256:priorPointer.launcher_sha256});
1603
1683
  await atomicWriteActivePointer(pointerInfo.file,priorPointer); pointerCommitted=true;
1604
1684
  await verifyActiveInstallation(state,priorPointer);
1605
1685
  await writeState(statePath,journal,'prior_pointer_restored',{active_pointer:publicPointer(priorPointer)});
@@ -1610,16 +1690,27 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
1610
1690
  if(finalStatus.process_armed||finalStatus.coding_armed||finalStatus.autonomous_armed||finalStatus.desktop_armed||finalStatus.host_armed) throw new Error('rollback predecessor authority must remain disarmed');
1611
1691
  const body={schema:ROLLBACK_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
1612
1692
  completed_at_utc:new Date().toISOString(),from_pointer:publicPointer(activePointer),active_pointer:publicPointer(priorPointer),
1613
- old_runtime:generationTuple(currentRuntime),new_runtime:generationTuple(priorRuntime),stabilization,authority_revoked:revoked.revoked,rollback_performed:true};
1693
+ launcher_restored:true,old_runtime:generationTuple(currentRuntime),new_runtime:generationTuple(priorRuntime),stabilization,
1694
+ authority_revoked:revoked.revoked,rollback_performed:true};
1614
1695
  const receipt=await writeEnvelope(receiptPath,body,key);
1615
1696
  await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
1616
1697
  return {ok:true,rollback_id:rollbackId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,pointer:priorPointer,new_runtime:priorRuntime};
1617
1698
  } catch(error){
1618
- const recovery={attempted:false,prior_runtime_stopped:false,successor_pointer_restored:false,successor_runtime_restarted:false,errors:[]};
1699
+ const recovery={attempted:false,prior_runtime_stopped:false,successor_launcher_restored:false,successor_pointer_restored:false,successor_runtime_restarted:false,errors:[]};
1619
1700
  if(stopped){
1620
1701
  recovery.attempted=true;
1621
1702
  if(pointerCommitted&&priorCtx){ try { controllerCaptured(priorCtx,'stop',30000); recovery.prior_runtime_stopped=true; } catch(recoveryError){ recovery.errors.push(`stop-prior:${recoveryError.message}`); } }
1622
- if(pointerCommitted){ try { await atomicWriteActivePointer(pointerInfo.file,activePointer); await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; } catch(recoveryError){ recovery.errors.push(`restore-pointer:${recoveryError.message}`); } }
1703
+ if(launchersRestored){
1704
+ try { await installLaunchers(rollbackLaunchers); recovery.successor_launcher_restored=true; }
1705
+ catch(recoveryError){ recovery.errors.push(`restore-successor-launcher:${recoveryError.message}`); }
1706
+ }
1707
+ if(pointerCommitted){
1708
+ try { await atomicWriteActivePointer(pointerInfo.file,activePointer); await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; }
1709
+ catch(recoveryError){ recovery.errors.push(`restore-pointer:${recoveryError.message}`); }
1710
+ }else if(recovery.successor_launcher_restored){
1711
+ try { await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; }
1712
+ catch(recoveryError){ recovery.errors.push(`verify-successor-pointer:${recoveryError.message}`); }
1713
+ }
1623
1714
  try { await startAndVerify(currentCtx,currentRuntime); recovery.successor_runtime_restarted=true; } catch(recoveryError){ recovery.errors.push(`restart-successor:${recoveryError.message}`); }
1624
1715
  }
1625
1716
  await writeState(statePath,journal,'failed',{error:{name:error?.name??'Error',message:String(error?.message??error)},recovery}).catch(()=>{});
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.32';
1
+ export const DEADBYTE_VERSION = '0.15.34';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -4,9 +4,13 @@ import test from 'node:test';
4
4
 
5
5
  const read=path=>readFile(new URL(`../${path}`,import.meta.url),'utf8');
6
6
 
7
- test('R55 canonical Windows gate is successor-bound and preserves R54/R53/R52/R51/R50/R49 historical gates',async()=>{
8
- const [pkgText,gate,evidence,historicalR54,historicalEvidenceR54,historicalR53,historicalEvidenceR53,historicalR52,historicalEvidenceR52,historicalR51,historicalEvidenceR51,historicalR50,historicalR49,deferred]=await Promise.all([
7
+ test('R57 canonical Windows gate is successor-bound and preserves R56/R55/R54/R53/R52/R51/R50/R49 historical gates',async()=>{
8
+ const [pkgText,gate,evidence,historicalR56,historicalEvidenceR56,historicalR55,historicalEvidenceR55,historicalR54,historicalEvidenceR54,historicalR53,historicalEvidenceR53,historicalR52,historicalEvidenceR52,historicalR51,historicalEvidenceR51,historicalR50,historicalR49,deferred]=await Promise.all([
9
9
  read('package.json'),
10
+ read('scripts/gate-windows-r57.ps1'),
11
+ read('scripts/windows-gate-evidence-r57.mjs'),
12
+ read('scripts/gate-windows-r56.ps1'),
13
+ read('scripts/windows-gate-evidence-r56.mjs'),
10
14
  read('scripts/gate-windows-r55.ps1'),
11
15
  read('scripts/windows-gate-evidence-r55.mjs'),
12
16
  read('scripts/gate-windows-r54.ps1'),
@@ -22,18 +26,22 @@ test('R55 canonical Windows gate is successor-bound and preserves R54/R53/R52/R5
22
26
  read('scripts/deferred-slot-operation-r46.mjs')
23
27
  ]);
24
28
  const pkg=JSON.parse(pkgText);
25
- assert.match(pkg.scripts['gate:windows'],/gate-windows-r55\.ps1$/);
26
- assert.match(gate,/DEADBYTE 0\.15\.32 \/ R55 WINDOWS GATE: PASS/);
27
- assert.match(gate,/r55-package-boundary/);
28
- assert.match(gate,/r55-focused-contracts/);
29
- assert.match(gate,/full-regression/);
29
+ assert.match(pkg.scripts['gate:windows'],/gate-windows-r57\.ps1$/);
30
+ assert.match(gate,/DEADBYTE 0\.15\.34 \/ R57 WINDOWS GATE: PASS/);
31
+ assert.match(gate,/r57-package-boundary/);
32
+ assert.match(gate,/r57-focused-contracts/);
33
+ assert.match(gate,/full-regression-evidence/);
30
34
  assert.match(gate,/manifest-verify/);
31
35
  assert.match(gate,/doctor/);
32
- assert.match(evidence,/deadbyte\.r55-windows-gate\.v1/);
33
- assert.match(evidence,/pkg\.version==='0\.15\.32'/);
34
- assert.match(evidence,/release\.train==='R55'/);
35
- assert.match(evidence,/release\.parent\.version==='0\.15\.31'/);
36
+ assert.match(evidence,/deadbyte\.r57-windows-gate\.v1/);
37
+ assert.match(evidence,/pkg\.version==='0\.15\.34'/);
38
+ assert.match(evidence,/release\.train==='R57'/);
39
+ assert.match(evidence,/release\.parent\.version==='0\.15\.33'/);
36
40
  assert.match(evidence,/four_hour_soak_completed:false/);
41
+ assert.match(historicalR56,/DEADBYTE 0\.15\.33 \/ R56 WINDOWS GATE: PASS/);
42
+ assert.match(historicalEvidenceR56,/deadbyte\.r56-windows-gate\.v1/);
43
+ assert.match(historicalR55,/DEADBYTE 0\.15\.32 \/ R55 WINDOWS GATE: PASS/);
44
+ assert.match(historicalEvidenceR55,/deadbyte\.r55-windows-gate\.v1/);
37
45
  assert.match(historicalR54,/DEADBYTE 0\.15\.31 \/ R54 WINDOWS GATE: PASS/);
38
46
  assert.match(historicalEvidenceR54,/deadbyte\.r54-windows-gate\.v1/);
39
47
  assert.match(historicalR53,/DEADBYTE 0\.15\.30 \/ R53 WINDOWS GATE: PASS/);
@@ -46,10 +54,5 @@ test('R55 canonical Windows gate is successor-bound and preserves R54/R53/R52/R5
46
54
  assert.match(historicalR49,/DEADBYTE 0\.15\.26 \/ R49 WINDOWS GATE: PASS/);
47
55
  assert.match(deferred,/const expectedVersion=packageIdentity\.version/);
48
56
  assert.match(deferred,/'--expected-version',expectedVersion/);
49
- assert.match(deferred,/async function awaitHandoffCompletion/);
50
57
  assert.match(deferred,/verifySlotUpdateEnvelope\(envelope,publicKey\)/);
51
- assert.match(deferred,/active pointer does not match signed activation receipt/);
52
- assert.match(deferred,/failure-receipt\.json/);
53
- assert.match(deferred,/await awaitHandoffCompletion\(await runSlotUpdateCli/);
54
- assert.doesNotMatch(deferred,/'--expected-version','0\.15\.5'/);
55
58
  });
@@ -5,8 +5,10 @@ import { readFile } from 'node:fs/promises';
5
5
  const root=new URL('../',import.meta.url);
6
6
  const read=rel=>readFile(new URL(rel,root),'utf8');
7
7
  const json=rel=>read(rel).then(JSON.parse);
8
+ const currentPackage=await json('package.json');
9
+ const r55test=currentPackage.version==='0.15.32'?test:test.skip;
8
10
 
9
- test('R55 release identity is coherent across package lock source metadata SBOM and gate',async()=>{
11
+ r55test('R55 release identity is coherent across package lock source metadata SBOM and gate',async()=>{
10
12
  const [pkg,lock,release,provenance,matrix,sbom,versionSource]=await Promise.all([
11
13
  json('package.json'),json('package-lock.json'),json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs')
12
14
  ]);
@@ -25,7 +27,7 @@ test('R55 release identity is coherent across package lock source metadata SBOM
25
27
  assert.match(pkg.scripts['r55:focused:test'],/r55-offline-predecessor-guardian-family\.test\.mjs/);
26
28
  });
27
29
 
28
- test('R55 source predecessor is exact immutable published 0.15.31 while runtime predecessor remains exact 0.15.29',async()=>{
30
+ r55test('R55 source predecessor is exact immutable published 0.15.31 while runtime predecessor remains exact 0.15.29',async()=>{
29
31
  const [release,provenance]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json')]);
30
32
  assert.equal(release.parent.version,'0.15.31');
31
33
  assert.equal(release.parent.release_id,'v0.15.31-16854fe5b3eb0fa1');
@@ -44,7 +46,7 @@ test('R55 source predecessor is exact immutable published 0.15.31 while runtime
44
46
  assert.deepEqual(provenance.runtime_predecessor,release.runtime_predecessor);
45
47
  });
46
48
 
47
- test('R55 records the guardian sibling RED to GREEN and preserves fail-closed negative controls',async()=>{
49
+ r55test('R55 records the guardian sibling RED to GREEN and preserves fail-closed negative controls',async()=>{
48
50
  const [release,provenance,matrix]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json')]);
49
51
  assert.equal(release.hotfix.activation_ancestry_excluded,true);
50
52
  assert.equal(release.hotfix.foreground_guardian_owner_parent_binding,true);
@@ -0,0 +1,65 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFile } from 'node:fs/promises';
4
+
5
+ const root=new URL('../',import.meta.url);
6
+ const read=rel=>readFile(new URL(rel,root),'utf8');
7
+ const json=rel=>read(rel).then(JSON.parse);
8
+ const currentPackage=await json('package.json');
9
+ const r56test=currentPackage.version==='0.15.33'?test:test.skip;
10
+
11
+ r56test('R56 release identity is coherent across package lock source metadata SBOM and gate',async()=>{
12
+ const [pkg,lock,release,provenance,matrix,sbom,versionSource]=await Promise.all([
13
+ json('package.json'),json('package-lock.json'),json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs')
14
+ ]);
15
+ assert.equal(pkg.version,'0.15.33');
16
+ assert.equal(lock.version,pkg.version);
17
+ assert.equal(lock.packages[''].version,pkg.version);
18
+ assert.match(versionSource,/DEADBYTE_VERSION = '0\.15\.33'/);
19
+ assert.equal(release.version,pkg.version);
20
+ assert.equal(release.train,'R56');
21
+ assert.equal(provenance.version,pkg.version);
22
+ assert.equal(matrix.version,pkg.version);
23
+ assert.equal(matrix.train,'R56');
24
+ assert.equal(sbom.metadata.component.version,pkg.version);
25
+ assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r56:0.15.33');
26
+ assert.match(pkg.scripts['gate:windows'],/gate-windows-r56\.ps1/);
27
+ assert.match(pkg.scripts['r56:focused:test'],/r56-rollback-launcher-restore\.test\.mjs/);
28
+ });
29
+
30
+ r56test('R56 source and runtime predecessor are exact active published 0.15.32',async()=>{
31
+ const [release,provenance]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json')]);
32
+ assert.equal(release.parent.version,'0.15.32');
33
+ assert.equal(release.parent.release_id,'v0.15.32-e7223f467777f5df');
34
+ assert.equal(release.parent.manifest_sha256,'e7223f467777f5dfeb20fd2cc7a6eca22619a44ed95e5bc0b32d9c7aabba361b');
35
+ assert.equal(release.parent.generation_manifest_sha256,'740443fd207c5e6ffd5952079214bf6e81e9281a6ec880de9bbe3b8ce1deb00e');
36
+ assert.equal(release.parent.tarball_sha256,'c9e1f5875dd2177cee0089305b40321c128c54ce7f5522e34e507d4ecfb22638');
37
+ assert.equal(release.parent.activation_status,'ACTIVATED_ROLLBACK_DEFECT_OBSERVED');
38
+ assert.equal(release.source_predecessor.version,'0.15.32');
39
+ assert.deepEqual(release.runtime_predecessor,{
40
+ release_id:'v0.15.32-e7223f467777f5df',
41
+ manifest_sha256:'e7223f467777f5dfeb20fd2cc7a6eca22619a44ed95e5bc0b32d9c7aabba361b',
42
+ generation_manifest_sha256:'740443fd207c5e6ffd5952079214bf6e81e9281a6ec880de9bbe3b8ce1deb00e'
43
+ });
44
+ assert.equal(provenance.parent_manifest_sha256,release.parent.manifest_sha256);
45
+ assert.equal(provenance.parent_generation_manifest_sha256,release.parent.generation_manifest_sha256);
46
+ assert.deepEqual(provenance.runtime_predecessor,release.runtime_predecessor);
47
+ });
48
+
49
+ r56test('R56 records signed rollback launcher ordering RED to GREEN and preserves non-final claim',async()=>{
50
+ const [release,provenance,matrix]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json')]);
51
+ assert.equal(release.hotfix.signed_handoff_launcher_snapshot_required,true);
52
+ assert.equal(release.hotfix.stored_predecessor_verified_without_live_launcher,true);
53
+ assert.equal(release.hotfix.predecessor_launcher_restored_before_pointer_commit,true);
54
+ assert.equal(release.hotfix.successor_launcher_restored_before_recovery_pointer,true);
55
+ assert.equal(release.hotfix.exact_parent_activation_receipt_sha256,'6518a04154f485cfae42e582269634d69a8b05a0183149d5e10006fbdd9d933c');
56
+ assert.equal(release.hotfix.observed_parent_rollback_failure_receipt_sha256,'00cbd497e4902c67ab39313a378e2fcf32159113b46339ad1d54b5e5a7714205');
57
+ assert.equal(release.hotfix.red_parent_0_15_32,'FAIL_5_OF_5');
58
+ assert.equal(release.hotfix.green_r56_candidate,'PASS_5_OF_5');
59
+ assert.equal(matrix.observed_pre_release_evidence.red_parent_0_15_32.receipt_sha256,'f3b446306691d61e97896ab4d237abaa20d1243237299812e790233c46451dba');
60
+ assert.equal(matrix.observed_pre_release_evidence.green_r56_candidate.receipt_sha256,'4198930f38a3fc4b7978cfc94ca4509127561c170b05f7566bdf79513e449a8b');
61
+ assert.equal(release.hotfix.stable_final_claim,false);
62
+ assert.equal(provenance.operator_override.four_hour_soak,'NOT_COMPLETED');
63
+ assert.equal(provenance.operator_override.stable_final_claim,false);
64
+ assert.equal(matrix.operator_override.stable_final_claim,false);
65
+ });
@@ -0,0 +1,103 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { readFile } from 'node:fs/promises';
5
+ import { pathToFileURL } from 'node:url';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+
9
+ const target=process.env.R56_RUNTIME_UPDATE_PATH
10
+ ? path.resolve(process.env.R56_RUNTIME_UPDATE_PATH)
11
+ : path.resolve(new URL('../src/runtime-update.mjs',import.meta.url).pathname.replace(/^\/(?:[A-Za-z]:)/,m=>m.slice(1)));
12
+ const runtimeUpdate=await import(pathToFileURL(target).href+'?r56='+Date.now());
13
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
14
+ const stateRoot=path.resolve(process.env.R56_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
15
+ const topLauncher=path.resolve(path.join(os.homedir(),'Downloads','DEADBYTE-MCP.cmd'));
16
+
17
+ function pointer(version,manifest,launcher){
18
+ const releaseId=`v${version}-${manifest.slice(0,16)}`;
19
+ const genId=`${releaseId}-g${'9'.repeat(16)}`;
20
+ return {
21
+ schema:'deadbyte.active-release.v1',
22
+ release_id:releaseId,
23
+ version,
24
+ manifest_sha256:manifest,
25
+ release_root:path.join(stateRoot,'releases',releaseId),
26
+ generation_root:path.join(stateRoot,'generations',genId),
27
+ generation_manifest_sha256:'8'.repeat(64),
28
+ launcher_sha256:launcher,
29
+ config_path:path.join(stateRoot,'generations',genId,'controller-config.psd1')
30
+ };
31
+ }
32
+ function snapshot(bytes){
33
+ const b=Buffer.from(bytes);
34
+ return {exists:true,sha256:sha256(b),bytes_base64:b.toString('base64')};
35
+ }
36
+ function fixture(){
37
+ const priorBytes=Buffer.from('prior-launcher-r55\r\n');
38
+ const activeBytes=Buffer.from('active-launcher-r56\r\n');
39
+ const cmdBytes=Buffer.from('@echo off\r\n');
40
+ const prior=pointer('0.15.29','1'.repeat(64),sha256(priorBytes));
41
+ const active=pointer('0.15.32','2'.repeat(64),sha256(activeBytes));
42
+ const body={
43
+ prior_pointer:prior,
44
+ new_pointer:active,
45
+ launchers:{
46
+ ps1:path.join(stateRoot,'bin','DEADBYTE-MCP.ps1'),
47
+ cmd:path.join(stateRoot,'bin','DEADBYTE-MCP.cmd'),
48
+ top:topLauncher,
49
+ launcher_sha256:sha256(activeBytes),
50
+ ps1_bytes:activeBytes.toString('base64'),
51
+ cmd_bytes:cmdBytes.toString('base64'),
52
+ snapshots:{
53
+ ps1:snapshot(priorBytes),
54
+ cmd:snapshot(cmdBytes),
55
+ top:snapshot(cmdBytes)
56
+ }
57
+ }
58
+ };
59
+ return {prior,active,body};
60
+ }
61
+
62
+ test('R56 exposes rollback launcher planning from the signed activation handoff',()=>{
63
+ assert.equal(typeof runtimeUpdate.rollbackLauncherPlanFromHandoff,'function');
64
+ });
65
+
66
+ test('R56 accepts a prior launcher snapshot whose hash differs from the active launcher and binds both signed pointers',()=>{
67
+ const {prior,active,body}=fixture();
68
+ const plan=runtimeUpdate.rollbackLauncherPlanFromHandoff(body,prior,active,stateRoot);
69
+ assert.equal(plan.snapshots.ps1.sha256,prior.launcher_sha256);
70
+ assert.equal(plan.launcher_sha256,active.launcher_sha256);
71
+ assert.notEqual(plan.snapshots.ps1.sha256,plan.launcher_sha256);
72
+ });
73
+
74
+ test('R56 rejects a predecessor launcher snapshot that does not match the signed prior pointer',()=>{
75
+ const {prior,active,body}=fixture();
76
+ body.launchers.snapshots.ps1=snapshot(Buffer.from('wrong-prior-launcher\r\n'));
77
+ assert.throws(()=>runtimeUpdate.rollbackLauncherPlanFromHandoff(body,prior,active,stateRoot),/predecessor launcher snapshot/i);
78
+ });
79
+
80
+ test('R56 rejects handoff pointer drift instead of using unsigned rollback state',()=>{
81
+ const {prior,active,body}=fixture();
82
+ const drift={...prior,manifest_sha256:'3'.repeat(64),release_id:`v0.15.29-${'3'.repeat(16)}`,release_root:path.join(stateRoot,'releases',`v0.15.29-${'3'.repeat(16)}`)};
83
+ assert.throws(()=>runtimeUpdate.rollbackLauncherPlanFromHandoff(body,drift,active,stateRoot),/pointer binding mismatch/i);
84
+ });
85
+
86
+ test('R56 rollback verifies stored predecessor bytes before stop, restores signed predecessor launcher before pointer commit, and reinstalls successor launcher before recovery pointer',async()=>{
87
+ const source=await readFile(target,'utf8');
88
+ const start=source.indexOf('export async function performSlotRollback');
89
+ const end=source.indexOf('export async function runSlotUpdateCli',start);
90
+ assert.ok(start>=0&&end>start);
91
+ const perform=source.slice(start,end);
92
+ const stored=perform.indexOf('verifyStoredInstallation(state,priorPointer)');
93
+ const stop=perform.indexOf('stopAndVerify(currentCtx,currentRuntime)');
94
+ const restoreLauncher=perform.indexOf('restoreRollbackLaunchers(rollbackLaunchers)');
95
+ const commit=perform.indexOf('atomicWriteActivePointer(pointerInfo.file,priorPointer)');
96
+ const verifyPrior=perform.indexOf('verifyActiveInstallation(state,priorPointer)');
97
+ const reinstall=perform.indexOf('installLaunchers(rollbackLaunchers)');
98
+ const restoreSuccessor=perform.indexOf('atomicWriteActivePointer(pointerInfo.file,activePointer)');
99
+ assert.ok(stored>=0&&stored<stop);
100
+ assert.ok(restoreLauncher>stop&&restoreLauncher<commit);
101
+ assert.ok(verifyPrior>commit);
102
+ assert.ok(reinstall>verifyPrior&&reinstall<restoreSuccessor);
103
+ });