deadbyte-mcp 0.15.31 → 0.15.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +19 -11
- package/PROVENANCE.json +26 -21
- package/README.txt +22 -14
- package/RELEASE.json +27 -20
- package/SBOM.cdx.json +3 -3
- package/TEST-MATRIX.json +54 -27
- package/controller/deadbyte-process-policy.json +4 -4
- package/package.json +5 -3
- package/scripts/gate-windows-r55.ps1 +27 -0
- package/scripts/gate-windows-r56.ps1 +27 -0
- package/scripts/windows-gate-evidence-r55.mjs +79 -0
- package/scripts/windows-gate-evidence-r56.mjs +81 -0
- package/src/runtime-update.mjs +81 -8
- package/src/version.mjs +1 -1
- package/test/r49-windows-gate.test.mjs +19 -16
- package/test/r54-release-contract.test.mjs +5 -3
- package/test/r55-offline-predecessor-guardian-family.test.mjs +51 -0
- package/test/r55-release-contract.test.mjs +63 -0
- package/test/r56-release-contract.test.mjs +63 -0
- package/test/r56-rollback-launcher-restore.test.mjs +103 -0
|
@@ -0,0 +1,79 @@
|
|
|
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.32'&&release.version===pkg.version&&release.train==='R55','R55 candidate identity mismatch');
|
|
20
|
+
insist(release.parent.version==='0.15.31','R55 source parent mismatch');
|
|
21
|
+
insist(release.parent.manifest_sha256==='16854fe5b3eb0fa1c6dd2b96e3c5d6f792c5adbc91dd2a71e7fc2771472bbdd4','R55 source parent manifest mismatch');
|
|
22
|
+
insist(release.parent.tarball_sha256==='ff6eba3036cb00ac639ac8907ad6063e6ea8169cf32aca8721f75b1e403e501a','R55 source parent tarball mismatch');
|
|
23
|
+
insist(
|
|
24
|
+
active.release_id===release.runtime_predecessor.release_id&&
|
|
25
|
+
active.manifest_sha256===release.runtime_predecessor.manifest_sha256&&
|
|
26
|
+
active.generation_manifest_sha256===release.runtime_predecessor.generation_manifest_sha256,
|
|
27
|
+
'active runtime is not exact sealed 0.15.29 predecessor'
|
|
28
|
+
);
|
|
29
|
+
insist(release.hotfix?.foreground_guardian_owner_parent_binding===true,'R55 guardian owner/parent binding missing');
|
|
30
|
+
insist(release.hotfix?.foreground_guardian_session_nonce_contract===true,'R55 guardian nonce contract missing');
|
|
31
|
+
insist(release.hotfix?.unrelated_stale_runtime_fail_closed===true,'R55 stale-runtime negative control missing');
|
|
32
|
+
insist(release.hotfix?.full_regression==='PASS','R55 full regression metadata is not PASS');
|
|
33
|
+
insist(matrix.observed_pre_release_evidence?.full_regression?.status==='PASS','R55 full regression evidence is not PASS');
|
|
34
|
+
insist(provenance.operator_override?.stable_final_claim===false,'R55 must not claim stable/final');
|
|
35
|
+
insist(matrix.operator_override?.four_hour_soak==='NOT_COMPLETED','R55 matrix must preserve soak bypass');
|
|
36
|
+
|
|
37
|
+
const health=await doctor();
|
|
38
|
+
insist(health.status==='ok'&&health.package_version===pkg.version&&health.manifest?.verified===true,'R55 doctor evidence invalid');
|
|
39
|
+
|
|
40
|
+
const evidence={
|
|
41
|
+
schema:'deadbyte.r55-windows-gate.v1',
|
|
42
|
+
status:'passed',
|
|
43
|
+
exit_code:0,
|
|
44
|
+
version:pkg.version,
|
|
45
|
+
train:'R55',
|
|
46
|
+
completed_at_utc:new Date().toISOString(),
|
|
47
|
+
manifest_sha256:manifestSha256,
|
|
48
|
+
parent:release.parent,
|
|
49
|
+
runtime_predecessor:release.runtime_predecessor,
|
|
50
|
+
hotfix:release.hotfix,
|
|
51
|
+
operator_override:provenance.operator_override,
|
|
52
|
+
active_predecessor:{
|
|
53
|
+
release_id:active.release_id,
|
|
54
|
+
version:active.version,
|
|
55
|
+
manifest_sha256:active.manifest_sha256,
|
|
56
|
+
generation_manifest_sha256:active.generation_manifest_sha256
|
|
57
|
+
},
|
|
58
|
+
doctor:{verified_entries:health.manifest.verified_entries},
|
|
59
|
+
gates:{
|
|
60
|
+
r55_focused:true,
|
|
61
|
+
package_boundary:true,
|
|
62
|
+
full_regression:true,
|
|
63
|
+
manifest_final_verify:true,
|
|
64
|
+
four_hour_soak_completed:false
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const bytes=Buffer.from(JSON.stringify(evidence,null,2)+'\n');
|
|
68
|
+
const dir=path.join(stateRoot,'evidence','release');
|
|
69
|
+
await mkdir(dir,{recursive:true});
|
|
70
|
+
const target=path.join(dir,`r55-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
71
|
+
try{
|
|
72
|
+
const handle=await open(target,'wx',0o600);
|
|
73
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
74
|
+
}catch(error){
|
|
75
|
+
if(error?.code!=='EEXIST')throw error;
|
|
76
|
+
const prior=JSON.parse(await readFile(target,'utf8'));
|
|
77
|
+
insist(prior.schema===evidence.schema&&prior.status==='passed'&&prior.version===evidence.version&&prior.manifest_sha256===manifestSha256,'conflicting R55 gate evidence identity');
|
|
78
|
+
}
|
|
79
|
+
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.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}));
|
package/src/runtime-update.mjs
CHANGED
|
@@ -647,6 +647,18 @@ async function readLedger(ctx,name){
|
|
|
647
647
|
function sameGeneration(a,b){
|
|
648
648
|
return a.pid===b.pid&&a.instance_nonce===b.instance_nonce&&a.creation_time_utc===b.creation_time_utc&&a.image_sha256===b.image_sha256;
|
|
649
649
|
}
|
|
650
|
+
function activationGuardianOwnedByLineage(item,selfLineage){
|
|
651
|
+
const name=String(item?.Name??'').toLowerCase();
|
|
652
|
+
if(name!=='node.exe') return false;
|
|
653
|
+
const commandLine=String(item?.CommandLine??'');
|
|
654
|
+
if(!commandLine.toLowerCase().includes('remote-session-guardian.mjs')) return false;
|
|
655
|
+
const ownerMatch=/(?:^|\s)--owner-pid(?:=|\s+)(\d+)(?=\s|$)/i.exec(commandLine);
|
|
656
|
+
const nonceMatch=/(?:^|\s)--session-nonce(?:=|\s+)([0-9a-f]{64})(?=\s|$)/i.exec(commandLine);
|
|
657
|
+
if(!ownerMatch||!nonceMatch) return false;
|
|
658
|
+
const ownerPid=Number(ownerMatch[1]);
|
|
659
|
+
const parentPid=Number(item?.ParentProcessId);
|
|
660
|
+
return Number.isInteger(ownerPid)&&ownerPid>0&&parentPid===ownerPid&&selfLineage.has(ownerPid);
|
|
661
|
+
}
|
|
650
662
|
export function selectOfflineRecoveryBlockers(processes,currentPid=process.pid){
|
|
651
663
|
const list=(Array.isArray(processes)?processes:[processes]).filter(Boolean);
|
|
652
664
|
const byPid=new Map();
|
|
@@ -668,6 +680,7 @@ export function selectOfflineRecoveryBlockers(processes,currentPid=process.pid){
|
|
|
668
680
|
return list.filter(item=>{
|
|
669
681
|
const pid=Number(item?.ProcessId);
|
|
670
682
|
if(selfLineage.has(pid)) return false;
|
|
683
|
+
if(activationGuardianOwnedByLineage(item,selfLineage)) return false;
|
|
671
684
|
const name=String(item?.Name??'').toLowerCase();
|
|
672
685
|
if(!runtimeNames.has(name)) return false;
|
|
673
686
|
const commandLine=String(item?.CommandLine??'').toLowerCase();
|
|
@@ -848,16 +861,21 @@ async function verifyPackageRoot(root,expectedVersion,expectedManifest,label){
|
|
|
848
861
|
if(pkg?.name!=='deadbyte-mcp'||pkg?.version!==expectedVersion) throw new Error(`${label} package identity mismatch`);
|
|
849
862
|
return {root:absolute,manifest_sha256:digest,entries:entries.size};
|
|
850
863
|
}
|
|
851
|
-
async function
|
|
864
|
+
async function verifyStoredInstallation(stateRoot,pointer){
|
|
852
865
|
const state=path.resolve(stateRoot); const expectedRelease=path.join(state,'releases',releaseId(pointer.version,pointer.manifest_sha256));
|
|
853
866
|
if(path.resolve(pointer.release_root)!==path.resolve(expectedRelease)) throw new Error('active pointer release root is not content-addressed store path');
|
|
854
867
|
const release=await verifyPackageRoot(pointer.release_root,pointer.version,pointer.manifest_sha256,'active release');
|
|
855
868
|
const generation=await verifyGenerationBundle(pointer.generation_root,pointer.generation_manifest_sha256);
|
|
856
869
|
const expectedConfig=path.join(path.resolve(pointer.generation_root),'controller-config.psd1');
|
|
857
870
|
if(path.resolve(pointer.config_path)!==path.resolve(expectedConfig)) throw new Error('active pointer config path mismatch');
|
|
871
|
+
return {release,generation};
|
|
872
|
+
}
|
|
873
|
+
async function verifyActiveInstallation(stateRoot,pointer){
|
|
874
|
+
const state=path.resolve(stateRoot);
|
|
875
|
+
const verified=await verifyStoredInstallation(state,pointer);
|
|
858
876
|
const currentLauncher=await sha256File(launcherPath(state));
|
|
859
877
|
if(currentLauncher!==pointer.launcher_sha256) throw new Error('active pointer launcher digest mismatch');
|
|
860
|
-
return
|
|
878
|
+
return verified;
|
|
861
879
|
}
|
|
862
880
|
async function loadPointerIfPresent(stateRoot){
|
|
863
881
|
const file=path.join(path.resolve(stateRoot),'active-release.json');
|
|
@@ -1071,6 +1089,41 @@ function decodeSnapshot(snapshot,label){
|
|
|
1071
1089
|
}
|
|
1072
1090
|
return {exists:false,bytes:null,sha256:null};
|
|
1073
1091
|
}
|
|
1092
|
+
export function rollbackLauncherPlanFromHandoff(body,priorPointer,activePointer,stateRoot=DEFAULT_STATE_ROOT){
|
|
1093
|
+
if(!body||typeof body!=='object') throw new Error('rollback handoff body required');
|
|
1094
|
+
const handoffPrior=body.prior_pointer?parseActivePointer(JSON.stringify(body.prior_pointer)):null;
|
|
1095
|
+
const handoffActive=body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
|
|
1096
|
+
if(!samePublicPointer(handoffPrior,priorPointer)||!samePublicPointer(handoffActive,activePointer)) throw new Error('rollback handoff pointer binding mismatch');
|
|
1097
|
+
const state=path.resolve(stateRoot);
|
|
1098
|
+
const launchers={
|
|
1099
|
+
ps1:path.resolve(String(body.launchers?.ps1??'')),
|
|
1100
|
+
cmd:path.resolve(String(body.launchers?.cmd??'')),
|
|
1101
|
+
top:path.resolve(String(body.launchers?.top??TOP_LAUNCHER)),
|
|
1102
|
+
launcher_sha256:String(body.launchers?.launcher_sha256??''),
|
|
1103
|
+
ps1Bytes:Buffer.from(String(body.launchers?.ps1_bytes??''),'base64'),
|
|
1104
|
+
cmdBytes:Buffer.from(String(body.launchers?.cmd_bytes??''),'base64'),
|
|
1105
|
+
snapshots:{
|
|
1106
|
+
ps1:decodeSnapshot(body.launchers?.snapshots?.ps1,'rollback.launcher.ps1'),
|
|
1107
|
+
cmd:decodeSnapshot(body.launchers?.snapshots?.cmd,'rollback.launcher.cmd'),
|
|
1108
|
+
top:decodeSnapshot(body.launchers?.snapshots?.top,'rollback.launcher.top')
|
|
1109
|
+
},
|
|
1110
|
+
installed:true
|
|
1111
|
+
};
|
|
1112
|
+
if(launchers.ps1!==path.resolve(launcherPath(state))||
|
|
1113
|
+
launchers.cmd!==path.resolve(path.join(state,'bin','DEADBYTE-MCP.cmd'))||
|
|
1114
|
+
launchers.top!==path.resolve(TOP_LAUNCHER)) throw new Error('rollback launcher paths are not canonical');
|
|
1115
|
+
if(!HEX64.test(launchers.launcher_sha256)||sha256Bytes(launchers.ps1Bytes)!==launchers.launcher_sha256) throw new Error('rollback successor launcher bytes/hash mismatch');
|
|
1116
|
+
if(launchers.launcher_sha256!==activePointer.launcher_sha256) throw new Error('rollback successor launcher hash does not match active pointer');
|
|
1117
|
+
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');
|
|
1118
|
+
return launchers;
|
|
1119
|
+
}
|
|
1120
|
+
async function restoreRollbackLaunchers(plan){
|
|
1121
|
+
await restoreFile(plan.ps1,plan.snapshots.ps1);
|
|
1122
|
+
await restoreFile(plan.cmd,plan.snapshots.cmd);
|
|
1123
|
+
await restoreFile(plan.top,plan.snapshots.top);
|
|
1124
|
+
if(await sha256File(plan.ps1)!==plan.snapshots.ps1.sha256) throw new Error('rollback predecessor launcher restore hash mismatch');
|
|
1125
|
+
return plan;
|
|
1126
|
+
}
|
|
1074
1127
|
const UPDATE_PHASE_RANK=Object.freeze({
|
|
1075
1128
|
initial:0,candidate_prepared:10,old_runtime_verified:20,shutdown_handoff_armed:25,supervisor_spawned:30,
|
|
1076
1129
|
authority_revoked:40,old_runtime_stopped:50,workspace_ready:60,launcher_installed:70,pointer_committed:80,
|
|
@@ -1574,19 +1627,28 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
|
|
|
1574
1627
|
const activePointer=parseActivePointer(JSON.stringify(activationEnvelope.body.active_pointer));
|
|
1575
1628
|
const pointerInfo=await loadPointerIfPresent(state);
|
|
1576
1629
|
if(!pointerInfo.pointer||canonicalJson(publicPointer(pointerInfo.pointer))!==canonicalJson(publicPointer(activePointer))) throw new Error('rollback current pointer does not match signed activation pointer');
|
|
1577
|
-
|
|
1630
|
+
|
|
1631
|
+
const handoffPath=path.join(path.dirname(receiptFile),'handoff.json');
|
|
1632
|
+
const {body:handoffBody}=await readHandoffEnvelope(handoffPath,state);
|
|
1633
|
+
if(handoffBody.update_id!==activationEnvelope.body.update_id) throw new Error('rollback handoff update identity mismatch');
|
|
1634
|
+
const rollbackLaunchers=rollbackLauncherPlanFromHandoff(handoffBody,priorPointer,activePointer,state);
|
|
1635
|
+
await verifyStoredInstallation(state,priorPointer);
|
|
1636
|
+
|
|
1578
1637
|
const currentCtx=slotContext(state,pointerInfo.pointer),currentRuntime=await captureRuntime(currentCtx);
|
|
1579
1638
|
const revoked=await revokeAuthority(currentCtx,currentRuntime.status);
|
|
1580
1639
|
const rollbackId=`r31-rollback-${activationEnvelope.receipt_sha256.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
|
|
1581
1640
|
const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json');
|
|
1582
1641
|
await mkdir(evidenceDir,{recursive:true});
|
|
1583
1642
|
const journal={schema:'deadbyte.r31-slot-rollback-state.v1',rollback_id:rollbackId,activation_receipt:path.resolve(receiptFile),
|
|
1584
|
-
activation_receipt_sha256:activationEnvelope.receipt_sha256,
|
|
1643
|
+
activation_receipt_sha256:activationEnvelope.receipt_sha256,handoff_path:handoffPath,
|
|
1644
|
+
from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),created_at_utc:new Date().toISOString()};
|
|
1585
1645
|
await writeState(statePath,journal,'verified');
|
|
1586
|
-
let stopped=false,pointerCommitted=false,priorCtx=null,priorRuntime=null;
|
|
1646
|
+
let stopped=false,launchersRestored=false,pointerCommitted=false,priorCtx=null,priorRuntime=null;
|
|
1587
1647
|
try {
|
|
1588
1648
|
await stopAndVerify(currentCtx,currentRuntime); stopped=true;
|
|
1589
1649
|
await writeState(statePath,journal,'successor_stopped');
|
|
1650
|
+
await restoreRollbackLaunchers(rollbackLaunchers); launchersRestored=true;
|
|
1651
|
+
await writeState(statePath,journal,'predecessor_launcher_restored',{launcher_sha256:priorPointer.launcher_sha256});
|
|
1590
1652
|
await atomicWriteActivePointer(pointerInfo.file,priorPointer); pointerCommitted=true;
|
|
1591
1653
|
await verifyActiveInstallation(state,priorPointer);
|
|
1592
1654
|
await writeState(statePath,journal,'prior_pointer_restored',{active_pointer:publicPointer(priorPointer)});
|
|
@@ -1597,16 +1659,27 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
|
|
|
1597
1659
|
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');
|
|
1598
1660
|
const body={schema:ROLLBACK_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
|
|
1599
1661
|
completed_at_utc:new Date().toISOString(),from_pointer:publicPointer(activePointer),active_pointer:publicPointer(priorPointer),
|
|
1600
|
-
old_runtime:generationTuple(currentRuntime),new_runtime:generationTuple(priorRuntime),stabilization,
|
|
1662
|
+
launcher_restored:true,old_runtime:generationTuple(currentRuntime),new_runtime:generationTuple(priorRuntime),stabilization,
|
|
1663
|
+
authority_revoked:revoked.revoked,rollback_performed:true};
|
|
1601
1664
|
const receipt=await writeEnvelope(receiptPath,body,key);
|
|
1602
1665
|
await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
|
|
1603
1666
|
return {ok:true,rollback_id:rollbackId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,pointer:priorPointer,new_runtime:priorRuntime};
|
|
1604
1667
|
} catch(error){
|
|
1605
|
-
const recovery={attempted:false,prior_runtime_stopped:false,successor_pointer_restored:false,successor_runtime_restarted:false,errors:[]};
|
|
1668
|
+
const recovery={attempted:false,prior_runtime_stopped:false,successor_launcher_restored:false,successor_pointer_restored:false,successor_runtime_restarted:false,errors:[]};
|
|
1606
1669
|
if(stopped){
|
|
1607
1670
|
recovery.attempted=true;
|
|
1608
1671
|
if(pointerCommitted&&priorCtx){ try { controllerCaptured(priorCtx,'stop',30000); recovery.prior_runtime_stopped=true; } catch(recoveryError){ recovery.errors.push(`stop-prior:${recoveryError.message}`); } }
|
|
1609
|
-
if(
|
|
1672
|
+
if(launchersRestored){
|
|
1673
|
+
try { await installLaunchers(rollbackLaunchers); recovery.successor_launcher_restored=true; }
|
|
1674
|
+
catch(recoveryError){ recovery.errors.push(`restore-successor-launcher:${recoveryError.message}`); }
|
|
1675
|
+
}
|
|
1676
|
+
if(pointerCommitted){
|
|
1677
|
+
try { await atomicWriteActivePointer(pointerInfo.file,activePointer); await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; }
|
|
1678
|
+
catch(recoveryError){ recovery.errors.push(`restore-pointer:${recoveryError.message}`); }
|
|
1679
|
+
}else if(recovery.successor_launcher_restored){
|
|
1680
|
+
try { await verifyActiveInstallation(state,activePointer); recovery.successor_pointer_restored=true; }
|
|
1681
|
+
catch(recoveryError){ recovery.errors.push(`verify-successor-pointer:${recoveryError.message}`); }
|
|
1682
|
+
}
|
|
1610
1683
|
try { await startAndVerify(currentCtx,currentRuntime); recovery.successor_runtime_restarted=true; } catch(recoveryError){ recovery.errors.push(`restart-successor:${recoveryError.message}`); }
|
|
1611
1684
|
}
|
|
1612
1685
|
await writeState(statePath,journal,'failed',{error:{name:error?.name??'Error',message:String(error?.message??error)},recovery}).catch(()=>{});
|
package/src/version.mjs
CHANGED
|
@@ -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('
|
|
8
|
-
const [pkgText,gate,evidence,historicalR53,historicalEvidenceR53,historicalR52,historicalEvidenceR52,historicalR51,historicalEvidenceR51,historicalR50,historicalR49,deferred]=await Promise.all([
|
|
7
|
+
test('R56 canonical Windows gate is successor-bound and preserves R55/R54/R53/R52/R51/R50/R49 historical gates',async()=>{
|
|
8
|
+
const [pkgText,gate,evidence,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-r56.ps1'),
|
|
11
|
+
read('scripts/windows-gate-evidence-r56.mjs'),
|
|
12
|
+
read('scripts/gate-windows-r55.ps1'),
|
|
13
|
+
read('scripts/windows-gate-evidence-r55.mjs'),
|
|
10
14
|
read('scripts/gate-windows-r54.ps1'),
|
|
11
15
|
read('scripts/windows-gate-evidence-r54.mjs'),
|
|
12
16
|
read('scripts/gate-windows-r53.ps1'),
|
|
@@ -20,18 +24,22 @@ test('R54 canonical Windows gate is successor-bound and preserves R53/R52/R51/R5
|
|
|
20
24
|
read('scripts/deferred-slot-operation-r46.mjs')
|
|
21
25
|
]);
|
|
22
26
|
const pkg=JSON.parse(pkgText);
|
|
23
|
-
assert.match(pkg.scripts['gate:windows'],/gate-windows-
|
|
24
|
-
assert.match(gate,/DEADBYTE 0\.15\.
|
|
25
|
-
assert.match(gate,/
|
|
26
|
-
assert.match(gate,/
|
|
27
|
-
assert.match(gate,/full-regression/);
|
|
27
|
+
assert.match(pkg.scripts['gate:windows'],/gate-windows-r56\.ps1$/);
|
|
28
|
+
assert.match(gate,/DEADBYTE 0\.15\.33 \/ R56 WINDOWS GATE: PASS/);
|
|
29
|
+
assert.match(gate,/r56-package-boundary/);
|
|
30
|
+
assert.match(gate,/r56-focused-contracts/);
|
|
31
|
+
assert.match(gate,/full-regression-evidence/);
|
|
28
32
|
assert.match(gate,/manifest-verify/);
|
|
29
33
|
assert.match(gate,/doctor/);
|
|
30
|
-
assert.match(evidence,/deadbyte\.
|
|
31
|
-
assert.match(evidence,/pkg\.version==='0\.15\.
|
|
32
|
-
assert.match(evidence,/release\.train==='
|
|
33
|
-
assert.match(evidence,/release\.parent\.version==='0\.15\.
|
|
34
|
+
assert.match(evidence,/deadbyte\.r56-windows-gate\.v1/);
|
|
35
|
+
assert.match(evidence,/pkg\.version==='0\.15\.33'/);
|
|
36
|
+
assert.match(evidence,/release\.train==='R56'/);
|
|
37
|
+
assert.match(evidence,/release\.parent\.version==='0\.15\.32'/);
|
|
34
38
|
assert.match(evidence,/four_hour_soak_completed:false/);
|
|
39
|
+
assert.match(historicalR55,/DEADBYTE 0\.15\.32 \/ R55 WINDOWS GATE: PASS/);
|
|
40
|
+
assert.match(historicalEvidenceR55,/deadbyte\.r55-windows-gate\.v1/);
|
|
41
|
+
assert.match(historicalR54,/DEADBYTE 0\.15\.31 \/ R54 WINDOWS GATE: PASS/);
|
|
42
|
+
assert.match(historicalEvidenceR54,/deadbyte\.r54-windows-gate\.v1/);
|
|
35
43
|
assert.match(historicalR53,/DEADBYTE 0\.15\.30 \/ R53 WINDOWS GATE: PASS/);
|
|
36
44
|
assert.match(historicalEvidenceR53,/deadbyte\.r53-windows-gate\.v1/);
|
|
37
45
|
assert.match(historicalR52,/DEADBYTE 0\.15\.29 \/ R52 WINDOWS GATE: PASS/);
|
|
@@ -42,10 +50,5 @@ test('R54 canonical Windows gate is successor-bound and preserves R53/R52/R51/R5
|
|
|
42
50
|
assert.match(historicalR49,/DEADBYTE 0\.15\.26 \/ R49 WINDOWS GATE: PASS/);
|
|
43
51
|
assert.match(deferred,/const expectedVersion=packageIdentity\.version/);
|
|
44
52
|
assert.match(deferred,/'--expected-version',expectedVersion/);
|
|
45
|
-
assert.match(deferred,/async function awaitHandoffCompletion/);
|
|
46
53
|
assert.match(deferred,/verifySlotUpdateEnvelope\(envelope,publicKey\)/);
|
|
47
|
-
assert.match(deferred,/active pointer does not match signed activation receipt/);
|
|
48
|
-
assert.match(deferred,/failure-receipt\.json/);
|
|
49
|
-
assert.match(deferred,/await awaitHandoffCompletion\(await runSlotUpdateCli/);
|
|
50
|
-
assert.doesNotMatch(deferred,/'--expected-version','0\.15\.5'/);
|
|
51
54
|
});
|
|
@@ -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 r54test=currentPackage.version==='0.15.31'?test:test.skip;
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
r54test('R54 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('R54 release identity is coherent across package lock source metadata SBOM
|
|
|
25
27
|
assert.match(pkg.scripts['r54:focused:test'],/r54-offline-predecessor-self-exclusion\.test\.mjs/);
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
r54test('R54 source predecessor is exact immutable published 0.15.30 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.30');
|
|
31
33
|
assert.equal(release.parent.release_id,'v0.15.30-ddf7c7338d9a371f');
|
|
@@ -44,7 +46,7 @@ test('R54 source predecessor is exact immutable published 0.15.30 while runtime
|
|
|
44
46
|
assert.deepEqual(provenance.runtime_predecessor,release.runtime_predecessor);
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
r54test('R54 records surgical activation recovery fix and preserves explicit non-final claim',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.unrelated_stale_runtime_fail_closed,true);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const target=process.env.R55_RUNTIME_UPDATE_PATH
|
|
7
|
+
? path.resolve(process.env.R55_RUNTIME_UPDATE_PATH)
|
|
8
|
+
: path.resolve(new URL('../src/runtime-update.mjs',import.meta.url).pathname.replace(/^\/(?:[A-Za-z]:)/,m=>m.slice(1)));
|
|
9
|
+
const runtimeUpdate=await import(pathToFileURL(target).href+'?r55='+Date.now());
|
|
10
|
+
const nonce='a'.repeat(64);
|
|
11
|
+
|
|
12
|
+
test('R55 excludes the verified foreground remote guardian sibling owned by an activation ancestor',()=>{
|
|
13
|
+
const processes=[
|
|
14
|
+
{ProcessId:100,ParentProcessId:50,Name:'node.exe',CommandLine:'node npx-cli.js --yes deadbyte-mcp@0.15.32 remote'},
|
|
15
|
+
{ProcessId:200,ParentProcessId:100,Name:'cmd.exe',CommandLine:'cmd /d /s /c deadbyte-mcp remote'},
|
|
16
|
+
{ProcessId:300,ParentProcessId:200,Name:'node.exe',CommandLine:'node C:\\cache\\deadbyte-mcp\\src\\deadbyte-cli.mjs remote'},
|
|
17
|
+
{ProcessId:350,ParentProcessId:300,Name:'node.exe',CommandLine:'node C:\\cache\\deadbyte-mcp\\scripts\\deploy-r48.mjs'},
|
|
18
|
+
{ProcessId:400,ParentProcessId:350,Name:'node.exe',CommandLine:'node C:\\slot\\deadbyte-mcp\\scripts\\runtime-self-update.mjs --resume-handoff x'},
|
|
19
|
+
{ProcessId:500,ParentProcessId:300,Name:'node.exe',CommandLine:`node C:\\cache\\deadbyte-mcp\\src\\remote-session-guardian.mjs --state-root C:\\Users\\DEADBYTE\\.deadbyte-mcp --owner-pid 300 --session-nonce ${nonce}`},
|
|
20
|
+
{ProcessId:900,ParentProcessId:1,Name:'node.exe',CommandLine:'node C:\\stale\\deadbyte-mcp\\src\\mcp-server.mjs'}
|
|
21
|
+
];
|
|
22
|
+
const blockers=runtimeUpdate.selectOfflineRecoveryBlockers(processes,400);
|
|
23
|
+
assert.deepEqual(blockers.map(item=>item.ProcessId),[900]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('R55 does not exempt a guardian whose parent does not equal its declared ancestor owner',()=>{
|
|
27
|
+
const processes=[
|
|
28
|
+
{ProcessId:300,ParentProcessId:200,Name:'node.exe',CommandLine:'node C:\\cache\\deadbyte-mcp\\src\\deadbyte-cli.mjs remote'},
|
|
29
|
+
{ProcessId:400,ParentProcessId:300,Name:'node.exe',CommandLine:'node C:\\slot\\deadbyte-mcp\\scripts\\runtime-self-update.mjs --resume-handoff x'},
|
|
30
|
+
{ProcessId:510,ParentProcessId:999,Name:'node.exe',CommandLine:`node C:\\cache\\deadbyte-mcp\\src\\remote-session-guardian.mjs --owner-pid 300 --session-nonce ${nonce}`}
|
|
31
|
+
];
|
|
32
|
+
assert.deepEqual(runtimeUpdate.selectOfflineRecoveryBlockers(processes,400).map(item=>item.ProcessId),[510]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('R55 does not exempt a guardian whose owner is outside the activation lineage',()=>{
|
|
36
|
+
const processes=[
|
|
37
|
+
{ProcessId:300,ParentProcessId:200,Name:'node.exe',CommandLine:'node C:\\cache\\deadbyte-mcp\\src\\deadbyte-cli.mjs remote'},
|
|
38
|
+
{ProcessId:400,ParentProcessId:300,Name:'node.exe',CommandLine:'node C:\\slot\\deadbyte-mcp\\scripts\\runtime-self-update.mjs --resume-handoff x'},
|
|
39
|
+
{ProcessId:520,ParentProcessId:777,Name:'node.exe',CommandLine:`node C:\\cache\\deadbyte-mcp\\src\\remote-session-guardian.mjs --owner-pid 777 --session-nonce ${nonce}`}
|
|
40
|
+
];
|
|
41
|
+
assert.deepEqual(runtimeUpdate.selectOfflineRecoveryBlockers(processes,400).map(item=>item.ProcessId),[520]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('R55 does not exempt a guardian without the signed-session-shaped nonce contract',()=>{
|
|
45
|
+
const processes=[
|
|
46
|
+
{ProcessId:300,ParentProcessId:200,Name:'node.exe',CommandLine:'node C:\\cache\\deadbyte-mcp\\src\\deadbyte-cli.mjs remote'},
|
|
47
|
+
{ProcessId:400,ParentProcessId:300,Name:'node.exe',CommandLine:'node C:\\slot\\deadbyte-mcp\\scripts\\runtime-self-update.mjs --resume-handoff x'},
|
|
48
|
+
{ProcessId:530,ParentProcessId:300,Name:'node.exe',CommandLine:'node C:\\cache\\deadbyte-mcp\\src\\remote-session-guardian.mjs --owner-pid 300 --session-nonce short'}
|
|
49
|
+
];
|
|
50
|
+
assert.deepEqual(runtimeUpdate.selectOfflineRecoveryBlockers(processes,400).map(item=>item.ProcessId),[530]);
|
|
51
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
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 r55test=currentPackage.version==='0.15.32'?test:test.skip;
|
|
10
|
+
|
|
11
|
+
r55test('R55 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.32');
|
|
16
|
+
assert.equal(lock.version,pkg.version);
|
|
17
|
+
assert.equal(lock.packages[''].version,pkg.version);
|
|
18
|
+
assert.match(versionSource,/DEADBYTE_VERSION = '0\.15\.32'/);
|
|
19
|
+
assert.equal(release.version,pkg.version);
|
|
20
|
+
assert.equal(release.train,'R55');
|
|
21
|
+
assert.equal(provenance.version,pkg.version);
|
|
22
|
+
assert.equal(matrix.version,pkg.version);
|
|
23
|
+
assert.equal(matrix.train,'R55');
|
|
24
|
+
assert.equal(sbom.metadata.component.version,pkg.version);
|
|
25
|
+
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r55:0.15.32');
|
|
26
|
+
assert.match(pkg.scripts['gate:windows'],/gate-windows-r55\.ps1/);
|
|
27
|
+
assert.match(pkg.scripts['r55:focused:test'],/r55-offline-predecessor-guardian-family\.test\.mjs/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
r55test('R55 source predecessor is exact immutable published 0.15.31 while runtime predecessor remains exact 0.15.29',async()=>{
|
|
31
|
+
const [release,provenance]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json')]);
|
|
32
|
+
assert.equal(release.parent.version,'0.15.31');
|
|
33
|
+
assert.equal(release.parent.release_id,'v0.15.31-16854fe5b3eb0fa1');
|
|
34
|
+
assert.equal(release.parent.manifest_sha256,'16854fe5b3eb0fa1c6dd2b96e3c5d6f792c5adbc91dd2a71e7fc2771472bbdd4');
|
|
35
|
+
assert.equal(release.parent.tarball_sha256,'ff6eba3036cb00ac639ac8907ad6063e6ea8169cf32aca8721f75b1e403e501a');
|
|
36
|
+
assert.equal(release.parent.activation_status,'PUBLISHED_NOT_ACTIVATED');
|
|
37
|
+
assert.equal(release.source_predecessor.version,'0.15.31');
|
|
38
|
+
assert.equal(release.source_predecessor.tarball_sha256,release.parent.tarball_sha256);
|
|
39
|
+
assert.equal(release.source_predecessor.manifest_file_sha256,release.parent.manifest_sha256);
|
|
40
|
+
assert.deepEqual(release.runtime_predecessor,{
|
|
41
|
+
release_id:'v0.15.29-f74eaa1b3d00e6fb',
|
|
42
|
+
manifest_sha256:'f74eaa1b3d00e6fb5a36b2358efb815e3686c9fe9dbd3b84d25a713ef957748c',
|
|
43
|
+
generation_manifest_sha256:'289b4fc065de0681da6864cc1bbe09aab198ca804945ffc27c786438dad13f5a'
|
|
44
|
+
});
|
|
45
|
+
assert.equal(provenance.parent_manifest_sha256,release.parent.manifest_sha256);
|
|
46
|
+
assert.deepEqual(provenance.runtime_predecessor,release.runtime_predecessor);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
r55test('R55 records the guardian sibling RED to GREEN and preserves fail-closed negative controls',async()=>{
|
|
50
|
+
const [release,provenance,matrix]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json')]);
|
|
51
|
+
assert.equal(release.hotfix.activation_ancestry_excluded,true);
|
|
52
|
+
assert.equal(release.hotfix.foreground_guardian_owner_parent_binding,true);
|
|
53
|
+
assert.equal(release.hotfix.foreground_guardian_session_nonce_contract,true);
|
|
54
|
+
assert.equal(release.hotfix.unrelated_stale_runtime_fail_closed,true);
|
|
55
|
+
assert.equal(release.hotfix.red_parent_0_15_31,'FAIL_1_OF_4');
|
|
56
|
+
assert.equal(release.hotfix.green_r55_candidate,'PASS_4_OF_4');
|
|
57
|
+
assert.equal(release.hotfix.stable_final_claim,false);
|
|
58
|
+
assert.equal(matrix.observed_pre_release_evidence.red_parent_0_15_31.receipt_sha256,'07ba973aa9224fbb1dde572af35640a1e07ca47273f1e50958d787bfe2aaf429');
|
|
59
|
+
assert.equal(matrix.observed_pre_release_evidence.green_r55_candidate.receipt_sha256,'a217f72cce51a9daf56136f9075ba487adb2a899c291a63af835ea15f8c632f0');
|
|
60
|
+
assert.equal(provenance.operator_override.four_hour_soak,'NOT_COMPLETED');
|
|
61
|
+
assert.equal(provenance.operator_override.stable_final_claim,false);
|
|
62
|
+
assert.equal(matrix.operator_override.stable_final_claim,false);
|
|
63
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
|
|
9
|
+
test('R56 release identity is coherent across package lock source metadata SBOM and gate',async()=>{
|
|
10
|
+
const [pkg,lock,release,provenance,matrix,sbom,versionSource]=await Promise.all([
|
|
11
|
+
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
|
+
]);
|
|
13
|
+
assert.equal(pkg.version,'0.15.33');
|
|
14
|
+
assert.equal(lock.version,pkg.version);
|
|
15
|
+
assert.equal(lock.packages[''].version,pkg.version);
|
|
16
|
+
assert.match(versionSource,/DEADBYTE_VERSION = '0\.15\.33'/);
|
|
17
|
+
assert.equal(release.version,pkg.version);
|
|
18
|
+
assert.equal(release.train,'R56');
|
|
19
|
+
assert.equal(provenance.version,pkg.version);
|
|
20
|
+
assert.equal(matrix.version,pkg.version);
|
|
21
|
+
assert.equal(matrix.train,'R56');
|
|
22
|
+
assert.equal(sbom.metadata.component.version,pkg.version);
|
|
23
|
+
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r56:0.15.33');
|
|
24
|
+
assert.match(pkg.scripts['gate:windows'],/gate-windows-r56\.ps1/);
|
|
25
|
+
assert.match(pkg.scripts['r56:focused:test'],/r56-rollback-launcher-restore\.test\.mjs/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('R56 source and runtime predecessor are exact active published 0.15.32',async()=>{
|
|
29
|
+
const [release,provenance]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json')]);
|
|
30
|
+
assert.equal(release.parent.version,'0.15.32');
|
|
31
|
+
assert.equal(release.parent.release_id,'v0.15.32-e7223f467777f5df');
|
|
32
|
+
assert.equal(release.parent.manifest_sha256,'e7223f467777f5dfeb20fd2cc7a6eca22619a44ed95e5bc0b32d9c7aabba361b');
|
|
33
|
+
assert.equal(release.parent.generation_manifest_sha256,'740443fd207c5e6ffd5952079214bf6e81e9281a6ec880de9bbe3b8ce1deb00e');
|
|
34
|
+
assert.equal(release.parent.tarball_sha256,'c9e1f5875dd2177cee0089305b40321c128c54ce7f5522e34e507d4ecfb22638');
|
|
35
|
+
assert.equal(release.parent.activation_status,'ACTIVATED_ROLLBACK_DEFECT_OBSERVED');
|
|
36
|
+
assert.equal(release.source_predecessor.version,'0.15.32');
|
|
37
|
+
assert.deepEqual(release.runtime_predecessor,{
|
|
38
|
+
release_id:'v0.15.32-e7223f467777f5df',
|
|
39
|
+
manifest_sha256:'e7223f467777f5dfeb20fd2cc7a6eca22619a44ed95e5bc0b32d9c7aabba361b',
|
|
40
|
+
generation_manifest_sha256:'740443fd207c5e6ffd5952079214bf6e81e9281a6ec880de9bbe3b8ce1deb00e'
|
|
41
|
+
});
|
|
42
|
+
assert.equal(provenance.parent_manifest_sha256,release.parent.manifest_sha256);
|
|
43
|
+
assert.equal(provenance.parent_generation_manifest_sha256,release.parent.generation_manifest_sha256);
|
|
44
|
+
assert.deepEqual(provenance.runtime_predecessor,release.runtime_predecessor);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('R56 records signed rollback launcher ordering RED to GREEN and preserves non-final claim',async()=>{
|
|
48
|
+
const [release,provenance,matrix]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json')]);
|
|
49
|
+
assert.equal(release.hotfix.signed_handoff_launcher_snapshot_required,true);
|
|
50
|
+
assert.equal(release.hotfix.stored_predecessor_verified_without_live_launcher,true);
|
|
51
|
+
assert.equal(release.hotfix.predecessor_launcher_restored_before_pointer_commit,true);
|
|
52
|
+
assert.equal(release.hotfix.successor_launcher_restored_before_recovery_pointer,true);
|
|
53
|
+
assert.equal(release.hotfix.exact_parent_activation_receipt_sha256,'6518a04154f485cfae42e582269634d69a8b05a0183149d5e10006fbdd9d933c');
|
|
54
|
+
assert.equal(release.hotfix.observed_parent_rollback_failure_receipt_sha256,'00cbd497e4902c67ab39313a378e2fcf32159113b46339ad1d54b5e5a7714205');
|
|
55
|
+
assert.equal(release.hotfix.red_parent_0_15_32,'FAIL_5_OF_5');
|
|
56
|
+
assert.equal(release.hotfix.green_r56_candidate,'PASS_5_OF_5');
|
|
57
|
+
assert.equal(matrix.observed_pre_release_evidence.red_parent_0_15_32.receipt_sha256,'f3b446306691d61e97896ab4d237abaa20d1243237299812e790233c46451dba');
|
|
58
|
+
assert.equal(matrix.observed_pre_release_evidence.green_r56_candidate.receipt_sha256,'4198930f38a3fc4b7978cfc94ca4509127561c170b05f7566bdf79513e449a8b');
|
|
59
|
+
assert.equal(release.hotfix.stable_final_claim,false);
|
|
60
|
+
assert.equal(provenance.operator_override.four_hour_soak,'NOT_COMPLETED');
|
|
61
|
+
assert.equal(provenance.operator_override.stable_final_claim,false);
|
|
62
|
+
assert.equal(matrix.operator_override.stable_final_claim,false);
|
|
63
|
+
});
|